@open-agent-toolkit/cli 0.1.45 → 0.1.48
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/assets/agents/oat-phase-implementer.md +202 -191
- package/assets/agents/oat-reviewer.md +12 -2
- package/assets/config/dispatch-matrix-recommendation.json +120 -13
- package/assets/docs/cli-utilities/config-and-local-state.md +7 -0
- package/assets/docs/cli-utilities/configuration.md +183 -84
- package/assets/docs/cli-utilities/workflow-gates.md +161 -27
- package/assets/docs/contributing/skills.md +14 -8
- package/assets/docs/provider-sync/config.md +5 -1
- package/assets/docs/provider-sync/manifest-and-drift.md +6 -1
- package/assets/docs/provider-sync/providers.md +42 -5
- package/assets/docs/provider-sync/scope-and-surface.md +3 -2
- package/assets/docs/reference/cli-reference.md +3 -1
- package/assets/docs/reference/oat-directory-structure.md +27 -26
- package/assets/docs/workflows/projects/artifacts.md +31 -1
- package/assets/docs/workflows/projects/dispatch-ceiling.md +235 -174
- package/assets/docs/workflows/projects/implementation-execution.md +283 -253
- package/assets/docs/workflows/projects/lifecycle.md +26 -5
- package/assets/docs/workflows/projects/reviews.md +27 -2
- package/assets/migration/pjm-restructure.md +1 -1
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-project-implement/SKILL.md +347 -178
- package/assets/skills/oat-project-import-plan/SKILL.md +173 -16
- package/assets/skills/oat-project-next/SKILL.md +2 -2
- package/assets/skills/oat-project-plan/SKILL.md +122 -92
- package/assets/skills/oat-project-plan-writing/SKILL.md +246 -15
- package/assets/skills/oat-project-quick-start/SKILL.md +156 -94
- package/assets/skills/oat-project-review-provide/SKILL.md +94 -22
- package/dist/commands/config/index.d.ts +2 -2
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +129 -53
- package/dist/commands/doctor/index.d.ts +2 -2
- package/dist/commands/doctor/index.d.ts.map +1 -1
- package/dist/commands/doctor/index.js +102 -31
- package/dist/commands/gate/index.d.ts +7 -1
- package/dist/commands/gate/index.d.ts.map +1 -1
- package/dist/commands/gate/index.js +544 -67
- package/dist/commands/gate/review-verdict.d.ts +16 -0
- package/dist/commands/gate/review-verdict.d.ts.map +1 -1
- package/dist/commands/gate/review-verdict.js +72 -9
- package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
- package/dist/commands/project/dispatch-ceiling/index.js +586 -23
- package/dist/commands/providers/codex/index.d.ts +4 -0
- package/dist/commands/providers/codex/index.d.ts.map +1 -0
- package/dist/commands/providers/codex/index.js +7 -0
- package/dist/commands/providers/codex/materialize.d.ts +5 -0
- package/dist/commands/providers/codex/materialize.d.ts.map +1 -0
- package/dist/commands/providers/codex/materialize.js +157 -0
- package/dist/commands/providers/index.d.ts +2 -2
- package/dist/commands/providers/index.d.ts.map +1 -1
- package/dist/commands/providers/index.js +4 -2
- package/dist/commands/providers/providers.types.d.ts +23 -0
- package/dist/commands/providers/providers.types.d.ts.map +1 -1
- package/dist/commands/shared/codex-strays.d.ts.map +1 -1
- package/dist/commands/shared/codex-strays.js +11 -1
- package/dist/commands/shared/frontmatter.d.ts +4 -0
- package/dist/commands/shared/frontmatter.d.ts.map +1 -1
- package/dist/commands/shared/frontmatter.js +23 -0
- package/dist/commands/status/index.d.ts +5 -1
- package/dist/commands/status/index.d.ts.map +1 -1
- package/dist/commands/status/index.js +10 -4
- package/dist/commands/sync/index.d.ts.map +1 -1
- package/dist/commands/sync/index.js +10 -3
- package/dist/commands/sync/sync.types.d.ts +5 -1
- package/dist/commands/sync/sync.types.d.ts.map +1 -1
- package/dist/config/dispatch-ceiling-preset.d.ts +4 -0
- package/dist/config/dispatch-ceiling-preset.d.ts.map +1 -1
- package/dist/config/dispatch-ceiling-preset.js +3 -0
- package/dist/config/dispatch-policy-options.d.ts +20 -0
- package/dist/config/dispatch-policy-options.d.ts.map +1 -0
- package/dist/config/dispatch-policy-options.js +96 -0
- package/dist/config/oat-config.d.ts +25 -2
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +154 -24
- package/dist/config/resolve.d.ts +8 -0
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +66 -2
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +1 -1
- package/dist/engine/scanner.d.ts +1 -0
- package/dist/engine/scanner.d.ts.map +1 -1
- package/dist/engine/scanner.js +17 -1
- package/dist/fs/paths.d.ts +4 -0
- package/dist/fs/paths.d.ts.map +1 -1
- package/dist/fs/paths.js +18 -1
- package/dist/providers/ceiling/registry.d.ts +8 -5
- package/dist/providers/ceiling/registry.d.ts.map +1 -1
- package/dist/providers/ceiling/registry.js +25 -7
- package/dist/providers/codex/codec/catalog.d.ts +14 -0
- package/dist/providers/codex/codec/catalog.d.ts.map +1 -0
- package/dist/providers/codex/codec/catalog.js +21 -0
- package/dist/providers/codex/codec/config-merge.d.ts +6 -0
- package/dist/providers/codex/codec/config-merge.d.ts.map +1 -1
- package/dist/providers/codex/codec/config-merge.js +7 -0
- package/dist/providers/codex/codec/materialize.d.ts +16 -0
- package/dist/providers/codex/codec/materialize.d.ts.map +1 -0
- package/dist/providers/codex/codec/materialize.js +58 -0
- package/dist/providers/codex/codec/shared.d.ts +20 -0
- package/dist/providers/codex/codec/shared.d.ts.map +1 -1
- package/dist/providers/codex/codec/shared.js +107 -6
- package/dist/providers/codex/codec/sync-extension.d.ts +7 -1
- package/dist/providers/codex/codec/sync-extension.d.ts.map +1 -1
- package/dist/providers/codex/codec/sync-extension.js +299 -52
- package/dist/providers/identity/availability.d.ts +24 -1
- package/dist/providers/identity/availability.d.ts.map +1 -1
- package/dist/providers/identity/availability.js +253 -19
- package/dist/providers/identity/stamp.d.ts.map +1 -1
- package/dist/providers/identity/stamp.js +4 -0
- package/dist/shared/types.d.ts +1 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js +4 -0
- package/package.json +2 -2
|
@@ -4,13 +4,14 @@ import { buildCommandContext, } from '../../../app/command-context.js';
|
|
|
4
4
|
import { getFrontmatterBlock } from '../../shared/frontmatter.js';
|
|
5
5
|
import { readGlobalOptions } from '../../shared/shared.utils.js';
|
|
6
6
|
import { compileDispatchPolicyPreset, } from '../../../config/dispatch-ceiling-preset.js';
|
|
7
|
-
import {
|
|
7
|
+
import { getDispatchPolicyChoices, managedDispatchPolicyValueList, renderDispatchPolicyChoicesMarkdown, } from '../../../config/dispatch-policy-options.js';
|
|
8
|
+
import { resolveActiveProject, VALID_CLAUDE_DISPATCH_CEILINGS, VALID_CODEX_DISPATCH_CEILINGS, VALID_DISPATCH_MATRIX_TIERS, VALID_MANAGED_DISPATCH_POLICIES, isWorkflowDispatchCandidateLadder, isWorkflowDispatchFallbackRoute, toWorkflowDispatchCandidateLadder, validateDispatchRouteTarget, } from '../../../config/oat-config.js';
|
|
8
9
|
import { resolveEffectiveConfig, } from '../../../config/resolve.js';
|
|
9
10
|
import { resolveProjectRoot } from '../../../fs/paths.js';
|
|
10
11
|
import TOML from '@iarna/toml';
|
|
11
|
-
import { getCeilingAdapter, } from '../../../providers/ceiling/registry.js';
|
|
12
|
+
import { getCeilingAdapter, isDirectDispatchRoleName, CLAUDE_TIER_ORDER, } from '../../../providers/ceiling/registry.js';
|
|
12
13
|
import { classifyModelFamily, } from '../../../providers/identity/family.js';
|
|
13
|
-
import { Command } from 'commander';
|
|
14
|
+
import { Command, Option } from 'commander';
|
|
14
15
|
import YAML from 'yaml';
|
|
15
16
|
const CODEX_VALUES = [
|
|
16
17
|
...VALID_CODEX_DISPATCH_CEILINGS,
|
|
@@ -84,6 +85,8 @@ function sourceLabel(source) {
|
|
|
84
85
|
return 'environment';
|
|
85
86
|
case 'project-state':
|
|
86
87
|
return 'project state';
|
|
88
|
+
case 'invocation':
|
|
89
|
+
return 'explicit invocation';
|
|
87
90
|
default:
|
|
88
91
|
return 'none';
|
|
89
92
|
}
|
|
@@ -106,7 +109,7 @@ function isValidManagedPolicy(value) {
|
|
|
106
109
|
VALID_MANAGED_DISPATCH_POLICIES.includes(value));
|
|
107
110
|
}
|
|
108
111
|
function validManagedPolicyList() {
|
|
109
|
-
return
|
|
112
|
+
return managedDispatchPolicyValueList(', ');
|
|
110
113
|
}
|
|
111
114
|
function validProviderValueList(provider) {
|
|
112
115
|
return providerValueOrder(provider)?.join(', ') ?? 'none';
|
|
@@ -150,11 +153,7 @@ function normalizeProjectMatrixRouteTarget(value) {
|
|
|
150
153
|
}
|
|
151
154
|
return Object.keys(target).length > 0 ? target : undefined;
|
|
152
155
|
}
|
|
153
|
-
function
|
|
154
|
-
const bareValue = normalizeProjectMatrixBareValue(provider, value);
|
|
155
|
-
if (bareValue !== undefined) {
|
|
156
|
-
return bareValue;
|
|
157
|
-
}
|
|
156
|
+
function normalizeProjectMatrixRoute(provider, value) {
|
|
158
157
|
if (!Array.isArray(value) || value.length === 0) {
|
|
159
158
|
return undefined;
|
|
160
159
|
}
|
|
@@ -172,6 +171,34 @@ function normalizeProjectMatrixCell(provider, value) {
|
|
|
172
171
|
}
|
|
173
172
|
return route.length > 0 ? route : undefined;
|
|
174
173
|
}
|
|
174
|
+
function normalizeProjectMatrixCandidate(provider, value) {
|
|
175
|
+
const bareValue = normalizeProjectMatrixBareValue(provider, value);
|
|
176
|
+
if (bareValue !== undefined) {
|
|
177
|
+
return bareValue;
|
|
178
|
+
}
|
|
179
|
+
if (isWorkflowDispatchFallbackRoute(value)) {
|
|
180
|
+
const route = normalizeProjectMatrixRoute(provider, value.route);
|
|
181
|
+
return route ? { route } : undefined;
|
|
182
|
+
}
|
|
183
|
+
return normalizeProjectMatrixRouteTarget(value);
|
|
184
|
+
}
|
|
185
|
+
function normalizeProjectMatrixCell(provider, value) {
|
|
186
|
+
const bareValue = normalizeProjectMatrixBareValue(provider, value);
|
|
187
|
+
if (bareValue !== undefined) {
|
|
188
|
+
return bareValue;
|
|
189
|
+
}
|
|
190
|
+
if (isWorkflowDispatchCandidateLadder(value)) {
|
|
191
|
+
const candidates = [];
|
|
192
|
+
for (const candidate of value.candidates) {
|
|
193
|
+
const normalized = normalizeProjectMatrixCandidate(provider, candidate);
|
|
194
|
+
if (normalized !== undefined) {
|
|
195
|
+
candidates.push(normalized);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return candidates.length > 0 ? { candidates } : undefined;
|
|
199
|
+
}
|
|
200
|
+
return normalizeProjectMatrixRoute(provider, value);
|
|
201
|
+
}
|
|
175
202
|
function normalizeProjectMatrixProviderValue(provider, value) {
|
|
176
203
|
const bareValue = normalizeProjectMatrixBareValue(provider, value);
|
|
177
204
|
if (bareValue !== undefined) {
|
|
@@ -230,6 +257,22 @@ function policyTier(policy) {
|
|
|
230
257
|
? policy
|
|
231
258
|
: null;
|
|
232
259
|
}
|
|
260
|
+
function uncappedPreferredTier(provider, role, preferredValue) {
|
|
261
|
+
if (provider !== 'codex' || role === 'reviewer' || preferredValue === null) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
if (VALID_DISPATCH_MATRIX_TIERS.includes(preferredValue)) {
|
|
265
|
+
return preferredValue;
|
|
266
|
+
}
|
|
267
|
+
let matchedTier = null;
|
|
268
|
+
for (const tier of VALID_DISPATCH_MATRIX_TIERS) {
|
|
269
|
+
const compiledValue = compiledPolicyValueForProvider(provider, compileDispatchPolicyPreset(tier));
|
|
270
|
+
if (compiledValue === preferredValue) {
|
|
271
|
+
matchedTier = tier;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return matchedTier;
|
|
275
|
+
}
|
|
233
276
|
function dispatchValueFromRouteTarget(target) {
|
|
234
277
|
const adapter = getCeilingAdapter(target.harness);
|
|
235
278
|
if (adapter.mechanism === 'pinned-variant') {
|
|
@@ -276,12 +319,25 @@ function resolveRouteMatrixCell(provider, route, escalationLevel, cellSource) {
|
|
|
276
319
|
const target = typeof entry === 'string'
|
|
277
320
|
? routeTargetFromBareValue(provider, entry, routeIndex, route.length)
|
|
278
321
|
: routeTargetFromObject(provider, entry, routeIndex, route.length);
|
|
279
|
-
const
|
|
322
|
+
const targetValidation = typeof entry === 'string'
|
|
323
|
+
? { valid: true }
|
|
324
|
+
: validateDispatchRouteTarget(provider, entry);
|
|
325
|
+
const value = targetValidation.valid
|
|
326
|
+
? typeof entry === 'string'
|
|
327
|
+
? entry
|
|
328
|
+
: dispatchValueFromRouteTarget(target)
|
|
329
|
+
: null;
|
|
330
|
+
const warnings = targetValidation.valid
|
|
331
|
+
? []
|
|
332
|
+
: [
|
|
333
|
+
`Ignoring incomplete Codex dispatch target at route index ${routeIndex}: ${targetValidation.reason}`,
|
|
334
|
+
];
|
|
280
335
|
return {
|
|
281
336
|
value,
|
|
282
337
|
cellSource,
|
|
283
338
|
target,
|
|
284
339
|
selectionBranch: routeIndex > 0 ? 'escalation-target' : 'matrix-pinned',
|
|
340
|
+
warnings,
|
|
285
341
|
};
|
|
286
342
|
}
|
|
287
343
|
function resolveProviderCellFromValue(provider, providerValue, tier, cellSource, escalationLevel) {
|
|
@@ -294,6 +350,7 @@ function resolveProviderCellFromValue(provider, providerValue, tier, cellSource,
|
|
|
294
350
|
cellSource,
|
|
295
351
|
target: null,
|
|
296
352
|
selectionBranch: 'prompt-persisted',
|
|
353
|
+
warnings: [],
|
|
297
354
|
};
|
|
298
355
|
}
|
|
299
356
|
if (tier === null) {
|
|
@@ -309,8 +366,22 @@ function resolveProviderCellFromValue(provider, providerValue, tier, cellSource,
|
|
|
309
366
|
cellSource,
|
|
310
367
|
target: null,
|
|
311
368
|
selectionBranch: 'matrix-pinned',
|
|
369
|
+
warnings: [],
|
|
312
370
|
};
|
|
313
371
|
}
|
|
372
|
+
if (isWorkflowDispatchCandidateLadder(cell)) {
|
|
373
|
+
const ceiling = cell.candidates.at(-1);
|
|
374
|
+
if (ceiling === undefined) {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
if (typeof ceiling === 'string') {
|
|
378
|
+
return resolveRouteMatrixCell(provider, [ceiling], escalationLevel, cellSource);
|
|
379
|
+
}
|
|
380
|
+
const route = isWorkflowDispatchFallbackRoute(ceiling)
|
|
381
|
+
? ceiling.route
|
|
382
|
+
: [ceiling];
|
|
383
|
+
return resolveRouteMatrixCell(provider, route, escalationLevel, cellSource);
|
|
384
|
+
}
|
|
314
385
|
return resolveRouteMatrixCell(provider, cell, escalationLevel, cellSource);
|
|
315
386
|
}
|
|
316
387
|
function resolveProviderMatrixCell(provider, tier, resolvedConfig, projectMatrix, escalationLevel) {
|
|
@@ -338,6 +409,212 @@ function resolveProviderMatrixCell(provider, tier, resolvedConfig, projectMatrix
|
|
|
338
409
|
}
|
|
339
410
|
return selected;
|
|
340
411
|
}
|
|
412
|
+
function resolveProviderMatrixCellDefinition(provider, tier, resolvedConfig, projectMatrix) {
|
|
413
|
+
let selected = null;
|
|
414
|
+
const layers = [
|
|
415
|
+
{
|
|
416
|
+
source: 'user-config',
|
|
417
|
+
providers: resolvedConfig.user.workflow?.dispatchCeiling?.providers,
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
source: 'repo-config',
|
|
421
|
+
providers: resolvedConfig.shared.workflow?.dispatchCeiling?.providers,
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
source: 'local-config',
|
|
425
|
+
providers: resolvedConfig.local.workflow?.dispatchCeiling?.providers,
|
|
426
|
+
},
|
|
427
|
+
{ source: 'project-state', providers: projectMatrix },
|
|
428
|
+
];
|
|
429
|
+
for (const layer of layers) {
|
|
430
|
+
const providerValue = layer.providers?.[provider];
|
|
431
|
+
if (typeof providerValue === 'string') {
|
|
432
|
+
selected = null;
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
const cell = providerValue?.[tier];
|
|
436
|
+
if (cell !== undefined) {
|
|
437
|
+
selected = { cell, cellSource: layer.source };
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return selected;
|
|
441
|
+
}
|
|
442
|
+
function candidateRoute(candidate) {
|
|
443
|
+
return isWorkflowDispatchFallbackRoute(candidate)
|
|
444
|
+
? candidate.route
|
|
445
|
+
: [candidate];
|
|
446
|
+
}
|
|
447
|
+
function candidatePrimaryTarget(provider, tier, candidate) {
|
|
448
|
+
const route = candidateRoute(candidate);
|
|
449
|
+
const entry = route[0];
|
|
450
|
+
if (entry === undefined) {
|
|
451
|
+
throw new Error(`Malformed ${provider} candidate ordering in ${tier}: fallback routes cannot be empty.`);
|
|
452
|
+
}
|
|
453
|
+
if (typeof entry !== 'string') {
|
|
454
|
+
const validation = validateDispatchRouteTarget(provider, entry);
|
|
455
|
+
if (!validation.valid) {
|
|
456
|
+
throw new Error(`Malformed ${provider} candidate ordering in ${tier}: ${validation.reason}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const target = typeof entry === 'string'
|
|
460
|
+
? routeTargetFromBareValue(provider, entry, 0, route.length)
|
|
461
|
+
: routeTargetFromObject(provider, entry, 0, route.length);
|
|
462
|
+
if (target.model && isDirectDispatchRoleName(target.model)) {
|
|
463
|
+
throw new Error(`Malformed ${provider} candidate ordering in ${tier}: direct dispatch role names are not candidate models.`);
|
|
464
|
+
}
|
|
465
|
+
const targetAdapter = getCeilingAdapter(target.harness);
|
|
466
|
+
if (targetAdapter.mechanism === 'pinned-variant' &&
|
|
467
|
+
(!target.model ||
|
|
468
|
+
!target.effort ||
|
|
469
|
+
!CODEX_VALUES.includes(target.effort))) {
|
|
470
|
+
throw new Error(`Malformed ${provider} candidate ordering in ${tier}: Codex candidates require a model and supported effort.`);
|
|
471
|
+
}
|
|
472
|
+
if (targetAdapter.mechanism === 'model-arg' && !target.model) {
|
|
473
|
+
throw new Error(`Malformed ${provider} candidate ordering in ${tier}: model-argument candidates require a model.`);
|
|
474
|
+
}
|
|
475
|
+
return target;
|
|
476
|
+
}
|
|
477
|
+
function candidateTargetKey(target) {
|
|
478
|
+
return JSON.stringify([
|
|
479
|
+
target.harness,
|
|
480
|
+
target.model ?? null,
|
|
481
|
+
target.effort ?? null,
|
|
482
|
+
]);
|
|
483
|
+
}
|
|
484
|
+
function assertCandidateOrder(provider, tier, ladder) {
|
|
485
|
+
if (ladder.candidates.length === 0) {
|
|
486
|
+
throw new Error(`Malformed ${provider} candidate ordering in ${tier}: candidates cannot be empty.`);
|
|
487
|
+
}
|
|
488
|
+
const targets = [];
|
|
489
|
+
const seen = new Set();
|
|
490
|
+
let previousClaudeRank = null;
|
|
491
|
+
const codexRanksByModel = new Map();
|
|
492
|
+
for (const candidate of ladder.candidates) {
|
|
493
|
+
const target = candidatePrimaryTarget(provider, tier, candidate);
|
|
494
|
+
const key = candidateTargetKey(target);
|
|
495
|
+
if (seen.has(key)) {
|
|
496
|
+
throw new Error(`Malformed ${provider} candidate ordering in ${tier}: duplicate candidate ${key}.`);
|
|
497
|
+
}
|
|
498
|
+
seen.add(key);
|
|
499
|
+
if (target.harness === 'claude' && target.model) {
|
|
500
|
+
const rank = CLAUDE_TIER_ORDER.indexOf(target.model);
|
|
501
|
+
if (rank < 0) {
|
|
502
|
+
throw new Error(`Malformed ${provider} candidate ordering in ${tier}: unsupported Claude model ${JSON.stringify(target.model)}.`);
|
|
503
|
+
}
|
|
504
|
+
if (previousClaudeRank !== null && rank < previousClaudeRank) {
|
|
505
|
+
throw new Error(`Malformed ${provider} candidate ordering in ${tier}: Claude candidates must be nondecreasing.`);
|
|
506
|
+
}
|
|
507
|
+
previousClaudeRank = rank;
|
|
508
|
+
}
|
|
509
|
+
if (target.harness === 'codex' && target.model && target.effort) {
|
|
510
|
+
const rank = CODEX_VALUES.indexOf(target.effort);
|
|
511
|
+
const previousRank = codexRanksByModel.get(target.model);
|
|
512
|
+
if (previousRank !== undefined && rank < previousRank) {
|
|
513
|
+
throw new Error(`Malformed ${provider} candidate ordering in ${tier}: Codex efforts for ${target.model} must be nondecreasing.`);
|
|
514
|
+
}
|
|
515
|
+
codexRanksByModel.set(target.model, rank);
|
|
516
|
+
}
|
|
517
|
+
targets.push(target);
|
|
518
|
+
}
|
|
519
|
+
return targets;
|
|
520
|
+
}
|
|
521
|
+
function assertTierCeilingsNondecreasing(provider, previous, current, tier) {
|
|
522
|
+
if (!previous || previous.harness !== current.harness) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (current.harness === 'claude' &&
|
|
526
|
+
previous.model &&
|
|
527
|
+
current.model &&
|
|
528
|
+
CLAUDE_TIER_ORDER.indexOf(current.model) <
|
|
529
|
+
CLAUDE_TIER_ORDER.indexOf(previous.model)) {
|
|
530
|
+
throw new Error(`Malformed ${provider} candidate ordering in ${tier}: named tier ceilings must be nondecreasing.`);
|
|
531
|
+
}
|
|
532
|
+
if (current.harness === 'codex' &&
|
|
533
|
+
previous.model === current.model &&
|
|
534
|
+
previous.effort &&
|
|
535
|
+
current.effort &&
|
|
536
|
+
CODEX_VALUES.indexOf(current.effort) <
|
|
537
|
+
CODEX_VALUES.indexOf(previous.effort)) {
|
|
538
|
+
throw new Error(`Malformed ${provider} candidate ordering in ${tier}: named tier ceilings must be nondecreasing.`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
function requestedCandidateMatches(provider, requested, target) {
|
|
542
|
+
if (target.harness !== provider || target.model !== requested.model) {
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
return provider !== 'codex' || target.effort === requested.effort;
|
|
546
|
+
}
|
|
547
|
+
function formatRequestedCandidate(provider, requested) {
|
|
548
|
+
return provider === 'codex'
|
|
549
|
+
? `${requested.model}/${requested.effort}`
|
|
550
|
+
: requested.model;
|
|
551
|
+
}
|
|
552
|
+
function resolveRequestedMatrixCandidate(provider, requested, ceilingTier, resolvedConfig, projectMatrix, escalationLevel) {
|
|
553
|
+
const ceilingIndex = ceilingTier === null
|
|
554
|
+
? VALID_DISPATCH_MATRIX_TIERS.length - 1
|
|
555
|
+
: VALID_DISPATCH_MATRIX_TIERS.indexOf(ceilingTier);
|
|
556
|
+
let ceilingCellFound = ceilingTier === null;
|
|
557
|
+
let previousCeiling = null;
|
|
558
|
+
let allowedMatch;
|
|
559
|
+
let foundAboveCeiling = false;
|
|
560
|
+
for (const [tierIndex, tier] of VALID_DISPATCH_MATRIX_TIERS.entries()) {
|
|
561
|
+
const definition = resolveProviderMatrixCellDefinition(provider, tier, resolvedConfig, projectMatrix);
|
|
562
|
+
if (!definition) {
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
if (tier === ceilingTier) {
|
|
566
|
+
ceilingCellFound = true;
|
|
567
|
+
}
|
|
568
|
+
const ladder = toWorkflowDispatchCandidateLadder(definition.cell);
|
|
569
|
+
const targets = assertCandidateOrder(provider, tier, ladder);
|
|
570
|
+
const currentCeiling = targets.at(-1);
|
|
571
|
+
assertTierCeilingsNondecreasing(provider, previousCeiling, currentCeiling, tier);
|
|
572
|
+
previousCeiling = currentCeiling;
|
|
573
|
+
for (const [candidateIndex, target] of targets.entries()) {
|
|
574
|
+
if (!requestedCandidateMatches(provider, requested, target)) {
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
const candidate = ladder.candidates[candidateIndex];
|
|
578
|
+
const routeSignature = JSON.stringify(candidateRoute(candidate));
|
|
579
|
+
if (tierIndex > ceilingIndex) {
|
|
580
|
+
foundAboveCeiling = true;
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
if (allowedMatch && allowedMatch.routeSignature !== routeSignature) {
|
|
584
|
+
throw new Error(`Ambiguous ${provider} candidate ${formatRequestedCandidate(provider, requested)} is configured with multiple routes at or below the ${ceilingTier ?? 'uncapped'} ceiling.`);
|
|
585
|
+
}
|
|
586
|
+
allowedMatch ??= {
|
|
587
|
+
tier,
|
|
588
|
+
candidate,
|
|
589
|
+
cellSource: definition.cellSource,
|
|
590
|
+
routeSignature,
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
if (!ceilingCellFound) {
|
|
595
|
+
throw new Error(`Exact candidate selection requires a configured ${ceilingTier} candidate ladder for ${provider}.`);
|
|
596
|
+
}
|
|
597
|
+
if (!allowedMatch) {
|
|
598
|
+
const label = formatRequestedCandidate(provider, requested);
|
|
599
|
+
if (foundAboveCeiling) {
|
|
600
|
+
throw new Error(`${provider} candidate ${label} is above the configured ${ceilingTier} ceiling.`);
|
|
601
|
+
}
|
|
602
|
+
throw new Error(`${provider} candidate ${label} is not present in the configured ${provider} candidate ladders.`);
|
|
603
|
+
}
|
|
604
|
+
const resolution = resolveRouteMatrixCell(provider, candidateRoute(allowedMatch.candidate), escalationLevel, allowedMatch.cellSource);
|
|
605
|
+
if (!resolution || resolution.value === null) {
|
|
606
|
+
throw new Error(`Configured ${provider} candidate ${formatRequestedCandidate(provider, requested)} cannot compile to an exact dispatch target.`);
|
|
607
|
+
}
|
|
608
|
+
return {
|
|
609
|
+
resolution: {
|
|
610
|
+
...resolution,
|
|
611
|
+
selectionBranch: resolution.selectionBranch === 'escalation-target'
|
|
612
|
+
? 'escalation-target'
|
|
613
|
+
: 'candidate-requested',
|
|
614
|
+
},
|
|
615
|
+
candidateTier: allowedMatch.tier,
|
|
616
|
+
};
|
|
617
|
+
}
|
|
341
618
|
function readProjectDispatchPolicy(provider, content) {
|
|
342
619
|
const frontmatter = getFrontmatterBlock(content);
|
|
343
620
|
if (!frontmatter) {
|
|
@@ -613,7 +890,13 @@ function readResolvedConfigCeiling(provider, resolvedConfig) {
|
|
|
613
890
|
return stripConfigCandidateSource(winner);
|
|
614
891
|
}
|
|
615
892
|
function normalizeRole(value) {
|
|
616
|
-
|
|
893
|
+
if (value === undefined) {
|
|
894
|
+
return 'implementer';
|
|
895
|
+
}
|
|
896
|
+
if (value === 'implementer' || value === 'reviewer') {
|
|
897
|
+
return value;
|
|
898
|
+
}
|
|
899
|
+
throw new Error(`Invalid dispatch role ${JSON.stringify(value)}. Expected implementer or reviewer.`);
|
|
617
900
|
}
|
|
618
901
|
function providerValueOrder(provider) {
|
|
619
902
|
if (provider === 'codex') {
|
|
@@ -642,6 +925,55 @@ function normalizePreferredValue(provider, value) {
|
|
|
642
925
|
}
|
|
643
926
|
return normalized;
|
|
644
927
|
}
|
|
928
|
+
function normalizeCeilingTier(value, role) {
|
|
929
|
+
if (value === undefined) {
|
|
930
|
+
return null;
|
|
931
|
+
}
|
|
932
|
+
const normalized = value.trim();
|
|
933
|
+
if (!VALID_DISPATCH_MATRIX_TIERS.includes(normalized)) {
|
|
934
|
+
throw new Error(`Invalid invocation ceiling tier "${normalized}". Valid tiers: ${VALID_DISPATCH_MATRIX_TIERS.join(', ')}.`);
|
|
935
|
+
}
|
|
936
|
+
if (role === 'reviewer') {
|
|
937
|
+
throw new Error('Invocation ceiling tiers are only supported for implementer/fix task dispatch; reviewers use the configured review ceiling.');
|
|
938
|
+
}
|
|
939
|
+
return normalized;
|
|
940
|
+
}
|
|
941
|
+
function normalizeRequestedCandidate(provider, role, options) {
|
|
942
|
+
const model = options.candidateModel?.trim() ?? '';
|
|
943
|
+
const effort = options.candidateEffort?.trim() ?? '';
|
|
944
|
+
if (!model && !effort) {
|
|
945
|
+
return null;
|
|
946
|
+
}
|
|
947
|
+
if (options.preferred?.trim()) {
|
|
948
|
+
throw new Error('Exact candidate flags cannot be combined with --preferred; use one selection path.');
|
|
949
|
+
}
|
|
950
|
+
if (role === 'reviewer') {
|
|
951
|
+
throw new Error('Reviewer candidate requests are not supported; reviewers use the configured review ceiling.');
|
|
952
|
+
}
|
|
953
|
+
if (!model) {
|
|
954
|
+
throw new Error('--candidate-model is required for an exact candidate.');
|
|
955
|
+
}
|
|
956
|
+
if (isDirectDispatchRoleName(model)) {
|
|
957
|
+
throw new Error('Direct dispatch role names are not candidate models; request the configured provider model and effort instead.');
|
|
958
|
+
}
|
|
959
|
+
if (provider === 'codex') {
|
|
960
|
+
if (!effort) {
|
|
961
|
+
throw new Error('--candidate-effort is required for an exact Codex candidate.');
|
|
962
|
+
}
|
|
963
|
+
if (!CODEX_VALUES.includes(effort)) {
|
|
964
|
+
throw new Error(`Invalid Codex candidate effort "${effort}". Valid values: ${CODEX_VALUES.join(', ')}.`);
|
|
965
|
+
}
|
|
966
|
+
return { model, effort };
|
|
967
|
+
}
|
|
968
|
+
if (effort) {
|
|
969
|
+
throw new Error(`--candidate-effort is only valid for Codex; ${provider} candidates use --candidate-model only.`);
|
|
970
|
+
}
|
|
971
|
+
if (provider === 'claude' &&
|
|
972
|
+
!CLAUDE_VALUES.includes(model)) {
|
|
973
|
+
throw new Error(`Invalid Claude candidate model "${model}". Valid values: ${CLAUDE_VALUES.join(', ')}.`);
|
|
974
|
+
}
|
|
975
|
+
return { model };
|
|
976
|
+
}
|
|
645
977
|
function normalizeEscalationLevel(value) {
|
|
646
978
|
if (value === undefined) {
|
|
647
979
|
return 0;
|
|
@@ -664,6 +996,10 @@ function selectDispatchValue(provider, role, policy, preferredValue) {
|
|
|
664
996
|
policy: policy.policy,
|
|
665
997
|
cellSource: policy.cellSource,
|
|
666
998
|
target: policy.target,
|
|
999
|
+
requestedCandidate: policy.requestedCandidate ?? null,
|
|
1000
|
+
candidateTier: policy.candidateTier ?? null,
|
|
1001
|
+
ceilingTier: policy.ceilingTier ?? policyTier(policy.policy),
|
|
1002
|
+
ceilingTarget: policy.ceilingTarget ?? policy.target,
|
|
667
1003
|
};
|
|
668
1004
|
if (policy.mode === 'inherit') {
|
|
669
1005
|
return {
|
|
@@ -677,17 +1013,40 @@ function selectDispatchValue(provider, role, policy, preferredValue) {
|
|
|
677
1013
|
target: null,
|
|
678
1014
|
};
|
|
679
1015
|
}
|
|
1016
|
+
if (policy.requestedCandidate) {
|
|
1017
|
+
const selectedValue = policy.target
|
|
1018
|
+
? dispatchValueFromRouteTarget(policy.target)
|
|
1019
|
+
: null;
|
|
1020
|
+
return {
|
|
1021
|
+
...baseSelection,
|
|
1022
|
+
preferredValue: null,
|
|
1023
|
+
selectedValue,
|
|
1024
|
+
capped: false,
|
|
1025
|
+
selectionMode: selectedValue ? 'candidate' : 'unresolved',
|
|
1026
|
+
selectionBranch: policy.selectionBranch,
|
|
1027
|
+
family: selectionFamily(provider, selectedValue, policy.target),
|
|
1028
|
+
target: policy.target,
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
680
1031
|
if (policy.policy === 'uncapped') {
|
|
681
|
-
const
|
|
1032
|
+
const targetValue = policy.target
|
|
1033
|
+
? dispatchValueFromRouteTarget(policy.target)
|
|
1034
|
+
: null;
|
|
1035
|
+
const selectedValue = role === 'reviewer' ? null : (targetValue ?? preferredValue);
|
|
1036
|
+
const target = role === 'reviewer' || !targetValue ? null : policy.target;
|
|
682
1037
|
return {
|
|
683
1038
|
...baseSelection,
|
|
684
1039
|
preferredValue: role === 'reviewer' ? null : preferredValue,
|
|
685
1040
|
selectedValue,
|
|
686
1041
|
capped: false,
|
|
687
1042
|
selectionMode: role === 'reviewer' ? 'no-review-target' : 'uncapped',
|
|
688
|
-
selectionBranch:
|
|
689
|
-
|
|
690
|
-
|
|
1043
|
+
selectionBranch: target
|
|
1044
|
+
? policy.selectionBranch
|
|
1045
|
+
: selectedValue
|
|
1046
|
+
? 'prompt-persisted'
|
|
1047
|
+
: 'unresolved',
|
|
1048
|
+
family: selectionFamily(provider, selectedValue, target),
|
|
1049
|
+
target,
|
|
691
1050
|
};
|
|
692
1051
|
}
|
|
693
1052
|
if (policy.value === null) {
|
|
@@ -729,6 +1088,10 @@ function selectDispatchValue(provider, role, policy, preferredValue) {
|
|
|
729
1088
|
}
|
|
730
1089
|
const selectedIndex = Math.min(preferredIndex, ceilingIndex);
|
|
731
1090
|
const selectedValue = order[selectedIndex];
|
|
1091
|
+
const targetValue = policy.target
|
|
1092
|
+
? dispatchValueFromRouteTarget(policy.target)
|
|
1093
|
+
: null;
|
|
1094
|
+
const selectedTarget = targetValue === selectedValue ? policy.target : null;
|
|
732
1095
|
return {
|
|
733
1096
|
...baseSelection,
|
|
734
1097
|
preferredValue,
|
|
@@ -736,10 +1099,44 @@ function selectDispatchValue(provider, role, policy, preferredValue) {
|
|
|
736
1099
|
capped: preferredIndex > ceilingIndex,
|
|
737
1100
|
selectionMode: 'capped',
|
|
738
1101
|
selectionBranch: policy.selectionBranch,
|
|
739
|
-
family: selectionFamily(provider, selectedValue,
|
|
740
|
-
target:
|
|
1102
|
+
family: selectionFamily(provider, selectedValue, selectedTarget),
|
|
1103
|
+
target: selectedTarget,
|
|
741
1104
|
};
|
|
742
1105
|
}
|
|
1106
|
+
function hasCodexVariantDispatchArgs(dispatchArgs) {
|
|
1107
|
+
return (dispatchArgs !== null &&
|
|
1108
|
+
'variant' in dispatchArgs &&
|
|
1109
|
+
typeof dispatchArgs.variant === 'string' &&
|
|
1110
|
+
dispatchArgs.variant.length > 0);
|
|
1111
|
+
}
|
|
1112
|
+
function hasModelDispatchArgs(dispatchArgs) {
|
|
1113
|
+
return (dispatchArgs !== null &&
|
|
1114
|
+
'model' in dispatchArgs &&
|
|
1115
|
+
typeof dispatchArgs.model === 'string' &&
|
|
1116
|
+
dispatchArgs.model.length > 0);
|
|
1117
|
+
}
|
|
1118
|
+
function modelAxis(selection, dispatchArgs) {
|
|
1119
|
+
if (selection.target?.model && dispatchArgs) {
|
|
1120
|
+
return `selected:${selection.target.model}`;
|
|
1121
|
+
}
|
|
1122
|
+
if (hasModelDispatchArgs(dispatchArgs)) {
|
|
1123
|
+
return `selected:${dispatchArgs.model}`;
|
|
1124
|
+
}
|
|
1125
|
+
if (selection.selectionMode === 'inherit-default') {
|
|
1126
|
+
return 'inherited';
|
|
1127
|
+
}
|
|
1128
|
+
return 'unresolved';
|
|
1129
|
+
}
|
|
1130
|
+
function codexEffortAxis(selection, dispatchArgs) {
|
|
1131
|
+
if (hasCodexVariantDispatchArgs(dispatchArgs) && selection.target?.effort) {
|
|
1132
|
+
return `selected:${selection.target.effort}`;
|
|
1133
|
+
}
|
|
1134
|
+
if (selection.selectionMode === 'inherit-default' ||
|
|
1135
|
+
selection.selectionMode === 'no-review-target') {
|
|
1136
|
+
return 'provider-default';
|
|
1137
|
+
}
|
|
1138
|
+
return 'unresolved';
|
|
1139
|
+
}
|
|
743
1140
|
/**
|
|
744
1141
|
* Join a resolved ceiling value with the active provider's adapter to compute
|
|
745
1142
|
* the enforcement mode, mechanism, dispatch args, and verify-on-upgrade flag.
|
|
@@ -753,12 +1150,18 @@ function buildProviderResolution(provider, policy, role, orchestratorTier, prefe
|
|
|
753
1150
|
mode: adapter.supportsCeiling ? 'advisory' : 'unsupported',
|
|
754
1151
|
mechanism: adapter.mechanism,
|
|
755
1152
|
dispatchArgs: null,
|
|
1153
|
+
modelAxis: 'unresolved',
|
|
1154
|
+
effortAxis: provider === 'codex' ? 'provider-default' : 'not-applicable',
|
|
756
1155
|
verifyOnDispatch: false,
|
|
757
1156
|
cellSource: null,
|
|
758
1157
|
target: null,
|
|
759
1158
|
selection: {
|
|
760
1159
|
role,
|
|
761
1160
|
preferredValue,
|
|
1161
|
+
requestedCandidate: null,
|
|
1162
|
+
candidateTier: null,
|
|
1163
|
+
ceilingTier: null,
|
|
1164
|
+
ceilingTarget: null,
|
|
762
1165
|
selectedValue: null,
|
|
763
1166
|
capped: false,
|
|
764
1167
|
selectionMode: 'unresolved',
|
|
@@ -777,6 +1180,7 @@ function buildProviderResolution(provider, policy, role, orchestratorTier, prefe
|
|
|
777
1180
|
const dispatchArgs = dispatchValue && !isCrossHarness
|
|
778
1181
|
? adapter.compileToDispatchArgs(dispatchValue, role, {
|
|
779
1182
|
orchestratorTier,
|
|
1183
|
+
target: selection.target,
|
|
780
1184
|
})
|
|
781
1185
|
: null;
|
|
782
1186
|
let mode;
|
|
@@ -794,9 +1198,14 @@ function buildProviderResolution(provider, policy, role, orchestratorTier, prefe
|
|
|
794
1198
|
mode,
|
|
795
1199
|
mechanism: adapter.mechanism,
|
|
796
1200
|
dispatchArgs,
|
|
1201
|
+
modelAxis: modelAxis(selection, dispatchArgs),
|
|
1202
|
+
effortAxis: provider === 'codex'
|
|
1203
|
+
? codexEffortAxis(selection, dispatchArgs)
|
|
1204
|
+
: 'not-applicable',
|
|
797
1205
|
verifyOnDispatch: dispatchValue && !isCrossHarness
|
|
798
1206
|
? adapter.verifyOnDispatch(dispatchValue, {
|
|
799
1207
|
orchestratorTier,
|
|
1208
|
+
target: selection.target,
|
|
800
1209
|
})
|
|
801
1210
|
: false,
|
|
802
1211
|
cellSource: policy.cellSource,
|
|
@@ -851,9 +1260,11 @@ async function resolveDispatchCeiling(context, dependencies, options) {
|
|
|
851
1260
|
const providerDefaultEffort = provider === 'codex'
|
|
852
1261
|
? await resolveCodexProviderDefaultEffort(repoRoot, context, dependencies)
|
|
853
1262
|
: 'not-applicable';
|
|
1263
|
+
const requestedCandidate = normalizeRequestedCandidate(provider, role, options);
|
|
854
1264
|
const preferredValue = normalizePreferredValue(provider, options.preferred);
|
|
1265
|
+
const ceilingTier = normalizeCeilingTier(options.ceilingTier, role);
|
|
855
1266
|
const escalationLevel = normalizeEscalationLevel(options.escalationLevel);
|
|
856
|
-
const resolvedValue = await resolveCeilingValue(provider, resolvedConfig, projectPath, dependencies, escalationLevel);
|
|
1267
|
+
const resolvedValue = await resolveCeilingValue(provider, resolvedConfig, projectPath, dependencies, escalationLevel, role, preferredValue, requestedCandidate, ceilingTier);
|
|
857
1268
|
for (const warning of resolvedValue?.warnings ?? []) {
|
|
858
1269
|
context.logger.warn(warning);
|
|
859
1270
|
}
|
|
@@ -862,6 +1273,32 @@ async function resolveDispatchCeiling(context, dependencies, options) {
|
|
|
862
1273
|
[provider]: providerResolution,
|
|
863
1274
|
};
|
|
864
1275
|
if (resolvedValue) {
|
|
1276
|
+
const incompleteManagedPreflight = options.preflight === true &&
|
|
1277
|
+
resolvedValue.mode === 'managed' &&
|
|
1278
|
+
providerResolution.mode === 'advisory' &&
|
|
1279
|
+
providerResolution.selection.target?.crossHarness !== true &&
|
|
1280
|
+
providerResolution.selection.selectionMode !== 'no-review-target';
|
|
1281
|
+
if (incompleteManagedPreflight) {
|
|
1282
|
+
const shouldBlock = options.nonInteractive === true ||
|
|
1283
|
+
isNonInteractiveEnv(dependencies.processEnv) ||
|
|
1284
|
+
(!context.interactive && !context.json);
|
|
1285
|
+
const message = shouldBlock ? blockMessage(provider) : undefined;
|
|
1286
|
+
return {
|
|
1287
|
+
status: shouldBlock ? 'blocked' : 'unresolved',
|
|
1288
|
+
provider,
|
|
1289
|
+
value: resolvedValue.value,
|
|
1290
|
+
policyMode: resolvedValue.mode,
|
|
1291
|
+
policy: resolvedValue.policy,
|
|
1292
|
+
source: resolvedValue.source,
|
|
1293
|
+
preset: resolvedValue.preset,
|
|
1294
|
+
unresolved: true,
|
|
1295
|
+
projectPath,
|
|
1296
|
+
providerDefaultEffort,
|
|
1297
|
+
matrix: resolvedValue.matrix,
|
|
1298
|
+
providers,
|
|
1299
|
+
message,
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
865
1302
|
return {
|
|
866
1303
|
status: 'resolved',
|
|
867
1304
|
provider,
|
|
@@ -903,25 +1340,111 @@ async function resolveDispatchCeiling(context, dependencies, options) {
|
|
|
903
1340
|
* Never reads the preset label for dispatch — the preset is surfaced as
|
|
904
1341
|
* provenance only.
|
|
905
1342
|
*/
|
|
906
|
-
async function resolveCeilingValue(provider, resolvedConfig, projectPath, dependencies, escalationLevel) {
|
|
1343
|
+
async function resolveCeilingValue(provider, resolvedConfig, projectPath, dependencies, escalationLevel, role, preferredValue, requestedCandidate, invocationCeilingTier) {
|
|
907
1344
|
const configCeiling = readResolvedConfigCeiling(provider, resolvedConfig);
|
|
908
1345
|
const projectCeiling = await resolveProjectStateCeiling(provider, projectPath, dependencies);
|
|
909
|
-
|
|
1346
|
+
let baseCeiling = configCeiling ?? projectCeiling;
|
|
910
1347
|
if (!baseCeiling) {
|
|
911
1348
|
return null;
|
|
912
1349
|
}
|
|
913
|
-
if (
|
|
1350
|
+
if (invocationCeilingTier) {
|
|
1351
|
+
baseCeiling = {
|
|
1352
|
+
...baseCeiling,
|
|
1353
|
+
mode: 'managed',
|
|
1354
|
+
policy: invocationCeilingTier,
|
|
1355
|
+
value: compiledPolicyValueForProvider(provider, compileDispatchPolicyPreset(invocationCeilingTier)),
|
|
1356
|
+
source: 'invocation',
|
|
1357
|
+
preset: invocationCeilingTier,
|
|
1358
|
+
matrix: projectCeiling?.matrix ?? baseCeiling.matrix,
|
|
1359
|
+
cellSource: null,
|
|
1360
|
+
target: null,
|
|
1361
|
+
selectionBranch: 'prompt-persisted',
|
|
1362
|
+
ceilingTier: invocationCeilingTier,
|
|
1363
|
+
ceilingTarget: null,
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
if (baseCeiling.mode === 'inherit') {
|
|
1367
|
+
if (requestedCandidate) {
|
|
1368
|
+
throw new Error('Exact candidate selection requires a managed dispatch policy and configured candidate ladder.');
|
|
1369
|
+
}
|
|
1370
|
+
return baseCeiling;
|
|
1371
|
+
}
|
|
1372
|
+
if (requestedCandidate) {
|
|
1373
|
+
if (baseCeiling.policy === 'legacy-ceiling') {
|
|
1374
|
+
throw new Error('Exact candidate selection requires a configured candidate ladder; legacy scalar dispatch ceilings support --preferred only during migration.');
|
|
1375
|
+
}
|
|
1376
|
+
const ceilingTier = policyTier(baseCeiling.policy);
|
|
1377
|
+
const selected = resolveRequestedMatrixCandidate(provider, requestedCandidate, ceilingTier, resolvedConfig, projectCeiling?.matrix ?? null, escalationLevel);
|
|
1378
|
+
const ceilingCell = ceilingTier
|
|
1379
|
+
? resolveProviderMatrixCell(provider, ceilingTier, resolvedConfig, projectCeiling?.matrix ?? null, escalationLevel)
|
|
1380
|
+
: null;
|
|
1381
|
+
return {
|
|
1382
|
+
...baseCeiling,
|
|
1383
|
+
value: ceilingCell?.value ?? baseCeiling.value,
|
|
1384
|
+
cellSource: selected.resolution.cellSource,
|
|
1385
|
+
target: selected.resolution.target,
|
|
1386
|
+
selectionBranch: selected.resolution.selectionBranch,
|
|
1387
|
+
warnings: [
|
|
1388
|
+
...baseCeiling.warnings,
|
|
1389
|
+
...(ceilingCell?.warnings ?? []),
|
|
1390
|
+
...selected.resolution.warnings,
|
|
1391
|
+
],
|
|
1392
|
+
requestedCandidate,
|
|
1393
|
+
candidateTier: selected.candidateTier,
|
|
1394
|
+
ceilingTarget: ceilingCell?.target ?? null,
|
|
1395
|
+
...(ceilingTier ? { ceilingTier } : {}),
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
if (baseCeiling.policy === 'uncapped') {
|
|
1399
|
+
const preferredTier = uncappedPreferredTier(provider, role, preferredValue);
|
|
1400
|
+
if (preferredTier) {
|
|
1401
|
+
const matrixCell = resolveProviderMatrixCell(provider, preferredTier, resolvedConfig, projectCeiling?.matrix ?? null, escalationLevel);
|
|
1402
|
+
if (matrixCell) {
|
|
1403
|
+
return {
|
|
1404
|
+
...baseCeiling,
|
|
1405
|
+
cellSource: matrixCell.cellSource,
|
|
1406
|
+
target: matrixCell.target,
|
|
1407
|
+
selectionBranch: matrixCell.selectionBranch,
|
|
1408
|
+
warnings: [...baseCeiling.warnings, ...matrixCell.warnings],
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
914
1412
|
return baseCeiling;
|
|
915
1413
|
}
|
|
916
1414
|
const tier = policyTier(baseCeiling.policy);
|
|
917
1415
|
const matrixCell = resolveProviderMatrixCell(provider, tier, resolvedConfig, projectCeiling?.matrix ?? null, escalationLevel);
|
|
918
1416
|
if (matrixCell) {
|
|
1417
|
+
if (provider === 'codex' &&
|
|
1418
|
+
role === 'implementer' &&
|
|
1419
|
+
preferredValue !== null &&
|
|
1420
|
+
matrixCell.value !== null) {
|
|
1421
|
+
const order = providerValueOrder(provider);
|
|
1422
|
+
const preferredIndex = order?.indexOf(preferredValue) ?? -1;
|
|
1423
|
+
const ceilingIndex = order?.indexOf(matrixCell.value) ?? -1;
|
|
1424
|
+
if (order && preferredIndex >= 0 && ceilingIndex >= 0) {
|
|
1425
|
+
const selectedValue = order[Math.min(preferredIndex, ceilingIndex)];
|
|
1426
|
+
if (selectedValue !== matrixCell.value) {
|
|
1427
|
+
const selectedTarget = matrixCell.target?.harness === 'codex'
|
|
1428
|
+
? { ...matrixCell.target, effort: selectedValue }
|
|
1429
|
+
: matrixCell.target;
|
|
1430
|
+
return {
|
|
1431
|
+
...baseCeiling,
|
|
1432
|
+
value: matrixCell.value,
|
|
1433
|
+
cellSource: matrixCell.cellSource,
|
|
1434
|
+
target: selectedTarget,
|
|
1435
|
+
selectionBranch: matrixCell.selectionBranch,
|
|
1436
|
+
warnings: [...baseCeiling.warnings, ...matrixCell.warnings],
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
919
1441
|
return {
|
|
920
1442
|
...baseCeiling,
|
|
921
1443
|
value: matrixCell.value,
|
|
922
1444
|
cellSource: matrixCell.cellSource,
|
|
923
1445
|
target: matrixCell.target,
|
|
924
1446
|
selectionBranch: matrixCell.selectionBranch,
|
|
1447
|
+
warnings: [...baseCeiling.warnings, ...matrixCell.warnings],
|
|
925
1448
|
};
|
|
926
1449
|
}
|
|
927
1450
|
if (baseCeiling.policy !== 'legacy-ceiling' &&
|
|
@@ -950,6 +1473,15 @@ function writeHumanResolution(context, resolution) {
|
|
|
950
1473
|
if (providerResolution) {
|
|
951
1474
|
context.logger.info(`Mode: ${providerResolution.mode} (${providerResolution.mechanism})`);
|
|
952
1475
|
context.logger.info(`Selection: ${providerResolution.selection.selectionMode}`);
|
|
1476
|
+
if (providerResolution.selection.requestedCandidate) {
|
|
1477
|
+
const requested = providerResolution.selection.requestedCandidate;
|
|
1478
|
+
context.logger.info(`Requested candidate: model=${requested.model}${requested.effort ? ` effort=${requested.effort}` : ''}`);
|
|
1479
|
+
context.logger.info(`Candidate tier: ${providerResolution.selection.candidateTier ?? 'none'}; ceiling tier: ${providerResolution.selection.ceilingTier ?? 'uncapped'}`);
|
|
1480
|
+
const ceilingTarget = providerResolution.selection.ceilingTarget;
|
|
1481
|
+
if (ceilingTarget) {
|
|
1482
|
+
context.logger.info(`Effective ceiling target: model=${ceilingTarget.model ?? 'none'}${ceilingTarget.effort ? ` effort=${ceilingTarget.effort}` : ''}`);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
953
1485
|
if (providerResolution.target) {
|
|
954
1486
|
const details = [
|
|
955
1487
|
`harness=${providerResolution.target.harness}`,
|
|
@@ -1023,18 +1555,49 @@ async function runDispatchCeilingResolve(context, dependencies, options) {
|
|
|
1023
1555
|
process.exitCode = 1;
|
|
1024
1556
|
}
|
|
1025
1557
|
}
|
|
1558
|
+
async function runDispatchCeilingChoices(context, options) {
|
|
1559
|
+
const choices = getDispatchPolicyChoices();
|
|
1560
|
+
const format = (options.format ?? 'markdown').trim().toLowerCase();
|
|
1561
|
+
if (context.json || options.json) {
|
|
1562
|
+
context.logger.json({ status: 'ok', choices });
|
|
1563
|
+
process.exitCode = 0;
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
if (format !== 'markdown') {
|
|
1567
|
+
context.logger.error(`Invalid choices format "${options.format}". Valid formats: markdown.`);
|
|
1568
|
+
process.exitCode = 1;
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
context.logger.info(renderDispatchPolicyChoicesMarkdown(choices));
|
|
1572
|
+
process.exitCode = 0;
|
|
1573
|
+
}
|
|
1026
1574
|
export function createProjectDispatchCeilingCommand(overrides = {}) {
|
|
1027
1575
|
const dependencies = {
|
|
1028
1576
|
...DEFAULT_DEPENDENCIES,
|
|
1029
1577
|
...overrides,
|
|
1030
1578
|
};
|
|
1031
1579
|
const command = new Command('dispatch-ceiling').description('Resolve OAT project dispatch ceiling metadata');
|
|
1580
|
+
command.addCommand(new Command('choices')
|
|
1581
|
+
.description('Print canonical dispatch policy choices')
|
|
1582
|
+
.option('--format <format>', 'Output format: markdown', 'markdown')
|
|
1583
|
+
.option('--json', 'Output machine-readable JSON')
|
|
1584
|
+
.action(async (options, cmd) => {
|
|
1585
|
+
const globalOptions = readGlobalOptions(cmd);
|
|
1586
|
+
const context = dependencies.buildCommandContext({
|
|
1587
|
+
...globalOptions,
|
|
1588
|
+
json: globalOptions.json === true || options.json === true,
|
|
1589
|
+
});
|
|
1590
|
+
await runDispatchCeilingChoices(context, options);
|
|
1591
|
+
}));
|
|
1032
1592
|
command.addCommand(new Command('resolve')
|
|
1033
1593
|
.description('Resolve dispatch policy for a provider')
|
|
1034
1594
|
.requiredOption('--provider <provider>', 'Provider name: codex, claude, or cursor are enforced; unregistered providers resolve as unsupported advisory')
|
|
1035
|
-
.
|
|
1595
|
+
.addOption(new Option('--role <role>', 'Dispatch role for variant compilation: implementer (default) or reviewer').choices(['implementer', 'reviewer']))
|
|
1036
1596
|
.option('--orchestrator-tier <tier>', 'Orchestrator tier, used to flag verify-on-upgrade for above-orchestrator requests')
|
|
1037
|
-
.option('--preferred <value>', '
|
|
1597
|
+
.option('--preferred <value>', 'Legacy preferred implementer/fix value before applying the resolved policy')
|
|
1598
|
+
.addOption(new Option('--ceiling-tier <tier>', 'Invocation-only named maximum tier; never persists configuration or project state').choices([...VALID_DISPATCH_MATRIX_TIERS]))
|
|
1599
|
+
.option('--candidate-model <model>', 'Exact configured implementer candidate model beneath the named ceiling')
|
|
1600
|
+
.option('--candidate-effort <effort>', 'Exact configured Codex candidate effort paired with --candidate-model')
|
|
1038
1601
|
.option('--escalation-level <level>', 'Ordered route entry to select; 0 is the floor and higher values advance through escalation targets')
|
|
1039
1602
|
.option('--project-path <path>', 'Read project-state policy from an explicit project path')
|
|
1040
1603
|
.option('--preflight', 'Treat unresolved non-interactive resolution as an implementation block')
|