claude-dev-env 7.6.1 → 7.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +48 -71
- package/_shared/advisor/advisor-protocol.md +2 -2
- package/_shared/advisor/reference/advisor-block.md +1 -1
- package/_shared/advisor/reference/astra-rung.md +2 -2
- package/_shared/advisor/scripts/codex_astra_advisor.py +122 -23
- package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +76 -9
- package/_shared/advisor/scripts/tests/test_advisor_route_constants.py +51 -0
- package/_shared/advisor/scripts/tests/test_codex_astra_advisor.py +52 -2
- package/bin/install-constants.mjs +3 -0
- package/bin/install-transaction.mjs +41 -44
- package/bin/install.cursor-rules.test.mjs +311 -3
- package/bin/install.mjs +156 -95
- package/bin/install.transaction.test.mjs +61 -1
- package/bin/pstack-model-preferences.codex.json +1 -0
- package/bin/pstack.mjs +28 -6
- package/bin/pstack.test.mjs +48 -0
- package/bin/select-pstack-models.test.mjs +375 -26
- package/bin/subagent-model-policy.test.mjs +399 -0
- package/docs/codex-compatibility.md +19 -5
- package/docs/subagent-model-routing.md +50 -0
- package/hooks/hooks.json +10 -0
- package/hooks/routing/subagent_model_routing.mjs +81 -0
- package/hooks/routing/subagent_model_routing.test.mjs +218 -0
- package/package.json +1 -1
- package/rules/pstack-models.md +22 -0
- package/rules/subagent-model-policy.json +85 -0
- package/scripts/resolve_advisor_model_route.mjs +52 -0
- package/scripts/select_pstack_models.mjs +203 -38
- package/scripts/subagent_model_policy.mjs +620 -0
- package/scripts/tests/resolve_advisor_model_route.test.mjs +51 -0
- package/scripts/tests/test_codex_compat_materializer.py +15 -0
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join, resolve } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import {
|
|
5
|
+
defaultSubagentModelPolicyPath,
|
|
6
|
+
isSelectorPairExcluded,
|
|
7
|
+
loadSubagentModelPolicy,
|
|
8
|
+
resolveSubagentModelRoute,
|
|
9
|
+
} from './subagent_model_policy.mjs';
|
|
4
10
|
|
|
5
|
-
const inheritanceAliases = new Set(['inherit-parent', 'auto']);
|
|
6
11
|
const hostNamePattern = /^[a-z0-9][a-z0-9_-]*$/;
|
|
7
12
|
const defaultSingleDelegationPanel = Object.freeze({
|
|
8
13
|
agentCount: 1,
|
|
@@ -42,10 +47,20 @@ export function selectPstackDelegation(input) {
|
|
|
42
47
|
validateSelectionInput(input);
|
|
43
48
|
const panel = resolvePanel(input);
|
|
44
49
|
validateDelegationIndex(input.delegationIndex, panel.agentCount);
|
|
45
|
-
const
|
|
46
|
-
const
|
|
50
|
+
const hostPreferences = readHostPreferences(input);
|
|
51
|
+
const routingInput = (input.requestedEffort === undefined || input.requestedEffort === null)
|
|
52
|
+
&& hostPreferences.defaultEffort !== undefined
|
|
53
|
+
? { ...input, requestedEffort: hostPreferences.defaultEffort }
|
|
54
|
+
: input;
|
|
55
|
+
const selectionPolicy = loadSelectionPolicy(input);
|
|
56
|
+
if (selectionPolicy.policy) validateModelIdInventories(input, selectionPolicy.policy);
|
|
57
|
+
const { allCandidates, routingFailures } = collectCandidates(
|
|
58
|
+
routingInput,
|
|
59
|
+
hostPreferences.modelsByRole[routingInput.role] ?? [],
|
|
60
|
+
selectionPolicy,
|
|
61
|
+
);
|
|
47
62
|
const allSelectedCandidates = selectPanelCandidates(allCandidates, panel);
|
|
48
|
-
return buildSelection(
|
|
63
|
+
return buildSelection(routingInput, panel, allSelectedCandidates, routingFailures);
|
|
49
64
|
}
|
|
50
65
|
|
|
51
66
|
function validateSelectionInput(input) {
|
|
@@ -56,7 +71,10 @@ function validateSelectionInput(input) {
|
|
|
56
71
|
validateDelegationIndex(input.delegationIndex);
|
|
57
72
|
requireStringArray(input.availableModelIds, 'available model ids');
|
|
58
73
|
requireStringArray(input.confirmedSuitableModelIds, 'confirmed suitable model ids');
|
|
59
|
-
|
|
74
|
+
if (input.requestedEffort !== undefined && input.requestedEffort !== null
|
|
75
|
+
&& (typeof input.requestedEffort !== 'string' || input.requestedEffort.trim() === '')) {
|
|
76
|
+
throw new Error('requested effort must be a non-empty string');
|
|
77
|
+
}
|
|
60
78
|
validateParentFallback(input.parentFallback);
|
|
61
79
|
validateConfirmedModels(input);
|
|
62
80
|
if (typeof input.role !== 'string' || !Object.hasOwn(allPstackRoleRequirements, input.role)) {
|
|
@@ -90,12 +108,6 @@ function requireStringArray(allEntries, label) {
|
|
|
90
108
|
}
|
|
91
109
|
}
|
|
92
110
|
|
|
93
|
-
function validateRealModelIds(allModelIds, label) {
|
|
94
|
-
if (allModelIds.some(eachModelId => inheritanceAliases.has(eachModelId))) {
|
|
95
|
-
throw new Error(label + ' cannot contain inheritance aliases');
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
111
|
function validateParentFallback(parentFallback) {
|
|
100
112
|
const hasBooleanFlags = parentFallback
|
|
101
113
|
&& typeof parentFallback === 'object'
|
|
@@ -107,7 +119,6 @@ function validateParentFallback(parentFallback) {
|
|
|
107
119
|
}
|
|
108
120
|
|
|
109
121
|
function validateConfirmedModels(input) {
|
|
110
|
-
validateRealModelIds(input.confirmedSuitableModelIds, 'confirmed suitable model ids');
|
|
111
122
|
const allAvailableModelIds = new Set(input.availableModelIds);
|
|
112
123
|
if (input.confirmedSuitableModelIds.some(
|
|
113
124
|
eachModelId => !allAvailableModelIds.has(eachModelId),
|
|
@@ -116,6 +127,18 @@ function validateConfirmedModels(input) {
|
|
|
116
127
|
}
|
|
117
128
|
}
|
|
118
129
|
|
|
130
|
+
function validateModelIdInventories(input, policy) {
|
|
131
|
+
const inventories = [
|
|
132
|
+
['confirmed suitable model ids', input.confirmedSuitableModelIds],
|
|
133
|
+
['available model ids', input.availableModelIds],
|
|
134
|
+
];
|
|
135
|
+
for (const [label, modelIds] of inventories) {
|
|
136
|
+
if (modelIds.some(eachModelId => policy.inheritanceAliases.has(eachModelId.trim().toLowerCase()))) {
|
|
137
|
+
throw new Error(label + ' must contain native model ids');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
119
142
|
function resolvePanel(input) {
|
|
120
143
|
const panel = input.panel
|
|
121
144
|
?? defaultPanelByPstackRole[input.role]
|
|
@@ -142,50 +165,182 @@ function readHostPreferences(input) {
|
|
|
142
165
|
'pstack-model-preferences.' + input.host + '.json',
|
|
143
166
|
);
|
|
144
167
|
if (!existsSync(preferencesPath)) {
|
|
145
|
-
return {};
|
|
168
|
+
return { modelsByRole: {}, defaultEffort: undefined };
|
|
146
169
|
}
|
|
147
170
|
const preferences = JSON.parse(readFileSync(preferencesPath, 'utf8'));
|
|
148
171
|
if (preferences.host !== input.host) {
|
|
149
172
|
throw new Error('preference host must match selected host');
|
|
150
173
|
}
|
|
151
|
-
return
|
|
174
|
+
return {
|
|
175
|
+
modelsByRole: preferences.modelsByRole ?? {},
|
|
176
|
+
defaultEffort: preferences.defaultEffort,
|
|
177
|
+
};
|
|
152
178
|
}
|
|
153
179
|
|
|
154
|
-
function
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
180
|
+
function loadSelectionPolicy(input) {
|
|
181
|
+
try {
|
|
182
|
+
return { policy: loadSubagentModelPolicy(resolvePolicyPath(input)), error: null };
|
|
183
|
+
} catch (error) {
|
|
184
|
+
return {
|
|
185
|
+
policy: null,
|
|
186
|
+
error: error instanceof Error ? error.message : String(error),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function collectCandidates(input, allPreferredModelEntries, selectionPolicy) {
|
|
192
|
+
validatePreferenceEntries(allPreferredModelEntries, 'role preferences');
|
|
193
|
+
if (!selectionPolicy.policy) {
|
|
194
|
+
return { allCandidates: [], routingFailures: [selectionPolicy.error] };
|
|
195
|
+
}
|
|
196
|
+
const routingFailures = [];
|
|
197
|
+
const allPreferredCandidates = allPreferredModelEntries
|
|
198
|
+
.map(eachEntry => preferenceCandidate(
|
|
199
|
+
eachEntry,
|
|
200
|
+
input,
|
|
201
|
+
'host-preference',
|
|
202
|
+
routingFailures,
|
|
203
|
+
selectionPolicy.policy,
|
|
204
|
+
))
|
|
205
|
+
.filter(Boolean);
|
|
206
|
+
const allAlternativeCandidates = input.confirmedSuitableModelIds
|
|
207
|
+
.map(eachModelId => preferenceCandidate(
|
|
208
|
+
eachModelId,
|
|
209
|
+
input,
|
|
210
|
+
'confirmed-host-alternative',
|
|
211
|
+
routingFailures,
|
|
212
|
+
selectionPolicy.policy,
|
|
213
|
+
))
|
|
159
214
|
.filter(Boolean);
|
|
160
|
-
const allAlternativeCandidates = input.confirmedSuitableModelIds.map(
|
|
161
|
-
eachModelId => modelCandidate(eachModelId, 'confirmed-host-alternative'),
|
|
162
|
-
);
|
|
163
215
|
const allCandidates = deduplicateCandidates([
|
|
164
216
|
...allPreferredCandidates,
|
|
165
217
|
...allAlternativeCandidates,
|
|
166
218
|
]);
|
|
167
|
-
const allCandidatesWithFallback = allCandidates.length === 0
|
|
219
|
+
const allCandidatesWithFallback = allCandidates.length === 0 && routingFailures.length === 0
|
|
168
220
|
&& input.parentFallback.isAllowed
|
|
169
221
|
? [parentCandidate()]
|
|
170
222
|
: allCandidates;
|
|
171
|
-
return
|
|
223
|
+
return {
|
|
224
|
+
allCandidates: orderCrossJudgeCandidates(input, allCandidatesWithFallback),
|
|
225
|
+
routingFailures,
|
|
226
|
+
};
|
|
172
227
|
}
|
|
173
228
|
|
|
174
|
-
function preferenceCandidate(
|
|
175
|
-
|
|
229
|
+
function preferenceCandidate(
|
|
230
|
+
preferenceEntry,
|
|
231
|
+
input,
|
|
232
|
+
source,
|
|
233
|
+
routingFailures,
|
|
234
|
+
policy,
|
|
235
|
+
) {
|
|
236
|
+
const resolvedEntry = resolvePreferenceEntry(preferenceEntry, input, policy);
|
|
237
|
+
if (resolvedEntry.error) {
|
|
238
|
+
routingFailures.push(resolvedEntry.error);
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const { modelId, effort } = resolvedEntry;
|
|
242
|
+
if (isSelectorPairExcluded(modelId, effort, policy)) return null;
|
|
243
|
+
if (policy.inheritanceAliases.has(modelId.trim().toLowerCase())) {
|
|
244
|
+
if (effort !== null) {
|
|
245
|
+
routingFailures.push('parent inheritance cannot set an effort');
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
176
248
|
return input.parentFallback.isAllowed ? parentCandidate() : null;
|
|
177
249
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
250
|
+
if (input.host !== 'codex') {
|
|
251
|
+
return input.availableModelIds.includes(modelId)
|
|
252
|
+
? modelCandidate(modelId, source)
|
|
253
|
+
: null;
|
|
254
|
+
}
|
|
255
|
+
const routeRequest = { model: modelId, agent_type: input.routingRole ?? 'worker' };
|
|
256
|
+
if (effort !== null) routeRequest.reasoning_effort = effort;
|
|
257
|
+
const route = resolveSubagentModelRoute(
|
|
258
|
+
routeRequest,
|
|
259
|
+
{
|
|
260
|
+
policyPath: resolvePolicyPath(input),
|
|
261
|
+
policy,
|
|
262
|
+
availableModelIds: input.availableModelIds,
|
|
263
|
+
allowMissingEffort: effort === null,
|
|
264
|
+
trustedSessionMetadata: input.trustedSessionMetadata,
|
|
265
|
+
},
|
|
266
|
+
);
|
|
267
|
+
if (route.status === 'blocked') {
|
|
268
|
+
routingFailures.push(route.diagnostic);
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
if (isSelectorPairExcluded(route.selected.model, route.selected.effort, policy)) return null;
|
|
272
|
+
if (route.status === 'inherited') return input.parentFallback.isAllowed ? parentCandidate() : null;
|
|
273
|
+
return modelCandidate(route.selected.model, source, route);
|
|
181
274
|
}
|
|
182
275
|
|
|
183
|
-
function modelCandidate(modelId, source) {
|
|
184
|
-
return {
|
|
276
|
+
function modelCandidate(modelId, source, route = null) {
|
|
277
|
+
return {
|
|
278
|
+
kind: 'model',
|
|
279
|
+
modelId,
|
|
280
|
+
source,
|
|
281
|
+
requestedPair: route?.requested ?? null,
|
|
282
|
+
selectedPair: route?.selected ?? null,
|
|
283
|
+
effort: route?.selected?.effort ?? null,
|
|
284
|
+
};
|
|
185
285
|
}
|
|
186
286
|
|
|
187
287
|
function parentCandidate() {
|
|
188
|
-
return {
|
|
288
|
+
return {
|
|
289
|
+
kind: 'parent',
|
|
290
|
+
modelId: null,
|
|
291
|
+
source: 'parent-inheritance',
|
|
292
|
+
requestedPair: null,
|
|
293
|
+
selectedPair: null,
|
|
294
|
+
effort: null,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function resolvePreferenceEntry(preferenceEntry, input, policy = null) {
|
|
299
|
+
const preferenceModel = typeof preferenceEntry === 'string'
|
|
300
|
+
? preferenceEntry
|
|
301
|
+
: preferenceEntry?.modelId ?? preferenceEntry?.model;
|
|
302
|
+
const isInheritanceAlias = typeof preferenceModel === 'string'
|
|
303
|
+
&& policy?.inheritanceAliases.has(preferenceModel.trim().toLowerCase());
|
|
304
|
+
const fallbackEffort = isInheritanceAlias ? null : input.requestedEffort ?? null;
|
|
305
|
+
if (typeof preferenceEntry === 'string' && preferenceEntry !== '') {
|
|
306
|
+
return { modelId: preferenceEntry, effort: fallbackEffort, error: null };
|
|
307
|
+
}
|
|
308
|
+
if (!preferenceEntry || typeof preferenceEntry !== 'object' || Array.isArray(preferenceEntry)) {
|
|
309
|
+
return { modelId: null, effort: null, error: 'role preferences must contain model ids or pair objects' };
|
|
310
|
+
}
|
|
311
|
+
const modelId = preferenceEntry.modelId ?? preferenceEntry.model;
|
|
312
|
+
const effortFields = ['reasoning_effort', 'effort'].filter(
|
|
313
|
+
field => Object.hasOwn(preferenceEntry, field),
|
|
314
|
+
);
|
|
315
|
+
if (effortFields.length > 1) {
|
|
316
|
+
return { modelId: null, effort: null, error: 'role preference effort fields are ambiguous' };
|
|
317
|
+
}
|
|
318
|
+
const effort = preferenceEntry[effortFields[0]] ?? fallbackEffort;
|
|
319
|
+
if (typeof modelId !== 'string' || modelId === '') {
|
|
320
|
+
return { modelId: null, effort: null, error: 'role preference model must be a non-empty string' };
|
|
321
|
+
}
|
|
322
|
+
return { modelId, effort, error: null };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function validatePreferenceEntries(allEntries, label) {
|
|
326
|
+
if (!Array.isArray(allEntries)) throw new Error(label + ' must be an array');
|
|
327
|
+
allEntries.forEach(eachEntry => {
|
|
328
|
+
const resolvedEntry = resolvePreferenceEntry(eachEntry, {});
|
|
329
|
+
if (resolvedEntry.error) throw new Error(resolvedEntry.error);
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function resolvePolicyPath(input) {
|
|
334
|
+
if (typeof input.policyPath === 'string' && input.policyPath.trim() !== '') {
|
|
335
|
+
return input.policyPath;
|
|
336
|
+
}
|
|
337
|
+
const allPolicyPaths = [
|
|
338
|
+
input.preferencesDirectory
|
|
339
|
+
? join(input.preferencesDirectory, 'subagent-model-policy.json')
|
|
340
|
+
: null,
|
|
341
|
+
defaultSubagentModelPolicyPath(),
|
|
342
|
+
].filter(Boolean);
|
|
343
|
+
return allPolicyPaths.find(eachPath => existsSync(eachPath)) ?? allPolicyPaths[0];
|
|
189
344
|
}
|
|
190
345
|
|
|
191
346
|
function deduplicateCandidates(allCandidates) {
|
|
@@ -230,9 +385,9 @@ function selectPanelCandidates(allCandidates, panel) {
|
|
|
230
385
|
);
|
|
231
386
|
}
|
|
232
387
|
|
|
233
|
-
function buildSelection(input, requestedPanel, allSelectedCandidates) {
|
|
388
|
+
function buildSelection(input, requestedPanel, allSelectedCandidates, routingFailures) {
|
|
234
389
|
const panel = reportPanel(requestedPanel, allSelectedCandidates);
|
|
235
|
-
const failure = selectionFailure(input, panel, allSelectedCandidates);
|
|
390
|
+
const failure = selectionFailure(input, panel, allSelectedCandidates, routingFailures);
|
|
236
391
|
const selectedCandidate = allSelectedCandidates[input.delegationIndex];
|
|
237
392
|
return {
|
|
238
393
|
selectedHost: input.host,
|
|
@@ -244,6 +399,7 @@ function buildSelection(input, requestedPanel, allSelectedCandidates) {
|
|
|
244
399
|
failure,
|
|
245
400
|
selectionSource: selectedCandidate?.source ?? null,
|
|
246
401
|
panel,
|
|
402
|
+
routingDiagnostics: routingFailures,
|
|
247
403
|
nativeSpawnArguments: nativeSpawnArguments(selectedCandidate, failure),
|
|
248
404
|
omitNativeModelArgument: selectedCandidate?.kind === 'parent',
|
|
249
405
|
};
|
|
@@ -253,18 +409,23 @@ function reportPanel(requestedPanel, allSelectedCandidates) {
|
|
|
253
409
|
const allSelectedModelIds = allSelectedCandidates.map(
|
|
254
410
|
eachCandidate => eachCandidate.modelId,
|
|
255
411
|
);
|
|
256
|
-
const
|
|
412
|
+
const distinctModelCount = new Set(allSelectedModelIds.filter(Boolean)).size;
|
|
257
413
|
return {
|
|
258
414
|
agentCount: requestedPanel.agentCount,
|
|
259
415
|
requiresDistinctModels: requestedPanel.requiresDistinctModels,
|
|
260
416
|
selectedModelIds: allSelectedModelIds,
|
|
417
|
+
requestedModelPairs: allSelectedCandidates.map(eachCandidate => eachCandidate.requestedPair),
|
|
418
|
+
selectedModelPairs: allSelectedCandidates.map(eachCandidate => eachCandidate.selectedPair),
|
|
261
419
|
selectionSources: allSelectedCandidates.map(eachCandidate => eachCandidate.source),
|
|
262
420
|
isCrossModelDiverse: requestedPanel.agentCount > 1
|
|
263
|
-
&&
|
|
421
|
+
&& distinctModelCount === requestedPanel.agentCount,
|
|
264
422
|
};
|
|
265
423
|
}
|
|
266
424
|
|
|
267
|
-
function selectionFailure(input, panel, allSelectedCandidates) {
|
|
425
|
+
function selectionFailure(input, panel, allSelectedCandidates, routingFailures) {
|
|
426
|
+
if (routingFailures.length > 0) {
|
|
427
|
+
return 'model-routing-failed: ' + routingFailures[0];
|
|
428
|
+
}
|
|
268
429
|
if (panel.requiresDistinctModels && !panel.isCrossModelDiverse) {
|
|
269
430
|
return 'distinct-models-unavailable';
|
|
270
431
|
}
|
|
@@ -282,7 +443,11 @@ function nativeSpawnArguments(selectedCandidate, failure) {
|
|
|
282
443
|
if (failure || !selectedCandidate || selectedCandidate.kind === 'parent') {
|
|
283
444
|
return {};
|
|
284
445
|
}
|
|
285
|
-
|
|
446
|
+
const spawnArguments = { model: selectedCandidate.modelId };
|
|
447
|
+
if (selectedCandidate.effort !== null) {
|
|
448
|
+
spawnArguments.reasoning_effort = selectedCandidate.effort;
|
|
449
|
+
}
|
|
450
|
+
return spawnArguments;
|
|
286
451
|
}
|
|
287
452
|
|
|
288
453
|
async function readSelectionInput() {
|