@kodrunhq/opencode-autopilot 1.1.3 → 1.2.1

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.
@@ -2,87 +2,26 @@
2
2
  description: Configure opencode-autopilot model assignments for each agent group
3
3
  agent: autopilot
4
4
  ---
5
- Help the user configure opencode-autopilot by walking through the model
6
- assignment process interactively.
5
+ Model configuration uses an interactive terminal wizard with searchable
6
+ model selection. This cannot be done inside the OpenCode TUI — it requires
7
+ a separate terminal.
7
8
 
8
- ## Step 1: Discover models and show the list
9
-
10
- Call the oc_configure tool with subcommand "start". The response contains:
11
- - `displayText`: a pre-formatted numbered list of ALL available models.
12
- **Show this to the user VERBATIM. Do not summarize, truncate, or reformat it.**
13
- - `modelIndex`: a map of number -> model ID (e.g. {"1": "anthropic/claude-opus-4-6"})
14
- - `groups`: the 8 agent groups with descriptions and recommendations
15
- - `currentConfig`: existing assignments if reconfiguring
16
- - `diversityRules`: adversarial diversity constraints
17
-
18
- Print `displayText` exactly as returned. This is the complete model list
19
- with instructions. Do not add, remove, or reorder entries.
20
-
21
- ## Step 2: Walk through each group
22
-
23
- For each of the 8 groups (architects first, utilities last):
24
-
25
- 1. Explain what the group does and which agents belong to it
26
- 2. Show the tier recommendation
27
- 3. For adversarial groups (challengers, reviewers, red-team): explain WHY
28
- model diversity matters and which group they are adversarial to
29
- 4. Re-print `displayText` so the user can see the numbered list
30
-
31
- Then ask:
9
+ Tell the user to open a terminal and run:
32
10
 
33
11
  ```
34
- Enter model numbers for [Group Name], separated by commas (e.g. 1,4,7):
35
- First = primary, rest = fallbacks in order.
12
+ bunx @kodrunhq/opencode-autopilot configure
36
13
  ```
37
14
 
38
- ### Parsing the user's response
39
-
40
- - Numbers like "1,4,7": look up each in `modelIndex` to get model IDs
41
- - Model IDs typed directly (e.g. "anthropic/claude-opus-4-6"): use as-is
42
- - Single number (e.g. "1"): primary only, no fallbacks
43
-
44
- The FIRST model is the primary. All subsequent models are fallbacks,
45
- tried in sequence when the primary is rate-limited or fails.
46
-
47
- Call oc_configure with subcommand "assign":
48
- - `group`: the group ID
49
- - `primary`: first model from the user's list
50
- - `fallbacks`: remaining models as comma-separated string
51
-
52
- ### Diversity warnings
53
-
54
- If the assign response contains `diversityWarnings`, explain them
55
- conversationally. Strong warnings should be highlighted — the user can
56
- still proceed, but make the quality trade-off clear.
57
-
58
- Example: "Heads up: Architects and Challengers both use Claude models.
59
- Challengers are supposed to critique Architect decisions — using the same
60
- model family means you get confirmation bias instead of genuine challenge.
61
- Consider picking a different family for one of them. Continue anyway?"
62
-
63
- ## Step 3: Commit and verify
64
-
65
- After all 8 groups are assigned, call oc_configure with subcommand "commit".
66
-
67
- Then call oc_configure with subcommand "doctor" to verify health.
68
-
69
- Show a final summary table:
70
-
71
- ```
72
- Group | Primary | Fallbacks
73
- ───────────────┼──────────────────────────────┼──────────────────────────
74
- Architects | anthropic/claude-opus-4-6 | openai/gpt-5.4
75
- Challengers | openai/gpt-5.4 | google/gemini-3.1-pro
76
- ...
77
- ```
15
+ This will:
16
+ 1. Discover all available models from their configured providers
17
+ 2. Walk through each of the 8 agent groups with a searchable model picker
18
+ 3. For each group, select a primary model and optional fallback models
19
+ 4. Check adversarial diversity (different families for groups that review each other)
20
+ 5. Save the configuration
78
21
 
79
- ## Rules
22
+ After running the wizard, tell the user to restart OpenCode to pick up
23
+ the new model assignments.
80
24
 
81
- - ALWAYS show `displayText` VERBATIM never summarize or truncate the model list.
82
- - ALWAYS re-print `displayText` before asking for each group's selection.
83
- - ALWAYS ask for comma-separated numbers (ordered list, not just one pick).
84
- - NEVER pre-select models for the user. They choose from the full list.
85
- - NEVER skip fallback collection. Emphasize: more fallbacks = more resilience.
86
- - If the user says "pick for me" or "use defaults", THEN you may suggest
87
- assignments based on the tier recommendations and diversity rules, but
88
- still show what you picked and ask for confirmation.
25
+ Do NOT attempt to configure models through the oc_configure tool's assign
26
+ subcommand directly. The interactive wizard is the supported configuration
27
+ method.
package/bin/cli.ts CHANGED
@@ -10,6 +10,7 @@ import { diagnose } from "../src/registry/doctor";
10
10
  import { ALL_GROUP_IDS, DIVERSITY_RULES, GROUP_DEFINITIONS } from "../src/registry/model-groups";
11
11
  import type { GroupId } from "../src/registry/types";
12
12
  import { fileExists } from "../src/utils/fs-helpers";
13
+ import { runConfigure } from "./configure-tui";
13
14
 
14
15
  const execFile = promisify(execFileCb);
15
16
 
@@ -124,14 +125,12 @@ export async function runInstall(options: CliOptions = {}): Promise<void> {
124
125
  console.log("");
125
126
  console.log(bold("Next steps:"));
126
127
  console.log("");
127
- console.log(" 1. Launch OpenCode");
128
- console.log(" 2. Run /oc-configure to set up your model assignments");
128
+ console.log(" Run the interactive configuration wizard:");
129
129
  console.log("");
130
- console.log(" Or paste this into your AI session for guided setup:");
130
+ console.log(` ${bold("bunx @kodrunhq/opencode-autopilot configure")}`);
131
131
  console.log("");
132
- console.log(
133
- " https://raw.githubusercontent.com/kodrunhq/opencode-autopilot/main/docs/guide/installation.md",
134
- );
132
+ console.log(" This walks through each agent group with searchable model");
133
+ console.log(" selection and fallback configuration.");
135
134
  console.log("");
136
135
  }
137
136
 
@@ -313,6 +312,7 @@ function printUsage(): void {
313
312
  console.log("");
314
313
  console.log("Commands:");
315
314
  console.log(" install Register the plugin and create starter config");
315
+ console.log(" configure Interactive model assignment for each agent group");
316
316
  console.log(" doctor Check installation health and model assignments");
317
317
  console.log("");
318
318
  console.log("Options:");
@@ -330,6 +330,9 @@ if (import.meta.main) {
330
330
  case "install":
331
331
  await runInstall({ noTui: args.includes("--no-tui") });
332
332
  break;
333
+ case "configure":
334
+ await runConfigure();
335
+ break;
333
336
  case "doctor":
334
337
  await runDoctor();
335
338
  break;
@@ -0,0 +1,338 @@
1
+ /**
2
+ * Interactive TUI for configuring model assignments.
3
+ * Uses @inquirer/search (filterable single-select) and @inquirer/checkbox
4
+ * (multi-select) to handle 100+ models deterministically — no LLM involved.
5
+ */
6
+
7
+ import { execFile as execFileCb } from "node:child_process";
8
+ import { promisify } from "node:util";
9
+ import checkbox, { Separator as CheckboxSeparator } from "@inquirer/checkbox";
10
+ import confirm from "@inquirer/confirm";
11
+ import search from "@inquirer/search";
12
+ import { CONFIG_PATH, createDefaultConfig, loadConfig, saveConfig } from "../src/config";
13
+ import { checkDiversity } from "../src/registry/diversity";
14
+ import { ALL_GROUP_IDS, DIVERSITY_RULES, GROUP_DEFINITIONS } from "../src/registry/model-groups";
15
+ import type { GroupId, GroupModelAssignment } from "../src/registry/types";
16
+
17
+ const execFile = promisify(execFileCb);
18
+
19
+ // ── ANSI helpers ───────────────────────────────────────────────────
20
+
21
+ const green = (s: string) => `\x1b[32m${s}\x1b[0m`;
22
+ const red = (s: string) => `\x1b[31m${s}\x1b[0m`;
23
+ const yellow = (s: string) => `\x1b[33m${s}\x1b[0m`;
24
+ const bold = (s: string) => `\x1b[1m${s}\x1b[0m`;
25
+ const dim = (s: string) => `\x1b[2m${s}\x1b[0m`;
26
+ const cyan = (s: string) => `\x1b[36m${s}\x1b[0m`;
27
+
28
+ // ── Model discovery ────────────────────────────────────────────────
29
+
30
+ interface DiscoveredModel {
31
+ readonly id: string; // "provider/model"
32
+ readonly provider: string;
33
+ readonly model: string;
34
+ }
35
+
36
+ /**
37
+ * Discover available models by running `opencode models`.
38
+ * Each line of output is a "provider/model" string.
39
+ */
40
+ async function discoverModels(): Promise<readonly DiscoveredModel[]> {
41
+ try {
42
+ const { stdout } = await execFile("opencode", ["models"]);
43
+ const lines = stdout
44
+ .split("\n")
45
+ .map((l) => l.trim())
46
+ .filter(Boolean);
47
+
48
+ return lines.map((id) => {
49
+ const slashIndex = id.indexOf("/");
50
+ return {
51
+ id,
52
+ provider: slashIndex > 0 ? id.slice(0, slashIndex) : "unknown",
53
+ model: slashIndex > 0 ? id.slice(slashIndex + 1) : id,
54
+ };
55
+ });
56
+ } catch {
57
+ return [];
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Group models by provider for display with separators.
63
+ */
64
+ function groupByProvider(models: readonly DiscoveredModel[]): Map<string, DiscoveredModel[]> {
65
+ const grouped = new Map<string, DiscoveredModel[]>();
66
+ for (const m of models) {
67
+ const existing = grouped.get(m.provider) ?? [];
68
+ existing.push(m);
69
+ grouped.set(m.provider, existing);
70
+ }
71
+ return grouped;
72
+ }
73
+
74
+ // ── Search source for @inquirer/search ─────────────────────────────
75
+
76
+ function createSearchSource(models: readonly DiscoveredModel[], exclude?: Set<string>) {
77
+ const byProvider = groupByProvider(models);
78
+
79
+ return async (term: string | undefined) => {
80
+ const results: Array<
81
+ { name: string; value: string; description: string } | typeof CheckboxSeparator.prototype
82
+ > = [];
83
+
84
+ for (const [provider, providerModels] of byProvider) {
85
+ const filtered = providerModels.filter((m) => {
86
+ if (exclude?.has(m.id)) return false;
87
+ if (!term) return true;
88
+ return m.id.toLowerCase().includes(term.toLowerCase());
89
+ });
90
+
91
+ if (filtered.length === 0) continue;
92
+
93
+ results.push(new CheckboxSeparator(`── ${provider} ──`));
94
+ for (const m of filtered) {
95
+ results.push({
96
+ name: m.id,
97
+ value: m.id,
98
+ description: m.model,
99
+ });
100
+ }
101
+ }
102
+
103
+ return results;
104
+ };
105
+ }
106
+
107
+ // ── Checkbox choices for fallback selection ─────────────────────────
108
+
109
+ function createCheckboxChoices(models: readonly DiscoveredModel[], excludePrimary: string) {
110
+ const byProvider = groupByProvider(models);
111
+ const choices: Array<{ name: string; value: string } | InstanceType<typeof CheckboxSeparator>> =
112
+ [];
113
+
114
+ for (const [provider, providerModels] of byProvider) {
115
+ const filtered = providerModels.filter((m) => m.id !== excludePrimary);
116
+ if (filtered.length === 0) continue;
117
+
118
+ choices.push(new CheckboxSeparator(`── ${provider} ──`));
119
+ for (const m of filtered) {
120
+ choices.push({
121
+ name: m.id,
122
+ value: m.id,
123
+ });
124
+ }
125
+ }
126
+
127
+ return choices;
128
+ }
129
+
130
+ // ── Diversity check display ────────────────────────────────────────
131
+
132
+ function extractFamily(model: string): string {
133
+ const slashIndex = model.indexOf("/");
134
+ return slashIndex > 0 ? model.slice(0, slashIndex) : model;
135
+ }
136
+
137
+ function showDiversityWarnings(assignments: Record<string, GroupModelAssignment>): void {
138
+ const warnings = checkDiversity(assignments);
139
+ if (warnings.length === 0) return;
140
+
141
+ console.log("");
142
+ for (const w of warnings) {
143
+ const groupLabels = w.groups.map((g) => GROUP_DEFINITIONS[g as GroupId]?.label ?? g);
144
+ const label = groupLabels.join(" & ");
145
+ const severity = w.rule.severity === "strong" ? red("WARNING") : yellow("note");
146
+ console.log(` ${severity}: ${label} both use ${cyan(w.sharedFamily)} family`);
147
+ console.log(` ${dim(w.rule.reason)}`);
148
+ console.log("");
149
+ }
150
+ }
151
+
152
+ // ── Group walkthrough ──────────────────────────────────────────────
153
+
154
+ async function configureGroup(
155
+ groupId: GroupId,
156
+ models: readonly DiscoveredModel[],
157
+ assignments: Record<string, GroupModelAssignment>,
158
+ ): Promise<GroupModelAssignment> {
159
+ const def = GROUP_DEFINITIONS[groupId];
160
+
161
+ console.log("");
162
+ console.log(bold(`── ${def.label} ──────────────────────────────────────`));
163
+ console.log(` ${dim("Purpose:")} ${def.purpose}`);
164
+ console.log(` ${dim("Recommendation:")} ${def.recommendation}`);
165
+
166
+ // Check if this group is adversarial to another
167
+ for (const rule of DIVERSITY_RULES) {
168
+ if (rule.groups.includes(groupId)) {
169
+ const others = rule.groups.filter((g) => g !== groupId);
170
+ const assignedOthers = others.filter((g) => assignments[g]);
171
+ if (assignedOthers.length > 0) {
172
+ const otherLabels = assignedOthers.map(
173
+ (g) =>
174
+ `${GROUP_DEFINITIONS[g as GroupId].label} (${extractFamily(assignments[g].primary)})`,
175
+ );
176
+ console.log(
177
+ ` ${yellow("⚡")} Adversarial to: ${otherLabels.join(", ")} — pick a ${bold("different")} family`,
178
+ );
179
+ }
180
+ }
181
+ }
182
+
183
+ console.log("");
184
+
185
+ // Primary model — searchable select
186
+ const primary = await search({
187
+ message: `Primary model for ${def.label}:`,
188
+ source: createSearchSource(models),
189
+ pageSize: 15,
190
+ });
191
+
192
+ // Fallback models — checkbox multi-select
193
+ const wantFallbacks = await confirm({
194
+ message: "Add fallback models? (recommended for resilience)",
195
+ default: true,
196
+ });
197
+
198
+ let fallbacks: string[] = [];
199
+ if (wantFallbacks) {
200
+ fallbacks = await checkbox({
201
+ message: `Fallback models for ${def.label} (space to select, enter to confirm):`,
202
+ choices: createCheckboxChoices(models, primary),
203
+ pageSize: 15,
204
+ });
205
+ }
206
+
207
+ const assignment: GroupModelAssignment = Object.freeze({
208
+ primary,
209
+ fallbacks: Object.freeze(fallbacks),
210
+ });
211
+
212
+ // Show what was selected
213
+ console.log(
214
+ ` ${green("✓")} ${def.label}: ${cyan(primary)}${fallbacks.length > 0 ? ` → ${fallbacks.map(cyan).join(" → ")}` : ""}`,
215
+ );
216
+
217
+ return assignment;
218
+ }
219
+
220
+ // ── Main configure flow ────────────────────────────────────────────
221
+
222
+ export async function runConfigure(configPath: string = CONFIG_PATH): Promise<void> {
223
+ console.log("");
224
+ console.log(bold("opencode-autopilot configure"));
225
+ console.log("────────────────────────────");
226
+ console.log("");
227
+
228
+ // 1. Discover models
229
+ console.log(" Discovering available models...");
230
+ const models = await discoverModels();
231
+
232
+ if (models.length === 0) {
233
+ console.log("");
234
+ console.log(red(" No models found."));
235
+ console.log(" Make sure OpenCode is installed and you have providers configured.");
236
+ console.log(" Run: opencode providers list");
237
+ console.log("");
238
+ process.exit(1);
239
+ }
240
+
241
+ const byProvider = groupByProvider(models);
242
+ console.log(
243
+ ` ${green("✓")} Found ${bold(String(models.length))} models from ${bold(String(byProvider.size))} providers`,
244
+ );
245
+
246
+ // 2. Load existing config
247
+ const existingConfig = await loadConfig(configPath);
248
+ if (existingConfig?.configured) {
249
+ console.log(` ${yellow("⚠")} Existing configuration found — this will overwrite it`);
250
+ const proceed = await confirm({
251
+ message: "Continue with reconfiguration?",
252
+ default: true,
253
+ });
254
+ if (!proceed) {
255
+ console.log(" Cancelled.");
256
+ return;
257
+ }
258
+ }
259
+
260
+ console.log("");
261
+ console.log(bold("Walk through each agent group and assign models."));
262
+ console.log(dim("Type to search, arrow keys to navigate, enter to select."));
263
+ console.log(dim("For fallbacks: space to toggle, enter to confirm selection."));
264
+
265
+ // 3. Walk through each group
266
+ const assignments: Record<string, GroupModelAssignment> = {};
267
+
268
+ for (const groupId of ALL_GROUP_IDS) {
269
+ assignments[groupId] = await configureGroup(groupId, models, assignments);
270
+ showDiversityWarnings(assignments);
271
+ }
272
+
273
+ // 4. Show summary
274
+ console.log("");
275
+ console.log(bold("── Summary ───────────────────────────────────────────"));
276
+ console.log("");
277
+
278
+ const labelWidth = Math.max(...ALL_GROUP_IDS.map((id) => GROUP_DEFINITIONS[id].label.length)) + 2;
279
+
280
+ for (const groupId of ALL_GROUP_IDS) {
281
+ const def = GROUP_DEFINITIONS[groupId];
282
+ const a = assignments[groupId];
283
+ const label = def.label.padEnd(labelWidth);
284
+ const fallbackStr = a.fallbacks.length > 0 ? ` → ${a.fallbacks.join(" → ")}` : "";
285
+ console.log(` ${label} ${cyan(a.primary)}${dim(fallbackStr)}`);
286
+ }
287
+
288
+ console.log("");
289
+
290
+ // 5. Confirm and save
291
+ const doCommit = await confirm({
292
+ message: "Save this configuration?",
293
+ default: true,
294
+ });
295
+
296
+ if (!doCommit) {
297
+ console.log(" Configuration discarded.");
298
+ return;
299
+ }
300
+
301
+ // Build and save config
302
+ const baseConfig = existingConfig ?? createDefaultConfig();
303
+ const groupsRecord: Record<string, { primary: string; fallbacks: string[] }> = {};
304
+ for (const [key, val] of Object.entries(assignments)) {
305
+ groupsRecord[key] = { primary: val.primary, fallbacks: [...val.fallbacks] };
306
+ }
307
+
308
+ const newConfig = {
309
+ ...baseConfig,
310
+ version: 4 as const,
311
+ configured: true,
312
+ groups: groupsRecord,
313
+ overrides: baseConfig.overrides ?? {},
314
+ };
315
+
316
+ await saveConfig(newConfig, configPath);
317
+ console.log(` ${green("✓")} Configuration saved`);
318
+
319
+ // 6. Final diversity check
320
+ console.log("");
321
+ console.log(bold("Adversarial Diversity Check"));
322
+ const finalWarnings = checkDiversity(groupsRecord);
323
+ if (finalWarnings.length === 0) {
324
+ console.log(` ${green("✓")} All adversarial groups use different model families`);
325
+ } else {
326
+ for (const w of finalWarnings) {
327
+ const groupLabels = w.groups.map((g) => GROUP_DEFINITIONS[g as GroupId]?.label ?? g);
328
+ console.log(
329
+ ` ${yellow("⚠")} ${groupLabels.join(" & ")}: shared ${cyan(w.sharedFamily)} family — ${dim(w.rule.reason)}`,
330
+ );
331
+ }
332
+ }
333
+
334
+ console.log("");
335
+ console.log(green("Configuration complete!"));
336
+ console.log(dim("Run 'bunx @kodrunhq/opencode-autopilot doctor' to verify."));
337
+ console.log("");
338
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kodrunhq/opencode-autopilot",
3
- "version": "1.1.3",
3
+ "version": "1.2.1",
4
4
  "description": "Curated agents, skills, and commands for the OpenCode AI coding CLI — autonomous orchestrator, multi-agent code review, model fallback, and in-session asset creation tools.",
5
5
  "main": "src/index.ts",
6
6
  "keywords": [
@@ -51,6 +51,10 @@
51
51
  },
52
52
  "type": "module",
53
53
  "dependencies": {
54
+ "@inquirer/checkbox": "^5.1.2",
55
+ "@inquirer/confirm": "^6.0.10",
56
+ "@inquirer/search": "^4.1.6",
57
+ "@inquirer/select": "^5.1.2",
54
58
  "yaml": "^2.8.3"
55
59
  }
56
60
  }
package/src/installer.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { readdir, unlink } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import { copyIfMissing, isEnoentError } from "./utils/fs-helpers";
1
+ import { access, copyFile, readdir, unlink } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { copyIfMissing, ensureDir, isEnoentError } from "./utils/fs-helpers";
4
4
  import { getAssetsDir, getGlobalConfigDir } from "./utils/paths";
5
5
 
6
6
  /**
@@ -9,6 +9,13 @@ import { getAssetsDir, getGlobalConfigDir } from "./utils/paths";
9
9
  */
10
10
  const DEPRECATED_ASSETS = ["agents/placeholder-agent.md", "commands/configure.md"] as const;
11
11
 
12
+ /**
13
+ * Assets that must be overwritten on every install, even if the user has a copy.
14
+ * Used when the shipped version has critical fixes that override user customizations.
15
+ * Remove entries once the fix has been deployed long enough.
16
+ */
17
+ const FORCE_UPDATE_ASSETS = ["commands/oc-configure.md"] as const;
18
+
12
19
  export interface InstallResult {
13
20
  readonly copied: readonly string[];
14
21
  readonly skipped: readonly string[];
@@ -138,6 +145,33 @@ async function cleanupDeprecatedAssets(
138
145
  return { removed, errors };
139
146
  }
140
147
 
148
+ async function forceUpdateAssets(
149
+ sourceDir: string,
150
+ targetDir: string,
151
+ ): Promise<{ readonly updated: readonly string[]; readonly errors: readonly string[] }> {
152
+ const updated: string[] = [];
153
+ const errors: string[] = [];
154
+ for (const asset of FORCE_UPDATE_ASSETS) {
155
+ const source = join(sourceDir, asset);
156
+ const target = join(targetDir, asset);
157
+ try {
158
+ // Verify source exists before attempting copy — skip silently if
159
+ // the source isn't in the bundle (test environments, partial installs)
160
+ await access(source);
161
+ await ensureDir(dirname(target));
162
+ await copyFile(source, target);
163
+ updated.push(asset);
164
+ } catch (error: unknown) {
165
+ if (!isEnoentError(error)) {
166
+ const message = error instanceof Error ? error.message : String(error);
167
+ errors.push(`force-update ${asset}: ${message}`);
168
+ }
169
+ // ENOENT = source not in bundle, skip silently
170
+ }
171
+ }
172
+ return { updated, errors };
173
+ }
174
+
141
175
  export async function installAssets(
142
176
  assetsDir: string = getAssetsDir(),
143
177
  targetDir: string = getGlobalConfigDir(),
@@ -145,6 +179,9 @@ export async function installAssets(
145
179
  // Remove deprecated assets before copying new ones
146
180
  const cleanup = await cleanupDeprecatedAssets(targetDir);
147
181
 
182
+ // Force-overwrite assets with critical fixes
183
+ const forceUpdate = await forceUpdateAssets(assetsDir, targetDir);
184
+
148
185
  const [agents, commands, skills] = await Promise.all([
149
186
  processFiles(assetsDir, targetDir, "agents"),
150
187
  processFiles(assetsDir, targetDir, "commands"),
@@ -152,8 +189,14 @@ export async function installAssets(
152
189
  ]);
153
190
 
154
191
  return {
155
- copied: [...agents.copied, ...commands.copied, ...skills.copied],
192
+ copied: [...forceUpdate.updated, ...agents.copied, ...commands.copied, ...skills.copied],
156
193
  skipped: [...agents.skipped, ...commands.skipped, ...skills.skipped],
157
- errors: [...cleanup.errors, ...agents.errors, ...commands.errors, ...skills.errors],
194
+ errors: [
195
+ ...cleanup.errors,
196
+ ...forceUpdate.errors,
197
+ ...agents.errors,
198
+ ...commands.errors,
199
+ ...skills.errors,
200
+ ],
158
201
  };
159
202
  }
@@ -197,17 +197,36 @@ async function handleStart(configPath?: string): Promise<string> {
197
197
  "then type model IDs manually (e.g. anthropic/claude-opus-4-6).",
198
198
  ].join("\n");
199
199
 
200
+ // Compact group summaries — only fields the LLM needs for the walkthrough
201
+ const compactGroups = groups.map((g) => ({
202
+ id: g.id,
203
+ label: g.label,
204
+ purpose: g.purpose,
205
+ recommendation: g.recommendation,
206
+ agents: g.agents,
207
+ currentAssignment: g.currentAssignment,
208
+ }));
209
+
210
+ // Compact diversity rules — just the text the LLM should mention
211
+ const compactRules = DIVERSITY_RULES.map((r) => ({
212
+ groups: r.groups,
213
+ severity: r.severity,
214
+ reason: r.reason,
215
+ }));
216
+
217
+ // NOTE: availableModels is intentionally excluded — it's redundant with
218
+ // displayText/modelIndex and can be 400KB+ with many providers, causing
219
+ // OpenCode to truncate the tool output and lose everything after it.
200
220
  return JSON.stringify({
201
221
  action: "configure",
202
222
  stage: "start",
203
- availableModels: Object.fromEntries(modelsByProvider),
204
- modelIndex: indexMap,
205
223
  displayText,
206
- groups,
224
+ modelIndex: indexMap,
225
+ groups: compactGroups,
226
+ diversityRules: compactRules,
207
227
  currentConfig: currentConfig
208
228
  ? { configured: currentConfig.configured, groups: currentConfig.groups }
209
229
  : null,
210
- diversityRules: DIVERSITY_RULES,
211
230
  });
212
231
  }
213
232