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 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
- > _모든 역할에 단일 모델을 쓸까요, 아니면 분할 `coder`/`explorer`에는 빠른 모델, `resolver`/`reviewer`/`deep-reviewer`에는강력한 모델을 쓸까요?_
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
- > A. 모든 역할에 단일 모델 (가장 단순, 단일 provider에 권장)
259
- > B. 분할 — fast + strong (저렴한 모델 + 강력한 모델 둘 다 가진 경우 권장)
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
- 사용자가 **A (단일)** 골랐다면:
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
- "primary": "<선택한-provider>/<선택한-모델>",
288
- "coder": "primary",
289
- "explorer": "primary",
290
- "resolver": "primary",
291
- "reviewer": "primary",
292
- "deep-reviewer": "primary"
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
- "maxParallelSubagents": 2
348
+ "autoUpdate": true
307
349
  }
308
350
  ```
309
351
 
310
- 사용자가 **B (분할)** 골랐다면:
352
+ **2단계 (B)** 모델이 두 개뿐이면 bronze와 silver 통합:
311
353
 
312
354
  ```json
313
- {
314
- "enabled": ["coder", "resolver", "explorer", "reviewer", "deep-reviewer"],
315
- "preserveNative": true,
316
- "context7": true,
317
- "commands": false,
318
- "models": {
319
- "fast": "<provider>/<fast-모델>",
320
- "strong": "<provider>/<strong-모델>",
321
- "coder": "fast",
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
- > _Do you want a single model for every role, or split a fast model for `coder`/`explorer` and a stronger model for `resolver`/`reviewer`/`deep-reviewer`?_
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
- > A. Single model for all roles (simplest, recommended for one-provider setups)
259
- > B. Split — fast + strong (recommended when you have both a cheap and a strong model)
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
- If the user picked **A (single-tier)**, write:
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
- "primary": "<chosen-provider>/<chosen-model>",
288
- "coder": "primary",
289
- "explorer": "primary",
290
- "resolver": "primary",
291
- "reviewer": "primary",
292
- "deep-reviewer": "primary"
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
- "maxParallelSubagents": 2
348
+ "autoUpdate": true
307
349
  }
308
350
  ```
309
351
 
310
- If the user picked **B (split)**, write:
352
+ **Two-tier (B)** — collapse bronze and silver if the user has only two models:
311
353
 
312
354
  ```json
313
- {
314
- "enabled": ["coder", "resolver", "explorer", "reviewer", "deep-reviewer"],
315
- "preserveNative": true,
316
- "context7": true,
317
- "commands": false,
318
- "models": {
319
- "fast": "<provider>/<fast-model>",
320
- "strong": "<provider>/<strong-model>",
321
- "coder": "fast",
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 { readFileSync } from "node:fs";
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 = ["coder", "reviewer", "resolver", "architect", "gpt-coder", "debugger", "researcher", "explorer", "deep-reviewer"];
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 = [...VALID_AGENT_NAMES, "glm", "gpt", "quick", "deep", "fast", "strong", "mini", "codex"];
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 limit = Math.max(1, Math.trunc(maxParallelSubagents));
51
- const parallelRule = limit === 1
52
- ? "CRITICAL: Dispatch at most ONE subagent of each role concurrently. Never run two coders in parallel. Wait for an in-flight coder to finish before dispatching another."
53
- : `CRITICAL: Dispatch at most ${limit} subagents of the same role concurrently. Never exceed ${limit} coders in parallel. Wait for in-flight subagents of a given role to finish before dispatching more of that role.`;
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(DEFAULT_MAX_PARALLEL_SUBAGENTS),
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 ?? DEFAULT_MAX_PARALLEL_SUBAGENTS;
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
- maxParallelSubagents: DEFAULT_MAX_PARALLEL_SUBAGENTS,
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
- "maxParallelSubagents": 2,
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.4",
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",
@@ -13,7 +13,6 @@ const exampleConfigPath = join(root, "opencode-resolve.example.json")
13
13
 
14
14
  const ADDITIVE_DEFAULTS = {
15
15
  autoApprove: true,
16
- maxParallelSubagents: 2,
17
16
  }
18
17
 
19
18
  if (process.env.OPENCODE_RESOLVE_SKIP_POSTINSTALL === "1") {