@open-agent-toolkit/cli 0.1.48 → 0.1.51

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.
Files changed (53) hide show
  1. package/assets/agents/oat-phase-implementer.md +16 -5
  2. package/assets/docs/cli-utilities/configuration.md +41 -0
  3. package/assets/docs/cli-utilities/workflow-gates.md +18 -1
  4. package/assets/docs/provider-sync/commands.md +29 -1
  5. package/assets/docs/provider-sync/providers.md +18 -5
  6. package/assets/docs/provider-sync/scope-and-surface.md +13 -0
  7. package/assets/docs/workflows/projects/dispatch-ceiling.md +59 -7
  8. package/assets/docs/workflows/projects/implementation-execution.md +63 -3
  9. package/assets/docs/workflows/projects/reviews.md +18 -1
  10. package/assets/public-package-versions.json +4 -4
  11. package/assets/skills/oat-agent-instructions-analyze/references/docs/provider-reference.md +8 -2
  12. package/assets/skills/oat-agent-instructions-apply/references/docs/provider-reference.md +8 -2
  13. package/assets/skills/oat-project-implement/SKILL.md +141 -59
  14. package/assets/skills/oat-project-review-provide/SKILL.md +44 -10
  15. package/assets/skills/oat-project-review-provide-remote/SKILL.md +28 -1
  16. package/dist/commands/config/index.d.ts +3 -0
  17. package/dist/commands/config/index.d.ts.map +1 -1
  18. package/dist/commands/config/index.js +70 -128
  19. package/dist/commands/doctor/index.d.ts +3 -0
  20. package/dist/commands/doctor/index.d.ts.map +1 -1
  21. package/dist/commands/doctor/index.js +205 -195
  22. package/dist/commands/gate/index.d.ts.map +1 -1
  23. package/dist/commands/gate/index.js +72 -1
  24. package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
  25. package/dist/commands/project/dispatch-ceiling/index.js +224 -112
  26. package/dist/commands/providers/codex/materialize.d.ts.map +1 -1
  27. package/dist/commands/providers/codex/materialize.js +31 -15
  28. package/dist/config/dispatch-matrix.d.ts +59 -0
  29. package/dist/config/dispatch-matrix.d.ts.map +1 -0
  30. package/dist/config/dispatch-matrix.js +264 -0
  31. package/dist/config/oat-config.d.ts +2 -28
  32. package/dist/config/oat-config.d.ts.map +1 -1
  33. package/dist/config/oat-config.js +8 -144
  34. package/dist/config/resolve.d.ts.map +1 -1
  35. package/dist/config/resolve.js +2 -1
  36. package/dist/providers/codex/codec/config-merge.d.ts +8 -2
  37. package/dist/providers/codex/codec/config-merge.d.ts.map +1 -1
  38. package/dist/providers/codex/codec/config-merge.js +17 -2
  39. package/dist/providers/codex/codec/sync-extension.d.ts.map +1 -1
  40. package/dist/providers/codex/codec/sync-extension.js +14 -4
  41. package/dist/providers/identity/availability.d.ts +12 -0
  42. package/dist/providers/identity/availability.d.ts.map +1 -1
  43. package/dist/providers/identity/availability.js +72 -29
  44. package/dist/providers/identity/dispatch-report.d.ts +124 -0
  45. package/dist/providers/identity/dispatch-report.d.ts.map +1 -0
  46. package/dist/providers/identity/dispatch-report.js +285 -0
  47. package/dist/providers/identity/dispatch-validation.d.ts +28 -0
  48. package/dist/providers/identity/dispatch-validation.d.ts.map +1 -0
  49. package/dist/providers/identity/dispatch-validation.js +149 -0
  50. package/dist/providers/identity/stamp.d.ts +2 -0
  51. package/dist/providers/identity/stamp.d.ts.map +1 -1
  52. package/dist/providers/identity/stamp.js +3 -1
  53. package/package.json +2 -2
@@ -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 normalizeProjectMatrixBareValue(provider, value) {
129
- if (typeof value !== 'string') {
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;
130
+ function isProjectMatrixRecord(value) {
131
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
141
132
  }
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;
155
- }
156
- function normalizeProjectMatrixRoute(provider, value) {
157
- if (!Array.isArray(value) || value.length === 0) {
158
- return undefined;
159
- }
160
- const route = [];
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 target = normalizeProjectMatrixRouteTarget(entry);
168
- if (target !== undefined) {
169
- route.push(target);
170
- }
171
- }
172
- return route.length > 0 ? route : undefined;
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);
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
- return candidates.length > 0 ? { candidates } : undefined;
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 Object.keys(tierMap).length > 0 ? tierMap : undefined;
163
+ return compatibility;
221
164
  }
222
165
  function readProjectDispatchMatrix(value) {
223
166
  if (value === undefined || value === null) {
224
- return { matrix: null, warnings: [] };
225
- }
226
- if (typeof value !== 'object' || Array.isArray(value)) {
227
- return {
228
- matrix: null,
229
- warnings: [
230
- 'Ignoring malformed oat_dispatch_policy.matrix in project state.',
231
- ],
232
- };
233
- }
234
- const matrix = {};
235
- for (const [provider, rawProviderValue] of Object.entries(value)) {
236
- if (!provider.trim()) {
237
- continue;
238
- }
239
- const normalized = normalizeProjectMatrixProviderValue(provider, rawProviderValue);
240
- if (normalized !== undefined) {
241
- matrix[provider] = normalized;
242
- }
243
- }
244
- if (Object.keys(matrix).length === 0) {
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) {
245
175
  return {
246
176
  matrix: null,
177
+ compatibilityMatrix: null,
247
178
  warnings: [
248
179
  'Ignoring malformed oat_dispatch_policy.matrix in project state.',
249
180
  ],
250
181
  };
251
182
  }
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,
@@ -1224,6 +1175,58 @@ function readCodexDefaultFromToml(content) {
1224
1175
  const value = parsed['model_reasoning_effort'];
1225
1176
  return typeof value === 'string' && value.trim() ? value.trim() : null;
1226
1177
  }
1178
+ function readCodexMaxDepthEntry(content) {
1179
+ let parsed;
1180
+ try {
1181
+ parsed = TOML.parse(content);
1182
+ }
1183
+ catch {
1184
+ return { present: true, value: null };
1185
+ }
1186
+ const agents = parsed.agents;
1187
+ if (!agents || typeof agents !== 'object' || Array.isArray(agents)) {
1188
+ return { present: false, value: undefined };
1189
+ }
1190
+ const record = agents;
1191
+ return {
1192
+ present: Object.prototype.hasOwnProperty.call(record, 'max_depth'),
1193
+ value: record.max_depth,
1194
+ };
1195
+ }
1196
+ async function readCodexMaxDepthAtPath(path, dependencies) {
1197
+ if (!(await dependencies.pathExists(path))) {
1198
+ return { present: false, value: undefined };
1199
+ }
1200
+ try {
1201
+ return readCodexMaxDepthEntry(await dependencies.readFile(path));
1202
+ }
1203
+ catch {
1204
+ return { present: true, value: null };
1205
+ }
1206
+ }
1207
+ /**
1208
+ * Resolves the depth seen by Codex: project config wins whenever it declares a
1209
+ * value, while an absent project value inherits the user config.
1210
+ */
1211
+ async function resolveEffectiveCodexMaxDepth(scope, repoRoot, context, dependencies) {
1212
+ const userPath = join(context.home, '.codex', 'config.toml');
1213
+ if (scope === 'user') {
1214
+ return readCodexMaxDepthAtPath(userPath, dependencies);
1215
+ }
1216
+ const projectEntry = await readCodexMaxDepthAtPath(join(repoRoot, '.codex', 'config.toml'), dependencies);
1217
+ return projectEntry.present
1218
+ ? projectEntry
1219
+ : readCodexMaxDepthAtPath(userPath, dependencies);
1220
+ }
1221
+ function managedCodexDepthBlockMessage(scope, entry) {
1222
+ const validDepth = typeof entry.value === 'number' && Number.isFinite(entry.value);
1223
+ const state = !entry.present
1224
+ ? 'agents.max_depth is missing'
1225
+ : validDepth
1226
+ ? `agents.max_depth is ${entry.value}`
1227
+ : 'agents.max_depth is not a valid number';
1228
+ return `BLOCKED: Codex ${state}; managed implementation requires root (0) → phase coordinator (1) → task worker (2).\nRun \`oat sync --scope ${scope}\`, or materialize a single role with \`oat providers codex materialize <agent-name> --model <model> --effort <effort> --scope ${scope}\`.`;
1229
+ }
1227
1230
  async function resolveCodexProviderDefaultEffort(repoRoot, context, dependencies) {
1228
1231
  const candidates = [
1229
1232
  join(repoRoot, '.codex', 'config.toml'),
@@ -1273,6 +1276,34 @@ async function resolveDispatchCeiling(context, dependencies, options) {
1273
1276
  [provider]: providerResolution,
1274
1277
  };
1275
1278
  if (resolvedValue) {
1279
+ const requiresManagedCodexDepth = options.preflight === true &&
1280
+ provider === 'codex' &&
1281
+ role === 'implementer' &&
1282
+ resolvedValue.mode === 'managed';
1283
+ if (requiresManagedCodexDepth) {
1284
+ const scope = context.scope === 'user' ? 'user' : 'project';
1285
+ const depth = await resolveEffectiveCodexMaxDepth(scope, repoRoot, context, dependencies);
1286
+ const numericDepth = typeof depth.value === 'number' && Number.isFinite(depth.value)
1287
+ ? depth.value
1288
+ : null;
1289
+ if (numericDepth === null || numericDepth < 2) {
1290
+ return {
1291
+ status: 'blocked',
1292
+ provider,
1293
+ value: resolvedValue.value,
1294
+ policyMode: resolvedValue.mode,
1295
+ policy: resolvedValue.policy,
1296
+ source: resolvedValue.source,
1297
+ preset: resolvedValue.preset,
1298
+ unresolved: true,
1299
+ projectPath,
1300
+ providerDefaultEffort,
1301
+ matrix: resolvedValue.matrix,
1302
+ providers,
1303
+ message: managedCodexDepthBlockMessage(scope, depth),
1304
+ };
1305
+ }
1306
+ }
1276
1307
  const incompleteManagedPreflight = options.preflight === true &&
1277
1308
  resolvedValue.mode === 'managed' &&
1278
1309
  providerResolution.mode === 'advisory' &&
@@ -1294,7 +1325,7 @@ async function resolveDispatchCeiling(context, dependencies, options) {
1294
1325
  unresolved: true,
1295
1326
  projectPath,
1296
1327
  providerDefaultEffort,
1297
- matrix: resolvedValue.matrix,
1328
+ matrix: resolvedValue.matrixCompatibility ?? resolvedValue.matrix,
1298
1329
  providers,
1299
1330
  message,
1300
1331
  };
@@ -1310,7 +1341,7 @@ async function resolveDispatchCeiling(context, dependencies, options) {
1310
1341
  unresolved: false,
1311
1342
  projectPath,
1312
1343
  providerDefaultEffort,
1313
- matrix: resolvedValue.matrix,
1344
+ matrix: resolvedValue.matrixCompatibility ?? resolvedValue.matrix,
1314
1345
  providers,
1315
1346
  };
1316
1347
  }
@@ -1356,6 +1387,7 @@ async function resolveCeilingValue(provider, resolvedConfig, projectPath, depend
1356
1387
  source: 'invocation',
1357
1388
  preset: invocationCeilingTier,
1358
1389
  matrix: projectCeiling?.matrix ?? baseCeiling.matrix,
1390
+ matrixCompatibility: projectCeiling?.matrixCompatibility ?? baseCeiling.matrixCompatibility,
1359
1391
  cellSource: null,
1360
1392
  target: null,
1361
1393
  selectionBranch: 'prompt-persisted',
@@ -1391,6 +1423,7 @@ async function resolveCeilingValue(provider, resolvedConfig, projectPath, depend
1391
1423
  ],
1392
1424
  requestedCandidate,
1393
1425
  candidateTier: selected.candidateTier,
1426
+ candidateIndex: selected.candidateIndex,
1394
1427
  ceilingTarget: ceilingCell?.target ?? null,
1395
1428
  ...(ceilingTier ? { ceilingTier } : {}),
1396
1429
  };
@@ -1533,14 +1566,91 @@ function writeHumanResolution(context, resolution) {
1533
1566
  }
1534
1567
  }
1535
1568
  }
1569
+ function reportRole(action) {
1570
+ if (action === 'review') {
1571
+ return 'reviewer';
1572
+ }
1573
+ return action === 'fix' ? 'fix' : 'implementer';
1574
+ }
1575
+ function reportControl(axis, dispatchArgs, reason) {
1576
+ if (axis.startsWith('selected:')) {
1577
+ return {
1578
+ value: axis.slice('selected:'.length),
1579
+ mechanism: dispatchArgs && 'variant' in dispatchArgs
1580
+ ? 'materialized-role'
1581
+ : 'task-model-argument',
1582
+ reason,
1583
+ };
1584
+ }
1585
+ if (axis === 'inherited') {
1586
+ return { value: null, mechanism: 'host-inherited', reason };
1587
+ }
1588
+ if (axis === 'provider-default') {
1589
+ return { value: null, mechanism: 'provider-default', reason };
1590
+ }
1591
+ if (axis === 'not-applicable') {
1592
+ return { value: null, mechanism: 'not-applicable', reason };
1593
+ }
1594
+ return { value: null, mechanism: 'base-role', reason };
1595
+ }
1596
+ function buildResolutionReport(resolution, options) {
1597
+ const hasScope = options.reportScope !== undefined;
1598
+ const hasAction = options.reportAction !== undefined;
1599
+ if (!hasScope && !hasAction) {
1600
+ return null;
1601
+ }
1602
+ if (!hasScope || !hasAction) {
1603
+ throw new Error('--report-scope and --report-action must be provided together.');
1604
+ }
1605
+ const scope = options.reportScope.trim();
1606
+ if (!scope) {
1607
+ throw new Error('--report-scope must be a non-empty value.');
1608
+ }
1609
+ const action = options.reportAction;
1610
+ const role = reportRole(action);
1611
+ const providerResolution = resolution.providers[resolution.provider];
1612
+ if (!providerResolution) {
1613
+ throw new Error(`Dispatch report resolution is missing provider data for "${resolution.provider}".`);
1614
+ }
1615
+ const expectedResolverRole = role === 'reviewer' ? 'reviewer' : 'implementer';
1616
+ if (providerResolution.selection.role !== expectedResolverRole) {
1617
+ throw new Error(`Invalid dispatch report action/role context: ${action}/${role} requires resolver role ${expectedResolverRole}, received ${providerResolution.selection.role}.`);
1618
+ }
1619
+ return buildDispatchReport({
1620
+ scope,
1621
+ action,
1622
+ role,
1623
+ resolution,
1624
+ requestedControls: {
1625
+ model: reportControl(providerResolution.modelAxis, providerResolution.dispatchArgs, 'Derived from the completed resolver model-axis result.'),
1626
+ effort: reportControl(providerResolution.effortAxis, providerResolution.dispatchArgs, 'Derived from the completed resolver effort-axis result.'),
1627
+ },
1628
+ configuredDefaults: {
1629
+ model: null,
1630
+ modelSource: null,
1631
+ effort: resolution.provider === 'codex' &&
1632
+ resolution.providerDefaultEffort !== 'unknown'
1633
+ ? resolution.providerDefaultEffort
1634
+ : null,
1635
+ effortSource: resolution.provider === 'codex' &&
1636
+ resolution.providerDefaultEffort !== 'unknown'
1637
+ ? 'codex-config'
1638
+ : null,
1639
+ },
1640
+ });
1641
+ }
1536
1642
  async function runDispatchCeilingResolve(context, dependencies, options) {
1537
1643
  try {
1538
1644
  const resolution = await resolveDispatchCeiling(context, dependencies, options);
1645
+ const dispatchReport = buildResolutionReport(resolution, options);
1539
1646
  if (context.json) {
1540
- context.logger.json(resolution);
1647
+ context.logger.json(dispatchReport ? { ...resolution, dispatchReport } : resolution);
1541
1648
  }
1542
1649
  else {
1543
1650
  writeHumanResolution(context, resolution);
1651
+ if (dispatchReport) {
1652
+ context.logger.info(formatDispatchReport(dispatchReport));
1653
+ }
1544
1654
  }
1545
1655
  process.exitCode = resolution.status === 'blocked' ? 1 : 0;
1546
1656
  }
@@ -1602,6 +1712,8 @@ export function createProjectDispatchCeilingCommand(overrides = {}) {
1602
1712
  .option('--project-path <path>', 'Read project-state policy from an explicit project path')
1603
1713
  .option('--preflight', 'Treat unresolved non-interactive resolution as an implementation block')
1604
1714
  .option('--non-interactive', 'Force non-interactive block behavior when the ceiling is unresolved')
1715
+ .option('--report-scope <scope>', 'Include Dispatch Report V1 for the explicit workflow scope')
1716
+ .addOption(new Option('--report-action <action>', 'Dispatch Report V1 action; must match the resolver role').choices(['implementation', 'fix', 'review']))
1605
1717
  .option('--json', 'Output machine-readable JSON')
1606
1718
  .action(async (options, cmd) => {
1607
1719
  const globalOptions = readGlobalOptions(cmd);
@@ -1 +1 @@
1
- {"version":3,"file":"materialize.d.ts","sourceRoot":"","sources":["../../../../src/commands/providers/codex/materialize.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,sBAAsB,EACtB,qCAAqC,EACtC,MAAM,qCAAqC,CAAC;AAU7C,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAsD5C,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,sBAAsB,GAC7B,MAAM,CAaR;AA+GD,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,OAAO,CAAC,qCAAqC,CAAM,GAC7D,OAAO,CAoCT"}
1
+ {"version":3,"file":"materialize.d.ts","sourceRoot":"","sources":["../../../../src/commands/providers/codex/materialize.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,sBAAsB,EACtB,qCAAqC,EACtC,MAAM,qCAAqC,CAAC;AAa7C,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAyD5C,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,sBAAsB,GAC7B,MAAM,CAaR;AAsID,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,OAAO,CAAC,qCAAqC,CAAM,GAC7D,OAAO,CAoCT"}
@@ -6,7 +6,7 @@ import { readGlobalOptions } from '../../shared/shared.utils.js';
6
6
  import { CliError } from '../../../errors/index.js';
7
7
  import { ensureDir, fileExists } from '../../../fs/io.js';
8
8
  import { resolveProjectRoot, resolveScopeRoot } from '../../../fs/paths.js';
9
- import { mergeCodexConfigForRole } from '../../../providers/codex/codec/config-merge.js';
9
+ import { mergeCodexConfigForRole, readCodexMaxDepth, } from '../../../providers/codex/codec/config-merge.js';
10
10
  import { materializeCodexRole } from '../../../providers/codex/codec/materialize.js';
11
11
  import { withOatManagedCodexRoleOwner } from '../../../providers/codex/codec/shared.js';
12
12
  import { Command, Option } from 'commander';
@@ -72,8 +72,29 @@ async function buildCodexMaterializePlan(agentName, options, context, dependenci
72
72
  };
73
73
  const rolePath = join(scopeRoot, '.codex', role.configFile);
74
74
  const configPath = join(scopeRoot, '.codex', 'config.toml');
75
+ const existingRoleContent = await readOptionalFile(rolePath);
76
+ const existingConfigContent = await readOptionalFile(configPath);
77
+ let inheritedMaxDepth;
78
+ if (scope === 'project') {
79
+ const userScopeRoot = await dependencies.resolveScopeRoot('user', context);
80
+ const userConfigContent = await readOptionalFile(join(userScopeRoot, '.codex', 'config.toml'));
81
+ inheritedMaxDepth = readCodexMaxDepth(userConfigContent) ?? undefined;
82
+ }
83
+ const mergedConfig = mergeCodexConfigForRole({
84
+ existingContent: existingConfigContent,
85
+ role: {
86
+ roleName: role.roleName,
87
+ description: role.description,
88
+ configFile: role.configFile,
89
+ },
90
+ inheritedMaxDepth,
91
+ });
75
92
  return {
93
+ configChanged: mergedConfig.changed,
94
+ mergedConfigContent: mergedConfig.mergedContent,
76
95
  role,
96
+ roleChanged: existingRoleContent === null ||
97
+ existingRoleContent.trimEnd() !== role.content.trimEnd(),
77
98
  result: {
78
99
  status: context.dryRun ? 'preview' : 'written',
79
100
  dryRun: context.dryRun,
@@ -93,20 +114,15 @@ async function readOptionalFile(filePath) {
93
114
  }
94
115
  return readFile(filePath, 'utf8');
95
116
  }
96
- async function applyCodexMaterializePlan({ result, role, }) {
97
- await ensureDir(dirname(result.rolePath));
98
- await writeFile(result.rolePath, role.content, 'utf8');
99
- const existingConfig = await readOptionalFile(result.configPath);
100
- const mergedConfig = mergeCodexConfigForRole({
101
- existingContent: existingConfig,
102
- role: {
103
- roleName: role.roleName,
104
- description: role.description,
105
- configFile: role.configFile,
106
- },
107
- });
108
- await ensureDir(dirname(result.configPath));
109
- await writeFile(result.configPath, mergedConfig.mergedContent, 'utf8');
117
+ async function applyCodexMaterializePlan({ configChanged, mergedConfigContent, result, role, roleChanged, }) {
118
+ if (roleChanged) {
119
+ await ensureDir(dirname(result.rolePath));
120
+ await writeFile(result.rolePath, role.content, 'utf8');
121
+ }
122
+ if (configChanged) {
123
+ await ensureDir(dirname(result.configPath));
124
+ await writeFile(result.configPath, mergedConfigContent, 'utf8');
125
+ }
110
126
  }
111
127
  async function runCodexMaterializeCommand(agentName, options, context, dependencies) {
112
128
  try {
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch-matrix.d.ts","sourceRoot":"","sources":["../../src/config/dispatch-matrix.ts"],"names":[],"mappings":"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"}