takomi 2.1.26 → 2.1.27

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,7 +2,6 @@
2
2
  name: architect
3
3
  description: Gather requirements, design architecture, and create implementation-ready plans.
4
4
  tools: read,bash,grep,find,ls
5
- model: gpt-5.4
6
5
  ---
7
6
  You are the Takomi Architect.
8
7
 
@@ -2,7 +2,6 @@
2
2
  name: coder
3
3
  description: Implement, fix, and refactor code with verification and controlled scope.
4
4
  tools: read,bash,edit,write,grep,find,ls
5
- model: gpt-5.4-mini
6
5
  ---
7
6
  You are the Takomi Code Specialist.
8
7
 
@@ -2,7 +2,6 @@
2
2
  name: designer
3
3
  description: Translate requirements into build-ready UI, UX, visual systems, and interaction direction.
4
4
  tools: read,bash,grep,find,ls
5
- model: gemini-3.1-pro-preview
6
5
  ---
7
6
  You are the Takomi Design Specialist.
8
7
 
@@ -2,7 +2,6 @@
2
2
  name: orchestrator
3
3
  description: Coordinate complex projects by decomposing, sequencing, delegating, and synthesizing specialist work.
4
4
  tools: read,bash,grep,find,ls
5
- model: gpt-5.4
6
5
  ---
7
6
  You are the Takomi Orchestrator.
8
7
 
@@ -2,7 +2,6 @@
2
2
  name: reviewer
3
3
  description: Review changes for correctness, risk, security, maintainability, and spec compliance.
4
4
  tools: read,bash,grep,find,ls
5
- model: gpt-5.4-mini
6
5
  ---
7
6
  You are the Takomi Review Specialist.
8
7
 
@@ -11,6 +11,7 @@ import { registerDiagnostics } from "./diagnostics-tools";
11
11
  import { installPrerequisiteGates } from "./prerequisite-gates";
12
12
  import { installModelPolicyGate } from "./model-policy-gate";
13
13
  import { detectDuplicateTakomiExtensions } from "./extension-conflicts";
14
+ import { loadTakomiModelRoutingSnapshot, renderCompactTakomiModelRoutingSummary } from "../takomi-runtime/model-routing-defaults";
14
15
  import type { ContextManagerConfig } from "./types";
15
16
 
16
17
  export default function takomiContextManager(pi: ExtensionAPI) {
@@ -33,6 +34,8 @@ export default function takomiContextManager(pi: ExtensionAPI) {
33
34
 
34
35
  const candidates = findCandidates(event.prompt, state.skills, config);
35
36
  const rewrite = rewritePrompt(event.systemPrompt, state.skills, candidates, config);
37
+ const routingSummary = renderCompactTakomiModelRoutingSummary(await loadTakomiModelRoutingSnapshot(ctx.cwd));
38
+ const rewrittenPrompt = routingSummary ? `${rewrite.prompt}\n\n${routingSummary}` : rewrite.prompt;
36
39
  state.report = {
37
40
  ...state.report,
38
41
  timestamp: new Date().toISOString(),
@@ -43,14 +46,14 @@ export default function takomiContextManager(pi: ExtensionAPI) {
43
46
  duplicateExtensionWarnings,
44
47
  promptRewrite: {
45
48
  attempted: true,
46
- changed: rewrite.changed,
49
+ changed: rewrite.changed || Boolean(routingSummary),
47
50
  originalLength: event.systemPrompt.length,
48
- rewrittenLength: rewrite.prompt.length,
51
+ rewrittenLength: rewrittenPrompt.length,
49
52
  removedSections: rewrite.removedSections,
50
53
  warnings: rewrite.warnings,
51
54
  },
52
55
  };
53
56
 
54
- return { systemPrompt: rewrite.prompt };
57
+ return { systemPrompt: rewrittenPrompt };
55
58
  });
56
59
  }
@@ -5,6 +5,7 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
5
5
  import type { ContextManagerState } from "./state";
6
6
  import { recordBlocked } from "./state";
7
7
  import { resolveTakomiRoutingPolicy } from "../takomi-runtime/routing-policy";
8
+ import { approvedModelEquivalent, isTakomiModelApproved } from "../takomi-runtime/model-routing-defaults";
8
9
 
9
10
  type Settings = {
10
11
  takomi?: { modelRoutingPolicyFile?: string };
@@ -79,12 +80,21 @@ function isModelLike(value: string): boolean {
79
80
  || lower.includes("lmstudio/");
80
81
  }
81
82
 
83
+ function extractPreferredProvider(text: string): string | undefined {
84
+ const match = text.match(/(?:preferred|default)\s+(?:provider|router)(?:\s*\/\s*(?:provider|router))?\s*:\s*([a-z0-9-]+)/i)
85
+ ?? text.match(/use\s+([a-z0-9-]+)\s+as\s+(?:the\s+)?(?:provider|router)/i);
86
+ return match?.[1];
87
+ }
88
+
82
89
  function collectModelsFromPolicy(text: string): string[] {
90
+ // Providerless names such as "GPT-5.5" are intent labels unless the policy
91
+ // declares a preferred provider/router header.
83
92
  const explicit = (text.match(/[a-z0-9-]+\/[a-z0-9._-]+/gi) ?? []).filter(isModelLike);
93
+ const preferredProvider = extractPreferredProvider(text);
84
94
  const inferred: string[] = [];
85
- if (/gpt[- ]?5\.5/i.test(text)) inferred.push("oauth-router/gpt-5.5");
86
- if (/gpt[- ]?5\.4(?!\s*mini)/i.test(text)) inferred.push("oauth-router/gpt-5.4");
87
- if (/gpt[- ]?5\.4\s*mini/i.test(text)) inferred.push("oauth-router/gpt-5.4-mini");
95
+ if (preferredProvider && /gpt[- ]?5\.5/i.test(text)) inferred.push(`${preferredProvider}/gpt-5.5`);
96
+ if (preferredProvider && /gpt[- ]?5\.4(?!\s*mini)/i.test(text)) inferred.push(`${preferredProvider}/gpt-5.4`);
97
+ if (preferredProvider && /gpt[- ]?5\.4\s*mini/i.test(text)) inferred.push(`${preferredProvider}/gpt-5.4-mini`);
88
98
  return unique([...explicit, ...inferred]);
89
99
  }
90
100
 
@@ -98,8 +108,8 @@ async function loadSnapshot(cwd: string): Promise<ModelPolicySnapshot> {
98
108
  return { approvedModels, preferredModels: settingsModels.length ? unique(settingsModels) : approvedModels, sourceFiles };
99
109
  }
100
110
 
101
- function collectRequestedModelRefs(input: unknown): Array<{ holder: Record<string, unknown>; key: string; value: string }> {
102
- const refs: Array<{ holder: Record<string, unknown>; key: string; value: string }> = [];
111
+ function collectRequestedModelRefs(input: unknown): Array<{ holder: Record<string, unknown>; key: string; value: string; index?: number }> {
112
+ const refs: Array<{ holder: Record<string, unknown>; key: string; value: string; index?: number }> = [];
103
113
  function visit(value: unknown): void {
104
114
  if (!value || typeof value !== "object") return;
105
115
  if (Array.isArray(value)) {
@@ -111,7 +121,9 @@ function collectRequestedModelRefs(input: unknown): Array<{ holder: Record<strin
111
121
  if (typeof record[key] === "string") refs.push({ holder: record, key, value: record[key] });
112
122
  }
113
123
  if (Array.isArray(record.fallbackModels)) {
114
- record.fallbackModels = record.fallbackModels.filter((item) => typeof item === "string");
124
+ const fallbackModels = record.fallbackModels.filter((item): item is string => typeof item === "string");
125
+ record.fallbackModels = fallbackModels;
126
+ fallbackModels.forEach((value: string, index: number) => refs.push({ holder: record, key: "fallbackModels", value, index }));
115
127
  }
116
128
  for (const key of ["tasks", "chain"]) visit(record[key]);
117
129
  }
@@ -119,9 +131,12 @@ function collectRequestedModelRefs(input: unknown): Array<{ holder: Record<strin
119
131
  return refs;
120
132
  }
121
133
 
122
- function approvedEquivalent(requested: string, approved: string[]): string | undefined {
123
- const requestedFamily = modelFamily(requested);
124
- return approved.find((candidate) => modelFamily(candidate) === requestedFamily);
134
+ function setModelRef(ref: { holder: Record<string, unknown>; key: string; value: string; index?: number }, value: string): void {
135
+ if (ref.key === "fallbackModels" && typeof ref.index === "number" && Array.isArray(ref.holder.fallbackModels)) {
136
+ ref.holder.fallbackModels[ref.index] = value;
137
+ return;
138
+ }
139
+ ref.holder[ref.key] = value;
125
140
  }
126
141
 
127
142
  function isModelFailure(text: string): boolean {
@@ -170,16 +185,16 @@ export function installModelPolicyGate(pi: ExtensionAPI, state: ContextManagerSt
170
185
  const refs = collectRequestedModelRefs(event.input);
171
186
  const corrections: string[] = [];
172
187
  for (const ref of refs) {
173
- if (approved.includes(ref.value)) continue;
174
- const equivalent = approvedEquivalent(ref.value, approved);
188
+ if (isTakomiModelApproved(ref.value, approved)) continue;
189
+ const equivalent = approvedModelEquivalent(ref.value, approved);
175
190
  if (equivalent) {
176
- ref.holder[ref.key] = equivalent;
191
+ setModelRef(ref, equivalent);
177
192
  corrections.push(`${ref.value} -> ${equivalent}`);
178
193
  continue;
179
194
  }
180
195
  const recovery = await askForInvalidModelRecovery(ctx, ref.value, approved);
181
196
  if (recovery.action === "retry") {
182
- ref.holder[ref.key] = recovery.model;
197
+ setModelRef(ref, recovery.model);
183
198
  corrections.push(`${ref.value} -> ${recovery.model} (user selected recovery)`);
184
199
  continue;
185
200
  }
@@ -7,7 +7,7 @@ import type {
7
7
  } from "../../../src/pi-takomi-core";
8
8
  import { commandHelp, completions, statusText, workflowPrompt } from "./command-text";
9
9
  import type { TakomiSubagentController } from "./subagent-types";
10
- import { installTakomiRoutingPolicy, resolveTakomiRoutingPolicy, type RoutingPolicyInstallScope } from "./routing-policy";
10
+ import { previewTakomiRoutingPolicy, renderRoutingPolicyPreview, resolveTakomiRoutingPolicy, type RoutingPolicyInstallScope } from "./routing-policy";
11
11
  import { collectTakomiStats, renderTakomiStats } from "./takomi-stats.js";
12
12
 
13
13
  export type TakomiRuntimeCommandState = {
@@ -158,12 +158,29 @@ export function registerTakomiCommands(pi: ExtensionAPI, options: RegisterTakomi
158
158
  const policyText = scopeMatch?.[2] ?? trimmed.replace(/^set\s+/i, "");
159
159
 
160
160
  try {
161
- const result = await installTakomiRoutingPolicy(ctx.cwd, policyText, { scope });
162
- const detected = result.detectedDefaults.length ? `\n\nDetected defaults:\n- ${result.detectedDefaults.join("\n- ")}` : "\n\nNo model names were auto-detected; saved policy only.";
163
- const overrideNote = scope === "global"
164
- ? "\n\nThis will be used by every project unless that project has its own .pi/takomi/model-routing.md override."
165
- : "\n\nThis project-local policy overrides the global policy for the current project.";
166
- ctx.ui.notify(`Takomi routing policy updated (${scope}).\n\nPolicy: ${result.policyPath}\nSettings: ${result.settingsPath}${detected}${overrideNote}`, "info");
161
+ const preview = previewTakomiRoutingPolicy(ctx.cwd, policyText, { scope });
162
+ const reviewPrompt = [
163
+ "Review this Takomi routing policy extraction before it is saved.",
164
+ "",
165
+ "Rules:",
166
+ "- Do not invent providers or model IDs not grounded in the policy.",
167
+ "- Providerless names like GPT-5.5 are routing intent unless a preferred provider/router is declared.",
168
+ "- Valid Takomi roles are: general, orchestrator, architect, designer, coder, reviewer.",
169
+ "- If the extraction is correct and safe, call takomi_apply_routing_policy with the exact policyText and scope below.",
170
+ "- If it is ambiguous or wrong, explain what the user should clarify and do not call the tool.",
171
+ "",
172
+ "Deterministic extraction:",
173
+ renderRoutingPolicyPreview(preview),
174
+ "",
175
+ "Original policy text:",
176
+ "```",
177
+ preview.policy,
178
+ "```",
179
+ "",
180
+ `Tool call to apply if safe: takomi_apply_routing_policy({ scope: ${JSON.stringify(scope)}, policyText: <original policy text> })`,
181
+ ].join("\n");
182
+ ctx.ui.notify("Takomi routing extraction prepared. Sending it to the active model for review before saving.", "info");
183
+ pi.sendUserMessage(reviewPrompt);
167
184
  } catch (error) {
168
185
  ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
169
186
  }
@@ -56,7 +56,7 @@ import {
56
56
  getProfileDefaults,
57
57
  loadTakomiProfile,
58
58
  } from "./profile";
59
- import { installTakomiRoutingPolicy, resolveTakomiRoutingPolicy } from "./routing-policy";
59
+ import { installTakomiRoutingPolicy, previewTakomiRoutingPolicy, renderRoutingPolicyPreview, resolveTakomiRoutingPolicy } from "./routing-policy";
60
60
 
61
61
  type TakomiState = {
62
62
  enabled: boolean;
@@ -799,6 +799,47 @@ export default function takomiRuntime(pi: ExtensionAPI) {
799
799
  },
800
800
  });
801
801
 
802
+ pi.registerTool({
803
+ name: "takomi_apply_routing_policy",
804
+ label: "Takomi Routing",
805
+ description: "Apply a Takomi model-routing policy after deterministic extraction and active-model review.",
806
+ promptSnippet: "Save reviewed Takomi model routing policy text to the active global or project policy file.",
807
+ promptGuidelines: [
808
+ "Use takomi_apply_routing_policy only after reviewing the deterministic extraction against the original routing policy text.",
809
+ "Do not call takomi_apply_routing_policy if the policy is ambiguous, invents providers, or maps to non-Takomi roles.",
810
+ ],
811
+ parameters: Type.Object({
812
+ policyText: Type.String({ description: "Original routing policy text to save" }),
813
+ scope: Type.Optional(StringEnum(["global", "project"] as const)),
814
+ reviewNotes: Type.Optional(Type.String({ description: "Brief notes from the active-model review" })),
815
+ }),
816
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
817
+ const scope = params.scope ?? "global";
818
+ const preview = previewTakomiRoutingPolicy(ctx.cwd, params.policyText, { scope });
819
+ const result = await installTakomiRoutingPolicy(ctx.cwd, params.policyText, { scope });
820
+ const scopeNote = scope === "global"
821
+ ? "This global policy applies unless a project-local override exists."
822
+ : "This project-local policy overrides the global policy for the current project.";
823
+ return {
824
+ content: [{
825
+ type: "text",
826
+ text: [
827
+ `Takomi routing policy saved (${scope}).`,
828
+ "",
829
+ `Policy: ${result.policyPath}`,
830
+ `Settings: ${result.settingsPath}`,
831
+ "",
832
+ renderRoutingPolicyPreview(preview),
833
+ params.reviewNotes ? `\nReview notes:\n${params.reviewNotes}` : "",
834
+ "",
835
+ scopeNote,
836
+ ].filter(Boolean).join("\n"),
837
+ }],
838
+ details: { result, preview, reviewNotes: params.reviewNotes },
839
+ };
840
+ },
841
+ });
842
+
802
843
  pi.registerTool({
803
844
  name: "takomi_workflow",
804
845
  label: "Takomi Workflow",
@@ -1067,11 +1108,28 @@ ${stateJson}`
1067
1108
  if (routingUpdateMatch) {
1068
1109
  state.enabled = true;
1069
1110
  try {
1070
- const result = await installTakomiRoutingPolicy(runtimeCtx?.cwd ?? process.cwd(), text);
1071
- const detected = result.detectedDefaults.length ? `\n\nDetected defaults:\n- ${result.detectedDefaults.join("\n- ")}` : "\n\nNo model names were auto-detected; saved policy only.";
1072
- return { action: "transform", text: `Takomi routing policy has been updated.\n\nPolicy: ${result.policyPath}\nSettings: ${result.settingsPath}${detected}\n\nAcknowledge the update briefly and explain that future Takomi turns will load this policy.` };
1111
+ const cwd = runtimeCtx?.cwd ?? process.cwd();
1112
+ const preview = previewTakomiRoutingPolicy(cwd, text, { scope: "global" });
1113
+ return { action: "transform", text: [
1114
+ "Review this Takomi routing policy extraction before it is saved.",
1115
+ "",
1116
+ "Rules:",
1117
+ "- Do not invent providers or model IDs not grounded in the policy.",
1118
+ "- Providerless names like GPT-5.5 are routing intent unless a preferred provider/router is declared.",
1119
+ "- Valid Takomi roles are: general, orchestrator, architect, designer, coder, reviewer.",
1120
+ "- If the extraction is correct and safe, call takomi_apply_routing_policy with scope=global and the exact original policy text.",
1121
+ "- If it is ambiguous or wrong, explain what the user should clarify and do not call the tool.",
1122
+ "",
1123
+ "Deterministic extraction:",
1124
+ renderRoutingPolicyPreview(preview),
1125
+ "",
1126
+ "Original policy text:",
1127
+ "```",
1128
+ preview.policy,
1129
+ "```",
1130
+ ].join("\n") };
1073
1131
  } catch (error) {
1074
- return { action: "transform", text: `Takomi routing policy update failed: ${error instanceof Error ? error.message : String(error)}` };
1132
+ return { action: "transform", text: `Takomi routing policy review failed: ${error instanceof Error ? error.message : String(error)}` };
1075
1133
  }
1076
1134
  }
1077
1135
 
@@ -0,0 +1,296 @@
1
+ import * as fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import {
5
+ BUNDLED_TAKOMI_ROUTING_POLICY_PATH,
6
+ GLOBAL_PI_SETTINGS_PATH,
7
+ GLOBAL_TAKOMI_ROUTING_POLICY_PATH,
8
+ PROJECT_PI_SETTINGS_RELATIVE,
9
+ TAKOMI_ROUTING_POLICY_RELATIVE,
10
+ resolveTakomiRoutingPolicy,
11
+ } from "./routing-policy";
12
+
13
+ export const TAKOMI_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"] as const;
14
+
15
+ type Settings = {
16
+ takomi?: { modelRoutingPolicyFile?: string };
17
+ subagents?: { agentOverrides?: Record<string, unknown> };
18
+ };
19
+
20
+ export type TakomiAgentModelDefault = {
21
+ agent: string;
22
+ model?: string;
23
+ thinking?: string;
24
+ fallbackModels?: string[];
25
+ };
26
+
27
+ export type TakomiModelRoutingSnapshot = {
28
+ approvedModels: string[];
29
+ preferredModels: string[];
30
+ sourceFiles: string[];
31
+ agentDefaults: TakomiAgentModelDefault[];
32
+ };
33
+
34
+ function asRecord(value: unknown): Record<string, unknown> {
35
+ return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
36
+ }
37
+
38
+ function unique(values: string[]): string[] {
39
+ return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
40
+ }
41
+
42
+ async function readSettingsFile(filePath: string): Promise<Settings> {
43
+ try {
44
+ return JSON.parse(await fs.promises.readFile(filePath, "utf8")) as Settings;
45
+ } catch {
46
+ return {};
47
+ }
48
+ }
49
+
50
+ function readSettingsFileSync(filePath: string): Settings {
51
+ try {
52
+ return JSON.parse(fs.readFileSync(filePath, "utf8")) as Settings;
53
+ } catch {
54
+ return {};
55
+ }
56
+ }
57
+
58
+ function mergeSettings(globalSettings: Settings, projectSettings: Settings): Settings {
59
+ const globalOverrides = asRecord(globalSettings.subagents?.agentOverrides);
60
+ const projectOverrides = asRecord(projectSettings.subagents?.agentOverrides);
61
+ return {
62
+ ...globalSettings,
63
+ ...projectSettings,
64
+ takomi: { ...(globalSettings.takomi ?? {}), ...(projectSettings.takomi ?? {}) },
65
+ subagents: {
66
+ ...(globalSettings.subagents ?? {}),
67
+ ...(projectSettings.subagents ?? {}),
68
+ agentOverrides: { ...globalOverrides, ...projectOverrides },
69
+ },
70
+ };
71
+ }
72
+
73
+ async function readSettings(cwd: string): Promise<Settings> {
74
+ const globalSettings = await readSettingsFile(path.join(os.homedir(), ".pi", "agent", "settings.json"));
75
+ const projectSettings = await readSettingsFile(path.resolve(cwd, ".pi/settings.json"));
76
+ return mergeSettings(globalSettings, projectSettings);
77
+ }
78
+
79
+ function readSettingsSync(cwd: string): Settings {
80
+ const globalSettings = readSettingsFileSync(path.join(os.homedir(), ".pi", "agent", "settings.json"));
81
+ const projectSettings = readSettingsFileSync(path.resolve(cwd, ".pi/settings.json"));
82
+ return mergeSettings(globalSettings, projectSettings);
83
+ }
84
+
85
+ function collectAgentDefaultsFromSettings(settings: Settings): TakomiAgentModelDefault[] {
86
+ const overrides = asRecord(settings.subagents?.agentOverrides);
87
+ const defaults: TakomiAgentModelDefault[] = [];
88
+ for (const [agent, value] of Object.entries(overrides)) {
89
+ const record = asRecord(value);
90
+ const fallbackModels = Array.isArray(record.fallbackModels)
91
+ ? record.fallbackModels.filter((item): item is string => typeof item === "string" && item.trim().length > 0)
92
+ : undefined;
93
+ defaults.push({
94
+ agent,
95
+ model: typeof record.model === "string" ? record.model : undefined,
96
+ thinking: typeof record.thinking === "string" ? record.thinking : undefined,
97
+ fallbackModels,
98
+ });
99
+ }
100
+ return defaults;
101
+ }
102
+
103
+ function collectModelsFromDefaults(defaults: TakomiAgentModelDefault[]): string[] {
104
+ const models: string[] = [];
105
+ for (const record of defaults) {
106
+ if (record.model) models.push(stripThinkingSuffix(record.model).baseModel);
107
+ if (Array.isArray(record.fallbackModels)) {
108
+ for (const fallback of record.fallbackModels) models.push(stripThinkingSuffix(fallback).baseModel);
109
+ }
110
+ }
111
+ return models;
112
+ }
113
+
114
+ function isModelLike(value: string): boolean {
115
+ const lower = value.toLowerCase();
116
+ return /(^|\/)(gpt|claude|gemini|o[0-9]|qwen|deepseek|llama|mistral|kimi|grok|sonnet|haiku|opus|codex|mini|max)/i.test(lower)
117
+ || lower.includes("oauth-router/")
118
+ || lower.includes("openai-codex/")
119
+ || lower.includes("lmstudio/");
120
+ }
121
+
122
+ function extractPreferredProvider(text: string): string | undefined {
123
+ const match = text.match(/(?:preferred|default)\s+(?:provider|router)(?:\s*\/\s*(?:provider|router))?\s*:\s*([a-z0-9-]+)/i)
124
+ ?? text.match(/use\s+([a-z0-9-]+)\s+as\s+(?:the\s+)?(?:provider|router)/i);
125
+ return match?.[1];
126
+ }
127
+
128
+ function collectModelsFromPolicy(text: string): string[] {
129
+ // Provider-agnostic policy text like "GPT-5.5" expresses routing intent, not
130
+ // a concrete provider. A preferred provider/router header intentionally binds
131
+ // those intent labels to executable provider-qualified IDs.
132
+ const explicit = (text.match(/[a-z0-9-]+\/[a-z0-9._-]+/gi) ?? []).filter(isModelLike);
133
+ const preferredProvider = extractPreferredProvider(text);
134
+ const inferred: string[] = [];
135
+ if (preferredProvider && /gpt[- ]?5\.5/i.test(text)) inferred.push(`${preferredProvider}/gpt-5.5`);
136
+ if (preferredProvider && /gpt[- ]?5\.4(?!\s*mini)/i.test(text)) inferred.push(`${preferredProvider}/gpt-5.4`);
137
+ if (preferredProvider && /gpt[- ]?5\.4\s*mini/i.test(text)) inferred.push(`${preferredProvider}/gpt-5.4-mini`);
138
+ return unique([...explicit, ...inferred].map((model) => stripThinkingSuffix(model).baseModel));
139
+ }
140
+
141
+ function readPolicyTextSync(filePath: string): string | undefined {
142
+ try {
143
+ const text = fs.readFileSync(filePath, "utf8").trim();
144
+ return text || undefined;
145
+ } catch {
146
+ return undefined;
147
+ }
148
+ }
149
+
150
+ function resolvePolicySync(cwd: string): { policyPath?: string; text?: string } {
151
+ const projectSettings = readSettingsFileSync(path.join(cwd, PROJECT_PI_SETTINGS_RELATIVE));
152
+ const projectTakomi = asRecord(projectSettings.takomi);
153
+ const configuredProject = typeof projectTakomi.modelRoutingPolicyFile === "string"
154
+ ? projectTakomi.modelRoutingPolicyFile
155
+ : TAKOMI_ROUTING_POLICY_RELATIVE;
156
+ const configuredProjectPath = path.isAbsolute(configuredProject) ? configuredProject : path.join(cwd, configuredProject);
157
+ const configuredProjectText = readPolicyTextSync(configuredProjectPath);
158
+ if (configuredProjectText) return { policyPath: configuredProjectPath, text: configuredProjectText };
159
+
160
+ const defaultProjectPath = path.join(cwd, TAKOMI_ROUTING_POLICY_RELATIVE);
161
+ if (path.resolve(defaultProjectPath) !== path.resolve(configuredProjectPath)) {
162
+ const defaultProjectText = readPolicyTextSync(defaultProjectPath);
163
+ if (defaultProjectText) return { policyPath: defaultProjectPath, text: defaultProjectText };
164
+ }
165
+
166
+ const globalSettings = readSettingsFileSync(GLOBAL_PI_SETTINGS_PATH);
167
+ const globalTakomi = asRecord(globalSettings.takomi);
168
+ const configuredGlobal = typeof globalTakomi.modelRoutingPolicyFile === "string"
169
+ ? globalTakomi.modelRoutingPolicyFile
170
+ : GLOBAL_TAKOMI_ROUTING_POLICY_PATH;
171
+ const configuredGlobalPath = path.isAbsolute(configuredGlobal) ? configuredGlobal : path.join(os.homedir(), configuredGlobal);
172
+ const configuredGlobalText = readPolicyTextSync(configuredGlobalPath);
173
+ if (configuredGlobalText) return { policyPath: configuredGlobalPath, text: configuredGlobalText };
174
+
175
+ if (path.resolve(configuredGlobalPath) !== path.resolve(GLOBAL_TAKOMI_ROUTING_POLICY_PATH)) {
176
+ const globalText = readPolicyTextSync(GLOBAL_TAKOMI_ROUTING_POLICY_PATH);
177
+ if (globalText) return { policyPath: GLOBAL_TAKOMI_ROUTING_POLICY_PATH, text: globalText };
178
+ }
179
+
180
+ const bundledText = readPolicyTextSync(BUNDLED_TAKOMI_ROUTING_POLICY_PATH);
181
+ return bundledText ? { policyPath: BUNDLED_TAKOMI_ROUTING_POLICY_PATH, text: bundledText } : {};
182
+ }
183
+
184
+ export function stripThinkingSuffix(model: string): { baseModel: string; thinkingSuffix: string } {
185
+ const colonIdx = model.lastIndexOf(":");
186
+ if (colonIdx === -1) return { baseModel: model, thinkingSuffix: "" };
187
+ const suffix = model.slice(colonIdx + 1).toLowerCase();
188
+ if (!(TAKOMI_THINKING_LEVELS as readonly string[]).includes(suffix)) return { baseModel: model, thinkingSuffix: "" };
189
+ return { baseModel: model.slice(0, colonIdx), thinkingSuffix: `:${suffix}` };
190
+ }
191
+
192
+ export function modelFamily(model: string): string {
193
+ const baseModel = stripThinkingSuffix(model).baseModel;
194
+ return baseModel.split("/").at(-1)?.toLowerCase() ?? baseModel.toLowerCase();
195
+ }
196
+
197
+ export function isTakomiModelApproved(requested: string, approved: string[]): boolean {
198
+ const requestedBase = stripThinkingSuffix(requested).baseModel;
199
+ return approved.some((candidate) => stripThinkingSuffix(candidate).baseModel === requestedBase);
200
+ }
201
+
202
+ export function approvedModelEquivalent(requested: string, approved: string[]): string | undefined {
203
+ const { thinkingSuffix } = stripThinkingSuffix(requested);
204
+ const requestedFamily = modelFamily(requested);
205
+ const equivalent = approved.find((candidate) => modelFamily(candidate) === requestedFamily);
206
+ return equivalent ? `${stripThinkingSuffix(equivalent).baseModel}${thinkingSuffix}` : undefined;
207
+ }
208
+
209
+ export function normalizeModelToApproved(requested: string | undefined, approved: string[], fallback?: string): string | undefined {
210
+ if (!requested) return fallback;
211
+ if (isTakomiModelApproved(requested, approved)) return requested;
212
+ return approvedModelEquivalent(requested, approved) ?? fallback ?? requested;
213
+ }
214
+
215
+ function normalizedAgentKey(agent: string): string {
216
+ return agent.toLowerCase().replace(/[^a-z0-9]+/g, "");
217
+ }
218
+
219
+ const DEFAULT_ALIAS_KEYS: Record<string, string[]> = {
220
+ architect: ["architect", "planner", "oracle", "worker"],
221
+ orchestrator: ["orchestrator", "planner", "oracle", "worker"],
222
+ planner: ["planner", "oracle", "worker"],
223
+ coder: ["coder", "code", "worker", "delegate"],
224
+ code: ["code", "coder", "worker", "delegate"],
225
+ designer: ["designer", "design", "worker", "delegate"],
226
+ design: ["design", "designer", "worker", "delegate"],
227
+ reviewer: ["reviewer", "review", "oracle"],
228
+ review: ["review", "reviewer", "oracle"],
229
+ general: ["general", "worker"],
230
+ };
231
+
232
+ export function resolveAgentRoutingDefault(snapshot: TakomiModelRoutingSnapshot, agent: string): TakomiAgentModelDefault | undefined {
233
+ const normalized = normalizedAgentKey(agent);
234
+ const candidates = [normalized, ...(DEFAULT_ALIAS_KEYS[normalized] ?? []), "worker"].map(normalizedAgentKey);
235
+ return snapshot.agentDefaults.find((entry) => candidates.includes(normalizedAgentKey(entry.agent)));
236
+ }
237
+
238
+ export function applyTakomiRoutingDefaults<T extends { agent: string; model?: string; fallbackModels?: string[]; thinking?: string }>(
239
+ task: T,
240
+ snapshot: TakomiModelRoutingSnapshot,
241
+ ): T {
242
+ const defaults = resolveAgentRoutingDefault(snapshot, task.agent);
243
+ if (!snapshot.approvedModels.length && !defaults) return task;
244
+ const approved = snapshot.approvedModels;
245
+ const model = normalizeModelToApproved(task.model, approved, defaults?.model);
246
+ const thinking = task.thinking ?? defaults?.thinking;
247
+ const mergedFallbacks = unique([...(task.fallbackModels ?? []), ...(defaults?.fallbackModels ?? [])]);
248
+ const fallbackModels = unique(mergedFallbacks
249
+ .map((fallback) => normalizeModelToApproved(fallback, approved))
250
+ .filter((fallback): fallback is string => Boolean(fallback))
251
+ .filter((fallback) => !approved.length || isTakomiModelApproved(fallback, approved)));
252
+
253
+ return {
254
+ ...task,
255
+ ...(model ? { model } : {}),
256
+ ...(thinking ? { thinking } : {}),
257
+ ...(fallbackModels.length ? { fallbackModels } : {}),
258
+ };
259
+ }
260
+
261
+ export async function loadTakomiModelRoutingSnapshot(cwd: string): Promise<TakomiModelRoutingSnapshot> {
262
+ const settings = await readSettings(cwd);
263
+ const agentDefaults = collectAgentDefaultsFromSettings(settings);
264
+ const settingsModels = collectModelsFromDefaults(agentDefaults);
265
+ const resolvedPolicy = await resolveTakomiRoutingPolicy(cwd);
266
+ const sourceFiles = resolvedPolicy.policyPath ? [resolvedPolicy.policyPath] : [];
267
+ const policyModels = resolvedPolicy.text ? collectModelsFromPolicy(resolvedPolicy.text) : [];
268
+ const approvedModels = unique([...settingsModels, ...policyModels]);
269
+ return { approvedModels, preferredModels: settingsModels.length ? unique(settingsModels) : approvedModels, sourceFiles, agentDefaults };
270
+ }
271
+
272
+ export function loadTakomiModelRoutingSnapshotSync(cwd: string): TakomiModelRoutingSnapshot {
273
+ const settings = readSettingsSync(cwd);
274
+ const agentDefaults = collectAgentDefaultsFromSettings(settings);
275
+ const settingsModels = collectModelsFromDefaults(agentDefaults);
276
+ const resolvedPolicy = resolvePolicySync(cwd);
277
+ const sourceFiles = resolvedPolicy.policyPath ? [resolvedPolicy.policyPath] : [];
278
+ const policyModels = resolvedPolicy.text ? collectModelsFromPolicy(resolvedPolicy.text) : [];
279
+ const approvedModels = unique([...settingsModels, ...policyModels]);
280
+ return { approvedModels, preferredModels: settingsModels.length ? unique(settingsModels) : approvedModels, sourceFiles, agentDefaults };
281
+ }
282
+
283
+ export function renderCompactTakomiModelRoutingSummary(snapshot: TakomiModelRoutingSnapshot): string {
284
+ if (!snapshot.approvedModels.length && !snapshot.agentDefaults.length) return "";
285
+ const defaultLines = snapshot.agentDefaults
286
+ .filter((entry) => entry.model)
287
+ .map((entry) => `- ${entry.agent}: ${entry.model}${entry.thinking ? ` (${entry.thinking})` : ""}${entry.fallbackModels?.length ? `; fallbacks ${entry.fallbackModels.join(", ")}` : ""}`);
288
+ return [
289
+ "Active Takomi subagent routing summary:",
290
+ snapshot.sourceFiles.length ? `Policy source: ${snapshot.sourceFiles.join(", ")}` : "Policy source: settings/defaults",
291
+ snapshot.approvedModels.length ? `Approved model IDs: ${snapshot.approvedModels.join(", ")}` : "Approved model IDs: none discovered",
292
+ "When calling takomi_subagent, omit model to use these defaults or use only the approved provider-qualified IDs above. Do not use openai-codex/* when an oauth-router/* equivalent is approved.",
293
+ defaultLines.length ? "Role defaults:" : "",
294
+ ...defaultLines,
295
+ ].filter(Boolean).join("\n");
296
+ }
@@ -22,6 +22,15 @@ export type RoutingPolicyInstallResult = {
22
22
  detectedDefaults: string[];
23
23
  };
24
24
 
25
+ export type RoutingPolicyPreviewResult = {
26
+ scope: RoutingPolicyInstallScope;
27
+ policy: string;
28
+ policyPath: string;
29
+ settingsPath: string;
30
+ detectedDefaults: string[];
31
+ overrides: JsonObject;
32
+ };
33
+
25
34
  export type RoutingPolicyInstallScope = "global" | "project";
26
35
  export type RoutingPolicySource = "project" | "global" | "bundled" | "missing";
27
36
 
@@ -64,6 +73,25 @@ function normalizeForSettings(filePath: string): string {
64
73
  return filePath.replaceAll(path.sep, "/");
65
74
  }
66
75
 
76
+ function extractPreferredProvider(policy: string): string | undefined {
77
+ const match = policy.match(/(?:preferred|default)\s+(?:provider|router)(?:\s*\/\s*(?:provider|router))?\s*:\s*([a-z0-9-]+)/i)
78
+ ?? policy.match(/use\s+([a-z0-9-]+)\s+as\s+(?:the\s+)?(?:provider|router)/i);
79
+ return match?.[1];
80
+ }
81
+
82
+ function findExplicitProviderModel(policy: string, family: RegExp): string | undefined {
83
+ const refs = policy.match(/[a-z0-9-]+\/[a-z0-9._-]+/gi) ?? [];
84
+ return refs.find((ref) => family.test(ref));
85
+ }
86
+
87
+ function providerModel(preferredProvider: string | undefined, model: string): string | undefined {
88
+ return preferredProvider ? `${preferredProvider}/${model}` : undefined;
89
+ }
90
+
91
+ function withOptionalModel(model: string | undefined, thinking: string, extra: JsonObject = {}): JsonObject {
92
+ return model ? { model, thinking, ...extra } : { thinking, ...extra };
93
+ }
94
+
67
95
  function deriveSubagentDefaults(policy: string): { overrides: JsonObject; detected: string[] } {
68
96
  const lower = policy.toLowerCase();
69
97
  const has55 = /gpt[- ]?5\.5/.test(lower);
@@ -71,28 +99,29 @@ function deriveSubagentDefaults(policy: string): { overrides: JsonObject; detect
71
99
  const hasMini = /gpt[- ]?5\.4\s*mini/.test(lower);
72
100
  if (!has55 && !has54 && !hasMini) return { overrides: {}, detected: [] };
73
101
 
74
- const model55 = "oauth-router/gpt-5.5";
75
- const model54 = "oauth-router/gpt-5.4";
76
- const modelMini = "oauth-router/gpt-5.4-mini";
102
+ // Keep generated settings provider-agnostic unless the policy explicitly
103
+ // declares provider-qualified models or a preferred provider/router header.
104
+ const preferredProvider = extractPreferredProvider(policy);
105
+ const model55 = findExplicitProviderModel(policy, /gpt[-_.]?5\.5/i) ?? providerModel(preferredProvider, "gpt-5.5");
106
+ const model54 = findExplicitProviderModel(policy, /gpt[-_.]?5\.4(?![-_.]?mini)/i) ?? providerModel(preferredProvider, "gpt-5.4");
107
+ const modelMini = findExplicitProviderModel(policy, /gpt[-_.]?5\.4[-_.]?mini/i) ?? providerModel(preferredProvider, "gpt-5.4-mini");
77
108
  const overrides: JsonObject = {};
78
109
  const detected: string[] = [];
79
110
 
80
111
  if (has55) {
81
- overrides.oracle = { model: model55, thinking: "high" };
82
- overrides.reviewer = { model: model55, thinking: "high" };
83
- overrides.planner = { model: model55, thinking: "medium" };
84
- detected.push("oracle/reviewer → GPT-5.5 high", "planner → GPT-5.5 medium");
112
+ overrides.orchestrator = withOptionalModel(model55, "high");
113
+ overrides.architect = withOptionalModel(model55, "high");
114
+ overrides.reviewer = withOptionalModel(model55, "high");
115
+ detected.push(model55 ? `orchestrator/architect/reviewer → ${model55} high` : "orchestrator/architect/reviewer → GPT-5.5 high intent");
85
116
  }
86
117
  if (has54) {
87
- overrides.worker = { model: model54, thinking: "high", fallbackModels: has55 ? [`${model55}:low`] : undefined };
88
- overrides.contextBuilder = { model: model54, thinking: "high" };
89
- overrides["context-builder"] = { model: model54, thinking: "high" };
90
- detected.push("worker/context-builder → GPT-5.4 high");
118
+ overrides.general = withOptionalModel(model54, "high", model55 ? { fallbackModels: [`${model55}:low`] } : {});
119
+ overrides.coder = withOptionalModel(model54, "high", model55 ? { fallbackModels: [`${model55}:low`] } : {});
120
+ overrides.designer = withOptionalModel(model54, "high", model55 ? { fallbackModels: [`${model55}:low`] } : {});
121
+ detected.push(model54 ? `general/coder/designer → ${model54} high` : "general/coder/designer → GPT-5.4 high intent");
91
122
  }
92
123
  if (hasMini) {
93
- overrides.scout = { model: modelMini, thinking: "high" };
94
- overrides.delegate = { model: modelMini, thinking: "high" };
95
- detected.push("scout/delegate → GPT-5.4 Mini high");
124
+ detected.push(modelMini ? `GPT-5.4 Mini available for explicit small-task overrides: ${modelMini}` : "GPT-5.4 Mini available as small-task intent only");
96
125
  }
97
126
  return { overrides, detected };
98
127
  }
@@ -148,7 +177,7 @@ export async function resolveTakomiRoutingPolicy(cwd: string): Promise<ResolvedR
148
177
  return { source: "missing" };
149
178
  }
150
179
 
151
- export async function installTakomiRoutingPolicy(cwd: string, input: string, options: { scope?: RoutingPolicyInstallScope } = {}): Promise<RoutingPolicyInstallResult> {
180
+ export function previewTakomiRoutingPolicy(cwd: string, input: string, options: { scope?: RoutingPolicyInstallScope } = {}): RoutingPolicyPreviewResult {
152
181
  const policy = extractQuotedPolicy(input);
153
182
  if (!policy) throw new Error("No routing policy text found. Paste the policy after /takomi routing or inside triple quotes.");
154
183
 
@@ -159,6 +188,13 @@ export async function installTakomiRoutingPolicy(cwd: string, input: string, opt
159
188
  const settingsPath = scope === "project"
160
189
  ? path.join(cwd, PROJECT_PI_SETTINGS_RELATIVE)
161
190
  : GLOBAL_PI_SETTINGS_PATH;
191
+ const { overrides, detected } = deriveSubagentDefaults(policy);
192
+ return { scope, policy, policyPath, settingsPath, detectedDefaults: detected, overrides };
193
+ }
194
+
195
+ export async function installTakomiRoutingPolicy(cwd: string, input: string, options: { scope?: RoutingPolicyInstallScope } = {}): Promise<RoutingPolicyInstallResult> {
196
+ const preview = previewTakomiRoutingPolicy(cwd, input, options);
197
+ const { scope, policy, policyPath, settingsPath, overrides, detectedDefaults } = preview;
162
198
  await mkdir(path.dirname(policyPath), { recursive: true });
163
199
  await mkdir(path.dirname(settingsPath), { recursive: true });
164
200
  await writeFile(policyPath, `# Takomi Model Routing Policy\n\n${policy}\n`, "utf8");
@@ -170,7 +206,6 @@ export async function installTakomiRoutingPolicy(cwd: string, input: string, opt
170
206
  : normalizeForSettings(GLOBAL_TAKOMI_ROUTING_POLICY_PATH);
171
207
  settings.takomi = takomi;
172
208
 
173
- const { overrides, detected } = deriveSubagentDefaults(policy);
174
209
  if (Object.keys(overrides).length > 0) {
175
210
  const subagents = asObject(settings.subagents);
176
211
  const existingOverrides = asObject(subagents.agentOverrides);
@@ -179,7 +214,22 @@ export async function installTakomiRoutingPolicy(cwd: string, input: string, opt
179
214
  }
180
215
 
181
216
  await writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
182
- return { policyPath, settingsPath, settingsUpdated: true, detectedDefaults: detected };
217
+ return { policyPath, settingsPath, settingsUpdated: true, detectedDefaults };
218
+ }
219
+
220
+ export function renderRoutingPolicyPreview(preview: RoutingPolicyPreviewResult): string {
221
+ const overrideLines = Object.entries(preview.overrides).map(([role, value]) => `- ${role}: ${JSON.stringify(value)}`);
222
+ return [
223
+ `Scope: ${preview.scope}`,
224
+ `Policy path: ${preview.policyPath}`,
225
+ `Settings path: ${preview.settingsPath}`,
226
+ "",
227
+ preview.detectedDefaults.length ? "Detected routing defaults:" : "Detected routing defaults: none",
228
+ ...preview.detectedDefaults.map((item) => `- ${item}`),
229
+ "",
230
+ overrideLines.length ? "Settings overrides to write:" : "Settings overrides to write: none",
231
+ ...overrideLines,
232
+ ].join("\n");
183
233
  }
184
234
 
185
235
  export async function loadTakomiRoutingPolicy(cwd: string): Promise<string | undefined> {
@@ -12,6 +12,7 @@ import {
12
12
  type SubagentState,
13
13
  } from "./pi-subagents-internal";
14
14
  import { resolveAgentName } from "./agent-aliases";
15
+ import { applyTakomiRoutingDefaults, loadTakomiModelRoutingSnapshotSync } from "../takomi-runtime/model-routing-defaults";
15
16
  import type { TakomiSubagentToolParams, TakomiSubagentToolTask } from "./tool-runner";
16
17
 
17
18
  type ToolUpdate = (partial: AgentToolResult<Details>) => void;
@@ -135,9 +136,18 @@ function defaultChildExtensions(): string[] {
135
136
  return candidates.filter((candidate) => fs.existsSync(candidate));
136
137
  }
137
138
 
138
- function withTakomiAgentDefaults(agent: AgentConfig): AgentConfig {
139
+ function withTakomiAgentDefaults(agent: AgentConfig, cwd: string): AgentConfig {
140
+ const routed = applyTakomiRoutingDefaults({
141
+ agent: agent.name,
142
+ model: agent.model,
143
+ fallbackModels: agent.fallbackModels,
144
+ thinking: agent.thinking,
145
+ }, loadTakomiModelRoutingSnapshotSync(cwd));
139
146
  return {
140
147
  ...agent,
148
+ model: routed.model,
149
+ fallbackModels: routed.fallbackModels,
150
+ thinking: routed.thinking,
141
151
  systemPromptMode: agent.systemPromptMode ?? "replace",
142
152
  inheritProjectContext: agent.inheritProjectContext ?? true,
143
153
  inheritSkills: agent.inheritSkills ?? false,
@@ -147,7 +157,7 @@ function withTakomiAgentDefaults(agent: AgentConfig): AgentConfig {
147
157
  }
148
158
 
149
159
  function discoverUnifiedAgents(discoverPiAgents: any, cwd: string, scope: AgentScope): { agents: AgentConfig[] } {
150
- return { agents: discoverPiAgents(cwd, scope).agents.map(withTakomiAgentDefaults) };
160
+ return { agents: discoverPiAgents(cwd, scope).agents.map((agent: AgentConfig) => withTakomiAgentDefaults(agent, cwd)) };
151
161
  }
152
162
 
153
163
  function agentNameSet(discoverPiAgents: any, cwd: string): Set<string> {
@@ -2,6 +2,7 @@ import path from "node:path";
2
2
  import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
3
3
  import type { TakomiThinkingLevel } from "../../../src/pi-takomi-core";
4
4
  import { loadTakomiProfile } from "../takomi-runtime/profile";
5
+ import { applyTakomiRoutingDefaults, loadTakomiModelRoutingSnapshot } from "../takomi-runtime/model-routing-defaults";
5
6
  import { resolveAgentName } from "./agent-aliases";
6
7
  import { discoverTakomiAgents, type TakomiAgentConfig, type TakomiAgentScope } from "./agents";
7
8
  import { createTakomiDelegationPlan, renderTakomiDelegationPlan } from "./delegation-plan";
@@ -202,10 +203,11 @@ export async function executeTakomiSubagentTool(
202
203
  const agents = discoverTakomiAgents(rootCwd, agentScope);
203
204
  const byName = new Map<string, TakomiAgentConfig>(agents.map((agent) => [agent.name, agent]));
204
205
  const mode = resolveMode(params);
205
- const tasks = resolveTasks(params).map((task) => ({
206
+ const routingSnapshot = await loadTakomiModelRoutingSnapshot(rootCwd);
207
+ const tasks = resolveTasks(params).map((task) => applyTakomiRoutingDefaults({
206
208
  ...task,
207
209
  agent: resolveAgentName(task.agent, byName),
208
- }));
210
+ }, routingSnapshot));
209
211
 
210
212
  if (!mode) {
211
213
  return textResult(
package/.pi/settings.json CHANGED
@@ -5,41 +5,39 @@
5
5
  },
6
6
  "subagents": {
7
7
  "agentOverrides": {
8
- "oracle": {
9
- "model": "oauth-router/gpt-5.5",
10
- "thinking": "high"
8
+ "general": {
9
+ "model": "oauth-router/gpt-5.4",
10
+ "thinking": "high",
11
+ "fallbackModels": [
12
+ "oauth-router/gpt-5.5:low"
13
+ ]
11
14
  },
12
- "reviewer": {
15
+ "orchestrator": {
13
16
  "model": "oauth-router/gpt-5.5",
14
17
  "thinking": "high"
15
18
  },
16
- "planner": {
19
+ "architect": {
17
20
  "model": "oauth-router/gpt-5.5",
18
- "thinking": "medium"
21
+ "thinking": "high"
19
22
  },
20
- "worker": {
23
+ "designer": {
21
24
  "model": "oauth-router/gpt-5.4",
22
25
  "thinking": "high",
23
26
  "fallbackModels": [
24
27
  "oauth-router/gpt-5.5:low"
25
28
  ]
26
29
  },
27
- "contextBuilder": {
30
+ "coder": {
28
31
  "model": "oauth-router/gpt-5.4",
29
- "thinking": "high"
30
- },
31
- "context-builder": {
32
- "model": "oauth-router/gpt-5.4",
33
- "thinking": "high"
34
- },
35
- "scout": {
36
- "model": "oauth-router/gpt-5.4-mini",
37
- "thinking": "high"
32
+ "thinking": "high",
33
+ "fallbackModels": [
34
+ "oauth-router/gpt-5.5:low"
35
+ ]
38
36
  },
39
- "delegate": {
40
- "model": "oauth-router/gpt-5.4-mini",
37
+ "reviewer": {
38
+ "model": "oauth-router/gpt-5.5",
41
39
  "thinking": "high"
42
40
  }
43
41
  }
44
42
  }
45
- }
43
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "takomi",
3
- "version": "2.1.26",
3
+ "version": "2.1.27",
4
4
  "description": "🎯 Stop wrestling with AI. Start building with purpose. The artisan's toolkit for agent workflows, Codex skills, and original Takomi capabilities like 21st.dev integration.",
5
5
  "type": "module",
6
6
  "bin": {