chati-dev 4.5.1 → 4.5.3

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.
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "catalog": {
3
- "snapshot_id": "chati-model-catalog-2026-08-26",
4
- "expires_at": "2026-11-24T00:00:00.000Z",
3
+ "snapshot_id": "chati-model-catalog-2026-08-27",
4
+ "published_at": "2026-08-27T00:00:00.000Z",
5
+ "expires_at": "2026-11-25T00:00:00.000Z",
5
6
  "models": [
6
7
  {
7
8
  "provider_id": "anthropic",
@@ -13,9 +14,87 @@
13
14
  "review",
14
15
  "adjudication"
15
16
  ],
17
+ "tier": "frontier",
18
+ "natural_role": "architecture and design",
19
+ "routing_priority": {
20
+ "discovery": 95,
21
+ "planning": 100,
22
+ "build": 45,
23
+ "review": 90,
24
+ "adjudication": 95,
25
+ "architect": 100,
26
+ "detail": 100,
27
+ "ux": 100
28
+ },
29
+ "adjudication_priority": 95,
30
+ "highest_reasoning_configuration": "xhigh"
31
+ },
32
+ {
33
+ "provider_id": "anthropic",
34
+ "model_id": "claude-opus-4-8",
35
+ "actions": [
36
+ "discovery",
37
+ "planning",
38
+ "build",
39
+ "review",
40
+ "adjudication"
41
+ ],
42
+ "tier": "frontier",
43
+ "natural_role": "quality rules and judgment-heavy work",
44
+ "routing_priority": {
45
+ "discovery": 90,
46
+ "planning": 95,
47
+ "build": 75,
48
+ "review": 95,
49
+ "adjudication": 100,
50
+ "qa-planning": 97,
51
+ "qa-implementation": 97
52
+ },
16
53
  "adjudication_priority": 100,
17
54
  "highest_reasoning_configuration": "xhigh"
18
55
  },
56
+ {
57
+ "provider_id": "anthropic",
58
+ "model_id": "claude-sonnet-5",
59
+ "actions": [
60
+ "discovery",
61
+ "planning",
62
+ "build",
63
+ "review"
64
+ ],
65
+ "tier": "workhorse",
66
+ "natural_role": "daily implementation and planning craft",
67
+ "routing_priority": {
68
+ "discovery": 85,
69
+ "planning": 88,
70
+ "build": 98,
71
+ "review": 80,
72
+ "brief": 95,
73
+ "phases": 95,
74
+ "tasks": 95,
75
+ "dev": 98
76
+ },
77
+ "adjudication_priority": 60
78
+ },
79
+ {
80
+ "provider_id": "anthropic",
81
+ "model_id": "claude-haiku-4-5",
82
+ "actions": [
83
+ "discovery",
84
+ "planning",
85
+ "build",
86
+ "review"
87
+ ],
88
+ "tier": "worker",
89
+ "natural_role": "small parallel workers and structured transforms",
90
+ "routing_priority": {
91
+ "discovery": 55,
92
+ "planning": 45,
93
+ "build": 65,
94
+ "review": 40
95
+ },
96
+ "adjudication_priority": 20
97
+ },
19
98
  {
20
99
  "provider_id": "openai",
21
100
  "model_id": "gpt-5.6-sol",
@@ -26,9 +105,79 @@
26
105
  "review",
27
106
  "adjudication"
28
107
  ],
108
+ "tier": "frontier",
109
+ "natural_role": "independent review and long reliable sessions",
110
+ "routing_priority": {
111
+ "discovery": 90,
112
+ "planning": 93,
113
+ "build": 85,
114
+ "review": 100,
115
+ "adjudication": 100,
116
+ "qa-planning": 100,
117
+ "qa-implementation": 100,
118
+ "qa-visual": 100
119
+ },
29
120
  "adjudication_priority": 100,
30
121
  "highest_reasoning_configuration": "xhigh"
31
122
  },
123
+ {
124
+ "provider_id": "openai",
125
+ "model_id": "terra",
126
+ "actions": [
127
+ "discovery",
128
+ "planning",
129
+ "build",
130
+ "review",
131
+ "adjudication"
132
+ ],
133
+ "tier": "workhorse",
134
+ "natural_role": "scoped implementation and efficient review",
135
+ "routing_priority": {
136
+ "discovery": 80,
137
+ "planning": 85,
138
+ "build": 100,
139
+ "review": 92,
140
+ "adjudication": 75,
141
+ "dev": 100,
142
+ "devops": 100
143
+ },
144
+ "adjudication_priority": 75,
145
+ "highest_reasoning_configuration": "xhigh"
146
+ },
147
+ {
148
+ "provider_id": "openai",
149
+ "model_id": "luna",
150
+ "actions": [
151
+ "discovery",
152
+ "planning",
153
+ "build",
154
+ "review"
155
+ ],
156
+ "tier": "worker",
157
+ "natural_role": "low-cost workers and routine tasks",
158
+ "routing_priority": {
159
+ "discovery": 60,
160
+ "planning": 55,
161
+ "build": 72,
162
+ "review": 60
163
+ },
164
+ "adjudication_priority": 30
165
+ },
166
+ {
167
+ "provider_id": "openai",
168
+ "model_id": "nano",
169
+ "actions": [
170
+ "discovery",
171
+ "build"
172
+ ],
173
+ "tier": "utility",
174
+ "natural_role": "classification, extraction and cleanup",
175
+ "routing_priority": {
176
+ "discovery": 35,
177
+ "build": 40
178
+ },
179
+ "adjudication_priority": 5
180
+ },
32
181
  {
33
182
  "provider_id": "xai",
34
183
  "model_id": "grok-4.6",
@@ -39,21 +188,91 @@
39
188
  "review",
40
189
  "adjudication"
41
190
  ],
42
- "adjudication_priority": 100,
191
+ "tier": "frontier",
192
+ "natural_role": "high-reasoning escalation and fast execution",
193
+ "routing_priority": {
194
+ "discovery": 88,
195
+ "planning": 92,
196
+ "build": 94,
197
+ "review": 90,
198
+ "adjudication": 92,
199
+ "architect": 94
200
+ },
201
+ "adjudication_priority": 92,
43
202
  "highest_reasoning_configuration": "xhigh"
44
203
  },
45
204
  {
46
205
  "provider_id": "xai",
47
206
  "model_id": "grok-4.5",
207
+ "actions": [
208
+ "discovery",
209
+ "planning",
210
+ "build",
211
+ "review",
212
+ "adjudication"
213
+ ],
214
+ "tier": "workhorse",
215
+ "natural_role": "volume waves and speed-first builds",
216
+ "routing_priority": {
217
+ "discovery": 82,
218
+ "planning": 84,
219
+ "build": 98,
220
+ "review": 82,
221
+ "adjudication": 70,
222
+ "dev": 98
223
+ },
224
+ "adjudication_priority": 70,
225
+ "highest_reasoning_configuration": "xhigh"
226
+ },
227
+ {
228
+ "provider_id": "xai",
229
+ "model_id": "grok-4.3",
48
230
  "actions": [
49
231
  "discovery",
50
232
  "planning",
51
233
  "build",
52
234
  "review"
53
235
  ],
54
- "adjudication_priority": 50
236
+ "tier": "worker",
237
+ "natural_role": "cost-efficient parallel execution",
238
+ "routing_priority": {
239
+ "discovery": 65,
240
+ "planning": 60,
241
+ "build": 78,
242
+ "review": 60
243
+ },
244
+ "adjudication_priority": 35
245
+ },
246
+ {
247
+ "provider_id": "xai",
248
+ "model_id": "grok-4.20",
249
+ "actions": [
250
+ "discovery",
251
+ "build"
252
+ ],
253
+ "tier": "worker",
254
+ "natural_role": "high-volume structured tasks",
255
+ "routing_priority": {
256
+ "discovery": 55,
257
+ "build": 70
258
+ },
259
+ "adjudication_priority": 20
260
+ },
261
+ {
262
+ "provider_id": "xai",
263
+ "model_id": "grok-build-0.1",
264
+ "actions": [
265
+ "build"
266
+ ],
267
+ "tier": "specialist",
268
+ "natural_role": "speed-first implementation in Grok Build",
269
+ "routing_priority": {
270
+ "build": 90,
271
+ "dev": 92
272
+ },
273
+ "adjudication_priority": 10
55
274
  }
56
275
  ]
57
276
  },
58
- "signature": "p7nTZ2/WlWVWeuqt2eKFTXnSEKZX0X6EM7C0T30+syGV09qjM8soj11U7G4Wjl9u5W6cwEErJZCXdP8WgXHeBA=="
277
+ "signature": "rNiDCOiVFtrfGUrQWXw3dqR1cYAh/xZElyt0AfLXjhgykWzcw4o0NygK0ciHNXMDwhJUqhsp5fKIPpwli/YdDA=="
59
278
  }
@@ -1,33 +1,130 @@
1
1
  {
2
- "snapshot_id": "chati-model-catalog-2026-08-26",
3
- "expires_at": "2026-11-24T00:00:00.000Z",
2
+ "snapshot_id": "chati-model-catalog-2026-08-27",
3
+ "published_at": "2026-08-27T00:00:00.000Z",
4
+ "expires_at": "2026-11-25T00:00:00.000Z",
4
5
  "models": [
5
6
  {
6
7
  "provider_id": "anthropic",
7
8
  "model_id": "fable",
8
9
  "actions": ["discovery", "planning", "build", "review", "adjudication"],
10
+ "tier": "frontier",
11
+ "natural_role": "architecture and design",
12
+ "routing_priority": { "discovery": 95, "planning": 100, "build": 45, "review": 90, "adjudication": 95, "architect": 100, "detail": 100, "ux": 100 },
13
+ "adjudication_priority": 95,
14
+ "highest_reasoning_configuration": "xhigh"
15
+ },
16
+ {
17
+ "provider_id": "anthropic",
18
+ "model_id": "claude-opus-4-8",
19
+ "actions": ["discovery", "planning", "build", "review", "adjudication"],
20
+ "tier": "frontier",
21
+ "natural_role": "quality rules and judgment-heavy work",
22
+ "routing_priority": { "discovery": 90, "planning": 95, "build": 75, "review": 95, "adjudication": 100, "qa-planning": 97, "qa-implementation": 97 },
9
23
  "adjudication_priority": 100,
10
24
  "highest_reasoning_configuration": "xhigh"
11
25
  },
26
+ {
27
+ "provider_id": "anthropic",
28
+ "model_id": "claude-sonnet-5",
29
+ "actions": ["discovery", "planning", "build", "review"],
30
+ "tier": "workhorse",
31
+ "natural_role": "daily implementation and planning craft",
32
+ "routing_priority": { "discovery": 85, "planning": 88, "build": 98, "review": 80, "brief": 95, "phases": 95, "tasks": 95, "dev": 98 },
33
+ "adjudication_priority": 60
34
+ },
35
+ {
36
+ "provider_id": "anthropic",
37
+ "model_id": "claude-haiku-4-5",
38
+ "actions": ["discovery", "planning", "build", "review"],
39
+ "tier": "worker",
40
+ "natural_role": "small parallel workers and structured transforms",
41
+ "routing_priority": { "discovery": 55, "planning": 45, "build": 65, "review": 40 },
42
+ "adjudication_priority": 20
43
+ },
12
44
  {
13
45
  "provider_id": "openai",
14
46
  "model_id": "gpt-5.6-sol",
15
47
  "actions": ["discovery", "planning", "build", "review", "adjudication"],
48
+ "tier": "frontier",
49
+ "natural_role": "independent review and long reliable sessions",
50
+ "routing_priority": { "discovery": 90, "planning": 93, "build": 85, "review": 100, "adjudication": 100, "qa-planning": 100, "qa-implementation": 100, "qa-visual": 100 },
16
51
  "adjudication_priority": 100,
17
52
  "highest_reasoning_configuration": "xhigh"
18
53
  },
54
+ {
55
+ "provider_id": "openai",
56
+ "model_id": "terra",
57
+ "actions": ["discovery", "planning", "build", "review", "adjudication"],
58
+ "tier": "workhorse",
59
+ "natural_role": "scoped implementation and efficient review",
60
+ "routing_priority": { "discovery": 80, "planning": 85, "build": 100, "review": 92, "adjudication": 75, "dev": 100, "devops": 100 },
61
+ "adjudication_priority": 75,
62
+ "highest_reasoning_configuration": "xhigh"
63
+ },
64
+ {
65
+ "provider_id": "openai",
66
+ "model_id": "luna",
67
+ "actions": ["discovery", "planning", "build", "review"],
68
+ "tier": "worker",
69
+ "natural_role": "low-cost workers and routine tasks",
70
+ "routing_priority": { "discovery": 60, "planning": 55, "build": 72, "review": 60 },
71
+ "adjudication_priority": 30
72
+ },
73
+ {
74
+ "provider_id": "openai",
75
+ "model_id": "nano",
76
+ "actions": ["discovery", "build"],
77
+ "tier": "utility",
78
+ "natural_role": "classification, extraction and cleanup",
79
+ "routing_priority": { "discovery": 35, "build": 40 },
80
+ "adjudication_priority": 5
81
+ },
19
82
  {
20
83
  "provider_id": "xai",
21
84
  "model_id": "grok-4.6",
22
85
  "actions": ["discovery", "planning", "build", "review", "adjudication"],
23
- "adjudication_priority": 100,
86
+ "tier": "frontier",
87
+ "natural_role": "high-reasoning escalation and fast execution",
88
+ "routing_priority": { "discovery": 88, "planning": 92, "build": 94, "review": 90, "adjudication": 92, "architect": 94 },
89
+ "adjudication_priority": 92,
24
90
  "highest_reasoning_configuration": "xhigh"
25
91
  },
26
92
  {
27
93
  "provider_id": "xai",
28
94
  "model_id": "grok-4.5",
95
+ "actions": ["discovery", "planning", "build", "review", "adjudication"],
96
+ "tier": "workhorse",
97
+ "natural_role": "volume waves and speed-first builds",
98
+ "routing_priority": { "discovery": 82, "planning": 84, "build": 98, "review": 82, "adjudication": 70, "dev": 98 },
99
+ "adjudication_priority": 70,
100
+ "highest_reasoning_configuration": "xhigh"
101
+ },
102
+ {
103
+ "provider_id": "xai",
104
+ "model_id": "grok-4.3",
29
105
  "actions": ["discovery", "planning", "build", "review"],
30
- "adjudication_priority": 50
106
+ "tier": "worker",
107
+ "natural_role": "cost-efficient parallel execution",
108
+ "routing_priority": { "discovery": 65, "planning": 60, "build": 78, "review": 60 },
109
+ "adjudication_priority": 35
110
+ },
111
+ {
112
+ "provider_id": "xai",
113
+ "model_id": "grok-4.20",
114
+ "actions": ["discovery", "build"],
115
+ "tier": "worker",
116
+ "natural_role": "high-volume structured tasks",
117
+ "routing_priority": { "discovery": 55, "build": 70 },
118
+ "adjudication_priority": 20
119
+ },
120
+ {
121
+ "provider_id": "xai",
122
+ "model_id": "grok-build-0.1",
123
+ "actions": ["build"],
124
+ "tier": "specialist",
125
+ "natural_role": "speed-first implementation in Grok Build",
126
+ "routing_priority": { "build": 90, "dev": 92 },
127
+ "adjudication_priority": 10
31
128
  }
32
129
  ]
33
130
  }
@@ -1 +1 @@
1
- p7nTZ2/WlWVWeuqt2eKFTXnSEKZX0X6EM7C0T30+syGV09qjM8soj11U7G4Wjl9u5W6cwEErJZCXdP8WgXHeBA==
1
+ rNiDCOiVFtrfGUrQWXw3dqR1cYAh/xZElyt0AfLXjhgykWzcw4o0NygK0ciHNXMDwhJUqhsp5fKIPpwli/YdDA==
@@ -22,9 +22,11 @@ function fail(code, message) {
22
22
  throw error;
23
23
  }
24
24
 
25
- function normalizeModels(provider, selections) {
25
+ function normalizeModels(provider, selections, capabilityCatalog) {
26
26
  const value = selections?.[provider];
27
- const values = value === undefined ? [PROVIDER_BINDINGS[provider].default_model] : (Array.isArray(value) ? value : [value]);
27
+ const values = value === undefined
28
+ ? wizardModelSuggestions(provider, capabilityCatalog)
29
+ : (Array.isArray(value) ? value : [value]);
28
30
  const models = [...new Set(values.map((model) => typeof model === 'string' ? model.trim() : '').filter(Boolean))];
29
31
  if (models.length === 0) fail('WIZARD_MODEL_REQUIRED', `${provider} requires at least one selected model`);
30
32
  return models;
@@ -48,17 +50,16 @@ export function wizardModelSuggestions(provider, capabilityCatalog) {
48
50
  export function buildWizardV2InstallationInput({
49
51
  projectName,
50
52
  selectedProviders,
51
- primaryProvider,
52
53
  modelSelections = {},
53
54
  capabilityCatalog,
54
- profile = 'other-supported-profile',
55
+ installationMode = 'open',
55
56
  clock = () => new Date(),
56
57
  } = {}) {
57
58
  if (typeof projectName !== 'string' || !projectName.trim()) fail('WIZARD_PROJECT_REQUIRED', 'projectName is required');
58
59
  if (!Array.isArray(selectedProviders) || selectedProviders.length === 0) fail('WIZARD_PROVIDER_REQUIRED', 'at least one v2 provider is required');
60
+ if (!['open', 'internal'].includes(installationMode)) fail('WIZARD_INSTALLATION_MODE_INVALID', 'installationMode must be open or internal');
59
61
  const providers = [...new Set(selectedProviders)];
60
62
  for (const provider of providers) if (!PROVIDER_BINDINGS[provider]) fail('V2_PROVIDER_UNSUPPORTED', `${provider} is not supported by v2`);
61
- if (!providers.includes(primaryProvider)) fail('WIZARD_PRIMARY_PROVIDER_REQUIRED', 'primaryProvider must be selected');
62
63
  const now = clock();
63
64
  const issuedAt = (now instanceof Date ? now : new Date(now));
64
65
  if (Number.isNaN(issuedAt.getTime())) fail('WIZARD_CLOCK_INVALID', 'clock must return a valid time');
@@ -74,11 +75,10 @@ export function buildWizardV2InstallationInput({
74
75
  return {
75
76
  provider_id: binding.provider_id,
76
77
  harness_id: binding.harness_id,
77
- allowed_models: normalizeModels(provider, modelSelections),
78
+ allowed_models: normalizeModels(provider, modelSelections, capabilityCatalog),
78
79
  allowed_reasoning_configurations: ['low', 'medium', 'high', 'xhigh'],
79
80
  };
80
81
  });
81
- const primary = PROVIDER_BINDINGS[primaryProvider];
82
82
  const models = enabled_providers.flatMap((binding) => binding.allowed_models.map((model_id) => {
83
83
  const catalogModel = capabilityCatalog?.models?.find((candidate) => candidate.provider_id === binding.provider_id && candidate.model_id === model_id);
84
84
  if (capabilityCatalog && !catalogModel) fail('WIZARD_MODEL_NOT_IN_CATALOG', `${model_id} is not in the signed catalog for ${binding.provider_id}`);
@@ -86,6 +86,9 @@ export function buildWizardV2InstallationInput({
86
86
  provider_id: binding.provider_id,
87
87
  model_id,
88
88
  actions: catalogModel?.actions || V2_ACTIONS,
89
+ ...(catalogModel?.routing_priority !== undefined ? { routing_priority: catalogModel.routing_priority } : {}),
90
+ ...(catalogModel?.tier !== undefined ? { tier: catalogModel.tier } : {}),
91
+ ...(catalogModel?.natural_role !== undefined ? { natural_role: catalogModel.natural_role } : {}),
89
92
  ...(catalogModel?.adjudication_priority !== undefined ? { adjudication_priority: catalogModel.adjudication_priority } : {}),
90
93
  ...(catalogModel?.highest_reasoning_configuration !== undefined
91
94
  ? { highest_reasoning_configuration: catalogModel.highest_reasoning_configuration }
@@ -97,13 +100,11 @@ export function buildWizardV2InstallationInput({
97
100
  const projectSlug = projectName.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '') || 'project';
98
101
  const installation = {
99
102
  installation_id: `wizard-${projectSlug}`,
100
- profile,
101
- primary_harness: primary.harness_id,
102
- primary_binding: { provider_id: primary.provider_id, harness_id: primary.harness_id },
103
+ profile: installationMode === 'internal' ? 'focus-ai-internal' : 'other-supported-profile',
103
104
  enabled_providers,
104
105
  capability_snapshot_ref: snapshotId,
105
106
  policy_ref: `policy://installation/${snapshotId}`,
106
- ...(profile === 'focus-ai-internal' ? { external_integrations: { clickup: 'required' } } : {}),
107
+ ...(installationMode === 'internal' ? { external_integrations: { clickup: 'required' } } : {}),
107
108
  };
108
109
  return Object.freeze({
109
110
  project_id: `project-${projectSlug}`,
@@ -37,6 +37,11 @@ function writeGlobal(data) {
37
37
  try { chmodSync(path, 0o600); } catch { /* non-POSIX FS — skip silently */ }
38
38
  }
39
39
 
40
+ function normalizeEntitlements(value) {
41
+ if (!Array.isArray(value)) return [];
42
+ return [...new Set(value.filter((item) => typeof item === 'string' && item.trim()).map((item) => item.trim()))];
43
+ }
44
+
40
45
  // ---------------------------------------------------------------------------
41
46
  // Key helpers
42
47
  // ---------------------------------------------------------------------------
@@ -96,6 +101,7 @@ export async function activateLicense(_projectDir, key) {
96
101
  plan: data.plan ?? 'trial',
97
102
  days_remaining: data.days_remaining ?? null,
98
103
  expires_at: data.expires_at ?? null,
104
+ entitlements: normalizeEntitlements(data.entitlements),
99
105
  checked_at: new Date().toISOString(),
100
106
  });
101
107
 
@@ -123,6 +129,7 @@ export async function validateLicense(_projectDir) {
123
129
  days_remaining: data.days_remaining ?? null,
124
130
  expires_at: data.expires_at ?? null,
125
131
  reason: data.reason ?? null,
132
+ entitlements: normalizeEntitlements(data.entitlements),
126
133
  checked_at: new Date().toISOString(),
127
134
  };
128
135
 
@@ -130,6 +137,25 @@ export async function validateLicense(_projectDir) {
130
137
  return result;
131
138
  }
132
139
 
140
+ export function hasLicenseEntitlement(status, entitlement) {
141
+ return status?.status === 'VALID' && normalizeEntitlements(status.entitlements).includes(entitlement);
142
+ }
143
+
144
+ /**
145
+ * Refreshes the server-authoritative license status and requires one
146
+ * entitlement before an internal installation can proceed.
147
+ */
148
+ export async function requireLicenseEntitlement(entitlement, { validate = validateLicense } = {}) {
149
+ const status = await validate(null);
150
+ if (!hasLicenseEntitlement(status, entitlement)) {
151
+ throw Object.assign(new Error(`The active license is not authorized for ${entitlement} mode.`), {
152
+ code: 'LICENSE_ENTITLEMENT_REQUIRED',
153
+ entitlement,
154
+ });
155
+ }
156
+ return status;
157
+ }
158
+
133
159
  /**
134
160
  * Remove license locally and deactivate machine slot on server (best-effort).
135
161
  * Fails silently if API is unreachable.
@@ -25,10 +25,29 @@ export function loadRuntimeInstallationV2(projectDir, { clock = () => new Date()
25
25
  export function resolveRuntimeInvocationV2({ artifact, agent, binding, model_id, reasoning_configuration, clock = () => new Date() }) {
26
26
  const action = AGENT_ACTIONS[agent];
27
27
  if (!action) throw Object.assign(new Error(`No v2 action mapping for agent ${agent}`), { code: 'UNMAPPED_AGENT_ACTION' });
28
- const selected = binding || artifact.installation.primary_binding;
29
- const model = model_id || artifact.installation.enabled_providers
30
- .find((candidate) => candidate.provider_id === selected.provider_id && candidate.harness_id === selected.harness_id)
31
- ?.allowed_models[0];
28
+ const candidates = artifact.installation.enabled_providers.flatMap((candidateBinding) => {
29
+ if (binding && (candidateBinding.provider_id !== binding.provider_id || candidateBinding.harness_id !== binding.harness_id)) return [];
30
+ return candidateBinding.allowed_models.flatMap((candidateModelId) => {
31
+ if (model_id && candidateModelId !== model_id) return [];
32
+ const catalogModel = artifact.capability_snapshot.models.find((candidate) =>
33
+ candidate.provider_id === candidateBinding.provider_id && candidate.model_id === candidateModelId
34
+ );
35
+ if (!catalogModel?.actions.includes(action)) return [];
36
+ return [{
37
+ binding: candidateBinding,
38
+ model: catalogModel,
39
+ priority: catalogModel.routing_priority?.[agent] ?? catalogModel.routing_priority?.[action] ?? 0,
40
+ }];
41
+ });
42
+ }).sort((left, right) =>
43
+ right.priority - left.priority
44
+ || (right.model.adjudication_priority ?? 0) - (left.model.adjudication_priority ?? 0)
45
+ || `${left.binding.harness_id}:${left.model.model_id}`.localeCompare(`${right.binding.harness_id}:${right.model.model_id}`)
46
+ );
47
+ const selectedCandidate = candidates[0];
48
+ if (!selectedCandidate) throw Object.assign(new Error(`No eligible installed model for ${agent}/${action}`), { code: 'NO_ELIGIBLE_MODEL' });
49
+ const selected = selectedCandidate.binding;
50
+ const model = selectedCandidate.model.model_id;
32
51
  const invocation = {
33
52
  provider_id: selected.provider_id, harness_id: selected.harness_id, action,
34
53
  model_pin: { model_id: model, catalog_snapshot_ref: artifact.capability_snapshot.snapshot_id, reasoning_configuration },
@@ -131,14 +131,14 @@ export function loadEnabledProviders(projectDir) {
131
131
 
132
132
  // Filter out invalid provider names (typos in config.yaml)
133
133
  const validEnabled = enabled.filter(name => validNames.includes(name));
134
- const validPrimary = validNames.includes(primary) ? primary : 'claude';
134
+ const validPrimary = validNames.includes(primary) ? primary : validEnabled[0] || null;
135
135
 
136
136
  return { primary: validPrimary, enabled: validEnabled };
137
137
  }
138
138
 
139
139
  /**
140
140
  * Resolve which provider should be used for a given agent.
141
- * Priority: agent_overrides > agent default > primary provider.
141
+ * Priority: agent_overrides > agent default > first enabled legacy fallback.
142
142
  *
143
143
  * @param {string} agent - Agent name
144
144
  * @param {string} projectDir - Project root directory
@@ -166,10 +166,11 @@ export function resolveProviderForAgent(agent, projectDir, agentModels) {
166
166
  }
167
167
  }
168
168
 
169
- // Fallback to primary provider with its default light-tier model
170
- const provider = PROVIDERS[primary];
169
+ // Legacy fallback only. Installation v2 routes from the signed task catalog.
170
+ const fallbackProvider = primary || enabled[0];
171
+ const provider = PROVIDERS[fallbackProvider];
171
172
  const defaultModel = provider ? Object.keys(provider.modelMap)[0] : 'sonnet';
172
- return { provider: primary, model: defaultModel };
173
+ return { provider: fallbackProvider, model: defaultModel };
173
174
  }
174
175
 
175
176
  /**
@@ -281,14 +281,14 @@ function buildPrismSection(config) {
281
281
 
282
282
  /**
283
283
  * Load the agent's full .md definition file.
284
- * For non-primary providers, resolves via overlay directory.
284
+ * For enabled providers, resolves via the provider-specific overlay directory.
285
285
  */
286
286
  function loadAgentDefinition(agent, projectDir, provider = null) {
287
287
  const relToFramework = AGENT_FILE_PATHS[agent];
288
288
  if (!relToFramework) return null;
289
289
 
290
290
  let fullPath;
291
- if (provider && provider !== 'claude') {
291
+ if (provider) {
292
292
  fullPath = resolveOverlayPath(projectDir, relToFramework, provider);
293
293
  } else {
294
294
  fullPath = join(projectDir, resolveFrameworkDir(projectDir), relToFramework);
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @fileoverview Shared YAML config parser for provider configuration.
3
3
  *
4
- * Extracts provider enabled/primary status and agent overrides from
4
+ * Extracts enabled providers and legacy primary status from
5
5
  * config.yaml using lightweight regex-based parsing. Used by
6
6
  * cli-registry.js, context-file-generator.js, and health/engine.js.
7
7
  */
@@ -23,9 +23,9 @@ export function parseProviderConfig(projectDir) {
23
23
  }
24
24
 
25
25
  const raw = readFileSync(configPath, 'utf-8');
26
- const providers = ['claude', 'gemini', 'codex'];
26
+ const providers = ['claude', 'gemini', 'codex', 'grok'];
27
27
  const enabled = [];
28
- let primary = 'claude';
28
+ let primary = null;
29
29
 
30
30
  for (const name of providers) {
31
31
  const enabledMatch = raw.match(new RegExp(`${name}:[\\s\\S]*?enabled:\\s*(true|false)`, 'm'));
@@ -38,11 +38,6 @@ export function parseProviderConfig(projectDir) {
38
38
  }
39
39
  }
40
40
 
41
- // Claude is always enabled as fallback
42
- if (!enabled.includes('claude')) {
43
- enabled.unshift('claude');
44
- }
45
-
46
41
  return { primary, enabled, raw };
47
42
  }
48
43
 
@@ -50,7 +50,7 @@ export function showQuickStart(title, steps) {
50
50
  console.log(` ${dim(`${i + 1}.`)} ${step}`);
51
51
  });
52
52
  console.log();
53
- console.log(`${dim('Documentation:')} chati.dev/constitution.md`);
53
+ console.log(`${dim('Documentation:')} .chati.dev/constitution.md`);
54
54
  console.log(`${dim('Session:')} .chati/session.yaml`);
55
55
  }
56
56