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 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.0",
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>;