@open-agent-toolkit/cli 0.1.42 → 0.1.45
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 +11 -7
- package/assets/agents/oat-reviewer.md +6 -4
- package/assets/config/dispatch-matrix-recommendation.json +23 -0
- package/assets/docs/cli-utilities/configuration.md +95 -40
- package/assets/docs/cli-utilities/workflow-gates.md +79 -16
- package/assets/docs/contributing/code.md +10 -4
- package/assets/docs/contributing/hooks-and-safety.md +23 -0
- package/assets/docs/reference/oat-directory-structure.md +26 -24
- package/assets/docs/workflows/projects/dispatch-ceiling.md +193 -80
- package/assets/docs/workflows/projects/implementation-execution.md +68 -45
- package/assets/docs/workflows/projects/index.md +2 -2
- package/assets/docs/workflows/projects/lifecycle.md +17 -2
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-project-implement/SKILL.md +232 -100
- package/assets/skills/oat-project-plan/SKILL.md +80 -30
- package/assets/skills/oat-project-plan-writing/SKILL.md +10 -10
- package/assets/skills/oat-project-quick-start/SKILL.md +80 -30
- package/assets/templates/plan.md +4 -4
- package/assets/templates/state.md +12 -3
- package/dist/commands/config/index.d.ts +6 -0
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +432 -11
- package/dist/commands/doctor/index.d.ts +6 -1
- package/dist/commands/doctor/index.d.ts.map +1 -1
- package/dist/commands/doctor/index.js +138 -4
- package/dist/commands/gate/index.d.ts +39 -2
- package/dist/commands/gate/index.d.ts.map +1 -1
- package/dist/commands/gate/index.js +343 -30
- package/dist/commands/internal/cursor-current-target.d.ts +37 -0
- package/dist/commands/internal/cursor-current-target.d.ts.map +1 -0
- package/dist/commands/internal/cursor-current-target.js +228 -0
- package/dist/commands/internal/index.d.ts.map +1 -1
- package/dist/commands/internal/index.js +2 -0
- package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
- package/dist/commands/project/dispatch-ceiling/index.js +646 -56
- package/dist/config/dispatch-ceiling-preset.d.ts +37 -1
- package/dist/config/dispatch-ceiling-preset.d.ts.map +1 -1
- package/dist/config/dispatch-ceiling-preset.js +20 -0
- package/dist/config/oat-config.d.ts +25 -6
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +123 -11
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +16 -0
- package/dist/manifest/manifest.types.d.ts +12 -12
- package/dist/providers/ceiling/registry.d.ts.map +1 -1
- package/dist/providers/ceiling/registry.js +22 -1
- package/dist/providers/codex/codec/sync-extension.js +1 -1
- package/dist/providers/identity/availability.d.ts +22 -0
- package/dist/providers/identity/availability.d.ts.map +1 -0
- package/dist/providers/identity/availability.js +119 -0
- package/dist/providers/identity/family.d.ts +13 -0
- package/dist/providers/identity/family.d.ts.map +1 -0
- package/dist/providers/identity/family.js +32 -0
- package/dist/providers/identity/provenance.d.ts +21 -0
- package/dist/providers/identity/provenance.d.ts.map +1 -0
- package/dist/providers/identity/provenance.js +65 -0
- package/dist/providers/identity/stamp.d.ts +35 -0
- package/dist/providers/identity/stamp.d.ts.map +1 -0
- package/dist/providers/identity/stamp.js +171 -0
- package/package.json +2 -2
|
@@ -3,23 +3,20 @@ import { isAbsolute, join } from 'node:path';
|
|
|
3
3
|
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
|
-
import {
|
|
6
|
+
import { compileDispatchPolicyPreset, } from '../../../config/dispatch-ceiling-preset.js';
|
|
7
|
+
import { resolveActiveProject, VALID_CLAUDE_DISPATCH_CEILINGS, VALID_CODEX_DISPATCH_CEILINGS, VALID_DISPATCH_MATRIX_TIERS, VALID_MANAGED_DISPATCH_POLICIES, } from '../../../config/oat-config.js';
|
|
7
8
|
import { resolveEffectiveConfig, } from '../../../config/resolve.js';
|
|
8
9
|
import { resolveProjectRoot } from '../../../fs/paths.js';
|
|
9
10
|
import TOML from '@iarna/toml';
|
|
10
11
|
import { getCeilingAdapter, } from '../../../providers/ceiling/registry.js';
|
|
12
|
+
import { classifyModelFamily, } from '../../../providers/identity/family.js';
|
|
11
13
|
import { Command } from 'commander';
|
|
12
14
|
import YAML from 'yaml';
|
|
13
15
|
const CODEX_VALUES = [
|
|
14
|
-
|
|
15
|
-
'medium',
|
|
16
|
-
'high',
|
|
17
|
-
'xhigh',
|
|
16
|
+
...VALID_CODEX_DISPATCH_CEILINGS,
|
|
18
17
|
];
|
|
19
18
|
const CLAUDE_VALUES = [
|
|
20
|
-
|
|
21
|
-
'sonnet',
|
|
22
|
-
'opus',
|
|
19
|
+
...VALID_CLAUDE_DISPATCH_CEILINGS,
|
|
23
20
|
];
|
|
24
21
|
const DEFAULT_DEPENDENCIES = {
|
|
25
22
|
buildCommandContext,
|
|
@@ -58,7 +55,7 @@ function isValidProviderValue(provider, value) {
|
|
|
58
55
|
return (typeof value === 'string' &&
|
|
59
56
|
CLAUDE_VALUES.includes(value));
|
|
60
57
|
}
|
|
61
|
-
return
|
|
58
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
62
59
|
}
|
|
63
60
|
function configSourceToCeilingSource(source) {
|
|
64
61
|
if (source === 'local') {
|
|
@@ -104,7 +101,244 @@ function providerLabel(provider) {
|
|
|
104
101
|
function resolveTargetProjectPath(repoRoot, projectPath) {
|
|
105
102
|
return isAbsolute(projectPath) ? projectPath : join(repoRoot, projectPath);
|
|
106
103
|
}
|
|
107
|
-
function
|
|
104
|
+
function isValidManagedPolicy(value) {
|
|
105
|
+
return (typeof value === 'string' &&
|
|
106
|
+
VALID_MANAGED_DISPATCH_POLICIES.includes(value));
|
|
107
|
+
}
|
|
108
|
+
function validManagedPolicyList() {
|
|
109
|
+
return VALID_MANAGED_DISPATCH_POLICIES.join(', ');
|
|
110
|
+
}
|
|
111
|
+
function validProviderValueList(provider) {
|
|
112
|
+
return providerValueOrder(provider)?.join(', ') ?? 'none';
|
|
113
|
+
}
|
|
114
|
+
function compiledPolicyValueForProvider(provider, compiled) {
|
|
115
|
+
if (!('providers' in compiled)) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
const value = compiled.providers[provider];
|
|
119
|
+
return isValidProviderValue(provider, value) ? value : null;
|
|
120
|
+
}
|
|
121
|
+
function invalidProjectPolicyMessage(value) {
|
|
122
|
+
const actual = typeof value === 'string' ? value : String(value);
|
|
123
|
+
return `Invalid project dispatch policy "${actual}". Valid managed policies: ${validManagedPolicyList()}. Use mode "inherit" for host defaults.`;
|
|
124
|
+
}
|
|
125
|
+
function normalizeProjectMatrixBareValue(provider, value) {
|
|
126
|
+
if (typeof value !== 'string') {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
const trimmed = value.trim();
|
|
130
|
+
if (!trimmed) {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
const order = providerValueOrder(provider);
|
|
134
|
+
if (order && !isValidProviderValue(provider, trimmed)) {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
return trimmed;
|
|
138
|
+
}
|
|
139
|
+
function normalizeProjectMatrixRouteTarget(value) {
|
|
140
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
const record = value;
|
|
144
|
+
const target = {};
|
|
145
|
+
for (const key of ['harness', 'model', 'effort']) {
|
|
146
|
+
const rawValue = record[key];
|
|
147
|
+
if (typeof rawValue === 'string' && rawValue.trim()) {
|
|
148
|
+
target[key] = rawValue.trim();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return Object.keys(target).length > 0 ? target : undefined;
|
|
152
|
+
}
|
|
153
|
+
function normalizeProjectMatrixCell(provider, value) {
|
|
154
|
+
const bareValue = normalizeProjectMatrixBareValue(provider, value);
|
|
155
|
+
if (bareValue !== undefined) {
|
|
156
|
+
return bareValue;
|
|
157
|
+
}
|
|
158
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
const route = [];
|
|
162
|
+
for (const entry of value) {
|
|
163
|
+
const bareEntry = normalizeProjectMatrixBareValue(provider, entry);
|
|
164
|
+
if (bareEntry !== undefined) {
|
|
165
|
+
route.push(bareEntry);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const target = normalizeProjectMatrixRouteTarget(entry);
|
|
169
|
+
if (target !== undefined) {
|
|
170
|
+
route.push(target);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return route.length > 0 ? route : undefined;
|
|
174
|
+
}
|
|
175
|
+
function normalizeProjectMatrixProviderValue(provider, value) {
|
|
176
|
+
const bareValue = normalizeProjectMatrixBareValue(provider, value);
|
|
177
|
+
if (bareValue !== undefined) {
|
|
178
|
+
return bareValue;
|
|
179
|
+
}
|
|
180
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
const tierMap = {};
|
|
184
|
+
for (const [tier, rawCell] of Object.entries(value)) {
|
|
185
|
+
if (!VALID_DISPATCH_MATRIX_TIERS.includes(tier)) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const normalized = normalizeProjectMatrixCell(provider, rawCell);
|
|
189
|
+
if (normalized !== undefined) {
|
|
190
|
+
tierMap[tier] = normalized;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return Object.keys(tierMap).length > 0 ? tierMap : undefined;
|
|
194
|
+
}
|
|
195
|
+
function readProjectDispatchMatrix(value) {
|
|
196
|
+
if (value === undefined || value === null) {
|
|
197
|
+
return { matrix: null, warnings: [] };
|
|
198
|
+
}
|
|
199
|
+
if (typeof value !== 'object' || Array.isArray(value)) {
|
|
200
|
+
return {
|
|
201
|
+
matrix: null,
|
|
202
|
+
warnings: [
|
|
203
|
+
'Ignoring malformed oat_dispatch_policy.matrix in project state.',
|
|
204
|
+
],
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const matrix = {};
|
|
208
|
+
for (const [provider, rawProviderValue] of Object.entries(value)) {
|
|
209
|
+
if (!provider.trim()) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
const normalized = normalizeProjectMatrixProviderValue(provider, rawProviderValue);
|
|
213
|
+
if (normalized !== undefined) {
|
|
214
|
+
matrix[provider] = normalized;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (Object.keys(matrix).length === 0) {
|
|
218
|
+
return {
|
|
219
|
+
matrix: null,
|
|
220
|
+
warnings: [
|
|
221
|
+
'Ignoring malformed oat_dispatch_policy.matrix in project state.',
|
|
222
|
+
],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
return { matrix, warnings: [] };
|
|
226
|
+
}
|
|
227
|
+
function policyTier(policy) {
|
|
228
|
+
return typeof policy === 'string' &&
|
|
229
|
+
VALID_DISPATCH_MATRIX_TIERS.includes(policy)
|
|
230
|
+
? policy
|
|
231
|
+
: null;
|
|
232
|
+
}
|
|
233
|
+
function dispatchValueFromRouteTarget(target) {
|
|
234
|
+
const adapter = getCeilingAdapter(target.harness);
|
|
235
|
+
if (adapter.mechanism === 'pinned-variant') {
|
|
236
|
+
return target.effort ?? null;
|
|
237
|
+
}
|
|
238
|
+
if (adapter.mechanism === 'model-arg') {
|
|
239
|
+
return target.model ?? null;
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
function routeTargetFromBareValue(provider, value, routeIndex, routeLength) {
|
|
244
|
+
const adapter = getCeilingAdapter(provider);
|
|
245
|
+
const target = {
|
|
246
|
+
harness: provider,
|
|
247
|
+
crossHarness: false,
|
|
248
|
+
routeIndex,
|
|
249
|
+
routeLength,
|
|
250
|
+
};
|
|
251
|
+
if (adapter.mechanism === 'pinned-variant') {
|
|
252
|
+
target.effort = value;
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
target.model = value;
|
|
256
|
+
}
|
|
257
|
+
return target;
|
|
258
|
+
}
|
|
259
|
+
function routeTargetFromObject(provider, target, routeIndex, routeLength) {
|
|
260
|
+
const harness = target.harness ?? provider;
|
|
261
|
+
return {
|
|
262
|
+
harness,
|
|
263
|
+
...(target.model ? { model: target.model } : {}),
|
|
264
|
+
...(target.effort ? { effort: target.effort } : {}),
|
|
265
|
+
crossHarness: harness !== provider,
|
|
266
|
+
routeIndex,
|
|
267
|
+
routeLength,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function resolveRouteMatrixCell(provider, route, escalationLevel, cellSource) {
|
|
271
|
+
const routeIndex = Math.min(escalationLevel, route.length - 1);
|
|
272
|
+
const entry = route[routeIndex];
|
|
273
|
+
if (entry === undefined) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
const target = typeof entry === 'string'
|
|
277
|
+
? routeTargetFromBareValue(provider, entry, routeIndex, route.length)
|
|
278
|
+
: routeTargetFromObject(provider, entry, routeIndex, route.length);
|
|
279
|
+
const value = typeof entry === 'string' ? entry : dispatchValueFromRouteTarget(target);
|
|
280
|
+
return {
|
|
281
|
+
value,
|
|
282
|
+
cellSource,
|
|
283
|
+
target,
|
|
284
|
+
selectionBranch: routeIndex > 0 ? 'escalation-target' : 'matrix-pinned',
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function resolveProviderCellFromValue(provider, providerValue, tier, cellSource, escalationLevel) {
|
|
288
|
+
if (providerValue === undefined) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
if (typeof providerValue === 'string') {
|
|
292
|
+
return {
|
|
293
|
+
value: providerValue,
|
|
294
|
+
cellSource,
|
|
295
|
+
target: null,
|
|
296
|
+
selectionBranch: 'prompt-persisted',
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
if (tier === null) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
const cell = providerValue[tier];
|
|
303
|
+
if (cell === undefined) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
if (typeof cell === 'string') {
|
|
307
|
+
return {
|
|
308
|
+
value: cell,
|
|
309
|
+
cellSource,
|
|
310
|
+
target: null,
|
|
311
|
+
selectionBranch: 'matrix-pinned',
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
return resolveRouteMatrixCell(provider, cell, escalationLevel, cellSource);
|
|
315
|
+
}
|
|
316
|
+
function resolveProviderMatrixCell(provider, tier, resolvedConfig, projectMatrix, escalationLevel) {
|
|
317
|
+
let selected = null;
|
|
318
|
+
const layers = [
|
|
319
|
+
{
|
|
320
|
+
source: 'user-config',
|
|
321
|
+
providers: resolvedConfig.user.workflow?.dispatchCeiling?.providers,
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
source: 'repo-config',
|
|
325
|
+
providers: resolvedConfig.shared.workflow?.dispatchCeiling?.providers,
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
source: 'local-config',
|
|
329
|
+
providers: resolvedConfig.local.workflow?.dispatchCeiling?.providers,
|
|
330
|
+
},
|
|
331
|
+
{ source: 'project-state', providers: projectMatrix },
|
|
332
|
+
];
|
|
333
|
+
for (const layer of layers) {
|
|
334
|
+
const resolved = resolveProviderCellFromValue(provider, layer.providers?.[provider], tier, layer.source, escalationLevel);
|
|
335
|
+
if (resolved) {
|
|
336
|
+
selected = resolved;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return selected;
|
|
340
|
+
}
|
|
341
|
+
function readProjectDispatchPolicy(provider, content) {
|
|
108
342
|
const frontmatter = getFrontmatterBlock(content);
|
|
109
343
|
if (!frontmatter) {
|
|
110
344
|
return null;
|
|
@@ -113,6 +347,78 @@ function readProjectDispatchCeiling(provider, content) {
|
|
|
113
347
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
114
348
|
return null;
|
|
115
349
|
}
|
|
350
|
+
const policy = parsed['oat_dispatch_policy'];
|
|
351
|
+
if (!policy || typeof policy !== 'object' || Array.isArray(policy)) {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
const policyRecord = policy;
|
|
355
|
+
const parsedMatrix = readProjectDispatchMatrix(policyRecord['matrix']);
|
|
356
|
+
const mode = policyRecord['mode'];
|
|
357
|
+
if (mode === 'inherit') {
|
|
358
|
+
return {
|
|
359
|
+
mode,
|
|
360
|
+
policy: null,
|
|
361
|
+
value: null,
|
|
362
|
+
source: 'project-state',
|
|
363
|
+
preset: null,
|
|
364
|
+
matrix: parsedMatrix.matrix,
|
|
365
|
+
cellSource: null,
|
|
366
|
+
target: null,
|
|
367
|
+
selectionBranch: 'inherit',
|
|
368
|
+
warnings: parsedMatrix.warnings,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
if (mode !== 'managed') {
|
|
372
|
+
const actual = typeof mode === 'string' ? mode : String(mode);
|
|
373
|
+
throw new Error(`Invalid project dispatch policy mode "${actual}". Valid modes: managed, inherit.`);
|
|
374
|
+
}
|
|
375
|
+
const policyValue = policyRecord['policy'];
|
|
376
|
+
if (!isValidManagedPolicy(policyValue)) {
|
|
377
|
+
throw new Error(invalidProjectPolicyMessage(policyValue));
|
|
378
|
+
}
|
|
379
|
+
const compiled = compileDispatchPolicyPreset(policyValue);
|
|
380
|
+
if (policyValue === 'uncapped') {
|
|
381
|
+
return {
|
|
382
|
+
mode,
|
|
383
|
+
policy: policyValue,
|
|
384
|
+
value: null,
|
|
385
|
+
source: 'project-state',
|
|
386
|
+
preset: policyValue,
|
|
387
|
+
matrix: parsedMatrix.matrix,
|
|
388
|
+
cellSource: null,
|
|
389
|
+
target: null,
|
|
390
|
+
selectionBranch: 'prompt-persisted',
|
|
391
|
+
warnings: parsedMatrix.warnings,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
const providers = policyRecord['providers'];
|
|
395
|
+
const explicitValue = providers && typeof providers === 'object' && !Array.isArray(providers)
|
|
396
|
+
? providers[provider]
|
|
397
|
+
: null;
|
|
398
|
+
const providerOrder = providerValueOrder(provider);
|
|
399
|
+
if (providerOrder &&
|
|
400
|
+
explicitValue !== null &&
|
|
401
|
+
explicitValue !== undefined &&
|
|
402
|
+
!isValidProviderValue(provider, explicitValue)) {
|
|
403
|
+
throw new Error(`Invalid project dispatch policy provider value "${String(explicitValue)}" for ${provider}. Valid values: ${validProviderValueList(provider)}.`);
|
|
404
|
+
}
|
|
405
|
+
const value = isValidProviderValue(provider, explicitValue)
|
|
406
|
+
? explicitValue
|
|
407
|
+
: compiledPolicyValueForProvider(provider, compiled);
|
|
408
|
+
return {
|
|
409
|
+
mode,
|
|
410
|
+
policy: policyValue,
|
|
411
|
+
value,
|
|
412
|
+
source: 'project-state',
|
|
413
|
+
preset: policyValue,
|
|
414
|
+
matrix: parsedMatrix.matrix,
|
|
415
|
+
cellSource: 'project-state',
|
|
416
|
+
target: null,
|
|
417
|
+
selectionBranch: 'prompt-persisted',
|
|
418
|
+
warnings: parsedMatrix.warnings,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
function readLegacyProjectDispatchCeiling(provider, parsed) {
|
|
116
422
|
const ceiling = parsed['oat_dispatch_ceiling'];
|
|
117
423
|
if (!ceiling || typeof ceiling !== 'object' || Array.isArray(ceiling)) {
|
|
118
424
|
return null;
|
|
@@ -130,21 +436,42 @@ function readProjectDispatchCeiling(provider, content) {
|
|
|
130
436
|
}
|
|
131
437
|
const presetValue = ceilingRecord['preset'];
|
|
132
438
|
return {
|
|
439
|
+
mode: 'managed',
|
|
440
|
+
policy: 'legacy-ceiling',
|
|
133
441
|
value,
|
|
442
|
+
source: 'project-state',
|
|
134
443
|
preset: typeof presetValue === 'string' ? presetValue : null,
|
|
444
|
+
matrix: null,
|
|
445
|
+
cellSource: 'project-state',
|
|
446
|
+
target: null,
|
|
447
|
+
selectionBranch: 'prompt-persisted',
|
|
448
|
+
warnings: [],
|
|
135
449
|
};
|
|
136
450
|
}
|
|
451
|
+
function readProjectDispatchCeiling(provider, content) {
|
|
452
|
+
const frontmatter = getFrontmatterBlock(content);
|
|
453
|
+
if (!frontmatter) {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
const parsed = YAML.parse(frontmatter);
|
|
457
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
return (readProjectDispatchPolicy(provider, content) ??
|
|
461
|
+
readLegacyProjectDispatchCeiling(provider, parsed));
|
|
462
|
+
}
|
|
137
463
|
async function resolveProjectStateCeiling(provider, projectPath, dependencies) {
|
|
138
464
|
if (!projectPath) {
|
|
139
465
|
return null;
|
|
140
466
|
}
|
|
467
|
+
let content;
|
|
141
468
|
try {
|
|
142
|
-
|
|
143
|
-
return readProjectDispatchCeiling(provider, content);
|
|
469
|
+
content = await dependencies.readFile(join(projectPath, 'state.md'));
|
|
144
470
|
}
|
|
145
471
|
catch {
|
|
146
472
|
return null;
|
|
147
473
|
}
|
|
474
|
+
return readProjectDispatchCeiling(provider, content);
|
|
148
475
|
}
|
|
149
476
|
async function resolveProjectPath(repoRoot, dependencies, options) {
|
|
150
477
|
if (options.projectPath) {
|
|
@@ -156,20 +483,135 @@ async function resolveProjectPath(repoRoot, dependencies, options) {
|
|
|
156
483
|
}
|
|
157
484
|
return join(repoRoot, activeProject.path);
|
|
158
485
|
}
|
|
159
|
-
function
|
|
486
|
+
function isConfigCandidateSource(source) {
|
|
487
|
+
return source !== undefined && source !== 'default';
|
|
488
|
+
}
|
|
489
|
+
function configSourcePrecedence(source) {
|
|
490
|
+
switch (source) {
|
|
491
|
+
case 'env':
|
|
492
|
+
return 4;
|
|
493
|
+
case 'local':
|
|
494
|
+
return 3;
|
|
495
|
+
case 'shared':
|
|
496
|
+
return 2;
|
|
497
|
+
case 'user':
|
|
498
|
+
return 1;
|
|
499
|
+
case 'default':
|
|
500
|
+
return 0;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
function lowerPrecedenceConfigSource(left, right) {
|
|
504
|
+
return configSourcePrecedence(left) <= configSourcePrecedence(right)
|
|
505
|
+
? left
|
|
506
|
+
: right;
|
|
507
|
+
}
|
|
508
|
+
function stripConfigCandidateSource(candidate) {
|
|
509
|
+
return {
|
|
510
|
+
mode: candidate.mode,
|
|
511
|
+
policy: candidate.policy,
|
|
512
|
+
value: candidate.value,
|
|
513
|
+
source: candidate.source,
|
|
514
|
+
preset: candidate.preset,
|
|
515
|
+
matrix: candidate.matrix,
|
|
516
|
+
cellSource: candidate.cellSource,
|
|
517
|
+
target: candidate.target,
|
|
518
|
+
selectionBranch: candidate.selectionBranch,
|
|
519
|
+
warnings: candidate.warnings,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
function readResolvedConfigPolicyCandidate(provider, resolvedConfig) {
|
|
523
|
+
const modeEntry = resolvedConfig.resolved['workflow.dispatchPolicy.mode'];
|
|
524
|
+
const policyEntry = resolvedConfig.resolved['workflow.dispatchPolicy.policy'];
|
|
525
|
+
if (modeEntry?.value === 'inherit' &&
|
|
526
|
+
isConfigCandidateSource(modeEntry.source)) {
|
|
527
|
+
const source = configSourceToCeilingSource(modeEntry.source);
|
|
528
|
+
if (source === null) {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
return {
|
|
532
|
+
mode: 'inherit',
|
|
533
|
+
policy: null,
|
|
534
|
+
value: null,
|
|
535
|
+
source,
|
|
536
|
+
preset: null,
|
|
537
|
+
matrix: null,
|
|
538
|
+
cellSource: null,
|
|
539
|
+
target: null,
|
|
540
|
+
selectionBranch: 'inherit',
|
|
541
|
+
warnings: [],
|
|
542
|
+
configSource: modeEntry.source,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
if (modeEntry?.value === 'managed' &&
|
|
546
|
+
isValidManagedPolicy(policyEntry?.value) &&
|
|
547
|
+
isConfigCandidateSource(modeEntry.source) &&
|
|
548
|
+
isConfigCandidateSource(policyEntry?.source)) {
|
|
549
|
+
const policy = policyEntry.value;
|
|
550
|
+
const compiled = compileDispatchPolicyPreset(policy);
|
|
551
|
+
const configSource = lowerPrecedenceConfigSource(modeEntry.source, policyEntry.source);
|
|
552
|
+
const source = configSourceToCeilingSource(configSource);
|
|
553
|
+
if (source === null) {
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
mode: 'managed',
|
|
558
|
+
policy,
|
|
559
|
+
value: compiledPolicyValueForProvider(provider, compiled),
|
|
560
|
+
source,
|
|
561
|
+
preset: policy,
|
|
562
|
+
matrix: null,
|
|
563
|
+
cellSource: null,
|
|
564
|
+
target: null,
|
|
565
|
+
selectionBranch: 'prompt-persisted',
|
|
566
|
+
warnings: [],
|
|
567
|
+
configSource,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
function readResolvedLegacyConfigCeilingCandidate(provider, resolvedConfig) {
|
|
160
573
|
// Read the concrete per-provider value from the nested key. The flat
|
|
161
574
|
// `workflow.dispatchCeiling.<provider>` shape was removed in p01; never read
|
|
162
575
|
// the preset label for dispatch.
|
|
163
576
|
const entry = resolvedConfig.resolved[`workflow.dispatchCeiling.providers.${provider}`];
|
|
164
|
-
|
|
165
|
-
|
|
577
|
+
if (!entry ||
|
|
578
|
+
!isConfigCandidateSource(entry.source) ||
|
|
579
|
+
!isValidProviderValue(provider, entry.value)) {
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
const source = configSourceToCeilingSource(entry.source);
|
|
583
|
+
if (source === null) {
|
|
166
584
|
return null;
|
|
167
585
|
}
|
|
168
586
|
return {
|
|
587
|
+
mode: 'managed',
|
|
588
|
+
policy: 'legacy-ceiling',
|
|
169
589
|
value: entry.value,
|
|
170
590
|
source,
|
|
591
|
+
preset: null,
|
|
592
|
+
matrix: null,
|
|
593
|
+
cellSource: source,
|
|
594
|
+
target: null,
|
|
595
|
+
selectionBranch: 'prompt-persisted',
|
|
596
|
+
warnings: [],
|
|
597
|
+
configSource: entry.source,
|
|
171
598
|
};
|
|
172
599
|
}
|
|
600
|
+
function readResolvedConfigCeiling(provider, resolvedConfig) {
|
|
601
|
+
const policyCandidate = readResolvedConfigPolicyCandidate(provider, resolvedConfig);
|
|
602
|
+
const legacyCandidate = readResolvedLegacyConfigCeilingCandidate(provider, resolvedConfig);
|
|
603
|
+
if (!policyCandidate) {
|
|
604
|
+
return legacyCandidate ? stripConfigCandidateSource(legacyCandidate) : null;
|
|
605
|
+
}
|
|
606
|
+
if (!legacyCandidate) {
|
|
607
|
+
return stripConfigCandidateSource(policyCandidate);
|
|
608
|
+
}
|
|
609
|
+
const winner = configSourcePrecedence(policyCandidate.configSource) >=
|
|
610
|
+
configSourcePrecedence(legacyCandidate.configSource)
|
|
611
|
+
? policyCandidate
|
|
612
|
+
: legacyCandidate;
|
|
613
|
+
return stripConfigCandidateSource(winner);
|
|
614
|
+
}
|
|
173
615
|
function normalizeRole(value) {
|
|
174
616
|
return value === 'reviewer' ? 'reviewer' : 'implementer';
|
|
175
617
|
}
|
|
@@ -200,32 +642,102 @@ function normalizePreferredValue(provider, value) {
|
|
|
200
642
|
}
|
|
201
643
|
return normalized;
|
|
202
644
|
}
|
|
203
|
-
function
|
|
645
|
+
function normalizeEscalationLevel(value) {
|
|
646
|
+
if (value === undefined) {
|
|
647
|
+
return 0;
|
|
648
|
+
}
|
|
649
|
+
const normalized = value.trim();
|
|
650
|
+
if (!/^\d+$/.test(normalized)) {
|
|
651
|
+
throw new Error(`Invalid escalation level "${value}". Use a non-negative integer.`);
|
|
652
|
+
}
|
|
653
|
+
return Number.parseInt(normalized, 10);
|
|
654
|
+
}
|
|
655
|
+
function selectionFamily(provider, value, target) {
|
|
656
|
+
return value
|
|
657
|
+
? classifyModelFamily({ value, providerId: target?.harness ?? provider })
|
|
658
|
+
: 'unknown';
|
|
659
|
+
}
|
|
660
|
+
function selectDispatchValue(provider, role, policy, preferredValue) {
|
|
661
|
+
const baseSelection = {
|
|
662
|
+
role,
|
|
663
|
+
policyMode: policy.mode,
|
|
664
|
+
policy: policy.policy,
|
|
665
|
+
cellSource: policy.cellSource,
|
|
666
|
+
target: policy.target,
|
|
667
|
+
};
|
|
668
|
+
if (policy.mode === 'inherit') {
|
|
669
|
+
return {
|
|
670
|
+
...baseSelection,
|
|
671
|
+
preferredValue: null,
|
|
672
|
+
selectedValue: null,
|
|
673
|
+
capped: false,
|
|
674
|
+
selectionMode: 'inherit-default',
|
|
675
|
+
selectionBranch: 'inherit',
|
|
676
|
+
family: 'unknown',
|
|
677
|
+
target: null,
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
if (policy.policy === 'uncapped') {
|
|
681
|
+
const selectedValue = role === 'reviewer' ? null : preferredValue;
|
|
682
|
+
return {
|
|
683
|
+
...baseSelection,
|
|
684
|
+
preferredValue: role === 'reviewer' ? null : preferredValue,
|
|
685
|
+
selectedValue,
|
|
686
|
+
capped: false,
|
|
687
|
+
selectionMode: role === 'reviewer' ? 'no-review-target' : 'uncapped',
|
|
688
|
+
selectionBranch: selectedValue ? 'prompt-persisted' : 'unresolved',
|
|
689
|
+
family: selectionFamily(provider, selectedValue, null),
|
|
690
|
+
target: null,
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
if (policy.value === null) {
|
|
694
|
+
return {
|
|
695
|
+
...baseSelection,
|
|
696
|
+
preferredValue: null,
|
|
697
|
+
selectedValue: null,
|
|
698
|
+
capped: false,
|
|
699
|
+
selectionMode: policy.target ? 'unresolved' : 'capped',
|
|
700
|
+
selectionBranch: policy.selectionBranch,
|
|
701
|
+
family: 'unknown',
|
|
702
|
+
target: policy.target,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
204
705
|
if (role === 'reviewer' || preferredValue === null) {
|
|
205
706
|
return {
|
|
206
|
-
|
|
707
|
+
...baseSelection,
|
|
207
708
|
preferredValue,
|
|
208
|
-
selectedValue:
|
|
709
|
+
selectedValue: policy.value,
|
|
209
710
|
capped: false,
|
|
711
|
+
selectionMode: role === 'reviewer' ? 'review-target' : 'capped',
|
|
712
|
+
selectionBranch: policy.selectionBranch,
|
|
713
|
+
family: selectionFamily(provider, policy.value, policy.target),
|
|
210
714
|
};
|
|
211
715
|
}
|
|
212
716
|
const order = providerValueOrder(provider);
|
|
213
717
|
const preferredIndex = order?.indexOf(preferredValue) ?? -1;
|
|
214
|
-
const ceilingIndex = order?.indexOf(
|
|
718
|
+
const ceilingIndex = order?.indexOf(policy.value) ?? -1;
|
|
215
719
|
if (!order || preferredIndex < 0 || ceilingIndex < 0) {
|
|
216
720
|
return {
|
|
217
|
-
|
|
721
|
+
...baseSelection,
|
|
218
722
|
preferredValue,
|
|
219
|
-
selectedValue:
|
|
723
|
+
selectedValue: policy.value,
|
|
220
724
|
capped: false,
|
|
725
|
+
selectionMode: 'capped',
|
|
726
|
+
selectionBranch: policy.selectionBranch,
|
|
727
|
+
family: selectionFamily(provider, policy.value, policy.target),
|
|
221
728
|
};
|
|
222
729
|
}
|
|
223
730
|
const selectedIndex = Math.min(preferredIndex, ceilingIndex);
|
|
731
|
+
const selectedValue = order[selectedIndex];
|
|
224
732
|
return {
|
|
225
|
-
|
|
733
|
+
...baseSelection,
|
|
226
734
|
preferredValue,
|
|
227
|
-
selectedValue
|
|
735
|
+
selectedValue,
|
|
228
736
|
capped: preferredIndex > ceilingIndex,
|
|
737
|
+
selectionMode: 'capped',
|
|
738
|
+
selectionBranch: policy.selectionBranch,
|
|
739
|
+
family: selectionFamily(provider, selectedValue, selectedValue === policy.value ? policy.target : null),
|
|
740
|
+
target: selectedValue === policy.value ? policy.target : null,
|
|
229
741
|
};
|
|
230
742
|
}
|
|
231
743
|
/**
|
|
@@ -233,28 +745,40 @@ function selectDispatchValue(provider, role, ceilingValue, preferredValue) {
|
|
|
233
745
|
* the enforcement mode, mechanism, dispatch args, and verify-on-upgrade flag.
|
|
234
746
|
* Mode is computed here at call time — it is never read from persisted state.
|
|
235
747
|
*/
|
|
236
|
-
function buildProviderResolution(provider,
|
|
748
|
+
function buildProviderResolution(provider, policy, role, orchestratorTier, preferredValue) {
|
|
237
749
|
const adapter = getCeilingAdapter(provider);
|
|
238
|
-
if (
|
|
750
|
+
if (policy === null) {
|
|
239
751
|
return {
|
|
240
752
|
value: null,
|
|
241
753
|
mode: adapter.supportsCeiling ? 'advisory' : 'unsupported',
|
|
242
754
|
mechanism: adapter.mechanism,
|
|
243
755
|
dispatchArgs: null,
|
|
244
756
|
verifyOnDispatch: false,
|
|
757
|
+
cellSource: null,
|
|
758
|
+
target: null,
|
|
245
759
|
selection: {
|
|
246
760
|
role,
|
|
247
761
|
preferredValue,
|
|
248
762
|
selectedValue: null,
|
|
249
763
|
capped: false,
|
|
764
|
+
selectionMode: 'unresolved',
|
|
765
|
+
selectionBranch: 'unresolved',
|
|
766
|
+
family: 'unknown',
|
|
767
|
+
target: null,
|
|
768
|
+
cellSource: null,
|
|
769
|
+
policyMode: null,
|
|
770
|
+
policy: null,
|
|
250
771
|
},
|
|
251
772
|
};
|
|
252
773
|
}
|
|
253
|
-
const selection = selectDispatchValue(provider, role,
|
|
254
|
-
const dispatchValue = selection.selectedValue
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
774
|
+
const selection = selectDispatchValue(provider, role, policy, preferredValue);
|
|
775
|
+
const dispatchValue = selection.selectedValue;
|
|
776
|
+
const isCrossHarness = selection.target?.crossHarness === true;
|
|
777
|
+
const dispatchArgs = dispatchValue && !isCrossHarness
|
|
778
|
+
? adapter.compileToDispatchArgs(dispatchValue, role, {
|
|
779
|
+
orchestratorTier,
|
|
780
|
+
})
|
|
781
|
+
: null;
|
|
258
782
|
let mode;
|
|
259
783
|
if (!adapter.supportsCeiling) {
|
|
260
784
|
mode = 'unsupported';
|
|
@@ -266,13 +790,17 @@ function buildProviderResolution(provider, value, role, orchestratorTier, prefer
|
|
|
266
790
|
mode = 'advisory';
|
|
267
791
|
}
|
|
268
792
|
return {
|
|
269
|
-
value,
|
|
793
|
+
value: policy.value,
|
|
270
794
|
mode,
|
|
271
795
|
mechanism: adapter.mechanism,
|
|
272
796
|
dispatchArgs,
|
|
273
|
-
verifyOnDispatch:
|
|
274
|
-
|
|
275
|
-
|
|
797
|
+
verifyOnDispatch: dispatchValue && !isCrossHarness
|
|
798
|
+
? adapter.verifyOnDispatch(dispatchValue, {
|
|
799
|
+
orchestratorTier,
|
|
800
|
+
})
|
|
801
|
+
: false,
|
|
802
|
+
cellSource: policy.cellSource,
|
|
803
|
+
target: selection.target,
|
|
276
804
|
selection,
|
|
277
805
|
};
|
|
278
806
|
}
|
|
@@ -305,7 +833,7 @@ async function resolveCodexProviderDefaultEffort(repoRoot, context, dependencies
|
|
|
305
833
|
}
|
|
306
834
|
function blockMessage(provider) {
|
|
307
835
|
const label = providerLabel(provider);
|
|
308
|
-
return `BLOCKED: ${label} dispatch
|
|
836
|
+
return `BLOCKED: ${label} dispatch policy is unresolved in non-interactive mode.\nSet workflow.dispatchPolicy.mode/workflow.dispatchPolicy.policy, workflow.dispatchCeiling.providers.${provider}, oat_dispatch_policy, or legacy oat_dispatch_ceiling.`;
|
|
309
837
|
}
|
|
310
838
|
function isNonInteractiveEnv(env) {
|
|
311
839
|
return env['OAT_NON_INTERACTIVE'] === '1';
|
|
@@ -324,8 +852,12 @@ async function resolveDispatchCeiling(context, dependencies, options) {
|
|
|
324
852
|
? await resolveCodexProviderDefaultEffort(repoRoot, context, dependencies)
|
|
325
853
|
: 'not-applicable';
|
|
326
854
|
const preferredValue = normalizePreferredValue(provider, options.preferred);
|
|
327
|
-
const
|
|
328
|
-
const
|
|
855
|
+
const escalationLevel = normalizeEscalationLevel(options.escalationLevel);
|
|
856
|
+
const resolvedValue = await resolveCeilingValue(provider, resolvedConfig, projectPath, dependencies, escalationLevel);
|
|
857
|
+
for (const warning of resolvedValue?.warnings ?? []) {
|
|
858
|
+
context.logger.warn(warning);
|
|
859
|
+
}
|
|
860
|
+
const providerResolution = buildProviderResolution(provider, resolvedValue, role, orchestratorTier, preferredValue);
|
|
329
861
|
const providers = {
|
|
330
862
|
[provider]: providerResolution,
|
|
331
863
|
};
|
|
@@ -334,11 +866,14 @@ async function resolveDispatchCeiling(context, dependencies, options) {
|
|
|
334
866
|
status: 'resolved',
|
|
335
867
|
provider,
|
|
336
868
|
value: resolvedValue.value,
|
|
869
|
+
policyMode: resolvedValue.mode,
|
|
870
|
+
policy: resolvedValue.policy,
|
|
337
871
|
source: resolvedValue.source,
|
|
338
872
|
preset: resolvedValue.preset,
|
|
339
873
|
unresolved: false,
|
|
340
874
|
projectPath,
|
|
341
875
|
providerDefaultEffort,
|
|
876
|
+
matrix: resolvedValue.matrix,
|
|
342
877
|
providers,
|
|
343
878
|
};
|
|
344
879
|
}
|
|
@@ -350,11 +885,14 @@ async function resolveDispatchCeiling(context, dependencies, options) {
|
|
|
350
885
|
status: shouldBlock ? 'blocked' : 'unresolved',
|
|
351
886
|
provider,
|
|
352
887
|
value: null,
|
|
888
|
+
policyMode: null,
|
|
889
|
+
policy: null,
|
|
353
890
|
source: null,
|
|
354
891
|
preset: null,
|
|
355
892
|
unresolved: true,
|
|
356
893
|
projectPath,
|
|
357
894
|
providerDefaultEffort,
|
|
895
|
+
matrix: null,
|
|
358
896
|
providers,
|
|
359
897
|
message,
|
|
360
898
|
};
|
|
@@ -365,24 +903,39 @@ async function resolveDispatchCeiling(context, dependencies, options) {
|
|
|
365
903
|
* Never reads the preset label for dispatch — the preset is surfaced as
|
|
366
904
|
* provenance only.
|
|
367
905
|
*/
|
|
368
|
-
async function resolveCeilingValue(provider, resolvedConfig, projectPath, dependencies) {
|
|
906
|
+
async function resolveCeilingValue(provider, resolvedConfig, projectPath, dependencies, escalationLevel) {
|
|
369
907
|
const configCeiling = readResolvedConfigCeiling(provider, resolvedConfig);
|
|
370
|
-
if (configCeiling) {
|
|
371
|
-
return {
|
|
372
|
-
value: configCeiling.value,
|
|
373
|
-
source: configCeiling.source,
|
|
374
|
-
preset: null,
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
908
|
const projectCeiling = await resolveProjectStateCeiling(provider, projectPath, dependencies);
|
|
378
|
-
|
|
909
|
+
const baseCeiling = configCeiling ?? projectCeiling;
|
|
910
|
+
if (!baseCeiling) {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
if (baseCeiling.mode === 'inherit' || baseCeiling.policy === 'uncapped') {
|
|
914
|
+
return baseCeiling;
|
|
915
|
+
}
|
|
916
|
+
const tier = policyTier(baseCeiling.policy);
|
|
917
|
+
const matrixCell = resolveProviderMatrixCell(provider, tier, resolvedConfig, projectCeiling?.matrix ?? null, escalationLevel);
|
|
918
|
+
if (matrixCell) {
|
|
379
919
|
return {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
920
|
+
...baseCeiling,
|
|
921
|
+
value: matrixCell.value,
|
|
922
|
+
cellSource: matrixCell.cellSource,
|
|
923
|
+
target: matrixCell.target,
|
|
924
|
+
selectionBranch: matrixCell.selectionBranch,
|
|
383
925
|
};
|
|
384
926
|
}
|
|
385
|
-
|
|
927
|
+
if (baseCeiling.policy !== 'legacy-ceiling' &&
|
|
928
|
+
baseCeiling.value === null &&
|
|
929
|
+
providerValueOrder(provider) === null) {
|
|
930
|
+
return null;
|
|
931
|
+
}
|
|
932
|
+
return baseCeiling;
|
|
933
|
+
}
|
|
934
|
+
function policyLabel(policy) {
|
|
935
|
+
if (policy === 'legacy-ceiling') {
|
|
936
|
+
return 'legacy capped';
|
|
937
|
+
}
|
|
938
|
+
return policy ?? 'inherit host defaults';
|
|
386
939
|
}
|
|
387
940
|
function writeHumanResolution(context, resolution) {
|
|
388
941
|
const label = providerLabel(resolution.provider);
|
|
@@ -390,15 +943,48 @@ function writeHumanResolution(context, resolution) {
|
|
|
390
943
|
context.logger.error(resolution.message);
|
|
391
944
|
return;
|
|
392
945
|
}
|
|
393
|
-
context.logger.info(`${label} dispatch
|
|
946
|
+
context.logger.info(`${label} dispatch policy: ${policyLabel(resolution.policy)}`);
|
|
947
|
+
context.logger.info(`Resolved cap: ${resolution.value ?? 'none'}`);
|
|
394
948
|
context.logger.info(`Source: ${sourceLabel(resolution.source)}`);
|
|
395
949
|
const providerResolution = resolution.providers[resolution.provider];
|
|
396
950
|
if (providerResolution) {
|
|
397
951
|
context.logger.info(`Mode: ${providerResolution.mode} (${providerResolution.mechanism})`);
|
|
952
|
+
context.logger.info(`Selection: ${providerResolution.selection.selectionMode}`);
|
|
953
|
+
if (providerResolution.target) {
|
|
954
|
+
const details = [
|
|
955
|
+
`harness=${providerResolution.target.harness}`,
|
|
956
|
+
providerResolution.target.model
|
|
957
|
+
? `model=${providerResolution.target.model}`
|
|
958
|
+
: null,
|
|
959
|
+
providerResolution.target.effort
|
|
960
|
+
? `effort=${providerResolution.target.effort}`
|
|
961
|
+
: null,
|
|
962
|
+
]
|
|
963
|
+
.filter(Boolean)
|
|
964
|
+
.join(' ');
|
|
965
|
+
const suffix = providerResolution.target.crossHarness
|
|
966
|
+
? ' (cross-harness target; native dispatch args deferred)'
|
|
967
|
+
: '';
|
|
968
|
+
context.logger.info(`Dispatch target: ${details}${suffix}`);
|
|
969
|
+
}
|
|
398
970
|
}
|
|
399
971
|
if (resolution.provider === 'codex') {
|
|
400
972
|
context.logger.info(`Codex provider default effort: ${resolution.providerDefaultEffort}`);
|
|
401
|
-
|
|
973
|
+
if (providerResolution?.selection.selectionMode === 'inherit-default') {
|
|
974
|
+
context.logger.info('Note: OAT will not select a Codex effort; base/unpinned roles resolve through the provider default.');
|
|
975
|
+
}
|
|
976
|
+
else if (providerResolution?.selection.selectionMode === 'uncapped') {
|
|
977
|
+
context.logger.info('Note: OAT will use the preferred pinned Codex variant with no configured cap. Actual host support for upward effort selection must be verified by the dispatching host.');
|
|
978
|
+
}
|
|
979
|
+
else if (providerResolution?.selection.selectionMode === 'review-target') {
|
|
980
|
+
context.logger.info('Note: Reviewer dispatch uses the configured target from the resolved capped policy.');
|
|
981
|
+
}
|
|
982
|
+
else if (providerResolution?.selection.selectionMode === 'no-review-target') {
|
|
983
|
+
context.logger.info('Note: Managed uncapped reviewer dispatch has no configured target; use the base/unpinned reviewer fallback.');
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
context.logger.info(`Note: OAT will use pinned subagent variants up to ${resolution.value ?? 'the resolved policy cap'}. Base/unpinned roles resolve through the provider default.`);
|
|
987
|
+
}
|
|
402
988
|
if (providerResolution?.selection.selectedValue &&
|
|
403
989
|
providerResolution.selection.preferredValue) {
|
|
404
990
|
context.logger.info(`Selected Codex effort: ${providerResolution.selection.selectedValue}`);
|
|
@@ -406,6 +992,9 @@ function writeHumanResolution(context, resolution) {
|
|
|
406
992
|
}
|
|
407
993
|
else {
|
|
408
994
|
context.logger.info('Effort axis: not-applicable');
|
|
995
|
+
if (providerResolution?.selection.selectionMode === 'inherit-default') {
|
|
996
|
+
context.logger.info('Note: OAT will not select a Claude model; Task dispatch inherits host/provider behavior.');
|
|
997
|
+
}
|
|
409
998
|
if (providerResolution?.selection.selectedValue &&
|
|
410
999
|
providerResolution.selection.preferredValue) {
|
|
411
1000
|
context.logger.info(`Selected dispatch value: ${providerResolution.selection.selectedValue}`);
|
|
@@ -441,12 +1030,13 @@ export function createProjectDispatchCeilingCommand(overrides = {}) {
|
|
|
441
1030
|
};
|
|
442
1031
|
const command = new Command('dispatch-ceiling').description('Resolve OAT project dispatch ceiling metadata');
|
|
443
1032
|
command.addCommand(new Command('resolve')
|
|
444
|
-
.description('Resolve dispatch
|
|
445
|
-
.requiredOption('--provider <provider>', 'Provider name: codex or
|
|
1033
|
+
.description('Resolve dispatch policy for a provider')
|
|
1034
|
+
.requiredOption('--provider <provider>', 'Provider name: codex, claude, or cursor are enforced; unregistered providers resolve as unsupported advisory')
|
|
446
1035
|
.option('--role <role>', 'Dispatch role for variant compilation: implementer (default) or reviewer')
|
|
447
1036
|
.option('--orchestrator-tier <tier>', 'Orchestrator tier, used to flag verify-on-upgrade for above-orchestrator requests')
|
|
448
|
-
.option('--preferred <value>', 'Preferred implementer/fix dispatch value before
|
|
449
|
-
.option('--
|
|
1037
|
+
.option('--preferred <value>', 'Preferred implementer/fix dispatch value before applying the resolved policy')
|
|
1038
|
+
.option('--escalation-level <level>', 'Ordered route entry to select; 0 is the floor and higher values advance through escalation targets')
|
|
1039
|
+
.option('--project-path <path>', 'Read project-state policy from an explicit project path')
|
|
450
1040
|
.option('--preflight', 'Treat unresolved non-interactive resolution as an implementation block')
|
|
451
1041
|
.option('--non-interactive', 'Force non-interactive block behavior when the ceiling is unresolved')
|
|
452
1042
|
.option('--json', 'Output machine-readable JSON')
|