@open-agent-toolkit/cli 0.1.48 → 0.1.50
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/docs/cli-utilities/configuration.md +41 -0
- package/assets/docs/cli-utilities/workflow-gates.md +17 -0
- package/assets/docs/provider-sync/providers.md +5 -1
- package/assets/docs/workflows/projects/dispatch-ceiling.md +59 -7
- package/assets/docs/workflows/projects/implementation-execution.md +34 -0
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-project-implement/SKILL.md +49 -22
- package/assets/skills/oat-project-review-provide/SKILL.md +18 -2
- package/assets/skills/oat-project-review-provide-remote/SKILL.md +28 -1
- package/dist/commands/config/index.d.ts +3 -0
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +70 -128
- package/dist/commands/doctor/index.d.ts +3 -0
- package/dist/commands/doctor/index.d.ts.map +1 -1
- package/dist/commands/doctor/index.js +57 -110
- package/dist/commands/gate/index.d.ts.map +1 -1
- package/dist/commands/gate/index.js +71 -0
- package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
- package/dist/commands/project/dispatch-ceiling/index.js +144 -112
- package/dist/config/dispatch-matrix.d.ts +59 -0
- package/dist/config/dispatch-matrix.d.ts.map +1 -0
- package/dist/config/dispatch-matrix.js +264 -0
- package/dist/config/oat-config.d.ts +2 -28
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +8 -144
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +2 -1
- package/dist/providers/identity/availability.d.ts +12 -0
- package/dist/providers/identity/availability.d.ts.map +1 -1
- package/dist/providers/identity/availability.js +72 -29
- package/dist/providers/identity/dispatch-report.d.ts +124 -0
- package/dist/providers/identity/dispatch-report.d.ts.map +1 -0
- package/dist/providers/identity/dispatch-report.js +285 -0
- package/dist/providers/identity/dispatch-validation.d.ts +28 -0
- package/dist/providers/identity/dispatch-validation.d.ts.map +1 -0
- package/dist/providers/identity/dispatch-validation.js +149 -0
- package/dist/providers/identity/stamp.d.ts +2 -0
- package/dist/providers/identity/stamp.d.ts.map +1 -1
- package/dist/providers/identity/stamp.js +3 -1
- package/package.json +2 -2
|
@@ -9,6 +9,7 @@ import { BUILTIN_EXEC_TARGETS, readOatConfig, readOatLocalConfig, readUserConfig
|
|
|
9
9
|
import { resolveEffectiveConfig, resolveExecTargetViews, resolveExecTargets, resolveGate, } from '../../config/resolve.js';
|
|
10
10
|
import { dirExists, fileExists } from '../../fs/io.js';
|
|
11
11
|
import { normalizeToPosixPath, resolveProjectRoot, validateRealPathWithinScope, } from '../../fs/paths.js';
|
|
12
|
+
import { buildDispatchReport, } from '../../providers/identity/dispatch-report.js';
|
|
12
13
|
import { classifyModelFamily, } from '../../providers/identity/family.js';
|
|
13
14
|
import { resolveIdentityConfidence, } from '../../providers/identity/provenance.js';
|
|
14
15
|
import { parseDispatchStamps } from '../../providers/identity/stamp.js';
|
|
@@ -94,6 +95,62 @@ function createGateInvocationMetadata(runId, selected) {
|
|
|
94
95
|
: {}),
|
|
95
96
|
});
|
|
96
97
|
}
|
|
98
|
+
function buildGateDispatchReport(invocation, scope) {
|
|
99
|
+
const report = buildDispatchReport({
|
|
100
|
+
scope,
|
|
101
|
+
action: 'review',
|
|
102
|
+
role: 'reviewer',
|
|
103
|
+
resolution: {
|
|
104
|
+
status: 'resolved',
|
|
105
|
+
provider: invocation.runtime,
|
|
106
|
+
value: null,
|
|
107
|
+
policyMode: null,
|
|
108
|
+
policy: null,
|
|
109
|
+
source: null,
|
|
110
|
+
providers: {
|
|
111
|
+
[invocation.runtime]: {
|
|
112
|
+
dispatchArgs: null,
|
|
113
|
+
selection: {
|
|
114
|
+
role: 'reviewer',
|
|
115
|
+
requestedCandidate: null,
|
|
116
|
+
candidateTier: null,
|
|
117
|
+
candidateIndex: null,
|
|
118
|
+
ceilingTier: null,
|
|
119
|
+
ceilingTarget: null,
|
|
120
|
+
selectedValue: null,
|
|
121
|
+
selectionMode: 'gate-invocation',
|
|
122
|
+
selectionBranch: 'gate-configured-invocation',
|
|
123
|
+
target: null,
|
|
124
|
+
cellSource: null,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
requestedControls: {
|
|
130
|
+
model: {
|
|
131
|
+
value: null,
|
|
132
|
+
mechanism: 'base-role',
|
|
133
|
+
reason: 'Configured gate invocation is reported separately from runtime identity.',
|
|
134
|
+
},
|
|
135
|
+
effort: {
|
|
136
|
+
value: null,
|
|
137
|
+
mechanism: 'base-role',
|
|
138
|
+
reason: 'Configured gate invocation is reported separately from runtime identity.',
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
configuredDefaults: {
|
|
142
|
+
model: null,
|
|
143
|
+
modelSource: null,
|
|
144
|
+
effort: null,
|
|
145
|
+
effortSource: null,
|
|
146
|
+
},
|
|
147
|
+
gateInvocation: invocation,
|
|
148
|
+
});
|
|
149
|
+
return {
|
|
150
|
+
...report,
|
|
151
|
+
route: { ...report.route, target: invocation.targetId },
|
|
152
|
+
};
|
|
153
|
+
}
|
|
97
154
|
function gateInvocationPromptContext(invocation) {
|
|
98
155
|
const frontmatter = YAML.stringify({
|
|
99
156
|
oat_gate_run_id: invocation.runId,
|
|
@@ -1165,6 +1222,7 @@ function writeReviewGateExecutionFailure(context, payload) {
|
|
|
1165
1222
|
project: payload.project,
|
|
1166
1223
|
projectResolutionSource: payload.projectResolutionSource,
|
|
1167
1224
|
gateInvocation: payload.gateInvocation,
|
|
1225
|
+
dispatchReport: payload.dispatchReport,
|
|
1168
1226
|
...(payload.corroboration
|
|
1169
1227
|
? { corroboration: payload.corroboration }
|
|
1170
1228
|
: {}),
|
|
@@ -1192,6 +1250,7 @@ function writeReviewGateUnexpectedFailure(context, payload) {
|
|
|
1192
1250
|
project: payload.project,
|
|
1193
1251
|
projectResolutionSource: payload.projectResolutionSource,
|
|
1194
1252
|
gateInvocation: payload.gateInvocation,
|
|
1253
|
+
dispatchReport: payload.dispatchReport,
|
|
1195
1254
|
message,
|
|
1196
1255
|
});
|
|
1197
1256
|
return;
|
|
@@ -1210,6 +1269,7 @@ function writeReviewGateArtifactValidationFailure(context, payload) {
|
|
|
1210
1269
|
artifactPath: payload.artifactPath,
|
|
1211
1270
|
generatedAt: payload.generatedAt,
|
|
1212
1271
|
gateInvocation: payload.gateInvocation,
|
|
1272
|
+
dispatchReport: payload.dispatchReport,
|
|
1213
1273
|
...(payload.corroboration
|
|
1214
1274
|
? { corroboration: payload.corroboration }
|
|
1215
1275
|
: {}),
|
|
@@ -1248,6 +1308,7 @@ function writeReviewGateTargetingFailure(context, payload) {
|
|
|
1248
1308
|
artifactPath: payload.artifactPath,
|
|
1249
1309
|
generatedAt: payload.generatedAt,
|
|
1250
1310
|
gateInvocation: payload.gateInvocation,
|
|
1311
|
+
dispatchReport: payload.dispatchReport,
|
|
1251
1312
|
corroboration,
|
|
1252
1313
|
receiveEligible: false,
|
|
1253
1314
|
remediable: false,
|
|
@@ -1418,11 +1479,13 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1418
1479
|
});
|
|
1419
1480
|
const selected = await resolveSelectedExecTarget(targets, options, producerIdentity, context, dependencies);
|
|
1420
1481
|
const gateInvocation = createGateInvocationMetadata(runId, selected);
|
|
1482
|
+
const dispatchReport = buildGateDispatchReport(gateInvocation, options.reviewScope?.trim() || 'gate-review');
|
|
1421
1483
|
postSelectionContext = {
|
|
1422
1484
|
project: projectPath,
|
|
1423
1485
|
projectResolutionSource: reviewProject.source,
|
|
1424
1486
|
target: selected.id,
|
|
1425
1487
|
gateInvocation,
|
|
1488
|
+
dispatchReport,
|
|
1426
1489
|
};
|
|
1427
1490
|
const threshold = parseReviewGateThreshold(options.exitNonzeroOn);
|
|
1428
1491
|
const before = await listReviewGateArtifactCandidates({
|
|
@@ -1454,6 +1517,7 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1454
1517
|
timedOut: childResult.timedOut ?? false,
|
|
1455
1518
|
timeoutMs: resolveGateExecTimeoutMs(dependencies.processEnv),
|
|
1456
1519
|
gateInvocation,
|
|
1520
|
+
dispatchReport,
|
|
1457
1521
|
});
|
|
1458
1522
|
process.exitCode = childExitCode;
|
|
1459
1523
|
return;
|
|
@@ -1493,6 +1557,7 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1493
1557
|
generatedAt: diagnosticArtifact?.generatedAt ?? null,
|
|
1494
1558
|
message,
|
|
1495
1559
|
gateInvocation,
|
|
1560
|
+
dispatchReport,
|
|
1496
1561
|
corroboration: initialTargetCorroboration,
|
|
1497
1562
|
});
|
|
1498
1563
|
process.exitCode = 1;
|
|
@@ -1514,6 +1579,7 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1514
1579
|
? 'Review artifact is missing oat_project for the explicitly declared project.'
|
|
1515
1580
|
: 'Review artifact project identity does not match the explicitly declared project.',
|
|
1516
1581
|
gateInvocation,
|
|
1582
|
+
dispatchReport,
|
|
1517
1583
|
corroboration: initialTargetCorroboration,
|
|
1518
1584
|
});
|
|
1519
1585
|
process.exitCode = 1;
|
|
@@ -1531,6 +1597,7 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1531
1597
|
message: 'Review artifact oat_generated_at is missing or is not a valid timestamp.',
|
|
1532
1598
|
recovery: `Set oat_generated_at to a valid timestamp in ${producedArtifact.path}, then rerun the gate. Invoke oat-project-review-receive only after the gate returns a receive-eligible result.`,
|
|
1533
1599
|
gateInvocation,
|
|
1600
|
+
dispatchReport,
|
|
1534
1601
|
corroboration: corroborateGateInvocation(gateInvocation, undefined, initialTargetCorroboration),
|
|
1535
1602
|
});
|
|
1536
1603
|
process.exitCode = 1;
|
|
@@ -1558,6 +1625,7 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1558
1625
|
message: detail,
|
|
1559
1626
|
recovery: `The review artifact was created at ${producedArtifact.path} but could not be consumed. Fix the artifact format, then rerun the gate to revalidate it. Invoke oat-project-review-receive only after the gate returns a receive-eligible result; if the only issue is a missing zero-count severity heading, rerun the gate to normalize the same artifact instead of creating a new review version.`,
|
|
1560
1627
|
gateInvocation,
|
|
1628
|
+
dispatchReport,
|
|
1561
1629
|
corroboration: corroborateGateInvocation(gateInvocation, undefined, initialTargetCorroboration),
|
|
1562
1630
|
});
|
|
1563
1631
|
process.exitCode = 1;
|
|
@@ -1581,6 +1649,7 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1581
1649
|
: 'Review artifact invocation metadata does not match the gate-owned configured invocation.',
|
|
1582
1650
|
recovery: `Copy the exact gate invocation fields from the review prompt into ${producedArtifact.path}, then run oat-project-review-receive only after the artifact validates.`,
|
|
1583
1651
|
gateInvocation,
|
|
1652
|
+
dispatchReport,
|
|
1584
1653
|
corroboration,
|
|
1585
1654
|
});
|
|
1586
1655
|
process.exitCode = 1;
|
|
@@ -1597,6 +1666,7 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1597
1666
|
message: 'Review artifact is missing the required gate invocation marker `oat_review_invocation: gate`.',
|
|
1598
1667
|
recovery: `Set oat_review_invocation: gate in ${producedArtifact.path}, then run oat-project-review-receive only after the artifact validates.`,
|
|
1599
1668
|
gateInvocation,
|
|
1669
|
+
dispatchReport,
|
|
1600
1670
|
corroboration,
|
|
1601
1671
|
});
|
|
1602
1672
|
process.exitCode = 1;
|
|
@@ -1627,6 +1697,7 @@ async function runReviewGate(prompt, options, context, dependencies) {
|
|
|
1627
1697
|
handoff,
|
|
1628
1698
|
diversity: selected.diversity,
|
|
1629
1699
|
gateInvocation,
|
|
1700
|
+
dispatchReport,
|
|
1630
1701
|
corroboration,
|
|
1631
1702
|
});
|
|
1632
1703
|
process.exitCode = blocking ? 1 : 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/dispatch-ceiling/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/dispatch-ceiling/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAa9B,OAAO,EAUL,KAAK,uBAAuB,EAY7B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,iBAAiB,CAAC;AAsBzB,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAiC5C,UAAU,2BAA2B;IACnC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,sBAAsB,EAAE,CACtB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,CAAC,UAAU,KACnB,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC7E,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B;AA63ED,wBAAgB,mCAAmC,CACjD,SAAS,GAAE,OAAO,CAAC,2BAA2B,CAAM,GACnD,OAAO,CAkGT"}
|
|
@@ -4,12 +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 { normalizeDispatchMatrix } from '../../../config/dispatch-matrix.js';
|
|
7
8
|
import { getDispatchPolicyChoices, managedDispatchPolicyValueList, renderDispatchPolicyChoicesMarkdown, } from '../../../config/dispatch-policy-options.js';
|
|
8
9
|
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';
|
|
9
10
|
import { resolveEffectiveConfig, } from '../../../config/resolve.js';
|
|
10
11
|
import { resolveProjectRoot } from '../../../fs/paths.js';
|
|
11
12
|
import TOML from '@iarna/toml';
|
|
12
13
|
import { getCeilingAdapter, isDirectDispatchRoleName, CLAUDE_TIER_ORDER, } from '../../../providers/ceiling/registry.js';
|
|
14
|
+
import { buildDispatchReport, formatDispatchReport, } from '../../../providers/identity/dispatch-report.js';
|
|
13
15
|
import { classifyModelFamily, } from '../../../providers/identity/family.js';
|
|
14
16
|
import { Command, Option } from 'commander';
|
|
15
17
|
import YAML from 'yaml';
|
|
@@ -125,131 +127,64 @@ function invalidProjectPolicyMessage(value) {
|
|
|
125
127
|
const actual = typeof value === 'string' ? value : String(value);
|
|
126
128
|
return `Invalid project dispatch policy "${actual}". Valid managed policies: ${validManagedPolicyList()}. Use mode "inherit" for host defaults.`;
|
|
127
129
|
}
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
return undefined;
|
|
131
|
-
}
|
|
132
|
-
const trimmed = value.trim();
|
|
133
|
-
if (!trimmed) {
|
|
134
|
-
return undefined;
|
|
135
|
-
}
|
|
136
|
-
const order = providerValueOrder(provider);
|
|
137
|
-
if (order && !isValidProviderValue(provider, trimmed)) {
|
|
138
|
-
return undefined;
|
|
139
|
-
}
|
|
140
|
-
return trimmed;
|
|
141
|
-
}
|
|
142
|
-
function normalizeProjectMatrixRouteTarget(value) {
|
|
143
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
144
|
-
return undefined;
|
|
145
|
-
}
|
|
146
|
-
const record = value;
|
|
147
|
-
const target = {};
|
|
148
|
-
for (const key of ['harness', 'model', 'effort']) {
|
|
149
|
-
const rawValue = record[key];
|
|
150
|
-
if (typeof rawValue === 'string' && rawValue.trim()) {
|
|
151
|
-
target[key] = rawValue.trim();
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return Object.keys(target).length > 0 ? target : undefined;
|
|
130
|
+
function isProjectMatrixRecord(value) {
|
|
131
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
155
132
|
}
|
|
156
|
-
function
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
for (const entry of value) {
|
|
162
|
-
const bareEntry = normalizeProjectMatrixBareValue(provider, entry);
|
|
163
|
-
if (bareEntry !== undefined) {
|
|
164
|
-
route.push(bareEntry);
|
|
133
|
+
function toProjectMatrixCompatibility(rawMatrix, matrix) {
|
|
134
|
+
const compatibility = {};
|
|
135
|
+
for (const [provider, providerValue] of Object.entries(matrix)) {
|
|
136
|
+
if (typeof providerValue === 'string') {
|
|
137
|
+
compatibility[provider] = providerValue;
|
|
165
138
|
continue;
|
|
166
139
|
}
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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);
|
|
140
|
+
const rawProvider = isProjectMatrixRecord(rawMatrix[provider])
|
|
141
|
+
? rawMatrix[provider]
|
|
142
|
+
: {};
|
|
143
|
+
const tiers = {};
|
|
144
|
+
for (const [tier, cell] of Object.entries(providerValue)) {
|
|
145
|
+
const matrixTier = tier;
|
|
146
|
+
const rawCell = rawProvider[matrixTier];
|
|
147
|
+
const ladder = toWorkflowDispatchCandidateLadder(cell);
|
|
148
|
+
const firstCandidate = ladder.candidates[0];
|
|
149
|
+
if (typeof rawCell === 'string' && typeof firstCandidate === 'string') {
|
|
150
|
+
tiers[matrixTier] = firstCandidate;
|
|
151
|
+
continue;
|
|
196
152
|
}
|
|
153
|
+
if (Array.isArray(rawCell) &&
|
|
154
|
+
firstCandidate !== undefined &&
|
|
155
|
+
isWorkflowDispatchFallbackRoute(firstCandidate)) {
|
|
156
|
+
tiers[matrixTier] = firstCandidate.route;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
tiers[matrixTier] = cell;
|
|
197
160
|
}
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
return normalizeProjectMatrixRoute(provider, value);
|
|
201
|
-
}
|
|
202
|
-
function normalizeProjectMatrixProviderValue(provider, value) {
|
|
203
|
-
const bareValue = normalizeProjectMatrixBareValue(provider, value);
|
|
204
|
-
if (bareValue !== undefined) {
|
|
205
|
-
return bareValue;
|
|
206
|
-
}
|
|
207
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
208
|
-
return undefined;
|
|
209
|
-
}
|
|
210
|
-
const tierMap = {};
|
|
211
|
-
for (const [tier, rawCell] of Object.entries(value)) {
|
|
212
|
-
if (!VALID_DISPATCH_MATRIX_TIERS.includes(tier)) {
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
const normalized = normalizeProjectMatrixCell(provider, rawCell);
|
|
216
|
-
if (normalized !== undefined) {
|
|
217
|
-
tierMap[tier] = normalized;
|
|
218
|
-
}
|
|
161
|
+
compatibility[provider] = tiers;
|
|
219
162
|
}
|
|
220
|
-
return
|
|
163
|
+
return compatibility;
|
|
221
164
|
}
|
|
222
165
|
function readProjectDispatchMatrix(value) {
|
|
223
166
|
if (value === undefined || value === null) {
|
|
224
|
-
return { matrix: null, warnings: [] };
|
|
225
|
-
}
|
|
226
|
-
|
|
167
|
+
return { matrix: null, compatibilityMatrix: null, warnings: [] };
|
|
168
|
+
}
|
|
169
|
+
const normalized = normalizeDispatchMatrix(value, {
|
|
170
|
+
pathPrefix: 'oat_dispatch_policy.matrix',
|
|
171
|
+
compatibilityMode: 'project-state',
|
|
172
|
+
});
|
|
173
|
+
const matrix = Object.fromEntries(Object.entries(normalized.providers).filter(([provider]) => Boolean(provider.trim())));
|
|
174
|
+
if (!isProjectMatrixRecord(value) || Object.keys(matrix).length === 0) {
|
|
227
175
|
return {
|
|
228
176
|
matrix: null,
|
|
177
|
+
compatibilityMatrix: null,
|
|
229
178
|
warnings: [
|
|
230
179
|
'Ignoring malformed oat_dispatch_policy.matrix in project state.',
|
|
231
180
|
],
|
|
232
181
|
};
|
|
233
182
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const normalized = normalizeProjectMatrixProviderValue(provider, rawProviderValue);
|
|
240
|
-
if (normalized !== undefined) {
|
|
241
|
-
matrix[provider] = normalized;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
if (Object.keys(matrix).length === 0) {
|
|
245
|
-
return {
|
|
246
|
-
matrix: null,
|
|
247
|
-
warnings: [
|
|
248
|
-
'Ignoring malformed oat_dispatch_policy.matrix in project state.',
|
|
249
|
-
],
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
return { matrix, warnings: [] };
|
|
183
|
+
return {
|
|
184
|
+
matrix,
|
|
185
|
+
compatibilityMatrix: toProjectMatrixCompatibility(value, matrix),
|
|
186
|
+
warnings: [],
|
|
187
|
+
};
|
|
253
188
|
}
|
|
254
189
|
function policyTier(policy) {
|
|
255
190
|
return typeof policy === 'string' &&
|
|
@@ -585,6 +520,7 @@ function resolveRequestedMatrixCandidate(provider, requested, ceilingTier, resol
|
|
|
585
520
|
}
|
|
586
521
|
allowedMatch ??= {
|
|
587
522
|
tier,
|
|
523
|
+
candidateIndex,
|
|
588
524
|
candidate,
|
|
589
525
|
cellSource: definition.cellSource,
|
|
590
526
|
routeSignature,
|
|
@@ -613,6 +549,7 @@ function resolveRequestedMatrixCandidate(provider, requested, ceilingTier, resol
|
|
|
613
549
|
: 'candidate-requested',
|
|
614
550
|
},
|
|
615
551
|
candidateTier: allowedMatch.tier,
|
|
552
|
+
candidateIndex: allowedMatch.candidateIndex,
|
|
616
553
|
};
|
|
617
554
|
}
|
|
618
555
|
function readProjectDispatchPolicy(provider, content) {
|
|
@@ -639,9 +576,11 @@ function readProjectDispatchPolicy(provider, content) {
|
|
|
639
576
|
source: 'project-state',
|
|
640
577
|
preset: null,
|
|
641
578
|
matrix: parsedMatrix.matrix,
|
|
579
|
+
matrixCompatibility: parsedMatrix.compatibilityMatrix,
|
|
642
580
|
cellSource: null,
|
|
643
581
|
target: null,
|
|
644
582
|
selectionBranch: 'inherit',
|
|
583
|
+
candidateIndex: null,
|
|
645
584
|
warnings: parsedMatrix.warnings,
|
|
646
585
|
};
|
|
647
586
|
}
|
|
@@ -662,9 +601,11 @@ function readProjectDispatchPolicy(provider, content) {
|
|
|
662
601
|
source: 'project-state',
|
|
663
602
|
preset: policyValue,
|
|
664
603
|
matrix: parsedMatrix.matrix,
|
|
604
|
+
matrixCompatibility: parsedMatrix.compatibilityMatrix,
|
|
665
605
|
cellSource: null,
|
|
666
606
|
target: null,
|
|
667
607
|
selectionBranch: 'prompt-persisted',
|
|
608
|
+
candidateIndex: null,
|
|
668
609
|
warnings: parsedMatrix.warnings,
|
|
669
610
|
};
|
|
670
611
|
}
|
|
@@ -689,9 +630,11 @@ function readProjectDispatchPolicy(provider, content) {
|
|
|
689
630
|
source: 'project-state',
|
|
690
631
|
preset: policyValue,
|
|
691
632
|
matrix: parsedMatrix.matrix,
|
|
633
|
+
matrixCompatibility: parsedMatrix.compatibilityMatrix,
|
|
692
634
|
cellSource: 'project-state',
|
|
693
635
|
target: null,
|
|
694
636
|
selectionBranch: 'prompt-persisted',
|
|
637
|
+
candidateIndex: null,
|
|
695
638
|
warnings: parsedMatrix.warnings,
|
|
696
639
|
};
|
|
697
640
|
}
|
|
@@ -722,6 +665,7 @@ function readLegacyProjectDispatchCeiling(provider, parsed) {
|
|
|
722
665
|
cellSource: 'project-state',
|
|
723
666
|
target: null,
|
|
724
667
|
selectionBranch: 'prompt-persisted',
|
|
668
|
+
candidateIndex: null,
|
|
725
669
|
warnings: [],
|
|
726
670
|
};
|
|
727
671
|
}
|
|
@@ -790,9 +734,11 @@ function stripConfigCandidateSource(candidate) {
|
|
|
790
734
|
source: candidate.source,
|
|
791
735
|
preset: candidate.preset,
|
|
792
736
|
matrix: candidate.matrix,
|
|
737
|
+
matrixCompatibility: candidate.matrixCompatibility,
|
|
793
738
|
cellSource: candidate.cellSource,
|
|
794
739
|
target: candidate.target,
|
|
795
740
|
selectionBranch: candidate.selectionBranch,
|
|
741
|
+
candidateIndex: candidate.candidateIndex,
|
|
796
742
|
warnings: candidate.warnings,
|
|
797
743
|
};
|
|
798
744
|
}
|
|
@@ -815,6 +761,7 @@ function readResolvedConfigPolicyCandidate(provider, resolvedConfig) {
|
|
|
815
761
|
cellSource: null,
|
|
816
762
|
target: null,
|
|
817
763
|
selectionBranch: 'inherit',
|
|
764
|
+
candidateIndex: null,
|
|
818
765
|
warnings: [],
|
|
819
766
|
configSource: modeEntry.source,
|
|
820
767
|
};
|
|
@@ -840,6 +787,7 @@ function readResolvedConfigPolicyCandidate(provider, resolvedConfig) {
|
|
|
840
787
|
cellSource: null,
|
|
841
788
|
target: null,
|
|
842
789
|
selectionBranch: 'prompt-persisted',
|
|
790
|
+
candidateIndex: null,
|
|
843
791
|
warnings: [],
|
|
844
792
|
configSource,
|
|
845
793
|
};
|
|
@@ -870,6 +818,7 @@ function readResolvedLegacyConfigCeilingCandidate(provider, resolvedConfig) {
|
|
|
870
818
|
cellSource: source,
|
|
871
819
|
target: null,
|
|
872
820
|
selectionBranch: 'prompt-persisted',
|
|
821
|
+
candidateIndex: null,
|
|
873
822
|
warnings: [],
|
|
874
823
|
configSource: entry.source,
|
|
875
824
|
};
|
|
@@ -998,6 +947,7 @@ function selectDispatchValue(provider, role, policy, preferredValue) {
|
|
|
998
947
|
target: policy.target,
|
|
999
948
|
requestedCandidate: policy.requestedCandidate ?? null,
|
|
1000
949
|
candidateTier: policy.candidateTier ?? null,
|
|
950
|
+
candidateIndex: policy.candidateIndex,
|
|
1001
951
|
ceilingTier: policy.ceilingTier ?? policyTier(policy.policy),
|
|
1002
952
|
ceilingTarget: policy.ceilingTarget ?? policy.target,
|
|
1003
953
|
};
|
|
@@ -1160,6 +1110,7 @@ function buildProviderResolution(provider, policy, role, orchestratorTier, prefe
|
|
|
1160
1110
|
preferredValue,
|
|
1161
1111
|
requestedCandidate: null,
|
|
1162
1112
|
candidateTier: null,
|
|
1113
|
+
candidateIndex: null,
|
|
1163
1114
|
ceilingTier: null,
|
|
1164
1115
|
ceilingTarget: null,
|
|
1165
1116
|
selectedValue: null,
|
|
@@ -1294,7 +1245,7 @@ async function resolveDispatchCeiling(context, dependencies, options) {
|
|
|
1294
1245
|
unresolved: true,
|
|
1295
1246
|
projectPath,
|
|
1296
1247
|
providerDefaultEffort,
|
|
1297
|
-
matrix: resolvedValue.matrix,
|
|
1248
|
+
matrix: resolvedValue.matrixCompatibility ?? resolvedValue.matrix,
|
|
1298
1249
|
providers,
|
|
1299
1250
|
message,
|
|
1300
1251
|
};
|
|
@@ -1310,7 +1261,7 @@ async function resolveDispatchCeiling(context, dependencies, options) {
|
|
|
1310
1261
|
unresolved: false,
|
|
1311
1262
|
projectPath,
|
|
1312
1263
|
providerDefaultEffort,
|
|
1313
|
-
matrix: resolvedValue.matrix,
|
|
1264
|
+
matrix: resolvedValue.matrixCompatibility ?? resolvedValue.matrix,
|
|
1314
1265
|
providers,
|
|
1315
1266
|
};
|
|
1316
1267
|
}
|
|
@@ -1356,6 +1307,7 @@ async function resolveCeilingValue(provider, resolvedConfig, projectPath, depend
|
|
|
1356
1307
|
source: 'invocation',
|
|
1357
1308
|
preset: invocationCeilingTier,
|
|
1358
1309
|
matrix: projectCeiling?.matrix ?? baseCeiling.matrix,
|
|
1310
|
+
matrixCompatibility: projectCeiling?.matrixCompatibility ?? baseCeiling.matrixCompatibility,
|
|
1359
1311
|
cellSource: null,
|
|
1360
1312
|
target: null,
|
|
1361
1313
|
selectionBranch: 'prompt-persisted',
|
|
@@ -1391,6 +1343,7 @@ async function resolveCeilingValue(provider, resolvedConfig, projectPath, depend
|
|
|
1391
1343
|
],
|
|
1392
1344
|
requestedCandidate,
|
|
1393
1345
|
candidateTier: selected.candidateTier,
|
|
1346
|
+
candidateIndex: selected.candidateIndex,
|
|
1394
1347
|
ceilingTarget: ceilingCell?.target ?? null,
|
|
1395
1348
|
...(ceilingTier ? { ceilingTier } : {}),
|
|
1396
1349
|
};
|
|
@@ -1533,14 +1486,91 @@ function writeHumanResolution(context, resolution) {
|
|
|
1533
1486
|
}
|
|
1534
1487
|
}
|
|
1535
1488
|
}
|
|
1489
|
+
function reportRole(action) {
|
|
1490
|
+
if (action === 'review') {
|
|
1491
|
+
return 'reviewer';
|
|
1492
|
+
}
|
|
1493
|
+
return action === 'fix' ? 'fix' : 'implementer';
|
|
1494
|
+
}
|
|
1495
|
+
function reportControl(axis, dispatchArgs, reason) {
|
|
1496
|
+
if (axis.startsWith('selected:')) {
|
|
1497
|
+
return {
|
|
1498
|
+
value: axis.slice('selected:'.length),
|
|
1499
|
+
mechanism: dispatchArgs && 'variant' in dispatchArgs
|
|
1500
|
+
? 'materialized-role'
|
|
1501
|
+
: 'task-model-argument',
|
|
1502
|
+
reason,
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
if (axis === 'inherited') {
|
|
1506
|
+
return { value: null, mechanism: 'host-inherited', reason };
|
|
1507
|
+
}
|
|
1508
|
+
if (axis === 'provider-default') {
|
|
1509
|
+
return { value: null, mechanism: 'provider-default', reason };
|
|
1510
|
+
}
|
|
1511
|
+
if (axis === 'not-applicable') {
|
|
1512
|
+
return { value: null, mechanism: 'not-applicable', reason };
|
|
1513
|
+
}
|
|
1514
|
+
return { value: null, mechanism: 'base-role', reason };
|
|
1515
|
+
}
|
|
1516
|
+
function buildResolutionReport(resolution, options) {
|
|
1517
|
+
const hasScope = options.reportScope !== undefined;
|
|
1518
|
+
const hasAction = options.reportAction !== undefined;
|
|
1519
|
+
if (!hasScope && !hasAction) {
|
|
1520
|
+
return null;
|
|
1521
|
+
}
|
|
1522
|
+
if (!hasScope || !hasAction) {
|
|
1523
|
+
throw new Error('--report-scope and --report-action must be provided together.');
|
|
1524
|
+
}
|
|
1525
|
+
const scope = options.reportScope.trim();
|
|
1526
|
+
if (!scope) {
|
|
1527
|
+
throw new Error('--report-scope must be a non-empty value.');
|
|
1528
|
+
}
|
|
1529
|
+
const action = options.reportAction;
|
|
1530
|
+
const role = reportRole(action);
|
|
1531
|
+
const providerResolution = resolution.providers[resolution.provider];
|
|
1532
|
+
if (!providerResolution) {
|
|
1533
|
+
throw new Error(`Dispatch report resolution is missing provider data for "${resolution.provider}".`);
|
|
1534
|
+
}
|
|
1535
|
+
const expectedResolverRole = role === 'reviewer' ? 'reviewer' : 'implementer';
|
|
1536
|
+
if (providerResolution.selection.role !== expectedResolverRole) {
|
|
1537
|
+
throw new Error(`Invalid dispatch report action/role context: ${action}/${role} requires resolver role ${expectedResolverRole}, received ${providerResolution.selection.role}.`);
|
|
1538
|
+
}
|
|
1539
|
+
return buildDispatchReport({
|
|
1540
|
+
scope,
|
|
1541
|
+
action,
|
|
1542
|
+
role,
|
|
1543
|
+
resolution,
|
|
1544
|
+
requestedControls: {
|
|
1545
|
+
model: reportControl(providerResolution.modelAxis, providerResolution.dispatchArgs, 'Derived from the completed resolver model-axis result.'),
|
|
1546
|
+
effort: reportControl(providerResolution.effortAxis, providerResolution.dispatchArgs, 'Derived from the completed resolver effort-axis result.'),
|
|
1547
|
+
},
|
|
1548
|
+
configuredDefaults: {
|
|
1549
|
+
model: null,
|
|
1550
|
+
modelSource: null,
|
|
1551
|
+
effort: resolution.provider === 'codex' &&
|
|
1552
|
+
resolution.providerDefaultEffort !== 'unknown'
|
|
1553
|
+
? resolution.providerDefaultEffort
|
|
1554
|
+
: null,
|
|
1555
|
+
effortSource: resolution.provider === 'codex' &&
|
|
1556
|
+
resolution.providerDefaultEffort !== 'unknown'
|
|
1557
|
+
? 'codex-config'
|
|
1558
|
+
: null,
|
|
1559
|
+
},
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1536
1562
|
async function runDispatchCeilingResolve(context, dependencies, options) {
|
|
1537
1563
|
try {
|
|
1538
1564
|
const resolution = await resolveDispatchCeiling(context, dependencies, options);
|
|
1565
|
+
const dispatchReport = buildResolutionReport(resolution, options);
|
|
1539
1566
|
if (context.json) {
|
|
1540
|
-
context.logger.json(resolution);
|
|
1567
|
+
context.logger.json(dispatchReport ? { ...resolution, dispatchReport } : resolution);
|
|
1541
1568
|
}
|
|
1542
1569
|
else {
|
|
1543
1570
|
writeHumanResolution(context, resolution);
|
|
1571
|
+
if (dispatchReport) {
|
|
1572
|
+
context.logger.info(formatDispatchReport(dispatchReport));
|
|
1573
|
+
}
|
|
1544
1574
|
}
|
|
1545
1575
|
process.exitCode = resolution.status === 'blocked' ? 1 : 0;
|
|
1546
1576
|
}
|
|
@@ -1602,6 +1632,8 @@ export function createProjectDispatchCeilingCommand(overrides = {}) {
|
|
|
1602
1632
|
.option('--project-path <path>', 'Read project-state policy from an explicit project path')
|
|
1603
1633
|
.option('--preflight', 'Treat unresolved non-interactive resolution as an implementation block')
|
|
1604
1634
|
.option('--non-interactive', 'Force non-interactive block behavior when the ceiling is unresolved')
|
|
1635
|
+
.option('--report-scope <scope>', 'Include Dispatch Report V1 for the explicit workflow scope')
|
|
1636
|
+
.addOption(new Option('--report-action <action>', 'Dispatch Report V1 action; must match the resolver role').choices(['implementation', 'fix', 'review']))
|
|
1605
1637
|
.option('--json', 'Output machine-readable JSON')
|
|
1606
1638
|
.action(async (options, cmd) => {
|
|
1607
1639
|
const globalOptions = readGlobalOptions(cmd);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type WorkflowDispatchMatrixTier = 'economy' | 'balanced' | 'high' | 'frontier';
|
|
2
|
+
export interface WorkflowDispatchRouteTarget {
|
|
3
|
+
harness?: string;
|
|
4
|
+
model?: string;
|
|
5
|
+
effort?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface DispatchRouteTargetValidation {
|
|
8
|
+
valid: boolean;
|
|
9
|
+
reason?: string;
|
|
10
|
+
}
|
|
11
|
+
export type WorkflowDispatchRouteEntry = string | WorkflowDispatchRouteTarget;
|
|
12
|
+
export type WorkflowDispatchRoute = WorkflowDispatchRouteEntry[];
|
|
13
|
+
export interface WorkflowDispatchFallbackRoute {
|
|
14
|
+
route: WorkflowDispatchRoute;
|
|
15
|
+
}
|
|
16
|
+
export type WorkflowDispatchCandidate = WorkflowDispatchRouteEntry | WorkflowDispatchFallbackRoute;
|
|
17
|
+
export interface WorkflowDispatchCandidateLadder {
|
|
18
|
+
candidates: WorkflowDispatchCandidate[];
|
|
19
|
+
}
|
|
20
|
+
export type WorkflowDispatchLegacyMatrixCell = string | WorkflowDispatchRoute;
|
|
21
|
+
export type WorkflowDispatchMatrixCell = WorkflowDispatchCandidateLadder | WorkflowDispatchLegacyMatrixCell;
|
|
22
|
+
export type WorkflowDispatchProviderValue = string | Partial<Record<WorkflowDispatchMatrixTier, WorkflowDispatchMatrixCell>>;
|
|
23
|
+
export type DispatchMatrixNormalizationIssueKind = 'malformed-provider' | 'malformed-tier' | 'malformed-candidate';
|
|
24
|
+
export interface DispatchMatrixNormalizationIssue {
|
|
25
|
+
path: string;
|
|
26
|
+
kind: DispatchMatrixNormalizationIssueKind;
|
|
27
|
+
value: unknown;
|
|
28
|
+
}
|
|
29
|
+
export interface NormalizedDispatchMatrix {
|
|
30
|
+
providers: Record<string, WorkflowDispatchProviderValue>;
|
|
31
|
+
issues: DispatchMatrixNormalizationIssue[];
|
|
32
|
+
}
|
|
33
|
+
export type DispatchMatrixSource = 'local-config' | 'repo-config' | 'user-config' | 'project-state';
|
|
34
|
+
export interface DispatchMatrixWalkContext {
|
|
35
|
+
source: DispatchMatrixSource;
|
|
36
|
+
pathPrefix: string;
|
|
37
|
+
}
|
|
38
|
+
export interface DispatchMatrixCellRef {
|
|
39
|
+
provider: string;
|
|
40
|
+
tier: WorkflowDispatchMatrixTier | null;
|
|
41
|
+
candidateIndex: number | null;
|
|
42
|
+
fallbackRouteIndex: number | null;
|
|
43
|
+
value: string | null;
|
|
44
|
+
target: WorkflowDispatchRouteTarget | null;
|
|
45
|
+
path: string;
|
|
46
|
+
source: DispatchMatrixSource;
|
|
47
|
+
}
|
|
48
|
+
export declare const VALID_DISPATCH_MATRIX_TIERS: readonly WorkflowDispatchMatrixTier[];
|
|
49
|
+
export declare function normalizeDispatchMatrix(value: unknown, options: {
|
|
50
|
+
pathPrefix: string;
|
|
51
|
+
compatibilityMode: 'layered-config' | 'project-state';
|
|
52
|
+
}): NormalizedDispatchMatrix;
|
|
53
|
+
export declare function isCodexMaterializedRouteTarget(provider: string, target: WorkflowDispatchRouteTarget): boolean;
|
|
54
|
+
export declare function validateDispatchRouteTarget(provider: string, target: WorkflowDispatchRouteTarget): DispatchRouteTargetValidation;
|
|
55
|
+
export declare function isWorkflowDispatchFallbackRoute(value: unknown): value is WorkflowDispatchFallbackRoute;
|
|
56
|
+
export declare function isWorkflowDispatchCandidateLadder(value: unknown): value is WorkflowDispatchCandidateLadder;
|
|
57
|
+
export declare function toWorkflowDispatchCandidateLadder(cell: WorkflowDispatchMatrixCell): WorkflowDispatchCandidateLadder;
|
|
58
|
+
export declare function walkDispatchMatrix(providers: Record<string, WorkflowDispatchProviderValue>, context: DispatchMatrixWalkContext): DispatchMatrixCellRef[];
|
|
59
|
+
//# sourceMappingURL=dispatch-matrix.d.ts.map
|