opencode-swarm 7.18.1 → 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 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
- > ⚠️ **On first run, Swarm auto-selects the architect and shows a welcome message.** The default OpenCode `Build` and `Plan` modes **bypass this plugin entirely** — none of the gates, reviewers, or test agents below run. If you ever need to switch architect manually, open the OpenCode mode/agent picker and choose the Swarm architect; it then coordinates every other agent automatically. If you ever see Swarm "do nothing," this is almost always the cause.
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 is auto-selected on first run and coordinates all other agents automatically.** 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).
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` with all agents enabled
130
- - Selects the Swarm architect as the default
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 Swarm auto-selects architect on first run
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! Architect auto-selected. Type /swarm help
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
- All 43 subcommands at a glance:
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 (43 commands).
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
- | reviewer | zai-coding-plan/glm-5 | Different training from coder |
385
- | test_engineer | minimax-coding-plan/MiniMax-M2.5 | Same strengths as coder |
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/gpt-5-nano"]
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": "anthropic/claude-opus-4-6" },
1148
- "coder": { "model": "minimax-coding-plan/MiniMax-M2.5", "fallback_models": ["minimax-coding-plan/MiniMax-M2.1"] },
1149
- "explorer": { "model": "minimax-coding-plan/MiniMax-M2.1" },
1150
- "sme": { "model": "kimi-for-coding/k2p5" },
1151
- "critic": { "model": "zai-coding-plan/glm-5" },
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-coding-plan/MiniMax-M2.5" },
1154
- "docs": { "model": "zai-coding-plan/glm-4.7-flash" },
1155
- "designer": { "model": "kimi-for-coding/k2p5" }
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) — all 41 `/swarm` subcommands
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.1",
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/gpt-5-nano", "opencode/big-pickle"]
16604
+ fallback_models: ["opencode/big-pickle"]
16606
16605
  },
16607
16606
  reviewer: {
16608
16607
  model: "opencode/big-pickle",
16609
- fallback_models: ["opencode/gpt-5-nano", "opencode/big-pickle"]
16608
+ fallback_models: ["opencode/minimax-m2.5-free"]
16610
16609
  },
16611
16610
  test_engineer: {
16612
- model: "opencode/gpt-5-nano",
16613
- fallback_models: ["opencode/big-pickle"]
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/gpt-5-nano", "opencode/big-pickle"]
16616
+ fallback_models: ["opencode/minimax-m2.5-free"]
16618
16617
  },
16619
16618
  sme: {
16620
16619
  model: "opencode/big-pickle",
16621
- fallback_models: ["opencode/gpt-5-nano", "opencode/big-pickle"]
16620
+ fallback_models: ["opencode/minimax-m2.5-free"]
16622
16621
  },
16623
16622
  critic: {
16624
16623
  model: "opencode/big-pickle",
16625
- fallback_models: ["opencode/gpt-5-nano", "opencode/big-pickle"]
16624
+ fallback_models: ["opencode/minimax-m2.5-free"]
16626
16625
  },
16627
16626
  docs: {
16628
16627
  model: "opencode/big-pickle",
16629
- fallback_models: ["opencode/gpt-5-nano", "opencode/big-pickle"]
16628
+ fallback_models: ["opencode/minimax-m2.5-free"]
16630
16629
  },
16631
16630
  designer: {
16632
16631
  model: "opencode/big-pickle",
16633
- fallback_models: ["opencode/gpt-5-nano", "opencode/big-pickle"]
16632
+ fallback_models: ["opencode/minimax-m2.5-free"]
16634
16633
  },
16635
16634
  critic_sounding_board: {
16636
- model: "opencode/gpt-5-nano",
16637
- fallback_models: ["opencode/big-pickle"]
16635
+ model: "opencode/big-pickle",
16636
+ fallback_models: ["opencode/minimax-m2.5-free"]
16638
16637
  },
16639
16638
  critic_drift_verifier: {
16640
- model: "opencode/gpt-5-nano",
16641
- fallback_models: ["opencode/big-pickle"]
16639
+ model: "opencode/big-pickle",
16640
+ fallback_models: ["opencode/minimax-m2.5-free"]
16642
16641
  },
16643
16642
  critic_hallucination_verifier: {
16644
- model: "opencode/gpt-5-nano",
16645
- fallback_models: ["opencode/big-pickle"]
16643
+ model: "opencode/big-pickle",
16644
+ fallback_models: ["opencode/minimax-m2.5-free"]
16646
16645
  },
16647
16646
  critic_oversight: {
16648
- model: "opencode/gpt-5-nano",
16649
- fallback_models: ["opencode/big-pickle"]
16647
+ model: "opencode/big-pickle",
16648
+ fallback_models: ["opencode/minimax-m2.5-free"]
16650
16649
  },
16651
16650
  curator_init: {
16652
- model: "opencode/gpt-5-nano",
16653
- fallback_models: ["opencode/big-pickle"]
16651
+ model: "opencode/big-pickle",
16652
+ fallback_models: ["opencode/minimax-m2.5-free"]
16654
16653
  },
16655
16654
  curator_phase: {
16656
- model: "opencode/gpt-5-nano",
16657
- fallback_models: ["opencode/big-pickle"]
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/gpt-5-nano"]
16660
+ fallback_models: ["opencode/minimax-m2.5-free"]
16662
16661
  },
16663
16662
  spec_writer: {
16664
16663
  model: "opencode/big-pickle",
16665
- fallback_models: ["opencode/gpt-5-nano"]
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
- lines.push(`- **${name}** (requires configuration)`);
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(_directory, args) {
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
- async function handleDoctorCommand(directory, args) {
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 result = runConfigDoctor(config3, directory);
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
- ` + `Run \`/swarm help\` to see all available commands, or \`/swarm config\` to review your configuration.
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(_directory: string, args: string[]): Promise<string>;
17
+ export declare function handleCouncilCommand(directory: string, args: string[]): Promise<string>;
@@ -1,15 +1,20 @@
1
- import { type ConfigDoctorResult } from '../services/config-doctor';
1
+ import { type ConfigDoctorResult, type ModelAvailability } from '../services/config-doctor';
2
2
  /**
3
3
  * Format tool doctor result as markdown for command output.
4
4
  *
5
5
  * Exported for unit testing of the BLOCKING footer enforcement path.
6
6
  */
7
7
  export declare function formatToolDoctorMarkdown(result: ConfigDoctorResult): string;
8
+ export declare function loadModelAvailability(directory: string, client: unknown, options?: {
9
+ timeoutMs?: number;
10
+ }): Promise<ModelAvailability | undefined>;
8
11
  /**
9
12
  * Handle /swarm config doctor command.
10
13
  * Maps to: config doctor service (runConfigDoctor)
11
14
  */
12
- export declare function handleDoctorCommand(directory: string, args: string[]): Promise<string>;
15
+ export declare function handleDoctorCommand(directory: string, args: string[], options?: {
16
+ client?: unknown;
17
+ }): Promise<string>;
13
18
  /**
14
19
  * Handle /swarm doctor tools command.
15
20
  * Maps to: tool doctor service (runToolDoctor)
@@ -45,7 +45,7 @@ export declare function buildHelpText(): string;
45
45
  * Creates a command.execute.before handler for /swarm commands.
46
46
  * Uses factory pattern to close over directory and agents.
47
47
  */
48
- export declare function createSwarmCommandHandler(directory: string, agents: Record<string, AgentDefinition>): (input: {
48
+ export declare function createSwarmCommandHandler(directory: string, agents: Record<string, AgentDefinition>, client?: unknown): (input: {
49
49
  command: string;
50
50
  sessionID: string;
51
51
  arguments: string;
@@ -8,6 +8,7 @@ export type CommandContext = {
8
8
  args: string[];
9
9
  sessionID: string;
10
10
  agents: Record<string, AgentDefinition>;
11
+ client?: unknown;
11
12
  };
12
13
  export type CommandResult = Promise<string>;
13
14
  export type CommandCategory = 'core' | 'agent' | 'config' | 'diagnostics' | 'utility';
package/dist/index.js CHANGED
@@ -33,7 +33,7 @@ var package_default;
33
33
  var init_package = __esm(() => {
34
34
  package_default = {
35
35
  name: "opencode-swarm",
36
- version: "7.18.1",
36
+ version: "7.18.2",
37
37
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
38
38
  main: "dist/index.js",
39
39
  types: "dist/index.d.ts",
@@ -253,7 +253,7 @@ function isLowCapabilityModel(modelId) {
253
253
  const lower = (modelId || "").toLowerCase();
254
254
  return LOW_CAPABILITY_MODELS.some((substr) => lower.includes(substr));
255
255
  }
256
- var QA_AGENTS, PIPELINE_AGENTS, ORCHESTRATOR_NAME = "architect", ALL_SUBAGENT_NAMES, ALL_AGENT_NAMES, OPENCODE_NATIVE_AGENTS, CLAUDE_CODE_NATIVE_COMMANDS, AGENT_TOOL_MAP, WRITE_TOOL_NAMES, TOOL_DESCRIPTIONS, DEFAULT_MODELS, DEFAULT_SCORING_CONFIG, LOW_CAPABILITY_MODELS, TURBO_MODE_BANNER = `## \uD83D\uDE80 TURBO MODE ACTIVE
256
+ var QA_AGENTS, PIPELINE_AGENTS, ORCHESTRATOR_NAME = "architect", ALL_SUBAGENT_NAMES, ALL_AGENT_NAMES, OPENCODE_NATIVE_AGENTS, CLAUDE_CODE_NATIVE_COMMANDS, AGENT_TOOL_MAP, WRITE_TOOL_NAMES, TOOL_DESCRIPTIONS, DEFAULT_MODELS, DEFAULT_AGENT_CONFIGS, DEFAULT_SCORING_CONFIG, LOW_CAPABILITY_MODELS, TURBO_MODE_BANNER = `## \uD83D\uDE80 TURBO MODE ACTIVE
257
257
 
258
258
  **Speed optimization enabled for this session.**
259
259
 
@@ -687,8 +687,7 @@ var init_constants = __esm(() => {
687
687
  "skill_improve",
688
688
  "search",
689
689
  "doc_scan",
690
- "doc_extract",
691
- "web_search"
690
+ "doc_extract"
692
691
  ],
693
692
  spec_writer: [
694
693
  "search",
@@ -800,21 +799,87 @@ var init_constants = __esm(() => {
800
799
  explorer: "opencode/big-pickle",
801
800
  coder: "opencode/minimax-m2.5-free",
802
801
  reviewer: "opencode/big-pickle",
803
- test_engineer: "opencode/gpt-5-nano",
802
+ test_engineer: "opencode/big-pickle",
804
803
  sme: "opencode/big-pickle",
805
804
  critic: "opencode/big-pickle",
806
- critic_sounding_board: "opencode/gpt-5-nano",
807
- critic_drift_verifier: "opencode/gpt-5-nano",
808
- critic_hallucination_verifier: "opencode/gpt-5-nano",
809
- critic_oversight: "opencode/gpt-5-nano",
805
+ critic_sounding_board: "opencode/big-pickle",
806
+ critic_drift_verifier: "opencode/big-pickle",
807
+ critic_hallucination_verifier: "opencode/big-pickle",
808
+ critic_oversight: "opencode/big-pickle",
810
809
  docs: "opencode/big-pickle",
811
810
  designer: "opencode/big-pickle",
812
- curator_init: "opencode/gpt-5-nano",
813
- curator_phase: "opencode/gpt-5-nano",
811
+ curator_init: "opencode/big-pickle",
812
+ curator_phase: "opencode/big-pickle",
814
813
  skill_improver: "opencode/big-pickle",
815
814
  spec_writer: "opencode/big-pickle",
816
815
  default: "opencode/big-pickle"
817
816
  };
817
+ DEFAULT_AGENT_CONFIGS = {
818
+ coder: {
819
+ model: "opencode/minimax-m2.5-free",
820
+ fallback_models: ["opencode/big-pickle"]
821
+ },
822
+ reviewer: {
823
+ model: "opencode/big-pickle",
824
+ fallback_models: ["opencode/minimax-m2.5-free"]
825
+ },
826
+ test_engineer: {
827
+ model: "opencode/big-pickle",
828
+ fallback_models: ["opencode/minimax-m2.5-free"]
829
+ },
830
+ explorer: {
831
+ model: "opencode/big-pickle",
832
+ fallback_models: ["opencode/minimax-m2.5-free"]
833
+ },
834
+ sme: {
835
+ model: "opencode/big-pickle",
836
+ fallback_models: ["opencode/minimax-m2.5-free"]
837
+ },
838
+ critic: {
839
+ model: "opencode/big-pickle",
840
+ fallback_models: ["opencode/minimax-m2.5-free"]
841
+ },
842
+ docs: {
843
+ model: "opencode/big-pickle",
844
+ fallback_models: ["opencode/minimax-m2.5-free"]
845
+ },
846
+ designer: {
847
+ model: "opencode/big-pickle",
848
+ fallback_models: ["opencode/minimax-m2.5-free"]
849
+ },
850
+ critic_sounding_board: {
851
+ model: "opencode/big-pickle",
852
+ fallback_models: ["opencode/minimax-m2.5-free"]
853
+ },
854
+ critic_drift_verifier: {
855
+ model: "opencode/big-pickle",
856
+ fallback_models: ["opencode/minimax-m2.5-free"]
857
+ },
858
+ critic_hallucination_verifier: {
859
+ model: "opencode/big-pickle",
860
+ fallback_models: ["opencode/minimax-m2.5-free"]
861
+ },
862
+ critic_oversight: {
863
+ model: "opencode/big-pickle",
864
+ fallback_models: ["opencode/minimax-m2.5-free"]
865
+ },
866
+ curator_init: {
867
+ model: "opencode/big-pickle",
868
+ fallback_models: ["opencode/minimax-m2.5-free"]
869
+ },
870
+ curator_phase: {
871
+ model: "opencode/big-pickle",
872
+ fallback_models: ["opencode/minimax-m2.5-free"]
873
+ },
874
+ skill_improver: {
875
+ model: "opencode/big-pickle",
876
+ fallback_models: ["opencode/minimax-m2.5-free"]
877
+ },
878
+ spec_writer: {
879
+ model: "opencode/big-pickle",
880
+ fallback_models: ["opencode/minimax-m2.5-free"]
881
+ }
882
+ };
818
883
  DEFAULT_SCORING_CONFIG = {
819
884
  enabled: false,
820
885
  max_candidates: 100,
@@ -18532,7 +18597,8 @@ function handleAgentsCommand(agents, guardrails) {
18532
18597
  if (hasUnregistered) {
18533
18598
  lines.push("", "### Unregistered Subagents");
18534
18599
  for (const name2 of unregistered) {
18535
- lines.push(`- **${name2}** (requires configuration)`);
18600
+ const hint = UNREGISTERED_AGENT_HINTS[name2] ?? "requires configuration";
18601
+ lines.push(`- **${name2}** (${hint})`);
18536
18602
  }
18537
18603
  }
18538
18604
  if (guardrails?.profiles && Object.keys(guardrails.profiles).length > 0) {
@@ -18561,9 +18627,18 @@ function handleAgentsCommand(agents, guardrails) {
18561
18627
  return lines.join(`
18562
18628
  `);
18563
18629
  }
18630
+ var UNREGISTERED_AGENT_HINTS;
18564
18631
  var init_agents = __esm(() => {
18565
18632
  init_constants();
18566
18633
  init_schema();
18634
+ UNREGISTERED_AGENT_HINTS = {
18635
+ designer: "enable ui_review.enabled",
18636
+ council_generalist: "enable council.general.enabled",
18637
+ council_skeptic: "enable council.general.enabled",
18638
+ council_domain_expert: "enable council.general.enabled",
18639
+ skill_improver: "registered by default unless agents.skill_improver.disabled is true",
18640
+ spec_writer: "registered by default unless agents.spec_writer.disabled is true"
18641
+ };
18567
18642
  });
18568
18643
 
18569
18644
  // src/commands/analyze.ts
@@ -46825,12 +46900,31 @@ function parseArgs(args2) {
46825
46900
  }
46826
46901
  return out2;
46827
46902
  }
46828
- async function handleCouncilCommand(_directory, args2) {
46903
+ async function handleCouncilCommand(directory, args2) {
46829
46904
  const parsed = parseArgs(args2);
46830
46905
  const question = sanitizeQuestion(parsed.rest.join(" "));
46831
46906
  if (!question) {
46832
46907
  return USAGE;
46833
46908
  }
46909
+ const config3 = loadPluginConfig(directory);
46910
+ if (config3.council?.general?.enabled !== true) {
46911
+ return [
46912
+ "General Council is not enabled for this project.",
46913
+ "",
46914
+ "Enable it in `.opencode/opencode-swarm.json` or `~/.config/opencode/opencode-swarm.json`:",
46915
+ "",
46916
+ "```json",
46917
+ "{",
46918
+ ' "council": {',
46919
+ ' "general": { "enabled": true }',
46920
+ " }",
46921
+ "}",
46922
+ "```",
46923
+ "",
46924
+ "Then restart OpenCode and run `/swarm config doctor` before trying `/swarm council` again."
46925
+ ].join(`
46926
+ `);
46927
+ }
46834
46928
  const tokens = ["MODE: COUNCIL"];
46835
46929
  if (parsed.preset) {
46836
46930
  tokens.push(`preset=${parsed.preset}`);
@@ -46842,6 +46936,7 @@ async function handleCouncilCommand(_directory, args2) {
46842
46936
  }
46843
46937
  var MAX_QUESTION_LEN = 2000, USAGE;
46844
46938
  var init_council = __esm(() => {
46939
+ init_loader();
46845
46940
  USAGE = [
46846
46941
  "Usage: /swarm council <question> [--preset <name>] [--spec-review]",
46847
46942
  "",
@@ -48369,6 +48464,7 @@ __export(exports_config_doctor, {
48369
48464
  restoreFromBackup: () => restoreFromBackup,
48370
48465
  getConfigPaths: () => getConfigPaths,
48371
48466
  createConfigBackup: () => createConfigBackup,
48467
+ collectConfiguredModelRefs: () => collectConfiguredModelRefs,
48372
48468
  applySafeAutoFixes: () => applySafeAutoFixes
48373
48469
  });
48374
48470
  import * as crypto3 from "node:crypto";
@@ -48762,6 +48858,129 @@ function validateConfigKey(path31, value, _config) {
48762
48858
  }
48763
48859
  return findings;
48764
48860
  }
48861
+ function addConfiguredModel(refs, model, configPath) {
48862
+ if (typeof model !== "string")
48863
+ return;
48864
+ const trimmed = model.trim();
48865
+ if (!trimmed)
48866
+ return;
48867
+ const paths = refs.get(trimmed) ?? new Set;
48868
+ paths.add(configPath);
48869
+ refs.set(trimmed, paths);
48870
+ }
48871
+ function addConfiguredAgentModels(refs, agents, prefix) {
48872
+ if (!agents || typeof agents !== "object" || Array.isArray(agents))
48873
+ return;
48874
+ for (const [agentName, value] of Object.entries(agents)) {
48875
+ if (!value || typeof value !== "object" || Array.isArray(value))
48876
+ continue;
48877
+ const agent = value;
48878
+ addConfiguredModel(refs, agent.model, `${prefix}.${agentName}.model`);
48879
+ if (Array.isArray(agent.fallback_models)) {
48880
+ agent.fallback_models.forEach((model, index) => {
48881
+ addConfiguredModel(refs, model, `${prefix}.${agentName}.fallback_models[${index}]`);
48882
+ });
48883
+ }
48884
+ }
48885
+ }
48886
+ function collectConfiguredModelRefs(config3) {
48887
+ const refs = new Map;
48888
+ const rawConfig = config3;
48889
+ addConfiguredAgentModels(refs, rawConfig.agents, "agents");
48890
+ if (rawConfig.swarms && typeof rawConfig.swarms === "object" && !Array.isArray(rawConfig.swarms)) {
48891
+ for (const [swarmId, value] of Object.entries(rawConfig.swarms)) {
48892
+ if (!value || typeof value !== "object" || Array.isArray(value))
48893
+ continue;
48894
+ addConfiguredAgentModels(refs, value.agents, `swarms.${swarmId}.agents`);
48895
+ }
48896
+ }
48897
+ if (rawConfig.full_auto && typeof rawConfig.full_auto === "object" && !Array.isArray(rawConfig.full_auto)) {
48898
+ addConfiguredModel(refs, rawConfig.full_auto.critic_model, "full_auto.critic_model");
48899
+ }
48900
+ if (rawConfig.skill_improver && typeof rawConfig.skill_improver === "object" && !Array.isArray(rawConfig.skill_improver)) {
48901
+ const skillImprover = rawConfig.skill_improver;
48902
+ addConfiguredModel(refs, skillImprover.model, "skill_improver.model");
48903
+ if (Array.isArray(skillImprover.fallback_models)) {
48904
+ skillImprover.fallback_models.forEach((model, index) => {
48905
+ addConfiguredModel(refs, model, `skill_improver.fallback_models[${index}]`);
48906
+ });
48907
+ }
48908
+ }
48909
+ if (rawConfig.spec_writer && typeof rawConfig.spec_writer === "object" && !Array.isArray(rawConfig.spec_writer)) {
48910
+ const specWriter = rawConfig.spec_writer;
48911
+ addConfiguredModel(refs, specWriter.model, "spec_writer.model");
48912
+ if (Array.isArray(specWriter.fallback_models)) {
48913
+ specWriter.fallback_models.forEach((model, index) => {
48914
+ addConfiguredModel(refs, model, `spec_writer.fallback_models[${index}]`);
48915
+ });
48916
+ }
48917
+ }
48918
+ const council = rawConfig.council;
48919
+ const general = council && typeof council === "object" && !Array.isArray(council) ? council.general : undefined;
48920
+ if (general && typeof general === "object" && !Array.isArray(general)) {
48921
+ addConfiguredModel(refs, general.moderatorModel, "council.general.moderatorModel");
48922
+ if (Array.isArray(general.members)) {
48923
+ general.members.forEach((member, index) => {
48924
+ if (!member || typeof member !== "object" || Array.isArray(member)) {
48925
+ return;
48926
+ }
48927
+ addConfiguredModel(refs, member.model, `council.general.members[${index}].model`);
48928
+ });
48929
+ }
48930
+ if (general.presets && typeof general.presets === "object" && !Array.isArray(general.presets)) {
48931
+ for (const [presetName, members] of Object.entries(general.presets)) {
48932
+ if (!Array.isArray(members))
48933
+ continue;
48934
+ members.forEach((member, index) => {
48935
+ if (!member || typeof member !== "object" || Array.isArray(member)) {
48936
+ return;
48937
+ }
48938
+ addConfiguredModel(refs, member.model, `council.general.presets.${presetName}[${index}].model`);
48939
+ });
48940
+ }
48941
+ }
48942
+ }
48943
+ return refs;
48944
+ }
48945
+ function validateConfiguredModels(config3, modelAvailability) {
48946
+ const refs = collectConfiguredModelRefs(config3);
48947
+ const findings = [];
48948
+ if (modelAvailability.error) {
48949
+ findings.push({
48950
+ id: "model-availability-unchecked",
48951
+ title: "Model availability check skipped",
48952
+ description: `Could not load OpenCode provider models from ${modelAvailability.source}: ` + modelAvailability.error,
48953
+ severity: "info",
48954
+ path: "agents",
48955
+ autoFixable: false
48956
+ });
48957
+ return findings;
48958
+ }
48959
+ if (refs.size === 0)
48960
+ return findings;
48961
+ for (const [modelId, paths] of refs.entries()) {
48962
+ if (modelAvailability.availableModelIds.has(modelId))
48963
+ continue;
48964
+ findings.push({
48965
+ id: "configured-model-unavailable",
48966
+ title: "Configured model is unavailable",
48967
+ 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.",
48968
+ severity: "error",
48969
+ path: [...paths].sort().join(", "),
48970
+ currentValue: modelId,
48971
+ autoFixable: false
48972
+ });
48973
+ }
48974
+ return findings;
48975
+ }
48976
+ function formatModelIdForDoctor(modelId) {
48977
+ const json3 = JSON.stringify(modelId);
48978
+ if (!json3)
48979
+ return '"<invalid model id>"';
48980
+ if (json3.length <= 160)
48981
+ return json3;
48982
+ return `${json3.slice(0, 157)}..."`;
48983
+ }
48765
48984
  function walkConfigAndValidate(obj, path31, config3, findings) {
48766
48985
  if (obj === null || obj === undefined) {
48767
48986
  return;
@@ -48786,9 +49005,12 @@ function walkConfigAndValidate(obj, path31, config3, findings) {
48786
49005
  walkConfigAndValidate(value, newPath, config3, findings);
48787
49006
  }
48788
49007
  }
48789
- function runConfigDoctor(config3, directory) {
49008
+ function runConfigDoctor(config3, directory, options = {}) {
48790
49009
  const findings = [];
48791
49010
  walkConfigAndValidate(config3, "", config3, findings);
49011
+ if (options.modelAvailability) {
49012
+ findings.push(...validateConfiguredModels(config3, options.modelAvailability));
49013
+ }
48792
49014
  const summary = {
48793
49015
  info: findings.filter((f) => f.severity === "info").length,
48794
49016
  warn: findings.filter((f) => f.severity === "warn").length,
@@ -48950,8 +49172,8 @@ function shouldRunOnStartup(automationConfig) {
48950
49172
  }
48951
49173
  return automationConfig.capabilities?.config_doctor_on_startup === true;
48952
49174
  }
48953
- async function runConfigDoctorWithFixes(directory, config3, autoFix = false) {
48954
- const result = runConfigDoctor(config3, directory);
49175
+ async function runConfigDoctorWithFixes(directory, config3, autoFix = false, options = {}) {
49176
+ const result = runConfigDoctor(config3, directory, options);
48955
49177
  const artifactPath = writeDoctorArtifact(directory, result);
48956
49178
  if (!autoFix) {
48957
49179
  return {
@@ -48971,7 +49193,7 @@ async function runConfigDoctorWithFixes(directory, config3, autoFix = false) {
48971
49193
  if (appliedFixes.length > 0) {
48972
49194
  const freshConfig = readConfigFromFile(directory);
48973
49195
  if (freshConfig) {
48974
- const newResult = runConfigDoctor(freshConfig.config, directory);
49196
+ const newResult = runConfigDoctor(freshConfig.config, directory, options);
48975
49197
  writeDoctorArtifact(directory, newResult);
48976
49198
  }
48977
49199
  }
@@ -50616,6 +50838,31 @@ var init_tool_doctor = __esm(() => {
50616
50838
  ];
50617
50839
  });
50618
50840
 
50841
+ // src/utils/timeout.ts
50842
+ async function withTimeout(promise3, ms, timeoutError) {
50843
+ let timer;
50844
+ const timeoutPromise = new Promise((_, reject) => {
50845
+ timer = setTimeout(() => reject(timeoutError), ms);
50846
+ if (typeof timer.unref === "function") {
50847
+ timer.unref();
50848
+ }
50849
+ });
50850
+ try {
50851
+ return await Promise.race([promise3, timeoutPromise]);
50852
+ } finally {
50853
+ if (timer !== undefined)
50854
+ clearTimeout(timer);
50855
+ }
50856
+ }
50857
+ function yieldToEventLoop() {
50858
+ return new Promise((resolve11) => {
50859
+ const t = setTimeout(resolve11, 0);
50860
+ if (typeof t.unref === "function") {
50861
+ t.unref();
50862
+ }
50863
+ });
50864
+ }
50865
+
50619
50866
  // src/commands/doctor.ts
50620
50867
  function formatToolDoctorMarkdown(result) {
50621
50868
  const lines = [
@@ -50689,13 +50936,67 @@ function formatDoctorMarkdown(result) {
50689
50936
  return lines.join(`
50690
50937
  `);
50691
50938
  }
50692
- async function handleDoctorCommand(directory, args2) {
50939
+ function extractAvailableModelIds(response) {
50940
+ const available = new Set;
50941
+ if (response?.providers !== undefined && !Array.isArray(response.providers)) {
50942
+ throw new Error("provider registry returned malformed provider list");
50943
+ }
50944
+ for (const provider of response?.providers ?? []) {
50945
+ if (!provider || typeof provider !== "object" || !provider.id || !provider.models || typeof provider.models !== "object" || Array.isArray(provider.models)) {
50946
+ continue;
50947
+ }
50948
+ for (const [modelKey, modelInfo] of Object.entries(provider.models)) {
50949
+ available.add(`${provider.id}/${modelKey}`);
50950
+ if (modelInfo && typeof modelInfo === "object" && modelInfo.id) {
50951
+ available.add(`${provider.id}/${modelInfo.id}`);
50952
+ }
50953
+ }
50954
+ }
50955
+ return available;
50956
+ }
50957
+ async function loadModelAvailability(directory, client, options = {}) {
50958
+ const providerClient = client;
50959
+ const providers = providerClient?.config?.providers;
50960
+ if (typeof providers !== "function") {
50961
+ return;
50962
+ }
50963
+ try {
50964
+ 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`));
50965
+ if (response.error) {
50966
+ return {
50967
+ availableModelIds: new Set,
50968
+ source: MODEL_REGISTRY_SOURCE,
50969
+ error: typeof response.error === "string" ? response.error : JSON.stringify(response.error)
50970
+ };
50971
+ }
50972
+ if (!response.data) {
50973
+ return {
50974
+ availableModelIds: new Set,
50975
+ source: MODEL_REGISTRY_SOURCE,
50976
+ error: "provider registry returned no data"
50977
+ };
50978
+ }
50979
+ return {
50980
+ availableModelIds: extractAvailableModelIds(response.data),
50981
+ source: MODEL_REGISTRY_SOURCE
50982
+ };
50983
+ } catch (error93) {
50984
+ return {
50985
+ availableModelIds: new Set,
50986
+ source: MODEL_REGISTRY_SOURCE,
50987
+ error: error93 instanceof Error ? error93.message : String(error93)
50988
+ };
50989
+ }
50990
+ }
50991
+ async function handleDoctorCommand(directory, args2, options = {}) {
50693
50992
  const enableAutoFix = args2.includes("--fix") || args2.includes("-f");
50694
50993
  const config3 = loadPluginConfig(directory);
50695
- const result = runConfigDoctor(config3, directory);
50994
+ const modelAvailability = await loadModelAvailability(directory, options.client);
50995
+ const doctorOptions = { modelAvailability };
50996
+ const result = runConfigDoctor(config3, directory, doctorOptions);
50696
50997
  if (enableAutoFix && result.hasAutoFixableIssues) {
50697
50998
  const { runConfigDoctorWithFixes: runConfigDoctorWithFixes2 } = await Promise.resolve().then(() => (init_config_doctor(), exports_config_doctor));
50698
- const fixResult = await runConfigDoctorWithFixes2(directory, config3, true);
50999
+ const fixResult = await runConfigDoctorWithFixes2(directory, config3, true, doctorOptions);
50699
51000
  return formatDoctorMarkdown(fixResult.result);
50700
51001
  }
50701
51002
  return formatDoctorMarkdown(result);
@@ -50704,6 +51005,7 @@ async function handleDoctorToolsCommand(directory, _args) {
50704
51005
  const result = runToolDoctor(directory);
50705
51006
  return formatToolDoctorMarkdown(result);
50706
51007
  }
51008
+ var MODEL_REGISTRY_TIMEOUT_MS = 3000, MODEL_REGISTRY_SOURCE = "OpenCode config.providers";
50707
51009
  var init_doctor = __esm(() => {
50708
51010
  init_loader();
50709
51011
  init_config_doctor();
@@ -60874,7 +61176,7 @@ function buildHelpText() {
60874
61176
  return lines.join(`
60875
61177
  `);
60876
61178
  }
60877
- function createSwarmCommandHandler(directory, agents) {
61179
+ function createSwarmCommandHandler(directory, agents, client) {
60878
61180
  return async (input, output) => {
60879
61181
  if (input.command !== "swarm" && !input.command.startsWith("swarm-")) {
60880
61182
  return;
@@ -60921,7 +61223,8 @@ ${similar.map((cmd) => ` • /swarm ${cmd}`).join(`
60921
61223
  directory,
60922
61224
  args: resolved.remainingArgs,
60923
61225
  sessionID: input.sessionID,
60924
- agents
61226
+ agents,
61227
+ client
60925
61228
  });
60926
61229
  } catch (_err) {
60927
61230
  const cmdName = tokens[0] || "unknown";
@@ -60937,7 +61240,10 @@ ${text}`;
60937
61240
  if (isFirstRun) {
60938
61241
  const welcomeMessage = `Welcome to OpenCode Swarm! \uD83D\uDC1D
60939
61242
  ` + `
60940
- ` + `Run \`/swarm help\` to see all available commands, or \`/swarm config\` to review your configuration.
61243
+ ` + `Start here: run \`/swarm diagnose\`, then \`/swarm agents\` to confirm the plugin loaded and see the exact models in use.
61244
+ ` + `If a model is unavailable, edit \`.opencode/opencode-swarm.json\` or \`~/.config/opencode/opencode-swarm.json\` and run \`/swarm config doctor\`.
61245
+ ` + `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.
61246
+
60941
61247
  `;
60942
61248
  text = welcomeMessage + text;
60943
61249
  }
@@ -61254,13 +61560,13 @@ var init_registry = __esm(() => {
61254
61560
  clashesWithNativeCcCommand: "/config"
61255
61561
  },
61256
61562
  "config doctor": {
61257
- handler: (ctx) => handleDoctorCommand(ctx.directory, ctx.args),
61563
+ handler: (ctx) => handleDoctorCommand(ctx.directory, ctx.args, { client: ctx.client }),
61258
61564
  description: "Run config doctor checks",
61259
61565
  subcommandOf: "config",
61260
61566
  category: "diagnostics"
61261
61567
  },
61262
61568
  "config-doctor": {
61263
- handler: (ctx) => handleDoctorCommand(ctx.directory, ctx.args),
61569
+ handler: (ctx) => handleDoctorCommand(ctx.directory, ctx.args, { client: ctx.client }),
61264
61570
  description: "Run config doctor checks",
61265
61571
  subcommandOf: "config",
61266
61572
  category: "diagnostics",
@@ -61335,7 +61641,7 @@ var init_registry = __esm(() => {
61335
61641
  deprecated: true
61336
61642
  },
61337
61643
  doctor: {
61338
- handler: (ctx) => handleDoctorCommand(ctx.directory, ctx.args),
61644
+ handler: (ctx) => handleDoctorCommand(ctx.directory, ctx.args, { client: ctx.client }),
61339
61645
  description: "Run config doctor checks",
61340
61646
  category: "diagnostics",
61341
61647
  aliasOf: "config doctor",
@@ -71623,13 +71929,7 @@ function writeSwarmConfigExampleIfNew(projectDirectory) {
71623
71929
  fs40.mkdirSync(swarmDir, { recursive: true });
71624
71930
  }
71625
71931
  const example = {
71626
- agents: Object.fromEntries(Object.entries(DEFAULT_MODELS).filter(([name2]) => name2 !== "default").map(([name2, model]) => [
71627
- name2,
71628
- {
71629
- model,
71630
- fallback_models: ["opencode/gpt-5-nano", "opencode/big-pickle"]
71631
- }
71632
- ])),
71932
+ agents: DEFAULT_AGENT_CONFIGS,
71633
71933
  max_iterations: 5
71634
71934
  };
71635
71935
  fs40.writeFileSync(dest, `${JSON.stringify(example, null, 2)}
@@ -74071,31 +74371,6 @@ import * as fsPromises5 from "node:fs/promises";
74071
74371
  import * as os7 from "node:os";
74072
74372
  import * as path66 from "node:path";
74073
74373
 
74074
- // src/utils/timeout.ts
74075
- async function withTimeout(promise3, ms, timeoutError) {
74076
- let timer;
74077
- const timeoutPromise = new Promise((_, reject) => {
74078
- timer = setTimeout(() => reject(timeoutError), ms);
74079
- if (typeof timer.unref === "function") {
74080
- timer.unref();
74081
- }
74082
- });
74083
- try {
74084
- return await Promise.race([promise3, timeoutPromise]);
74085
- } finally {
74086
- if (timer !== undefined)
74087
- clearTimeout(timer);
74088
- }
74089
- }
74090
- function yieldToEventLoop() {
74091
- return new Promise((resolve19) => {
74092
- const t = setTimeout(resolve19, 0);
74093
- if (typeof t.unref === "function") {
74094
- t.unref();
74095
- }
74096
- });
74097
- }
74098
-
74099
74374
  // src/tools/symbols.ts
74100
74375
  init_zod();
74101
74376
  init_create_tool();
@@ -103393,7 +103668,6 @@ init_write_retro();
103393
103668
 
103394
103669
  // src/index.ts
103395
103670
  init_utils();
103396
-
103397
103671
  // src/utils/tool-output.ts
103398
103672
  function truncateToolOutput(output, maxLines, toolName, tailLines = 10) {
103399
103673
  if (!output) {
@@ -103536,7 +103810,7 @@ async function initializeOpenCodeSwarm(ctx) {
103536
103810
  const systemEnhancerHook = createSystemEnhancerHook(config3, ctx.directory);
103537
103811
  const compactionHook = createCompactionCustomizerHook(config3, ctx.directory);
103538
103812
  const contextBudgetHandler = createContextBudgetHandler(config3);
103539
- const commandHandler = createSwarmCommandHandler(ctx.directory, Object.fromEntries(agentDefinitions.map((agent) => [agent.name, agent])));
103813
+ const commandHandler = createSwarmCommandHandler(ctx.directory, Object.fromEntries(agentDefinitions.map((agent) => [agent.name, agent])), ctx.client);
103540
103814
  const activityHooks = createAgentActivityHooks(config3, ctx.directory);
103541
103815
  const prmHook = createPrmHook(config3.prm ?? PrmConfigSchema.parse({}), ctx.directory);
103542
103816
  const trajectoryLoggerHook = createTrajectoryLoggerHook({
@@ -56,6 +56,18 @@ export interface ConfigDoctorResult {
56
56
  /** The config that was analyzed */
57
57
  configSource: string;
58
58
  }
59
+ /** Model availability snapshot from the active OpenCode provider registry. */
60
+ export interface ModelAvailability {
61
+ /** Fully-qualified model IDs in provider/model form. */
62
+ availableModelIds: ReadonlySet<string>;
63
+ /** Human-readable source for diagnostics. */
64
+ source: string;
65
+ /** Optional failure message when the registry could not be loaded. */
66
+ error?: string;
67
+ }
68
+ export interface ConfigDoctorOptions {
69
+ modelAvailability?: ModelAvailability;
70
+ }
59
71
  /** Backup artifact for rollback */
60
72
  export interface ConfigBackup {
61
73
  /** When the backup was created */
@@ -90,10 +102,11 @@ export declare function writeBackupArtifact(directory: string, backup: ConfigBac
90
102
  * @returns the path to the restored config file, or null if restore failed
91
103
  */
92
104
  export declare function restoreFromBackup(backupPath: string, directory: string): string | null;
105
+ export declare function collectConfiguredModelRefs(config: PluginConfig): Map<string, Set<string>>;
93
106
  /**
94
107
  * Run the config doctor on a loaded config
95
108
  */
96
- export declare function runConfigDoctor(config: PluginConfig, directory: string): ConfigDoctorResult;
109
+ export declare function runConfigDoctor(config: PluginConfig, directory: string, options?: ConfigDoctorOptions): ConfigDoctorResult;
97
110
  /**
98
111
  * Apply safe auto-fixes to config
99
112
  * Only applies low-risk, non-destructive fixes
@@ -116,7 +129,7 @@ export declare function shouldRunOnStartup(automationConfig: {
116
129
  /**
117
130
  * Full config doctor run with backup and fix application
118
131
  */
119
- export declare function runConfigDoctorWithFixes(directory: string, config: PluginConfig, autoFix?: boolean): Promise<{
132
+ export declare function runConfigDoctorWithFixes(directory: string, config: PluginConfig, autoFix?: boolean, options?: ConfigDoctorOptions): Promise<{
120
133
  result: ConfigDoctorResult;
121
134
  backupPath: string | null;
122
135
  appliedFixes: ConfigFix[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.18.1",
3
+ "version": "7.18.2",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",