@phi-code-admin/phi-code 0.75.5 → 0.75.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -369,14 +369,25 @@ _Edit this file to customize Phi Code's behavior for your project._
369
369
  await writeModelsConfig(config);
370
370
  }
371
371
 
372
- // ─── Manual model assignment (one model per agent role) ──────────────
372
+ // ─── Manual model assignment (one model per orchestration role) ─────
373
+ //
374
+ // As of 0.75.6, `/phi-init` ONLY configures orchestration role models
375
+ // (used by `/plan` and the orchestrator). The chat default model is
376
+ // owned exclusively by `/model` and persisted via the settings manager.
377
+ // We intentionally do NOT ask "Default model" here — that would override
378
+ // the user's `/model` choice on every routing decision.
373
379
 
374
380
  async function manualMode(
375
381
  availableModels: string[],
376
382
  ctx: any,
377
383
  ): Promise<Record<string, { preferred: string; fallback: string }>> {
378
- ctx.ui.notify("Manual mode: assign a model to each task category.\n", "info");
379
- const modelOptions = ["default (use current model)", ...availableModels];
384
+ ctx.ui.notify(
385
+ "Assign a model to each orchestration role.\n" +
386
+ "These models are used by `/plan` and the orchestrator — NOT by normal chat.\n" +
387
+ "The chat default model is controlled via `/model` (and stays sticky across prompts).\n",
388
+ "info",
389
+ );
390
+ const modelOptions = ["default (use current chat model)", ...availableModels];
380
391
  const assignments: Record<string, { preferred: string; fallback: string }> = {};
381
392
 
382
393
  for (const role of TASK_ROLES) {
@@ -391,9 +402,12 @@ _Edit this file to customize Phi Code's behavior for your project._
391
402
  ctx.ui.notify(` ${role.label}: ${preferredModel} (fallback: ${fallback})`, "info");
392
403
  }
393
404
 
394
- const defaultChoice = await ctx.ui.select("Default model (for general tasks)", modelOptions);
395
- const defaultModel = defaultChoice && defaultChoice !== modelOptions[0] ? defaultChoice : "default";
396
- assignments["default"] = { preferred: defaultModel, fallback: availableModels[0] || "default" };
405
+ // Orchestrator fallback (used only when a specific route has no model).
406
+ // This is NOT the chat default `/model` controls that.
407
+ assignments["default"] = {
408
+ preferred: "default",
409
+ fallback: availableModels[0] || "default",
410
+ };
397
411
  return assignments;
398
412
  }
399
413
 
@@ -505,14 +519,14 @@ _Edit this file to customize Phi Code's behavior for your project._
505
519
  handler: async (_args, ctx) => {
506
520
  try {
507
521
  ctx.ui.notify(
508
- "NOTE: `/phi-init` is the legacy wizard. The refined replacement is `/setup` " +
509
- "(richer flow: Alibaba dual-endpoint, OpenCode Go auto-fetch, ping validation, " +
510
- "separate chat/orchestration assignments, hot-reload integration). " +
511
- "This legacy command still works for backwards compatibility.",
522
+ "`/phi-init` configures **orchestration** roles only (Code / Debug / Plan / Explore / " +
523
+ "Test / Review used by `/plan` and the orchestrator).\n\n" +
524
+ "The **chat default model** is owned exclusively by `/model` and stays sticky across " +
525
+ "prompts. This wizard will NOT change it.",
512
526
  "info",
513
527
  );
514
528
 
515
- ctx.ui.notify(" Phi Code Setup Wizard", "info");
529
+ ctx.ui.notify(" Phi Code Setup Wizard (orchestration roles)", "info");
516
530
 
517
531
  // 1. Detect providers (env vars + local servers + previously saved keys)
518
532
  ctx.ui.notify("Detecting providers...\n", "info");
@@ -619,17 +633,21 @@ _Edit this file to customize Phi Code's behavior for your project._
619
633
  ctx.ui.notify(` Config: ${agentDir}`, "info");
620
634
  ctx.ui.notify(` Memory: ${memoryDir}`, "info");
621
635
  ctx.ui.notify(` Agents: ${agentsDir}`, "info");
622
- ctx.ui.notify("\nModel Assignments:", "info");
636
+ ctx.ui.notify("\nOrchestration role assignments (used by `/plan`):", "info");
623
637
  for (const role of TASK_ROLES) {
624
638
  const a = assignments[role.key];
625
639
  ctx.ui.notify(` ${role.label}: \`${a.preferred}\` (fallback: \`${a.fallback}\`)`, "info");
626
640
  }
627
- ctx.ui.notify(` Default: \`${assignments["default"].preferred}\``, "info");
641
+ ctx.ui.notify(
642
+ "\nChat default model: use `/model` (this wizard does NOT change the chat default).",
643
+ "info",
644
+ );
628
645
  ctx.ui.notify("\nNext steps:", "info");
646
+ ctx.ui.notify(" - `/model` to pick the chat default model (sticky across prompts)", "info");
647
+ ctx.ui.notify(" - `/plan <description>` to run the orchestrator with the roles above", "info");
648
+ ctx.ui.notify(" - `/routing` to inspect the route table (auto-switch is OFF by default)", "info");
649
+ ctx.ui.notify(" - `/models refresh` to re-fetch the live model catalog", "info");
629
650
  ctx.ui.notify(" - Edit `~/.phi/memory/AGENTS.md` with your project instructions", "info");
630
- ctx.ui.notify(" - Run `/agents` to see available sub-agents", "info");
631
- ctx.ui.notify(" - Run `/skills` to see available skills", "info");
632
- ctx.ui.notify(" - Run `/models refresh` to re-fetch the model catalog", "info");
633
651
  ctx.ui.notify(" - Start coding!\n", "info");
634
652
  } catch (error) {
635
653
  const message = error instanceof Error ? error.message : String(error);
@@ -199,8 +199,7 @@ function buildStatusWidget(
199
199
  lines.push(` ${icon} ${p.displayName}${note}`);
200
200
  }
201
201
  lines.push("");
202
- lines.push("Assignments:");
203
- lines.push(` Default chat : ${assignments.default ?? "(not set)"}`);
202
+ lines.push("Orchestration roles (used by /plan — NOT chat):");
204
203
  for (const role of ORCHESTRATION_ROLES) {
205
204
  const a = assignments.orchestration[role.key];
206
205
  const preferred = a?.preferred ?? "(not set)";
@@ -208,6 +207,7 @@ function buildStatusWidget(
208
207
  lines.push(` ${role.label.padEnd(8)} : ${preferred} / ${fallback}`);
209
208
  }
210
209
  lines.push("");
210
+ lines.push("Chat default model : controlled via `/model` (this wizard never overrides it)");
211
211
  lines.push(`Keys file : ${store.configPath} (chmod 0600 on Unix)`);
212
212
  return lines;
213
213
  }
@@ -537,15 +537,22 @@ async function configureAssignments(
537
537
  return { defaultModel: "default", orchestration: {} };
538
538
  }
539
539
 
540
- const defaultModel =
541
- (await pickModelFromCatalog(ui, "Default chat model (used when no orchestration is active)", allModelIds)) ??
542
- allModelIds[0];
540
+ ui.notify(
541
+ "Assigning orchestration role models. The chat default is controlled via `/model` " +
542
+ "this wizard does NOT change it.",
543
+ "info",
544
+ );
545
+
546
+ // Sentinel: the orchestrator falls back to the current chat model when a
547
+ // route doesn't pin a specific one. We never ask the user for a "default"
548
+ // chat model here — `/model` owns that.
549
+ const defaultModel = "default";
543
550
 
544
551
  const orchestration: Record<string, RouteAssignment> = {};
545
552
  for (const role of ORCHESTRATION_ROLES) {
546
553
  const preferred =
547
554
  (await pickModelFromCatalog(ui, `${role.label} - preferred model (${role.desc})`, allModelIds)) ??
548
- defaultModel;
555
+ allModelIds[0];
549
556
  const fallbackOptions = allModelIds.filter((m) => m !== preferred);
550
557
  const fallback = fallbackOptions.length > 0
551
558
  ? (await pickModelFromCatalog(ui, `${role.label} - fallback model`, fallbackOptions)) ?? preferred
@@ -590,8 +597,11 @@ export default function setupExtension(pi: ExtensionAPI) {
590
597
  try {
591
598
  ui.notify(
592
599
  "**φ Phi Code Setup Wizard**\n\n" +
593
- "This wizard configures providers and assigns models to agent roles.\n" +
594
- "Keys are stored in `~/.phi/agent/models.json` (chmod 0600 on Unix).\n" +
600
+ "This wizard configures providers and assigns models to **orchestration roles** " +
601
+ "(used by `/plan`).\n" +
602
+ "The **chat default model is controlled via `/model`** and stays sticky across " +
603
+ "prompts — this wizard will never change it.\n\n" +
604
+ "Keys are stored in `~/.phi/agent/models.json` (chmod 0600 on Unix). " +
595
605
  "Edit that file directly later to hot-reload (no restart needed).",
596
606
  "info",
597
607
  );
@@ -19,6 +19,17 @@ import { homedir } from "node:os";
19
19
 
20
20
  interface ExtensionConfig {
21
21
  enabled: boolean;
22
+ /**
23
+ * When false (default), the smart router never changes the chat model — it
24
+ * only emits an informational notification per prompt. Use `/model` to
25
+ * choose the chat model; that choice is now the single source of truth
26
+ * and is persisted across sessions via the settings manager.
27
+ *
28
+ * When true (opt-in via `/routing autoswitch on`), the router switches
29
+ * the chat model to the preferred route per prompt, matching the
30
+ * pre-0.75.6 behavior.
31
+ */
32
+ autoSwitch: boolean;
22
33
  notifyOnRecommendation: boolean;
23
34
  }
24
35
 
@@ -31,7 +42,8 @@ export default function smartRouterExtension(pi: ExtensionAPI) {
31
42
  let router = new SmartRouter(SmartRouter.defaultConfig());
32
43
  let extConfig: ExtensionConfig = {
33
44
  enabled: true,
34
- notifyOnRecommendation: true,
45
+ autoSwitch: false,
46
+ notifyOnRecommendation: false,
35
47
  };
36
48
 
37
49
  /**
@@ -95,20 +107,30 @@ export default function smartRouterExtension(pi: ExtensionAPI) {
95
107
 
96
108
  const modelToUse = targetModel || fallbackModel;
97
109
 
98
- if (modelToUse && modelToUse.id !== ctx.model?.id) {
99
- // Actually switch the model
110
+ if (!modelToUse) {
111
+ if (extConfig.notifyOnRecommendation) {
112
+ ctx.ui.notify(
113
+ `Routing suggestion: ${recommendation.category} → \`${recommendation.model}\` (not in registry).`,
114
+ "info",
115
+ );
116
+ }
117
+ } else if (modelToUse.id === ctx.model?.id) {
118
+ // Already on the recommended model — nothing to do.
119
+ } else if (extConfig.autoSwitch) {
120
+ // Legacy opt-in behavior: actually swap the chat model.
100
121
  const switched = await pi.setModel(modelToUse);
101
122
  if (switched && extConfig.notifyOnRecommendation) {
102
123
  ctx.ui.notify(
103
- `🔀 ${recommendation.category} → \`${modelToUse.id}\`${modelToUse.id !== recommendation.model ? ` (fallback)` : ""}`,
104
- "info"
124
+ `Auto-switched (${recommendation.category}) → \`${modelToUse.id}\`${modelToUse.id !== recommendation.model ? " (fallback)" : ""}`,
125
+ "info",
105
126
  );
106
127
  }
107
- } else if (extConfig.notifyOnRecommendation && !modelToUse) {
108
- // Model not available just notify
128
+ } else if (extConfig.notifyOnRecommendation) {
129
+ // Default behavior: never override the user's `/model` choice. Just
130
+ // advertise the suggestion — the chat model stays sticky.
109
131
  ctx.ui.notify(
110
- `🔀 ${recommendation.category} → \`${recommendation.model}\` (not available, keeping current)`,
111
- "info"
132
+ `Routing suggestion: ${recommendation.category} → \`${modelToUse.id}\`. Stay on \`${ctx.model?.id}\` (use \`/routing autoswitch on\` to auto-apply, or \`/model\` to change manually).`,
133
+ "info",
112
134
  );
113
135
  }
114
136
  }
@@ -125,44 +147,66 @@ export default function smartRouterExtension(pi: ExtensionAPI) {
125
147
 
126
148
  if (!arg) {
127
149
  const routingConfig = (router as any).config as RoutingConfig;
128
- let output = `**🔀 Smart Router** (powered by sigma-agents)\n\n`;
129
- output += `Status: ${extConfig.enabled ? "✅ Enabled" : "❌ Disabled"}\n`;
130
- output += `Notifications: ${extConfig.notifyOnRecommendation ? "On" : "Off"}\n\n`;
150
+ let output = `**Smart Router** (powered by sigma-agents)\n\n`;
151
+ output += `Status: ${extConfig.enabled ? "enabled" : "disabled"}\n`;
152
+ output += `Auto-switch: ${extConfig.autoSwitch ? "on (overrides /model per prompt)" : "off (suggestions only — /model is sticky)"}\n`;
153
+ output += `Notifications: ${extConfig.notifyOnRecommendation ? "on" : "off"}\n\n`;
131
154
 
132
155
  output += `**Routes:**\n`;
133
156
  for (const [cat, route] of Object.entries(routingConfig.routes)) {
134
157
  output += ` **${cat}** → \`${route.preferredModel}\` (fallback: \`${route.fallback}\`) [agent: ${route.agent || "none"}]\n`;
135
158
  output += ` Keywords: ${route.keywords.slice(0, 6).join(", ")}${route.keywords.length > 6 ? "..." : ""}\n`;
136
159
  }
137
- output += `\n **default** → \`${routingConfig.default.model}\`\n`;
160
+ output += `\n **default** → \`${routingConfig.default.model}\` (orchestrator fallback only; chat default comes from \`/model\`)\n`;
138
161
 
139
162
  output += `\nConfig: \`${configPath}\``;
140
- output += `\nCommands: \`/routing enable|disable|notify-on|notify-off|reload|test\``;
163
+ output += `\nCommands: \`/routing [enable|disable|autoswitch on|autoswitch off|notify-on|notify-off|reload|test]\``;
141
164
 
142
165
  ctx.ui.notify(output, "info");
143
166
  return;
144
167
  }
145
168
 
169
+ const tokens = arg.split(/\s+/);
170
+ const head = tokens[0];
171
+
172
+ if (head === "autoswitch") {
173
+ const flag = tokens[1];
174
+ if (flag === "on") {
175
+ extConfig.autoSwitch = true;
176
+ ctx.ui.notify(
177
+ "Auto-switch enabled: the smart router will override the chat model per prompt. " +
178
+ "Disable with `/routing autoswitch off` if it gets in your way.",
179
+ "info",
180
+ );
181
+ } else if (flag === "off") {
182
+ extConfig.autoSwitch = false;
183
+ ctx.ui.notify("Auto-switch disabled. The model selected via `/model` is now sticky.", "info");
184
+ } else {
185
+ ctx.ui.notify("Usage: `/routing autoswitch on|off`", "warning");
186
+ }
187
+ return;
188
+ }
189
+
146
190
  switch (arg) {
147
191
  case "enable":
148
192
  extConfig.enabled = true;
149
- ctx.ui.notify("Smart routing enabled.", "info");
193
+ ctx.ui.notify("Smart routing enabled.", "info");
150
194
  break;
151
195
  case "disable":
152
196
  extConfig.enabled = false;
153
- ctx.ui.notify("Smart routing disabled.", "info");
197
+ ctx.ui.notify("Smart routing disabled.", "info");
154
198
  break;
155
199
  case "notify-on":
156
200
  extConfig.notifyOnRecommendation = true;
157
- ctx.ui.notify("🔔 Routing notifications enabled.", "info");
201
+ ctx.ui.notify("Routing notifications enabled.", "info");
158
202
  break;
159
203
  case "notify-off":
160
204
  extConfig.notifyOnRecommendation = false;
161
- ctx.ui.notify("🔕 Routing notifications disabled.", "info");
205
+ ctx.ui.notify("Routing notifications disabled.", "info");
162
206
  break;
163
207
  case "reload":
164
208
  await loadConfig();
165
- ctx.ui.notify("🔄 Routing config reloaded from disk.", "info");
209
+ ctx.ui.notify("Routing config reloaded from disk.", "info");
166
210
  break;
167
211
  case "test": {
168
212
  const tests = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phi-code-admin/phi-code",
3
- "version": "0.75.5",
3
+ "version": "0.75.6",
4
4
  "description": "Coding agent CLI with persistent memory, sub-agents, intelligent routing, and orchestration",
5
5
  "type": "module",
6
6
  "piConfig": {