opencode-swarm 7.18.0 → 7.18.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 +27 -22
- package/dist/cli/index.js +273 -41
- package/dist/commands/council.d.ts +1 -1
- package/dist/commands/doctor.d.ts +7 -2
- package/dist/commands/index.d.ts +1 -1
- package/dist/commands/registry.d.ts +1 -0
- package/dist/index.js +410 -73
- package/dist/services/config-doctor.d.ts +15 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ bunx opencode-swarm install
|
|
|
24
24
|
|
|
25
25
|
> This single command installs the package, registers it as an OpenCode plugin, disables conflicting default agents, and creates a ready-to-edit config at `~/.config/opencode/opencode-swarm.json`. Requires [Bun](https://bun.sh) (`bun --version` to check). If you must use npm: `npm install -g opencode-swarm && opencode-swarm install`.
|
|
26
26
|
|
|
27
|
-
> ⚠️ **
|
|
27
|
+
> ⚠️ **Select a Swarm architect before starting work.** The installer registers Swarm architect agents as primary choices, but the default OpenCode `Build` and `Plan` modes **bypass this plugin entirely** — none of the gates, reviewers, or test agents below run. Open the OpenCode mode/agent picker and choose `architect` or a `*_architect`; it then coordinates every other agent automatically. If you ever see Swarm "do nothing," this is almost always the cause.
|
|
28
28
|
|
|
29
29
|
### Why Swarm?
|
|
30
30
|
|
|
@@ -41,7 +41,7 @@ Most AI coding tools let one model write code and ask that same model whether th
|
|
|
41
41
|
- 🆓 **Free tier** — works with OpenCode Zen's free model roster
|
|
42
42
|
- ⚙️ **Fully configurable** — override any agent's model, disable agents, tune guardrails
|
|
43
43
|
|
|
44
|
-
> **The Swarm architect
|
|
44
|
+
> **The Swarm architect coordinates all other agents automatically after you select it.** You never manually switch between internal roles. If you use the default OpenCode `Build` / `Plan` modes, the plugin is bypassed entirely (see the install warning above).
|
|
45
45
|
|
|
46
46
|
---
|
|
47
47
|
|
|
@@ -126,9 +126,9 @@ The 15-minute guide covers:
|
|
|
126
126
|
- Troubleshooting common issues
|
|
127
127
|
|
|
128
128
|
On first run, Swarm automatically:
|
|
129
|
-
- Creates project config at `.opencode/opencode-swarm.json`
|
|
130
|
-
-
|
|
131
|
-
- Shows a welcome message with next steps
|
|
129
|
+
- Creates project config at `.opencode/opencode-swarm.json` if missing
|
|
130
|
+
- Registers Swarm architect agents as primary choices in OpenCode
|
|
131
|
+
- Shows a welcome message with next steps the first time you run `/swarm`
|
|
132
132
|
|
|
133
133
|
---
|
|
134
134
|
|
|
@@ -142,8 +142,7 @@ No animated GIF is shipped in the repo — instead, here is the exact terminal s
|
|
|
142
142
|
# 1. Install the plugin (5s)
|
|
143
143
|
bunx opencode-swarm install
|
|
144
144
|
|
|
145
|
-
# 2. Open opencode
|
|
146
|
-
# (the architect is auto-selected; manual selection is only needed to override)
|
|
145
|
+
# 2. Open opencode and select the Swarm architect in the agent/mode picker
|
|
147
146
|
opencode
|
|
148
147
|
|
|
149
148
|
# 3. Inside the OpenCode session, verify Swarm is live (5s)
|
|
@@ -167,7 +166,7 @@ Build me a JWT auth helper with tests.
|
|
|
167
166
|
│ ✓ created .opencode/opencode-swarm.json │
|
|
168
167
|
│ │
|
|
169
168
|
│ $ opencode │
|
|
170
|
-
│ [Swarm] Welcome!
|
|
169
|
+
│ [Swarm] Welcome! Run /swarm diagnose, then /swarm agents │
|
|
171
170
|
│ │
|
|
172
171
|
│ > /swarm help │
|
|
173
172
|
│ Available commands: status, plan, agents, help, diagnose... │
|
|
@@ -218,7 +217,7 @@ in your `opencode-swarm.json`.
|
|
|
218
217
|
|
|
219
218
|
## Commands
|
|
220
219
|
|
|
221
|
-
|
|
220
|
+
Core subcommands at a glance:
|
|
222
221
|
|
|
223
222
|
```bash
|
|
224
223
|
/swarm help [command] # List all commands or get detailed help for a specific command
|
|
@@ -234,7 +233,7 @@ Use `/swarm help` to see all available commands categorized by function. Use `/s
|
|
|
234
233
|
|
|
235
234
|
Nine commands display a ⚠️ warning in help output because they share names with Claude Code built-in slash commands (e.g., `/plan`, `/reset`, `/status`). The warning reminds you to always use `/swarm <command>` — the bare CC command does something different and sometimes destructive. See [docs/commands.md#claude-code-command-conflicts](docs/commands.md#claude-code-command-conflicts) for the full conflict registry.
|
|
236
235
|
|
|
237
|
-
See [docs/commands.md](docs/commands.md) for the full reference
|
|
236
|
+
See [docs/commands.md](docs/commands.md) for the full command reference.
|
|
238
237
|
|
|
239
238
|
## Command Aliases
|
|
240
239
|
|
|
@@ -368,11 +367,14 @@ No API key required. Excellent starting point:
|
|
|
368
367
|
"agents": {
|
|
369
368
|
"coder": { "model": "opencode/minimax-m2.5-free" },
|
|
370
369
|
"reviewer": { "model": "opencode/big-pickle" },
|
|
370
|
+
"critic": { "model": "opencode/big-pickle" },
|
|
371
371
|
"explorer": { "model": "opencode/big-pickle" }
|
|
372
372
|
}
|
|
373
373
|
}
|
|
374
374
|
```
|
|
375
375
|
|
|
376
|
+
Zen's roster changes. Always confirm current IDs with `/models` in OpenCode or `https://opencode.ai/zen/v1/models` before pasting a model into config. Do not copy private workspace providers such as `grove-openai/*` unless that provider appears in your own OpenCode model list.
|
|
377
|
+
|
|
376
378
|
### Paid Providers
|
|
377
379
|
|
|
378
380
|
For production, mix providers by role:
|
|
@@ -381,11 +383,14 @@ For production, mix providers by role:
|
|
|
381
383
|
|---|---|---|
|
|
382
384
|
| architect | OpenCode UI selection | Needs strongest reasoning |
|
|
383
385
|
| coder | minimax-coding-plan/MiniMax-M2.5 | Fast, accurate code generation |
|
|
384
|
-
|
|
|
385
|
-
|
|
|
386
|
+
| critic | opencode/gpt-5.5 or your strongest reasoning model | Challenges the architect before coding |
|
|
387
|
+
| reviewer | zai-coding-plan/glm-5 or a different strong model | Different training from coder |
|
|
388
|
+
| test_engineer | opencode/big-pickle or another model distinct from coder | Catches test blind spots |
|
|
386
389
|
| explorer | google/gemini-2.5-flash | Fast read-heavy analysis |
|
|
387
390
|
| sme | kimi-for-coding/k2p5 | Strong domain expertise |
|
|
388
391
|
|
|
392
|
+
Model assignment rule of thumb: architect and critic should be your strongest pair, and they should not be the same blind spot. Coder/test_engineer can be faster coding models; explorer/docs/curator can be cheaper readers. Do not put a premium model on `designer` while leaving `critic` on a weaker model.
|
|
393
|
+
|
|
389
394
|
### Provider Formats
|
|
390
395
|
|
|
391
396
|
| Provider | Format | Example |
|
|
@@ -406,7 +411,7 @@ Automatic fallback to a secondary model on transient errors:
|
|
|
406
411
|
"agents": {
|
|
407
412
|
"coder": {
|
|
408
413
|
"model": "anthropic/claude-sonnet-4-20250514",
|
|
409
|
-
"fallback_models": ["opencode/
|
|
414
|
+
"fallback_models": ["opencode/big-pickle"]
|
|
410
415
|
}
|
|
411
416
|
}
|
|
412
417
|
}
|
|
@@ -1144,15 +1149,15 @@ Config file location: `~/.config/opencode/opencode-swarm.json` (global) or `.ope
|
|
|
1144
1149
|
```json
|
|
1145
1150
|
{
|
|
1146
1151
|
"agents": {
|
|
1147
|
-
"architect": { "model": "
|
|
1148
|
-
"coder": { "model": "minimax-
|
|
1149
|
-
"explorer": { "model": "
|
|
1150
|
-
"sme": { "model": "kimi-
|
|
1151
|
-
"critic": { "model": "
|
|
1152
|
+
"architect": { "model": "opencode/claude-opus-4-6" },
|
|
1153
|
+
"coder": { "model": "opencode/minimax-m2.5", "fallback_models": ["opencode/big-pickle"] },
|
|
1154
|
+
"explorer": { "model": "opencode/big-pickle" },
|
|
1155
|
+
"sme": { "model": "opencode/kimi-k2.6" },
|
|
1156
|
+
"critic": { "model": "opencode/gpt-5.5" },
|
|
1152
1157
|
"reviewer": { "model": "zai-coding-plan/glm-5", "fallback_models": ["opencode/big-pickle"] },
|
|
1153
|
-
"test_engineer": { "model": "minimax-
|
|
1154
|
-
"docs": { "model": "
|
|
1155
|
-
"designer": { "model": "kimi-
|
|
1158
|
+
"test_engineer": { "model": "opencode/minimax-m2.5" },
|
|
1159
|
+
"docs": { "model": "opencode/big-pickle" },
|
|
1160
|
+
"designer": { "model": "opencode/kimi-k2.6" }
|
|
1156
1161
|
},
|
|
1157
1162
|
"guardrails": {
|
|
1158
1163
|
"max_tool_calls": 200,
|
|
@@ -1401,7 +1406,7 @@ bun test
|
|
|
1401
1406
|
- [Installation Guide](docs/installation.md) — comprehensive reference
|
|
1402
1407
|
- [Architecture Deep Dive](docs/architecture.md) — control model, pipeline, tools
|
|
1403
1408
|
- [Design Rationale](docs/design-rationale.md) — why every major decision
|
|
1404
|
-
- [Commands Reference](docs/commands.md) —
|
|
1409
|
+
- [Commands Reference](docs/commands.md) — full `/swarm` command reference
|
|
1405
1410
|
- [Modes Guide](docs/modes.md) — session modes (Turbo, Full-Auto) and project modes (strict/balanced/fast)
|
|
1406
1411
|
- [Configuration](docs/configuration.md) — all config keys and examples
|
|
1407
1412
|
- [Planning Guide](docs/planning.md) — task format, phase structure, sizing
|
package/dist/cli/index.js
CHANGED
|
@@ -34,7 +34,7 @@ var package_default;
|
|
|
34
34
|
var init_package = __esm(() => {
|
|
35
35
|
package_default = {
|
|
36
36
|
name: "opencode-swarm",
|
|
37
|
-
version: "7.18.
|
|
37
|
+
version: "7.18.2",
|
|
38
38
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
39
39
|
main: "dist/index.js",
|
|
40
40
|
types: "dist/index.d.ts",
|
|
@@ -16501,8 +16501,7 @@ var init_constants = __esm(() => {
|
|
|
16501
16501
|
"skill_improve",
|
|
16502
16502
|
"search",
|
|
16503
16503
|
"doc_scan",
|
|
16504
|
-
"doc_extract"
|
|
16505
|
-
"web_search"
|
|
16504
|
+
"doc_extract"
|
|
16506
16505
|
],
|
|
16507
16506
|
spec_writer: [
|
|
16508
16507
|
"search",
|
|
@@ -16602,67 +16601,67 @@ var init_constants = __esm(() => {
|
|
|
16602
16601
|
DEFAULT_AGENT_CONFIGS = {
|
|
16603
16602
|
coder: {
|
|
16604
16603
|
model: "opencode/minimax-m2.5-free",
|
|
16605
|
-
fallback_models: ["opencode/
|
|
16604
|
+
fallback_models: ["opencode/big-pickle"]
|
|
16606
16605
|
},
|
|
16607
16606
|
reviewer: {
|
|
16608
16607
|
model: "opencode/big-pickle",
|
|
16609
|
-
fallback_models: ["opencode/
|
|
16608
|
+
fallback_models: ["opencode/minimax-m2.5-free"]
|
|
16610
16609
|
},
|
|
16611
16610
|
test_engineer: {
|
|
16612
|
-
model: "opencode/
|
|
16613
|
-
fallback_models: ["opencode/
|
|
16611
|
+
model: "opencode/big-pickle",
|
|
16612
|
+
fallback_models: ["opencode/minimax-m2.5-free"]
|
|
16614
16613
|
},
|
|
16615
16614
|
explorer: {
|
|
16616
16615
|
model: "opencode/big-pickle",
|
|
16617
|
-
fallback_models: ["opencode/
|
|
16616
|
+
fallback_models: ["opencode/minimax-m2.5-free"]
|
|
16618
16617
|
},
|
|
16619
16618
|
sme: {
|
|
16620
16619
|
model: "opencode/big-pickle",
|
|
16621
|
-
fallback_models: ["opencode/
|
|
16620
|
+
fallback_models: ["opencode/minimax-m2.5-free"]
|
|
16622
16621
|
},
|
|
16623
16622
|
critic: {
|
|
16624
16623
|
model: "opencode/big-pickle",
|
|
16625
|
-
fallback_models: ["opencode/
|
|
16624
|
+
fallback_models: ["opencode/minimax-m2.5-free"]
|
|
16626
16625
|
},
|
|
16627
16626
|
docs: {
|
|
16628
16627
|
model: "opencode/big-pickle",
|
|
16629
|
-
fallback_models: ["opencode/
|
|
16628
|
+
fallback_models: ["opencode/minimax-m2.5-free"]
|
|
16630
16629
|
},
|
|
16631
16630
|
designer: {
|
|
16632
16631
|
model: "opencode/big-pickle",
|
|
16633
|
-
fallback_models: ["opencode/
|
|
16632
|
+
fallback_models: ["opencode/minimax-m2.5-free"]
|
|
16634
16633
|
},
|
|
16635
16634
|
critic_sounding_board: {
|
|
16636
|
-
model: "opencode/
|
|
16637
|
-
fallback_models: ["opencode/
|
|
16635
|
+
model: "opencode/big-pickle",
|
|
16636
|
+
fallback_models: ["opencode/minimax-m2.5-free"]
|
|
16638
16637
|
},
|
|
16639
16638
|
critic_drift_verifier: {
|
|
16640
|
-
model: "opencode/
|
|
16641
|
-
fallback_models: ["opencode/
|
|
16639
|
+
model: "opencode/big-pickle",
|
|
16640
|
+
fallback_models: ["opencode/minimax-m2.5-free"]
|
|
16642
16641
|
},
|
|
16643
16642
|
critic_hallucination_verifier: {
|
|
16644
|
-
model: "opencode/
|
|
16645
|
-
fallback_models: ["opencode/
|
|
16643
|
+
model: "opencode/big-pickle",
|
|
16644
|
+
fallback_models: ["opencode/minimax-m2.5-free"]
|
|
16646
16645
|
},
|
|
16647
16646
|
critic_oversight: {
|
|
16648
|
-
model: "opencode/
|
|
16649
|
-
fallback_models: ["opencode/
|
|
16647
|
+
model: "opencode/big-pickle",
|
|
16648
|
+
fallback_models: ["opencode/minimax-m2.5-free"]
|
|
16650
16649
|
},
|
|
16651
16650
|
curator_init: {
|
|
16652
|
-
model: "opencode/
|
|
16653
|
-
fallback_models: ["opencode/
|
|
16651
|
+
model: "opencode/big-pickle",
|
|
16652
|
+
fallback_models: ["opencode/minimax-m2.5-free"]
|
|
16654
16653
|
},
|
|
16655
16654
|
curator_phase: {
|
|
16656
|
-
model: "opencode/
|
|
16657
|
-
fallback_models: ["opencode/
|
|
16655
|
+
model: "opencode/big-pickle",
|
|
16656
|
+
fallback_models: ["opencode/minimax-m2.5-free"]
|
|
16658
16657
|
},
|
|
16659
16658
|
skill_improver: {
|
|
16660
16659
|
model: "opencode/big-pickle",
|
|
16661
|
-
fallback_models: ["opencode/
|
|
16660
|
+
fallback_models: ["opencode/minimax-m2.5-free"]
|
|
16662
16661
|
},
|
|
16663
16662
|
spec_writer: {
|
|
16664
16663
|
model: "opencode/big-pickle",
|
|
16665
|
-
fallback_models: ["opencode/
|
|
16664
|
+
fallback_models: ["opencode/minimax-m2.5-free"]
|
|
16666
16665
|
}
|
|
16667
16666
|
};
|
|
16668
16667
|
});
|
|
@@ -17545,7 +17544,8 @@ function handleAgentsCommand(agents, guardrails) {
|
|
|
17545
17544
|
if (hasUnregistered) {
|
|
17546
17545
|
lines.push("", "### Unregistered Subagents");
|
|
17547
17546
|
for (const name of unregistered) {
|
|
17548
|
-
|
|
17547
|
+
const hint = UNREGISTERED_AGENT_HINTS[name] ?? "requires configuration";
|
|
17548
|
+
lines.push(`- **${name}** (${hint})`);
|
|
17549
17549
|
}
|
|
17550
17550
|
}
|
|
17551
17551
|
if (guardrails?.profiles && Object.keys(guardrails.profiles).length > 0) {
|
|
@@ -17574,9 +17574,18 @@ function handleAgentsCommand(agents, guardrails) {
|
|
|
17574
17574
|
return lines.join(`
|
|
17575
17575
|
`);
|
|
17576
17576
|
}
|
|
17577
|
+
var UNREGISTERED_AGENT_HINTS;
|
|
17577
17578
|
var init_agents = __esm(() => {
|
|
17578
17579
|
init_constants();
|
|
17579
17580
|
init_schema();
|
|
17581
|
+
UNREGISTERED_AGENT_HINTS = {
|
|
17582
|
+
designer: "enable ui_review.enabled",
|
|
17583
|
+
council_generalist: "enable council.general.enabled",
|
|
17584
|
+
council_skeptic: "enable council.general.enabled",
|
|
17585
|
+
council_domain_expert: "enable council.general.enabled",
|
|
17586
|
+
skill_improver: "registered by default unless agents.skill_improver.disabled is true",
|
|
17587
|
+
spec_writer: "registered by default unless agents.spec_writer.disabled is true"
|
|
17588
|
+
};
|
|
17580
17589
|
});
|
|
17581
17590
|
|
|
17582
17591
|
// src/commands/analyze.ts
|
|
@@ -38247,12 +38256,31 @@ function parseArgs(args) {
|
|
|
38247
38256
|
}
|
|
38248
38257
|
return out;
|
|
38249
38258
|
}
|
|
38250
|
-
async function handleCouncilCommand(
|
|
38259
|
+
async function handleCouncilCommand(directory, args) {
|
|
38251
38260
|
const parsed = parseArgs(args);
|
|
38252
38261
|
const question = sanitizeQuestion(parsed.rest.join(" "));
|
|
38253
38262
|
if (!question) {
|
|
38254
38263
|
return USAGE;
|
|
38255
38264
|
}
|
|
38265
|
+
const config3 = loadPluginConfig(directory);
|
|
38266
|
+
if (config3.council?.general?.enabled !== true) {
|
|
38267
|
+
return [
|
|
38268
|
+
"General Council is not enabled for this project.",
|
|
38269
|
+
"",
|
|
38270
|
+
"Enable it in `.opencode/opencode-swarm.json` or `~/.config/opencode/opencode-swarm.json`:",
|
|
38271
|
+
"",
|
|
38272
|
+
"```json",
|
|
38273
|
+
"{",
|
|
38274
|
+
' "council": {',
|
|
38275
|
+
' "general": { "enabled": true }',
|
|
38276
|
+
" }",
|
|
38277
|
+
"}",
|
|
38278
|
+
"```",
|
|
38279
|
+
"",
|
|
38280
|
+
"Then restart OpenCode and run `/swarm config doctor` before trying `/swarm council` again."
|
|
38281
|
+
].join(`
|
|
38282
|
+
`);
|
|
38283
|
+
}
|
|
38256
38284
|
const tokens = ["MODE: COUNCIL"];
|
|
38257
38285
|
if (parsed.preset) {
|
|
38258
38286
|
tokens.push(`preset=${parsed.preset}`);
|
|
@@ -38264,6 +38292,7 @@ async function handleCouncilCommand(_directory, args) {
|
|
|
38264
38292
|
}
|
|
38265
38293
|
var MAX_QUESTION_LEN = 2000, USAGE;
|
|
38266
38294
|
var init_council = __esm(() => {
|
|
38295
|
+
init_loader();
|
|
38267
38296
|
USAGE = [
|
|
38268
38297
|
"Usage: /swarm council <question> [--preset <name>] [--spec-review]",
|
|
38269
38298
|
"",
|
|
@@ -39740,6 +39769,7 @@ __export(exports_config_doctor, {
|
|
|
39740
39769
|
restoreFromBackup: () => restoreFromBackup,
|
|
39741
39770
|
getConfigPaths: () => getConfigPaths,
|
|
39742
39771
|
createConfigBackup: () => createConfigBackup,
|
|
39772
|
+
collectConfiguredModelRefs: () => collectConfiguredModelRefs,
|
|
39743
39773
|
applySafeAutoFixes: () => applySafeAutoFixes
|
|
39744
39774
|
});
|
|
39745
39775
|
import * as crypto3 from "crypto";
|
|
@@ -40133,6 +40163,129 @@ function validateConfigKey(path24, value, _config) {
|
|
|
40133
40163
|
}
|
|
40134
40164
|
return findings;
|
|
40135
40165
|
}
|
|
40166
|
+
function addConfiguredModel(refs, model, configPath) {
|
|
40167
|
+
if (typeof model !== "string")
|
|
40168
|
+
return;
|
|
40169
|
+
const trimmed = model.trim();
|
|
40170
|
+
if (!trimmed)
|
|
40171
|
+
return;
|
|
40172
|
+
const paths = refs.get(trimmed) ?? new Set;
|
|
40173
|
+
paths.add(configPath);
|
|
40174
|
+
refs.set(trimmed, paths);
|
|
40175
|
+
}
|
|
40176
|
+
function addConfiguredAgentModels(refs, agents, prefix) {
|
|
40177
|
+
if (!agents || typeof agents !== "object" || Array.isArray(agents))
|
|
40178
|
+
return;
|
|
40179
|
+
for (const [agentName, value] of Object.entries(agents)) {
|
|
40180
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
40181
|
+
continue;
|
|
40182
|
+
const agent = value;
|
|
40183
|
+
addConfiguredModel(refs, agent.model, `${prefix}.${agentName}.model`);
|
|
40184
|
+
if (Array.isArray(agent.fallback_models)) {
|
|
40185
|
+
agent.fallback_models.forEach((model, index) => {
|
|
40186
|
+
addConfiguredModel(refs, model, `${prefix}.${agentName}.fallback_models[${index}]`);
|
|
40187
|
+
});
|
|
40188
|
+
}
|
|
40189
|
+
}
|
|
40190
|
+
}
|
|
40191
|
+
function collectConfiguredModelRefs(config3) {
|
|
40192
|
+
const refs = new Map;
|
|
40193
|
+
const rawConfig = config3;
|
|
40194
|
+
addConfiguredAgentModels(refs, rawConfig.agents, "agents");
|
|
40195
|
+
if (rawConfig.swarms && typeof rawConfig.swarms === "object" && !Array.isArray(rawConfig.swarms)) {
|
|
40196
|
+
for (const [swarmId, value] of Object.entries(rawConfig.swarms)) {
|
|
40197
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
40198
|
+
continue;
|
|
40199
|
+
addConfiguredAgentModels(refs, value.agents, `swarms.${swarmId}.agents`);
|
|
40200
|
+
}
|
|
40201
|
+
}
|
|
40202
|
+
if (rawConfig.full_auto && typeof rawConfig.full_auto === "object" && !Array.isArray(rawConfig.full_auto)) {
|
|
40203
|
+
addConfiguredModel(refs, rawConfig.full_auto.critic_model, "full_auto.critic_model");
|
|
40204
|
+
}
|
|
40205
|
+
if (rawConfig.skill_improver && typeof rawConfig.skill_improver === "object" && !Array.isArray(rawConfig.skill_improver)) {
|
|
40206
|
+
const skillImprover = rawConfig.skill_improver;
|
|
40207
|
+
addConfiguredModel(refs, skillImprover.model, "skill_improver.model");
|
|
40208
|
+
if (Array.isArray(skillImprover.fallback_models)) {
|
|
40209
|
+
skillImprover.fallback_models.forEach((model, index) => {
|
|
40210
|
+
addConfiguredModel(refs, model, `skill_improver.fallback_models[${index}]`);
|
|
40211
|
+
});
|
|
40212
|
+
}
|
|
40213
|
+
}
|
|
40214
|
+
if (rawConfig.spec_writer && typeof rawConfig.spec_writer === "object" && !Array.isArray(rawConfig.spec_writer)) {
|
|
40215
|
+
const specWriter = rawConfig.spec_writer;
|
|
40216
|
+
addConfiguredModel(refs, specWriter.model, "spec_writer.model");
|
|
40217
|
+
if (Array.isArray(specWriter.fallback_models)) {
|
|
40218
|
+
specWriter.fallback_models.forEach((model, index) => {
|
|
40219
|
+
addConfiguredModel(refs, model, `spec_writer.fallback_models[${index}]`);
|
|
40220
|
+
});
|
|
40221
|
+
}
|
|
40222
|
+
}
|
|
40223
|
+
const council = rawConfig.council;
|
|
40224
|
+
const general = council && typeof council === "object" && !Array.isArray(council) ? council.general : undefined;
|
|
40225
|
+
if (general && typeof general === "object" && !Array.isArray(general)) {
|
|
40226
|
+
addConfiguredModel(refs, general.moderatorModel, "council.general.moderatorModel");
|
|
40227
|
+
if (Array.isArray(general.members)) {
|
|
40228
|
+
general.members.forEach((member, index) => {
|
|
40229
|
+
if (!member || typeof member !== "object" || Array.isArray(member)) {
|
|
40230
|
+
return;
|
|
40231
|
+
}
|
|
40232
|
+
addConfiguredModel(refs, member.model, `council.general.members[${index}].model`);
|
|
40233
|
+
});
|
|
40234
|
+
}
|
|
40235
|
+
if (general.presets && typeof general.presets === "object" && !Array.isArray(general.presets)) {
|
|
40236
|
+
for (const [presetName, members] of Object.entries(general.presets)) {
|
|
40237
|
+
if (!Array.isArray(members))
|
|
40238
|
+
continue;
|
|
40239
|
+
members.forEach((member, index) => {
|
|
40240
|
+
if (!member || typeof member !== "object" || Array.isArray(member)) {
|
|
40241
|
+
return;
|
|
40242
|
+
}
|
|
40243
|
+
addConfiguredModel(refs, member.model, `council.general.presets.${presetName}[${index}].model`);
|
|
40244
|
+
});
|
|
40245
|
+
}
|
|
40246
|
+
}
|
|
40247
|
+
}
|
|
40248
|
+
return refs;
|
|
40249
|
+
}
|
|
40250
|
+
function validateConfiguredModels(config3, modelAvailability) {
|
|
40251
|
+
const refs = collectConfiguredModelRefs(config3);
|
|
40252
|
+
const findings = [];
|
|
40253
|
+
if (modelAvailability.error) {
|
|
40254
|
+
findings.push({
|
|
40255
|
+
id: "model-availability-unchecked",
|
|
40256
|
+
title: "Model availability check skipped",
|
|
40257
|
+
description: `Could not load OpenCode provider models from ${modelAvailability.source}: ` + modelAvailability.error,
|
|
40258
|
+
severity: "info",
|
|
40259
|
+
path: "agents",
|
|
40260
|
+
autoFixable: false
|
|
40261
|
+
});
|
|
40262
|
+
return findings;
|
|
40263
|
+
}
|
|
40264
|
+
if (refs.size === 0)
|
|
40265
|
+
return findings;
|
|
40266
|
+
for (const [modelId, paths] of refs.entries()) {
|
|
40267
|
+
if (modelAvailability.availableModelIds.has(modelId))
|
|
40268
|
+
continue;
|
|
40269
|
+
findings.push({
|
|
40270
|
+
id: "configured-model-unavailable",
|
|
40271
|
+
title: "Configured model is unavailable",
|
|
40272
|
+
description: `Configured model ${formatModelIdForDoctor(modelId)} was not found in the active OpenCode provider model registry. ` + "Run `/models` to choose a currently available model, then update opencode-swarm.json.",
|
|
40273
|
+
severity: "error",
|
|
40274
|
+
path: [...paths].sort().join(", "),
|
|
40275
|
+
currentValue: modelId,
|
|
40276
|
+
autoFixable: false
|
|
40277
|
+
});
|
|
40278
|
+
}
|
|
40279
|
+
return findings;
|
|
40280
|
+
}
|
|
40281
|
+
function formatModelIdForDoctor(modelId) {
|
|
40282
|
+
const json3 = JSON.stringify(modelId);
|
|
40283
|
+
if (!json3)
|
|
40284
|
+
return '"<invalid model id>"';
|
|
40285
|
+
if (json3.length <= 160)
|
|
40286
|
+
return json3;
|
|
40287
|
+
return `${json3.slice(0, 157)}..."`;
|
|
40288
|
+
}
|
|
40136
40289
|
function walkConfigAndValidate(obj, path24, config3, findings) {
|
|
40137
40290
|
if (obj === null || obj === undefined) {
|
|
40138
40291
|
return;
|
|
@@ -40157,9 +40310,12 @@ function walkConfigAndValidate(obj, path24, config3, findings) {
|
|
|
40157
40310
|
walkConfigAndValidate(value, newPath, config3, findings);
|
|
40158
40311
|
}
|
|
40159
40312
|
}
|
|
40160
|
-
function runConfigDoctor(config3, directory) {
|
|
40313
|
+
function runConfigDoctor(config3, directory, options = {}) {
|
|
40161
40314
|
const findings = [];
|
|
40162
40315
|
walkConfigAndValidate(config3, "", config3, findings);
|
|
40316
|
+
if (options.modelAvailability) {
|
|
40317
|
+
findings.push(...validateConfiguredModels(config3, options.modelAvailability));
|
|
40318
|
+
}
|
|
40163
40319
|
const summary = {
|
|
40164
40320
|
info: findings.filter((f) => f.severity === "info").length,
|
|
40165
40321
|
warn: findings.filter((f) => f.severity === "warn").length,
|
|
@@ -40321,8 +40477,8 @@ function shouldRunOnStartup(automationConfig) {
|
|
|
40321
40477
|
}
|
|
40322
40478
|
return automationConfig.capabilities?.config_doctor_on_startup === true;
|
|
40323
40479
|
}
|
|
40324
|
-
async function runConfigDoctorWithFixes(directory, config3, autoFix = false) {
|
|
40325
|
-
const result = runConfigDoctor(config3, directory);
|
|
40480
|
+
async function runConfigDoctorWithFixes(directory, config3, autoFix = false, options = {}) {
|
|
40481
|
+
const result = runConfigDoctor(config3, directory, options);
|
|
40326
40482
|
const artifactPath = writeDoctorArtifact(directory, result);
|
|
40327
40483
|
if (!autoFix) {
|
|
40328
40484
|
return {
|
|
@@ -40342,7 +40498,7 @@ async function runConfigDoctorWithFixes(directory, config3, autoFix = false) {
|
|
|
40342
40498
|
if (appliedFixes.length > 0) {
|
|
40343
40499
|
const freshConfig = readConfigFromFile(directory);
|
|
40344
40500
|
if (freshConfig) {
|
|
40345
|
-
const newResult = runConfigDoctor(freshConfig.config, directory);
|
|
40501
|
+
const newResult = runConfigDoctor(freshConfig.config, directory, options);
|
|
40346
40502
|
writeDoctorArtifact(directory, newResult);
|
|
40347
40503
|
}
|
|
40348
40504
|
}
|
|
@@ -41964,6 +42120,23 @@ var init_tool_doctor = __esm(() => {
|
|
|
41964
42120
|
];
|
|
41965
42121
|
});
|
|
41966
42122
|
|
|
42123
|
+
// src/utils/timeout.ts
|
|
42124
|
+
async function withTimeout(promise3, ms, timeoutError) {
|
|
42125
|
+
let timer;
|
|
42126
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
42127
|
+
timer = setTimeout(() => reject(timeoutError), ms);
|
|
42128
|
+
if (typeof timer.unref === "function") {
|
|
42129
|
+
timer.unref();
|
|
42130
|
+
}
|
|
42131
|
+
});
|
|
42132
|
+
try {
|
|
42133
|
+
return await Promise.race([promise3, timeoutPromise]);
|
|
42134
|
+
} finally {
|
|
42135
|
+
if (timer !== undefined)
|
|
42136
|
+
clearTimeout(timer);
|
|
42137
|
+
}
|
|
42138
|
+
}
|
|
42139
|
+
|
|
41967
42140
|
// src/commands/doctor.ts
|
|
41968
42141
|
function formatToolDoctorMarkdown(result) {
|
|
41969
42142
|
const lines = [
|
|
@@ -42037,13 +42210,67 @@ function formatDoctorMarkdown(result) {
|
|
|
42037
42210
|
return lines.join(`
|
|
42038
42211
|
`);
|
|
42039
42212
|
}
|
|
42040
|
-
|
|
42213
|
+
function extractAvailableModelIds(response) {
|
|
42214
|
+
const available = new Set;
|
|
42215
|
+
if (response?.providers !== undefined && !Array.isArray(response.providers)) {
|
|
42216
|
+
throw new Error("provider registry returned malformed provider list");
|
|
42217
|
+
}
|
|
42218
|
+
for (const provider of response?.providers ?? []) {
|
|
42219
|
+
if (!provider || typeof provider !== "object" || !provider.id || !provider.models || typeof provider.models !== "object" || Array.isArray(provider.models)) {
|
|
42220
|
+
continue;
|
|
42221
|
+
}
|
|
42222
|
+
for (const [modelKey, modelInfo] of Object.entries(provider.models)) {
|
|
42223
|
+
available.add(`${provider.id}/${modelKey}`);
|
|
42224
|
+
if (modelInfo && typeof modelInfo === "object" && modelInfo.id) {
|
|
42225
|
+
available.add(`${provider.id}/${modelInfo.id}`);
|
|
42226
|
+
}
|
|
42227
|
+
}
|
|
42228
|
+
}
|
|
42229
|
+
return available;
|
|
42230
|
+
}
|
|
42231
|
+
async function loadModelAvailability(directory, client, options = {}) {
|
|
42232
|
+
const providerClient = client;
|
|
42233
|
+
const providers = providerClient?.config?.providers;
|
|
42234
|
+
if (typeof providers !== "function") {
|
|
42235
|
+
return;
|
|
42236
|
+
}
|
|
42237
|
+
try {
|
|
42238
|
+
const response = await withTimeout(providers({ directory }), options.timeoutMs ?? MODEL_REGISTRY_TIMEOUT_MS, new Error(`OpenCode provider model registry lookup exceeded ${options.timeoutMs ?? MODEL_REGISTRY_TIMEOUT_MS}ms`));
|
|
42239
|
+
if (response.error) {
|
|
42240
|
+
return {
|
|
42241
|
+
availableModelIds: new Set,
|
|
42242
|
+
source: MODEL_REGISTRY_SOURCE,
|
|
42243
|
+
error: typeof response.error === "string" ? response.error : JSON.stringify(response.error)
|
|
42244
|
+
};
|
|
42245
|
+
}
|
|
42246
|
+
if (!response.data) {
|
|
42247
|
+
return {
|
|
42248
|
+
availableModelIds: new Set,
|
|
42249
|
+
source: MODEL_REGISTRY_SOURCE,
|
|
42250
|
+
error: "provider registry returned no data"
|
|
42251
|
+
};
|
|
42252
|
+
}
|
|
42253
|
+
return {
|
|
42254
|
+
availableModelIds: extractAvailableModelIds(response.data),
|
|
42255
|
+
source: MODEL_REGISTRY_SOURCE
|
|
42256
|
+
};
|
|
42257
|
+
} catch (error93) {
|
|
42258
|
+
return {
|
|
42259
|
+
availableModelIds: new Set,
|
|
42260
|
+
source: MODEL_REGISTRY_SOURCE,
|
|
42261
|
+
error: error93 instanceof Error ? error93.message : String(error93)
|
|
42262
|
+
};
|
|
42263
|
+
}
|
|
42264
|
+
}
|
|
42265
|
+
async function handleDoctorCommand(directory, args, options = {}) {
|
|
42041
42266
|
const enableAutoFix = args.includes("--fix") || args.includes("-f");
|
|
42042
42267
|
const config3 = loadPluginConfig(directory);
|
|
42043
|
-
const
|
|
42268
|
+
const modelAvailability = await loadModelAvailability(directory, options.client);
|
|
42269
|
+
const doctorOptions = { modelAvailability };
|
|
42270
|
+
const result = runConfigDoctor(config3, directory, doctorOptions);
|
|
42044
42271
|
if (enableAutoFix && result.hasAutoFixableIssues) {
|
|
42045
42272
|
const { runConfigDoctorWithFixes: runConfigDoctorWithFixes2 } = await Promise.resolve().then(() => (init_config_doctor(), exports_config_doctor));
|
|
42046
|
-
const fixResult = await runConfigDoctorWithFixes2(directory, config3, true);
|
|
42273
|
+
const fixResult = await runConfigDoctorWithFixes2(directory, config3, true, doctorOptions);
|
|
42047
42274
|
return formatDoctorMarkdown(fixResult.result);
|
|
42048
42275
|
}
|
|
42049
42276
|
return formatDoctorMarkdown(result);
|
|
@@ -42052,6 +42279,7 @@ async function handleDoctorToolsCommand(directory, _args) {
|
|
|
42052
42279
|
const result = runToolDoctor(directory);
|
|
42053
42280
|
return formatToolDoctorMarkdown(result);
|
|
42054
42281
|
}
|
|
42282
|
+
var MODEL_REGISTRY_TIMEOUT_MS = 3000, MODEL_REGISTRY_SOURCE = "OpenCode config.providers";
|
|
42055
42283
|
var init_doctor = __esm(() => {
|
|
42056
42284
|
init_loader();
|
|
42057
42285
|
init_config_doctor();
|
|
@@ -51783,7 +52011,7 @@ function buildHelpText() {
|
|
|
51783
52011
|
return lines.join(`
|
|
51784
52012
|
`);
|
|
51785
52013
|
}
|
|
51786
|
-
function createSwarmCommandHandler(directory, agents) {
|
|
52014
|
+
function createSwarmCommandHandler(directory, agents, client) {
|
|
51787
52015
|
return async (input, output) => {
|
|
51788
52016
|
if (input.command !== "swarm" && !input.command.startsWith("swarm-")) {
|
|
51789
52017
|
return;
|
|
@@ -51830,7 +52058,8 @@ ${similar.map((cmd) => ` \u2022 /swarm ${cmd}`).join(`
|
|
|
51830
52058
|
directory,
|
|
51831
52059
|
args: resolved.remainingArgs,
|
|
51832
52060
|
sessionID: input.sessionID,
|
|
51833
|
-
agents
|
|
52061
|
+
agents,
|
|
52062
|
+
client
|
|
51834
52063
|
});
|
|
51835
52064
|
} catch (_err) {
|
|
51836
52065
|
const cmdName = tokens[0] || "unknown";
|
|
@@ -51846,7 +52075,10 @@ ${text}`;
|
|
|
51846
52075
|
if (isFirstRun) {
|
|
51847
52076
|
const welcomeMessage = `Welcome to OpenCode Swarm! \uD83D\uDC1D
|
|
51848
52077
|
` + `
|
|
51849
|
-
` + `
|
|
52078
|
+
` + `Start here: run \`/swarm diagnose\`, then \`/swarm agents\` to confirm the plugin loaded and see the exact models in use.
|
|
52079
|
+
` + `If a model is unavailable, edit \`.opencode/opencode-swarm.json\` or \`~/.config/opencode/opencode-swarm.json\` and run \`/swarm config doctor\`.
|
|
52080
|
+
` + `Useful next steps: \`/swarm brainstorm <task>\` for guided planning, \`/swarm full-auto on\` for autonomous runs after enabling it in config, and \`/swarm council <question>\` after enabling council.general.
|
|
52081
|
+
|
|
51850
52082
|
`;
|
|
51851
52083
|
text = welcomeMessage + text;
|
|
51852
52084
|
}
|
|
@@ -52163,13 +52395,13 @@ var init_registry = __esm(() => {
|
|
|
52163
52395
|
clashesWithNativeCcCommand: "/config"
|
|
52164
52396
|
},
|
|
52165
52397
|
"config doctor": {
|
|
52166
|
-
handler: (ctx) => handleDoctorCommand(ctx.directory, ctx.args),
|
|
52398
|
+
handler: (ctx) => handleDoctorCommand(ctx.directory, ctx.args, { client: ctx.client }),
|
|
52167
52399
|
description: "Run config doctor checks",
|
|
52168
52400
|
subcommandOf: "config",
|
|
52169
52401
|
category: "diagnostics"
|
|
52170
52402
|
},
|
|
52171
52403
|
"config-doctor": {
|
|
52172
|
-
handler: (ctx) => handleDoctorCommand(ctx.directory, ctx.args),
|
|
52404
|
+
handler: (ctx) => handleDoctorCommand(ctx.directory, ctx.args, { client: ctx.client }),
|
|
52173
52405
|
description: "Run config doctor checks",
|
|
52174
52406
|
subcommandOf: "config",
|
|
52175
52407
|
category: "diagnostics",
|
|
@@ -52244,7 +52476,7 @@ var init_registry = __esm(() => {
|
|
|
52244
52476
|
deprecated: true
|
|
52245
52477
|
},
|
|
52246
52478
|
doctor: {
|
|
52247
|
-
handler: (ctx) => handleDoctorCommand(ctx.directory, ctx.args),
|
|
52479
|
+
handler: (ctx) => handleDoctorCommand(ctx.directory, ctx.args, { client: ctx.client }),
|
|
52248
52480
|
description: "Run config doctor checks",
|
|
52249
52481
|
category: "diagnostics",
|
|
52250
52482
|
aliasOf: "config doctor",
|
|
@@ -14,4 +14,4 @@
|
|
|
14
14
|
* Sanitizes the question to prevent prompt injection of rival MODE: headers
|
|
15
15
|
* or control sequences (mirrors brainstorm.ts).
|
|
16
16
|
*/
|
|
17
|
-
export declare function handleCouncilCommand(
|
|
17
|
+
export declare function handleCouncilCommand(directory: string, args: string[]): Promise<string>;
|