opencode-resolve 0.1.4 → 0.1.6
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.ko.md +81 -41
- package/README.md +81 -41
- package/dist/index.js +143 -12
- package/opencode-resolve.example.json +6 -2
- package/package.json +1 -1
- package/scripts/postinstall.mjs +0 -1
package/README.ko.md
CHANGED
|
@@ -222,6 +222,29 @@ opencode plugin opencode-resolve --global --force
|
|
|
222
222
|
|
|
223
223
|
> 이미 다른 플러그인이 있다면, 기존 배열에 `"opencode-resolve"`를 **추가**하세요. 기존 설정을 **덮어쓰지 마세요**.
|
|
224
224
|
|
|
225
|
+
#### 2b. 권장 companion 플러그인 제안 (2개)
|
|
226
|
+
|
|
227
|
+
모델 선택으로 넘어가기 전에, **독립적인** 두 companion 플러그인을 각각 한 번씩 물어봅니다 — 둘 다 권장 기본값. 별개의 OpenCode 플러그인이라 필수는 아니지만 opencode-resolve와 궁합이 좋습니다.
|
|
228
|
+
|
|
229
|
+
**Companion 1 — `@tarquinen/opencode-dcp` (Dynamic Context Pruning)**
|
|
230
|
+
|
|
231
|
+
> _opencode-resolve는 **`@tarquinen/opencode-dcp`** 와 궁합이 좋아요 — obsolete tool output을 가지치기해서 긴 resolver 루프의 토큰 비용을 줄입니다. 설치할까요? (권장)_
|
|
232
|
+
|
|
233
|
+
**Companion 2 — `@slkiser/opencode-quota` (사용량/quota 추적)**
|
|
234
|
+
|
|
235
|
+
> _그리고 **`@slkiser/opencode-quota`** — 컨텍스트 윈도우를 오염시키지 않고 실시간 토큰/quota 사용량을 보여줍니다. GLM coding-plan, OpenAI Plus/Pro, Qwen 등 지원. 내 사용량/잔여 quota 확인용으로 유용. 설치할까요? (권장)_
|
|
236
|
+
|
|
237
|
+
사용자가 **예** (각 질문마다):
|
|
238
|
+
|
|
239
|
+
1. `opencode plugin <package>@latest --global --force` 실행 (캐시 priming).
|
|
240
|
+
2. `~/.config/opencode/opencode.json`의 `plugin` 배열에 `"<package>@latest"` 추가 — 이미 있으면 스킵.
|
|
241
|
+
|
|
242
|
+
`<package>`는 `@tarquinen/opencode-dcp` 또는 `@slkiser/opencode-quota`.
|
|
243
|
+
|
|
244
|
+
각 **아니오**: 그 항목 스킵 — `opencode.json`을 그것 때문에 수정하지 마세요. 어느 쪽이든 3단계로 진행.
|
|
245
|
+
|
|
246
|
+
> `context7` MCP는 `resolve.json`에서 `context7: true`(기본값)이면 opencode-resolve가 런타임에 자동 등록합니다. 별도 질문 불필요.
|
|
247
|
+
|
|
225
248
|
### 3단계 — 모델을 인터랙티브하게 고른 뒤 `~/.config/opencode/resolve.json` 작성
|
|
226
249
|
|
|
227
250
|
여기서 LLM이 **짧은 Q&A를 주도**합니다. 목표: 사용자가 자신의 설정에서 직접 고른 모델 ID로 `resolve.json`을 마무리하는 것.
|
|
@@ -251,12 +274,17 @@ provider가 **하나도 설정되지 않은 경우** 진행을 멈추고, `openc
|
|
|
251
274
|
|
|
252
275
|
후보 provider가 정확히 하나면 질문을 건너뛰고 사용합니다.
|
|
253
276
|
|
|
254
|
-
#### 3c. 사용자에게 질문: 단일 /
|
|
277
|
+
#### 3c. 사용자에게 질문: 단일 / 2단계 / 3단계?
|
|
255
278
|
|
|
256
|
-
|
|
279
|
+
기본 권장은 **C (3단계, 브론즈/실버/골드)**. opencode-resolve는 질적으로 다른 세 가지 작업(read-only 스카웃 → write/patch → reason/judge)을 가지며, 이를 3단계로 매핑하는 게 2단계보다 역할별 비용을 더 정확히 반영합니다. 사용자가 모델 두 개만 있으면 B로 fallback, 하나뿐이면 A.
|
|
280
|
+
|
|
281
|
+
> _권장: **3단계** — `explorer`에 가벼운 스카웃, `coder`에 중간 구현 모델, `resolver`/`reviewer`/`deep-reviewer`/`planner`에 강한 reasoner. 선택:_
|
|
257
282
|
>
|
|
258
|
-
>
|
|
259
|
-
> B.
|
|
283
|
+
> **C. 3단계 — 브론즈(스카웃) + 실버(코더) + 골드(추론) — 권장**
|
|
284
|
+
> B. 2단계 — fast + strong (모델 두 개만 있을 때)
|
|
285
|
+
> A. 모든 역할에 단일 모델 (모델 하나뿐이거나 최대한 단순한 게 필요한 경우만)
|
|
286
|
+
|
|
287
|
+
사용자가 그냥 엔터를 누르거나 "권장"이라고 답하면 C로. 모델 부족 시에만 fallback. **same-provider 분할도 완전 유효** — 예: `openai/gpt-4o-mini` / `openai/gpt-5.3-codex` / `openai/o4-mini`, 또는 `zai/glm-4.7-flash` / `zai-coding-plan/glm-5.1` / `zai/glm-5`.
|
|
260
288
|
|
|
261
289
|
#### 3d. 사용자에게 질문: 어떤 모델?
|
|
262
290
|
|
|
@@ -273,23 +301,36 @@ provider가 **하나도 설정되지 않은 경우** 진행을 멈추고, `openc
|
|
|
273
301
|
|
|
274
302
|
> _`coder`와 `explorer`를 `zai-coding-plan/glm-5.1`로, `resolver`/`reviewer`/`deep-reviewer`를 `openai/gpt-5.5`로 핀닝합니다. 진행할까요?_
|
|
275
303
|
|
|
304
|
+
#### 3d-bis. 사용자가 명시적으로 hard cap 원할 때만 `maxParallelSubagents` 설정
|
|
305
|
+
|
|
306
|
+
기본 흐름은 `maxParallelSubagents`를 `resolve.json`에 **쓰지 않습니다**. resolver prompt에 soft 가이드라인이 들어있어요 — 진짜 독립적인 작업이면 coder fan out, rate-limit 에러 보이면 backoff, explorer 무제한, reviewer/deep-reviewer/planner는 본질적으로 singleton. oh-my-openagent (per-model semaphore, 사실상 default 무제한) 와 OpenCode core (built-in concurrency 룰 없음) 의 처리와 동일한 결, rate limit을 이미 인지한 모델을 과제약하지 않음.
|
|
307
|
+
|
|
308
|
+
`maxParallelSubagents`는 사용자가 hard cap을 명시적으로 원할 때만 물어보세요. 흔한 케이스: GLM coding-plan 사용자가 "burst 절대 안 됨" 보장 원할 때. 권장 멘트:
|
|
309
|
+
|
|
310
|
+
> _(optional, 관련된 경우만)_ _`coder` 모델이 GLM이네요 — coding-plan은 burst 시 throttle 걸립니다. 안전하게 `maxParallelSubagents: 2`로 핀닝할까요?_
|
|
311
|
+
|
|
312
|
+
그 외엔 필드 자체를 생략.
|
|
313
|
+
|
|
276
314
|
#### 3e. `~/.config/opencode/resolve.json` 작성
|
|
277
315
|
|
|
278
|
-
|
|
316
|
+
**3단계 (C, 권장)** — `bronze`/`silver`/`gold` alias 사용:
|
|
279
317
|
|
|
280
318
|
```json
|
|
281
319
|
{
|
|
282
|
-
"enabled": ["coder", "resolver", "explorer", "reviewer", "deep-reviewer"],
|
|
320
|
+
"enabled": ["coder", "resolver", "explorer", "reviewer", "deep-reviewer", "planner"],
|
|
283
321
|
"preserveNative": true,
|
|
284
322
|
"context7": true,
|
|
285
323
|
"commands": false,
|
|
286
324
|
"models": {
|
|
287
|
-
"
|
|
288
|
-
"
|
|
289
|
-
"
|
|
290
|
-
"
|
|
291
|
-
"
|
|
292
|
-
"
|
|
325
|
+
"bronze": "<provider>/<스카웃-모델>",
|
|
326
|
+
"silver": "<provider>/<코더-모델>",
|
|
327
|
+
"gold": "<provider>/<추론-모델>",
|
|
328
|
+
"explorer": "bronze",
|
|
329
|
+
"coder": "silver",
|
|
330
|
+
"resolver": "gold",
|
|
331
|
+
"reviewer": "gold",
|
|
332
|
+
"deep-reviewer": "gold",
|
|
333
|
+
"planner": "gold"
|
|
293
334
|
},
|
|
294
335
|
"agents": {
|
|
295
336
|
"coder": { "enabled": true, "mode": "all" },
|
|
@@ -297,49 +338,48 @@ provider가 **하나도 설정되지 않은 경우** 진행을 멈추고, `openc
|
|
|
297
338
|
"explorer": { "enabled": true, "mode": "subagent" },
|
|
298
339
|
"reviewer": { "enabled": true, "mode": "subagent" },
|
|
299
340
|
"deep-reviewer": { "enabled": true, "mode": "subagent" },
|
|
341
|
+
"planner": { "enabled": true, "mode": "subagent" },
|
|
300
342
|
"architect": { "enabled": false },
|
|
301
343
|
"gpt-coder": { "enabled": false },
|
|
302
344
|
"debugger": { "enabled": false },
|
|
303
345
|
"researcher": { "enabled": false }
|
|
304
346
|
},
|
|
305
347
|
"autoApprove": true,
|
|
306
|
-
"
|
|
348
|
+
"autoUpdate": true
|
|
307
349
|
}
|
|
308
350
|
```
|
|
309
351
|
|
|
310
|
-
|
|
352
|
+
**2단계 (B)** — 모델이 두 개뿐이면 bronze와 silver 통합:
|
|
311
353
|
|
|
312
354
|
```json
|
|
313
|
-
{
|
|
314
|
-
"
|
|
315
|
-
"
|
|
316
|
-
"
|
|
317
|
-
"
|
|
318
|
-
"
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
"explorer": "fast",
|
|
323
|
-
"resolver": "strong",
|
|
324
|
-
"reviewer": "strong",
|
|
325
|
-
"deep-reviewer": "strong"
|
|
326
|
-
},
|
|
327
|
-
"agents": {
|
|
328
|
-
"coder": { "enabled": true, "mode": "all" },
|
|
329
|
-
"resolver": { "enabled": true },
|
|
330
|
-
"explorer": { "enabled": true, "mode": "subagent" },
|
|
331
|
-
"reviewer": { "enabled": true, "mode": "subagent" },
|
|
332
|
-
"deep-reviewer": { "enabled": true, "mode": "subagent" },
|
|
333
|
-
"architect": { "enabled": false },
|
|
334
|
-
"gpt-coder": { "enabled": false },
|
|
335
|
-
"debugger": { "enabled": false },
|
|
336
|
-
"researcher": { "enabled": false }
|
|
337
|
-
},
|
|
338
|
-
"autoApprove": true,
|
|
339
|
-
"maxParallelSubagents": 2
|
|
355
|
+
"models": {
|
|
356
|
+
"silver": "<provider>/<코더-모델>",
|
|
357
|
+
"gold": "<provider>/<추론-모델>",
|
|
358
|
+
"explorer": "silver",
|
|
359
|
+
"coder": "silver",
|
|
360
|
+
"resolver": "gold",
|
|
361
|
+
"reviewer": "gold",
|
|
362
|
+
"deep-reviewer": "gold",
|
|
363
|
+
"planner": "gold"
|
|
340
364
|
}
|
|
341
365
|
```
|
|
342
366
|
|
|
367
|
+
**단일 (A)** — 모든 역할이 단일 모델:
|
|
368
|
+
|
|
369
|
+
```json
|
|
370
|
+
"models": {
|
|
371
|
+
"gold": "<provider>/<단일-모델>",
|
|
372
|
+
"explorer": "gold",
|
|
373
|
+
"coder": "gold",
|
|
374
|
+
"resolver": "gold",
|
|
375
|
+
"reviewer": "gold",
|
|
376
|
+
"deep-reviewer": "gold",
|
|
377
|
+
"planner": "gold"
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
`<provider>/<모델>` 플레이스홀더는 모두 사용자가 고른 **정확한** ID로 교체. **`maxParallelSubagents`는 사용자가 명시적으로 hard cap 요청한 경우에만 추가** (3d-bis 참고).
|
|
382
|
+
|
|
343
383
|
`<provider>/<모델>` 플레이스홀더는 모두 사용자가 3b/3d에서 고른 **정확한** ID 문자열로 교체 — 임의 생성 금지, 자동완성 금지, 버전 드리프트 금지. 고른 모델을 `provider/model` 문자열로 매핑할 수 없으면 추측하지 말고 사용자에게 다시 물어보세요.
|
|
344
384
|
|
|
345
385
|
> 활성 에이전트마다 명시적인 `enabled: true`를 두어 파일이 자체 문서화됩니다. `enabled` 배열이 여전히 권위 있으며, 에이전트별 플래그는 사람이 파일을 읽을 때의 모호함만 제거합니다.
|
package/README.md
CHANGED
|
@@ -222,6 +222,29 @@ Add the string `"opencode-resolve"` to the `plugin` array. **Do not use the inli
|
|
|
222
222
|
|
|
223
223
|
> If the user already has other plugins, append `"opencode-resolve"` to the existing array. **Never overwrite** their existing config.
|
|
224
224
|
|
|
225
|
+
#### 2b. Offer the recommended companion plugins
|
|
226
|
+
|
|
227
|
+
Before moving to model selection, ask the user about two **independent** companion plugins — one question each, recommended-by-default. Both are separate OpenCode plugins; opencode-resolve does not require them but pairs well with both.
|
|
228
|
+
|
|
229
|
+
**Companion 1 — `@tarquinen/opencode-dcp` (Dynamic Context Pruning)**
|
|
230
|
+
|
|
231
|
+
> _opencode-resolve pairs well with **`@tarquinen/opencode-dcp`** — it prunes obsolete tool outputs so long resolver loops cost fewer tokens. Want to install it? (recommended)_
|
|
232
|
+
|
|
233
|
+
**Companion 2 — `@slkiser/opencode-quota` (quota & usage tracking)**
|
|
234
|
+
|
|
235
|
+
> _And **`@slkiser/opencode-quota`** — shows your live token/quota usage without polluting the context window. Supports GLM coding-plan, OpenAI Plus/Pro, Qwen, and more. Useful for keeping an eye on your own usage / remaining quota. Install it? (recommended)_
|
|
236
|
+
|
|
237
|
+
For each **yes** answer:
|
|
238
|
+
|
|
239
|
+
1. Run `opencode plugin <package>@latest --global --force` (primes the cache).
|
|
240
|
+
2. Append `"<package>@latest"` to the `plugin` array in `~/.config/opencode/opencode.json`, only if it is not already present.
|
|
241
|
+
|
|
242
|
+
Where `<package>` is `@tarquinen/opencode-dcp` or `@slkiser/opencode-quota`.
|
|
243
|
+
|
|
244
|
+
For each **no**, skip — do not modify `opencode.json` for that entry. Proceed to Step 3 either way.
|
|
245
|
+
|
|
246
|
+
> `context7` MCP is registered automatically by opencode-resolve at runtime when `context7: true` (the default in `resolve.json`). No separate question is needed for that.
|
|
247
|
+
|
|
225
248
|
### Step 3 — Pick models interactively, then write `~/.config/opencode/resolve.json`
|
|
226
249
|
|
|
227
250
|
The LLM **drives a short Q&A** here. Goal: end with `resolve.json` containing model IDs the user explicitly chose from their own configuration.
|
|
@@ -251,12 +274,17 @@ Print the candidate list and ask. Example wording the LLM should use:
|
|
|
251
274
|
|
|
252
275
|
If exactly one provider candidate exists, skip the question and use it.
|
|
253
276
|
|
|
254
|
-
#### 3c. Ask the user: single-tier or split?
|
|
277
|
+
#### 3c. Ask the user: single, two-tier, or three-tier split?
|
|
255
278
|
|
|
256
|
-
|
|
279
|
+
The default recommendation is **C (three-tier bronze/silver/gold)** — opencode-resolve has three qualitatively distinct workloads (read-only scout → write/patch → reason/judge) and three matching tiers cost about the same as two-tier but read role costs more accurately. Fall back to B if the user only has two models, A only when they have one.
|
|
280
|
+
|
|
281
|
+
> _Recommended: **three-tier** — a cheap scout model for `explorer`, a mid coder for `coder`, and a strong reasoner for `resolver`/`reviewer`/`deep-reviewer`/`planner`. Choose:_
|
|
257
282
|
>
|
|
258
|
-
>
|
|
259
|
-
> B.
|
|
283
|
+
> **C. Three-tier — bronze (scout) + silver (coder) + gold (reasoner) — recommended**
|
|
284
|
+
> B. Two-tier — fast + strong (when you only have two models)
|
|
285
|
+
> A. Single model for all roles (only if you have one model or want maximum simplicity)
|
|
286
|
+
|
|
287
|
+
Default to C if the user just hits enter or says "recommended". Only fall back if they don't have enough distinct models. Same-provider split is fully valid (e.g. `openai/gpt-4o-mini` / `openai/gpt-5.3-codex` / `openai/o4-mini`, or `zai/glm-4.7-flash` / `zai-coding-plan/glm-5.1` / `zai/glm-5`).
|
|
260
288
|
|
|
261
289
|
#### 3d. Ask the user: which model(s)?
|
|
262
290
|
|
|
@@ -273,23 +301,36 @@ Confirm each pick back to the user before writing the file. Example:
|
|
|
273
301
|
|
|
274
302
|
> _I'll pin `coder` and `explorer` to `zai-coding-plan/glm-5.1`, and `resolver`/`reviewer`/`deep-reviewer` to `openai/gpt-5.5`. Proceed?_
|
|
275
303
|
|
|
304
|
+
#### 3d-bis. Skip `maxParallelSubagents` unless the user explicitly wants a hard cap
|
|
305
|
+
|
|
306
|
+
The default flow does **not** write `maxParallelSubagents` to `resolve.json`. The resolver prompt ships with soft fan-out guidance — fan out coders when work is genuinely independent, back off on rate-limit errors, explorer is unrestricted, reviewer/deep-reviewer/planner are singletons by nature. This matches how oh-my-openagent (per-model semaphore, default no real cap) and OpenCode core (no built-in concurrency rule) handle the same concern, and avoids overconstraining a model that's already aware of rate limits.
|
|
307
|
+
|
|
308
|
+
Only ask about `maxParallelSubagents` if the user explicitly mentions wanting to cap fan-out. Common case: GLM coding-plan users who want to **guarantee** they never burst beyond 1 or 2 coder calls. Suggested wording:
|
|
309
|
+
|
|
310
|
+
> _(optional, ask only if relevant)_ _Your `coder` model is GLM — the coding-plan throttles under bursts. Pin `maxParallelSubagents: 2` to be safe?_
|
|
311
|
+
|
|
312
|
+
Otherwise, omit the field entirely.
|
|
313
|
+
|
|
276
314
|
#### 3e. Write `~/.config/opencode/resolve.json`
|
|
277
315
|
|
|
278
|
-
|
|
316
|
+
**Three-tier (C, recommended)** — use the `bronze`/`silver`/`gold` aliases:
|
|
279
317
|
|
|
280
318
|
```json
|
|
281
319
|
{
|
|
282
|
-
"enabled": ["coder", "resolver", "explorer", "reviewer", "deep-reviewer"],
|
|
320
|
+
"enabled": ["coder", "resolver", "explorer", "reviewer", "deep-reviewer", "planner"],
|
|
283
321
|
"preserveNative": true,
|
|
284
322
|
"context7": true,
|
|
285
323
|
"commands": false,
|
|
286
324
|
"models": {
|
|
287
|
-
"
|
|
288
|
-
"
|
|
289
|
-
"
|
|
290
|
-
"
|
|
291
|
-
"
|
|
292
|
-
"
|
|
325
|
+
"bronze": "<provider>/<scout-model>",
|
|
326
|
+
"silver": "<provider>/<coder-model>",
|
|
327
|
+
"gold": "<provider>/<reasoner-model>",
|
|
328
|
+
"explorer": "bronze",
|
|
329
|
+
"coder": "silver",
|
|
330
|
+
"resolver": "gold",
|
|
331
|
+
"reviewer": "gold",
|
|
332
|
+
"deep-reviewer": "gold",
|
|
333
|
+
"planner": "gold"
|
|
293
334
|
},
|
|
294
335
|
"agents": {
|
|
295
336
|
"coder": { "enabled": true, "mode": "all" },
|
|
@@ -297,49 +338,48 @@ If the user picked **A (single-tier)**, write:
|
|
|
297
338
|
"explorer": { "enabled": true, "mode": "subagent" },
|
|
298
339
|
"reviewer": { "enabled": true, "mode": "subagent" },
|
|
299
340
|
"deep-reviewer": { "enabled": true, "mode": "subagent" },
|
|
341
|
+
"planner": { "enabled": true, "mode": "subagent" },
|
|
300
342
|
"architect": { "enabled": false },
|
|
301
343
|
"gpt-coder": { "enabled": false },
|
|
302
344
|
"debugger": { "enabled": false },
|
|
303
345
|
"researcher": { "enabled": false }
|
|
304
346
|
},
|
|
305
347
|
"autoApprove": true,
|
|
306
|
-
"
|
|
348
|
+
"autoUpdate": true
|
|
307
349
|
}
|
|
308
350
|
```
|
|
309
351
|
|
|
310
|
-
|
|
352
|
+
**Two-tier (B)** — collapse bronze and silver if the user has only two models:
|
|
311
353
|
|
|
312
354
|
```json
|
|
313
|
-
{
|
|
314
|
-
"
|
|
315
|
-
"
|
|
316
|
-
"
|
|
317
|
-
"
|
|
318
|
-
"
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
"explorer": "fast",
|
|
323
|
-
"resolver": "strong",
|
|
324
|
-
"reviewer": "strong",
|
|
325
|
-
"deep-reviewer": "strong"
|
|
326
|
-
},
|
|
327
|
-
"agents": {
|
|
328
|
-
"coder": { "enabled": true, "mode": "all" },
|
|
329
|
-
"resolver": { "enabled": true },
|
|
330
|
-
"explorer": { "enabled": true, "mode": "subagent" },
|
|
331
|
-
"reviewer": { "enabled": true, "mode": "subagent" },
|
|
332
|
-
"deep-reviewer": { "enabled": true, "mode": "subagent" },
|
|
333
|
-
"architect": { "enabled": false },
|
|
334
|
-
"gpt-coder": { "enabled": false },
|
|
335
|
-
"debugger": { "enabled": false },
|
|
336
|
-
"researcher": { "enabled": false }
|
|
337
|
-
},
|
|
338
|
-
"autoApprove": true,
|
|
339
|
-
"maxParallelSubagents": 2
|
|
355
|
+
"models": {
|
|
356
|
+
"silver": "<provider>/<coder-model>",
|
|
357
|
+
"gold": "<provider>/<reasoner-model>",
|
|
358
|
+
"explorer": "silver",
|
|
359
|
+
"coder": "silver",
|
|
360
|
+
"resolver": "gold",
|
|
361
|
+
"reviewer": "gold",
|
|
362
|
+
"deep-reviewer": "gold",
|
|
363
|
+
"planner": "gold"
|
|
340
364
|
}
|
|
341
365
|
```
|
|
342
366
|
|
|
367
|
+
**Single-tier (A)** — every role on one model:
|
|
368
|
+
|
|
369
|
+
```json
|
|
370
|
+
"models": {
|
|
371
|
+
"gold": "<provider>/<single-model>",
|
|
372
|
+
"explorer": "gold",
|
|
373
|
+
"coder": "gold",
|
|
374
|
+
"resolver": "gold",
|
|
375
|
+
"reviewer": "gold",
|
|
376
|
+
"deep-reviewer": "gold",
|
|
377
|
+
"planner": "gold"
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Replace every `<provider>/<model>` placeholder with the **exact** ID strings the user picked. **Do not** add `maxParallelSubagents` unless the user explicitly asked for a hard cap (see 3d-bis).
|
|
382
|
+
|
|
343
383
|
Replace every `<provider>/<model>` placeholder with the **exact** ID strings the user picked in 3b/3d — no inventing, no autocompletion, no version drift. If you cannot map the picked model to a `provider/model` string, ask the user to clarify rather than guessing.
|
|
344
384
|
|
|
345
385
|
> Each enabled agent carries an explicit `enabled: true` so the file is self-documenting. The `enabled` array remains authoritative; the per-agent flag just removes ambiguity for humans reading the file.
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { access, readFile } from "node:fs/promises";
|
|
3
4
|
import { homedir } from "node:os";
|
|
4
5
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
const PLUGIN_VERSION = readPluginVersion();
|
|
8
|
+
const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
9
|
+
const UPDATE_CHECK_FILE = join(homedir(), ".cache", "opencode-resolve", "update-check.json");
|
|
10
|
+
const PLUGIN_CACHE_DIR = join(homedir(), ".cache", "opencode", "packages", "opencode-resolve@latest");
|
|
7
11
|
function readPluginVersion() {
|
|
8
12
|
try {
|
|
9
13
|
const pkgRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
@@ -16,10 +20,34 @@ function readPluginVersion() {
|
|
|
16
20
|
}
|
|
17
21
|
console.log(`[opencode-resolve] v${PLUGIN_VERSION} loaded`);
|
|
18
22
|
const DEFAULT_MODELS = {};
|
|
19
|
-
const DEFAULT_ENABLED = ["coder", "resolver", "explorer", "reviewer", "deep-reviewer"];
|
|
20
|
-
const VALID_AGENT_NAMES = [
|
|
23
|
+
const DEFAULT_ENABLED = ["coder", "resolver", "explorer", "reviewer", "deep-reviewer", "planner"];
|
|
24
|
+
const VALID_AGENT_NAMES = [
|
|
25
|
+
"coder",
|
|
26
|
+
"reviewer",
|
|
27
|
+
"resolver",
|
|
28
|
+
"architect",
|
|
29
|
+
"gpt-coder",
|
|
30
|
+
"debugger",
|
|
31
|
+
"researcher",
|
|
32
|
+
"explorer",
|
|
33
|
+
"deep-reviewer",
|
|
34
|
+
"planner",
|
|
35
|
+
];
|
|
21
36
|
const VALID_AGENT_NAME_SET = new Set(VALID_AGENT_NAMES);
|
|
22
|
-
const VALID_MODEL_ALIASES = [
|
|
37
|
+
const VALID_MODEL_ALIASES = [
|
|
38
|
+
...VALID_AGENT_NAMES,
|
|
39
|
+
"glm",
|
|
40
|
+
"gpt",
|
|
41
|
+
"quick",
|
|
42
|
+
"deep",
|
|
43
|
+
"fast",
|
|
44
|
+
"strong",
|
|
45
|
+
"mini",
|
|
46
|
+
"codex",
|
|
47
|
+
"bronze",
|
|
48
|
+
"silver",
|
|
49
|
+
"gold",
|
|
50
|
+
];
|
|
23
51
|
const VALID_MODEL_ALIAS_SET = new Set(VALID_MODEL_ALIASES);
|
|
24
52
|
const VALID_MODES = new Set(["subagent", "primary", "all"]);
|
|
25
53
|
const VALID_PERMISSION_VALUES = new Set(["ask", "allow", "deny"]);
|
|
@@ -32,6 +60,7 @@ const VALID_TOP_LEVEL_KEYS = new Set([
|
|
|
32
60
|
"commands",
|
|
33
61
|
"autoApprove",
|
|
34
62
|
"maxParallelSubagents",
|
|
63
|
+
"autoUpdate",
|
|
35
64
|
"config",
|
|
36
65
|
]);
|
|
37
66
|
const DEFAULT_MAX_PARALLEL_SUBAGENTS = 2;
|
|
@@ -47,16 +76,20 @@ const VALID_AGENT_KEYS = new Set([
|
|
|
47
76
|
"permission",
|
|
48
77
|
]);
|
|
49
78
|
function buildResolverPrompt(maxParallelSubagents) {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
79
|
+
const explicitLimit = typeof maxParallelSubagents === "number" && Number.isFinite(maxParallelSubagents)
|
|
80
|
+
? Math.max(1, Math.trunc(maxParallelSubagents))
|
|
81
|
+
: undefined;
|
|
82
|
+
const parallelRule = explicitLimit === undefined
|
|
83
|
+
? "Parallel dispatch policy: fan out coder dispatches when the work is genuinely independent (different files, no shared state). Rate limits are per-model — coder/silver-tier models (e.g. GLM coding-plan) can throttle under heavy burst, so back off if you see rate-limit errors. Explorer runs on a lighter model and fans out freely when scope discovery genuinely needs it. Reviewer, deep-reviewer, and planner are dispatched as singletons by their nature."
|
|
84
|
+
: explicitLimit === 1
|
|
85
|
+
? "CRITICAL: User has pinned the coder concurrency cap to 1. Dispatch at most ONE coder concurrently. Wait for an in-flight coder to finish before dispatching another. Explorer is unrestricted (read-only, light model). Reviewer, deep-reviewer, and planner are singletons by nature."
|
|
86
|
+
: `CRITICAL: User has pinned the coder concurrency cap to ${explicitLimit}. Dispatch at most ${explicitLimit} coders concurrently. Wait for in-flight coders to finish before dispatching more. Explorer is unrestricted (read-only, light model). Reviewer, deep-reviewer, and planner are singletons by nature.`;
|
|
54
87
|
return [
|
|
55
88
|
"You are Resolver, the context-efficient orchestrator agent for OpenCode Resolve.",
|
|
56
89
|
"Your job is to drive the user's task to a verified resolution using minimal context and the fewest LLM calls possible.",
|
|
57
90
|
"",
|
|
58
91
|
"Core path: You and Coder form the fixed-role verified resolve loop — this is the default path.",
|
|
59
|
-
"Internal specialist subagents (explorer, reviewer, deep-reviewer) are available by default as subagents, but they are NOT the default path.",
|
|
92
|
+
"Internal specialist subagents (explorer, reviewer, deep-reviewer, planner) are available by default as subagents, but they are NOT the default path.",
|
|
60
93
|
"Dispatch them only when justified — avoid context waste.",
|
|
61
94
|
"",
|
|
62
95
|
"Checkpointed execution: for large tasks, decompose work into small verified checkpoints. For each checkpoint, iterate up to 3 attempts on the same failing checkpoint. When a checkpoint passes verification, carry forward only: decisions, changed files, verification results, and blockers — then proceed to the next checkpoint. If blocked after max 3 attempts on one checkpoint, report the exact blocker with evidence. This preserves context and handles arbitrarily long tasks.",
|
|
@@ -75,6 +108,7 @@ function buildResolverPrompt(maxParallelSubagents) {
|
|
|
75
108
|
"- explorer: fast read-only codebase scout. Prefer local read/grep/glob for narrow scope; dispatch explorer only when scope is genuinely unknown and local tools are insufficient.",
|
|
76
109
|
"- reviewer: lightweight read-only audit. Dispatch only for post-change verification gaps on non-trivial changes.",
|
|
77
110
|
"- deep-reviewer: thorough read-only review. Dispatch ONLY for risky, security-sensitive, architectural, or high-impact changes.",
|
|
111
|
+
"- planner: advanced read-only planner. Dispatch ONLY when the user explicitly asks for a plan, decomposition, or implementation strategy. Do NOT call planner for routine sub-task planning you can absorb inline.",
|
|
78
112
|
"",
|
|
79
113
|
"Note: this parallel rule is enforced via prompt only — there is no runtime cap on subagent dispatches. Honor it strictly to avoid file conflicts and wasted context.",
|
|
80
114
|
].join("\n");
|
|
@@ -95,6 +129,8 @@ const DEFAULT_AGENT_CONFIG = {
|
|
|
95
129
|
"Implement the smallest patch that satisfies the requirement.",
|
|
96
130
|
"Run targeted verification when practical (single test file, type check, or lint — not full suites).",
|
|
97
131
|
"Return a concise summary: changed files list + command results. No unnecessary prose.",
|
|
132
|
+
"",
|
|
133
|
+
"Scope-discovery gate: dispatch the `explorer` subagent ONLY when the scope is genuinely unclear — i.e. you need to locate 3+ unknown files, OR a named pattern's location is genuinely unknown. For known files, single-file uncertainty, or routine doubt, read directly with the local read/grep/glob tools. Never dispatch explorer for casual exploration — token efficiency is the discipline.",
|
|
98
134
|
].join("\n"),
|
|
99
135
|
permission: {
|
|
100
136
|
edit: "ask",
|
|
@@ -127,7 +163,7 @@ const DEFAULT_AGENT_CONFIG = {
|
|
|
127
163
|
color: "#FF7AC6",
|
|
128
164
|
maxSteps: 30,
|
|
129
165
|
description: "Primary orchestrator in the fixed-role verified loop (resolver→coder). Decomposes work into verified checkpoints, dispatches coder, verifies each, and carries forward progress. Internal subagents (explorer, reviewer, deep-reviewer) are available by default but dispatched only when justified.",
|
|
130
|
-
prompt: buildResolverPrompt(
|
|
166
|
+
prompt: buildResolverPrompt(undefined),
|
|
131
167
|
permission: {
|
|
132
168
|
edit: "ask",
|
|
133
169
|
bash: "ask",
|
|
@@ -236,15 +272,107 @@ const DEFAULT_AGENT_CONFIG = {
|
|
|
236
272
|
webfetch: "ask",
|
|
237
273
|
},
|
|
238
274
|
},
|
|
275
|
+
planner: {
|
|
276
|
+
mode: "subagent",
|
|
277
|
+
color: "#F4A300",
|
|
278
|
+
maxSteps: 10,
|
|
279
|
+
description: "Internal advanced planner dispatched by the resolver when the user explicitly asks for a plan, decomposition, or implementation strategy. Read-only. Returns a concrete plan; never edits code.",
|
|
280
|
+
prompt: [
|
|
281
|
+
"You are Planner, the advanced planning subagent for OpenCode Resolve.",
|
|
282
|
+
"You are dispatched by the resolver only when the user explicitly asks for a plan, decomposition, or implementation strategy — not for routine sub-task planning the resolver handles inline.",
|
|
283
|
+
"You MUST NOT modify the project: no file edits, no writes, no shell commands that change state.",
|
|
284
|
+
"Inspect the relevant code with read-only tools (read, grep, glob, list) before proposing.",
|
|
285
|
+
"Return: clear phasing, file-level boundaries per phase, verification checkpoints, risks, and explicit trade-offs. Be concrete — name files, name decisions, name the cost of each option.",
|
|
286
|
+
"Be token-efficient: produce the smallest plan that fully covers the user's intent. No filler, no boilerplate, no restating the request.",
|
|
287
|
+
].join("\n"),
|
|
288
|
+
permission: {
|
|
289
|
+
edit: "deny",
|
|
290
|
+
bash: "deny",
|
|
291
|
+
webfetch: "ask",
|
|
292
|
+
},
|
|
293
|
+
},
|
|
239
294
|
};
|
|
240
295
|
export const OpencodeResolve = async ({ directory }, options) => {
|
|
241
296
|
return {
|
|
242
297
|
config: async (config) => {
|
|
243
298
|
const resolveConfig = await loadResolveConfig(directory, config, options);
|
|
244
299
|
applyResolveConfig(config, resolveConfig);
|
|
300
|
+
if (resolveConfig.autoUpdate !== false && process.env.OPENCODE_RESOLVE_NO_AUTO_UPDATE !== "1") {
|
|
301
|
+
maybeAutoUpdate().catch(() => { });
|
|
302
|
+
}
|
|
245
303
|
},
|
|
246
304
|
};
|
|
247
305
|
};
|
|
306
|
+
async function maybeAutoUpdate() {
|
|
307
|
+
try {
|
|
308
|
+
const previous = readUpdateCheckCache();
|
|
309
|
+
if (previous && Date.now() - previous.checkedAt < UPDATE_CHECK_INTERVAL_MS) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
// ignore corrupt cache and re-check
|
|
315
|
+
}
|
|
316
|
+
let latest;
|
|
317
|
+
try {
|
|
318
|
+
const response = await fetch("https://registry.npmjs.org/opencode-resolve/latest", {
|
|
319
|
+
headers: { Accept: "application/json" },
|
|
320
|
+
signal: AbortSignal.timeout(5000),
|
|
321
|
+
});
|
|
322
|
+
if (!response.ok)
|
|
323
|
+
return;
|
|
324
|
+
const data = (await response.json());
|
|
325
|
+
if (typeof data?.version !== "string")
|
|
326
|
+
return;
|
|
327
|
+
latest = data.version;
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
mkdirSync(dirname(UPDATE_CHECK_FILE), { recursive: true });
|
|
334
|
+
writeFileSync(UPDATE_CHECK_FILE, JSON.stringify({ checkedAt: Date.now(), latest }));
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
// best-effort; don't block on cache write failure
|
|
338
|
+
}
|
|
339
|
+
if (!isNewerVersion(latest, PLUGIN_VERSION))
|
|
340
|
+
return;
|
|
341
|
+
console.log(`[opencode-resolve] new version v${latest} available (current: v${PLUGIN_VERSION}) — refreshing cache in background. Restart OpenCode to activate (current session stays on v${PLUGIN_VERSION}).`);
|
|
342
|
+
try {
|
|
343
|
+
spawn("sh", ["-c", `rm -rf "${PLUGIN_CACHE_DIR}" && opencode plugin opencode-resolve --global --force`], { detached: true, stdio: "ignore" }).unref();
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
// If spawn fails, the user already saw the notice and can run the command manually.
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function readUpdateCheckCache() {
|
|
350
|
+
try {
|
|
351
|
+
const raw = readFileSync(UPDATE_CHECK_FILE, "utf8");
|
|
352
|
+
const parsed = JSON.parse(raw);
|
|
353
|
+
if (typeof parsed?.checkedAt === "number" &&
|
|
354
|
+
typeof parsed?.latest === "string") {
|
|
355
|
+
return { checkedAt: parsed.checkedAt, latest: parsed.latest };
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
// file missing or unparseable
|
|
360
|
+
}
|
|
361
|
+
return undefined;
|
|
362
|
+
}
|
|
363
|
+
function isNewerVersion(candidate, baseline) {
|
|
364
|
+
const a = candidate.split(".").map((n) => Number.parseInt(n, 10));
|
|
365
|
+
const b = baseline.split(".").map((n) => Number.parseInt(n, 10));
|
|
366
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
367
|
+
const av = Number.isFinite(a[i]) ? a[i] : 0;
|
|
368
|
+
const bv = Number.isFinite(b[i]) ? b[i] : 0;
|
|
369
|
+
if (av > bv)
|
|
370
|
+
return true;
|
|
371
|
+
if (av < bv)
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
248
376
|
export default OpencodeResolve;
|
|
249
377
|
async function loadResolveConfig(directory, opencodeConfig, options) {
|
|
250
378
|
const pluginOptions = normalizeResolveConfig(options ?? getPluginOptions(opencodeConfig), "plugin options");
|
|
@@ -265,7 +393,7 @@ function applyResolveConfig(config, resolveConfig) {
|
|
|
265
393
|
const models = { ...DEFAULT_MODELS, ...resolveConfig.models };
|
|
266
394
|
const defaultModel = typeof config.model === "string" ? config.model : undefined;
|
|
267
395
|
const autoApprove = resolveConfig.autoApprove !== false;
|
|
268
|
-
const maxParallelSubagents = resolveConfig.maxParallelSubagents
|
|
396
|
+
const maxParallelSubagents = resolveConfig.maxParallelSubagents;
|
|
269
397
|
config.agent ??= {};
|
|
270
398
|
for (const name of Object.keys(DEFAULT_AGENT_CONFIG)) {
|
|
271
399
|
const override = resolveConfig.agents?.[name];
|
|
@@ -327,7 +455,7 @@ function defaultResolveConfig() {
|
|
|
327
455
|
context7: true,
|
|
328
456
|
commands: false,
|
|
329
457
|
autoApprove: true,
|
|
330
|
-
|
|
458
|
+
autoUpdate: true,
|
|
331
459
|
};
|
|
332
460
|
}
|
|
333
461
|
function mergeResolveConfig(...configs) {
|
|
@@ -341,6 +469,7 @@ function mergeResolveConfig(...configs) {
|
|
|
341
469
|
result.commands = config.commands ?? result.commands;
|
|
342
470
|
result.autoApprove = config.autoApprove ?? result.autoApprove;
|
|
343
471
|
result.maxParallelSubagents = config.maxParallelSubagents ?? result.maxParallelSubagents;
|
|
472
|
+
result.autoUpdate = config.autoUpdate ?? result.autoUpdate;
|
|
344
473
|
result.models = { ...result.models, ...config.models };
|
|
345
474
|
result.agents = mergeAgents(result.agents, config.agents);
|
|
346
475
|
}
|
|
@@ -451,6 +580,8 @@ function normalizeResolveConfig(value, source) {
|
|
|
451
580
|
result.commands = expectBoolean(config.commands, `${source}.commands`);
|
|
452
581
|
if (config.autoApprove !== undefined)
|
|
453
582
|
result.autoApprove = expectBoolean(config.autoApprove, `${source}.autoApprove`);
|
|
583
|
+
if (config.autoUpdate !== undefined)
|
|
584
|
+
result.autoUpdate = expectBoolean(config.autoUpdate, `${source}.autoUpdate`);
|
|
454
585
|
if (config.maxParallelSubagents !== undefined) {
|
|
455
586
|
const limit = expectNumber(config.maxParallelSubagents, `${source}.maxParallelSubagents`);
|
|
456
587
|
if (!Number.isInteger(limit) || limit < 1) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"enabled": ["coder", "resolver", "explorer", "reviewer", "deep-reviewer"],
|
|
2
|
+
"enabled": ["coder", "resolver", "explorer", "reviewer", "deep-reviewer", "planner"],
|
|
3
3
|
"preserveNative": true,
|
|
4
4
|
"context7": true,
|
|
5
5
|
"commands": false,
|
|
6
6
|
"autoApprove": true,
|
|
7
|
-
"
|
|
7
|
+
"autoUpdate": true,
|
|
8
8
|
"models": {},
|
|
9
9
|
"agents": {
|
|
10
10
|
"coder": {
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
"enabled": true,
|
|
27
27
|
"mode": "subagent"
|
|
28
28
|
},
|
|
29
|
+
"planner": {
|
|
30
|
+
"enabled": true,
|
|
31
|
+
"mode": "subagent"
|
|
32
|
+
},
|
|
29
33
|
"architect": {
|
|
30
34
|
"enabled": false
|
|
31
35
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-resolve",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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",
|
package/scripts/postinstall.mjs
CHANGED