agentxchain 2.93.0 → 2.94.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.93.0",
3
+ "version": "2.94.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -575,6 +575,11 @@ function renderContext(state, config, root, turn, role) {
575
575
  lines.push(` - ${req}`);
576
576
  }
577
577
  }
578
+ if (Array.isArray(dc.required_decision_ids) && dc.required_decision_ids.length > 0) {
579
+ lines.push(`- **Required decisions:** ${dc.required_decision_ids.join(', ')}`);
580
+ lines.push('');
581
+ lines.push('Your accepted turn must emit these decision IDs in `decisions[]` before the parent review may advance the phase or complete the run.');
582
+ }
578
583
  lines.push('');
579
584
  lines.push('Focus exclusively on the charter above. Do not expand scope beyond the delegation.');
580
585
  lines.push('');
@@ -599,9 +604,14 @@ function renderContext(state, config, root, turn, role) {
599
604
  if (result.verification?.status) {
600
605
  lines.push(`- **Verification:** ${result.verification.status}`);
601
606
  }
607
+ if (Array.isArray(result.required_decision_ids) && result.required_decision_ids.length > 0) {
608
+ lines.push(`- **Required decisions:** ${result.required_decision_ids.join(', ')}`);
609
+ lines.push(`- **Satisfied decisions:** ${(result.satisfied_decision_ids || []).join(', ') || 'none'}`);
610
+ lines.push(`- **Missing decisions:** ${(result.missing_decision_ids || []).join(', ') || 'none'}`);
611
+ }
602
612
  lines.push('');
603
613
  }
604
- lines.push('Evaluate whether each delegation met its acceptance contract.');
614
+ lines.push('Evaluate whether each delegation met its acceptance contract and returned any required named decisions.');
605
615
  lines.push('Your turn result should assess the delegation outcomes and decide next steps.');
606
616
  lines.push('');
607
617
  }
package/src/lib/export.js CHANGED
@@ -279,6 +279,9 @@ export function buildDelegationSummary(files) {
279
279
  delegation_id: del.id,
280
280
  to_role: del.to_role,
281
281
  charter: del.charter,
282
+ required_decision_ids: Array.isArray(del.required_decision_ids) ? del.required_decision_ids : [],
283
+ satisfied_decision_ids: Array.isArray(reviewResult?.satisfied_decision_ids) ? reviewResult.satisfied_decision_ids : [],
284
+ missing_decision_ids: Array.isArray(reviewResult?.missing_decision_ids) ? reviewResult.missing_decision_ids : [],
282
285
  status: reviewResult?.status || child?.status || 'pending',
283
286
  child_turn_id: child?.turn_id || null,
284
287
  };
@@ -2152,6 +2152,7 @@ export function assignGovernedTurn(root, config, roleId) {
2152
2152
  parent_role: pendingDelegation.parent_role,
2153
2153
  charter: pendingDelegation.charter,
2154
2154
  acceptance_contract: pendingDelegation.acceptance_contract,
2155
+ required_decision_ids: pendingDelegation.required_decision_ids || [],
2155
2156
  };
2156
2157
  // Mark the delegation as active
2157
2158
  pendingDelegation.status = 'active';
@@ -2742,6 +2743,7 @@ function _acceptGovernedTurnLocked(root, config, opts) {
2742
2743
  to_role: delegation.to_role,
2743
2744
  charter: delegation.charter,
2744
2745
  acceptance_contract: delegation.acceptance_contract,
2746
+ required_decision_ids: delegation.required_decision_ids || [],
2745
2747
  })),
2746
2748
  }
2747
2749
  : {}),
@@ -2753,6 +2755,7 @@ function _acceptGovernedTurnLocked(root, config, opts) {
2753
2755
  parent_role: currentTurn.delegation_context.parent_role,
2754
2756
  charter: currentTurn.delegation_context.charter,
2755
2757
  acceptance_contract: currentTurn.delegation_context.acceptance_contract,
2758
+ required_decision_ids: currentTurn.delegation_context.required_decision_ids || [],
2756
2759
  },
2757
2760
  }
2758
2761
  : {}),
@@ -2833,6 +2836,7 @@ function _acceptGovernedTurnLocked(root, config, opts) {
2833
2836
  to_role: del.to_role,
2834
2837
  charter: del.charter,
2835
2838
  acceptance_contract: del.acceptance_contract,
2839
+ required_decision_ids: del.required_decision_ids || [],
2836
2840
  status: 'pending',
2837
2841
  child_turn_id: null,
2838
2842
  created_at: now,
@@ -2859,12 +2863,23 @@ function _acceptGovernedTurnLocked(root, config, opts) {
2859
2863
  // Build delegation review context
2860
2864
  const delegationResults = parentDelegations.map(d => {
2861
2865
  const childHistory = nextHistoryEntries.find(h => h.turn_id === d.child_turn_id);
2866
+ const childDecisionIds = Array.isArray(childHistory?.decisions)
2867
+ ? childHistory.decisions
2868
+ .map((decision) => decision?.id)
2869
+ .filter((id) => typeof id === 'string')
2870
+ : [];
2871
+ const requiredDecisionIds = Array.isArray(d.required_decision_ids) ? d.required_decision_ids : [];
2872
+ const satisfiedDecisionIds = requiredDecisionIds.filter((id) => childDecisionIds.includes(id));
2873
+ const missingDecisionIds = requiredDecisionIds.filter((id) => !childDecisionIds.includes(id));
2862
2874
  return {
2863
2875
  delegation_id: d.delegation_id,
2864
2876
  child_turn_id: d.child_turn_id,
2865
2877
  to_role: d.to_role,
2866
2878
  charter: d.charter,
2867
2879
  acceptance_contract: d.acceptance_contract,
2880
+ required_decision_ids: requiredDecisionIds,
2881
+ satisfied_decision_ids: satisfiedDecisionIds,
2882
+ missing_decision_ids: missingDecisionIds,
2868
2883
  summary: childHistory?.summary || '(no summary)',
2869
2884
  status: d.status,
2870
2885
  files_changed: childHistory?.files_changed || [],
package/src/lib/report.js CHANGED
@@ -33,6 +33,9 @@ function normalizeDelegationSummary(summary) {
33
33
  delegation_id: delegation.delegation_id,
34
34
  to_role: delegation.to_role,
35
35
  charter: delegation.charter,
36
+ required_decision_ids: Array.isArray(delegation.required_decision_ids) ? delegation.required_decision_ids : [],
37
+ satisfied_decision_ids: Array.isArray(delegation.satisfied_decision_ids) ? delegation.satisfied_decision_ids : [],
38
+ missing_decision_ids: Array.isArray(delegation.missing_decision_ids) ? delegation.missing_decision_ids : [],
36
39
  status: delegation.status,
37
40
  child_turn_id: delegation.child_turn_id,
38
41
  });
@@ -1290,6 +1293,11 @@ export function formatGovernanceReportText(report) {
1290
1293
  lines.push(` - ${chain.parent_role} (${chain.parent_turn_id}) | outcome: ${chain.outcome} | review: ${chain.review_turn_id || 'pending'}`);
1291
1294
  for (const delegation of chain.delegations) {
1292
1295
  lines.push(` ${delegation.delegation_id} -> ${delegation.to_role} | ${delegation.status} | child: ${delegation.child_turn_id || 'pending'} | ${delegation.charter}`);
1296
+ if (delegation.required_decision_ids?.length > 0) {
1297
+ lines.push(` required decisions: ${delegation.required_decision_ids.join(', ')}`);
1298
+ lines.push(` satisfied decisions: ${delegation.satisfied_decision_ids.join(', ') || 'none'}`);
1299
+ lines.push(` missing decisions: ${delegation.missing_decision_ids.join(', ') || 'none'}`);
1300
+ }
1293
1301
  }
1294
1302
  }
1295
1303
  }
@@ -1773,7 +1781,7 @@ export function formatGovernanceReportMarkdown(report) {
1773
1781
  if (run.delegation_summary?.delegation_chains?.length > 0) {
1774
1782
  lines.push('', '## Delegation Summary', '');
1775
1783
  lines.push(`- Total delegations issued: ${run.delegation_summary.total_delegations_issued}`, '');
1776
- lines.push('| Parent Role | Parent Turn | Outcome | Review Turn | Delegation | Child Turn | Status | Charter |', '|-------------|-------------|---------|-------------|------------|------------|--------|---------|');
1784
+ lines.push('| Parent Role | Parent Turn | Outcome | Review Turn | Delegation | Child Turn | Status | Required Decisions | Missing Decisions | Charter |', '|-------------|-------------|---------|-------------|------------|------------|--------|--------------------|-------------------|---------|');
1777
1785
  for (const chain of run.delegation_summary.delegation_chains) {
1778
1786
  for (let i = 0; i < chain.delegations.length; i++) {
1779
1787
  const delegation = chain.delegations[i];
@@ -1782,7 +1790,9 @@ export function formatGovernanceReportMarkdown(report) {
1782
1790
  const outcome = i === 0 ? `\`${chain.outcome}\`` : '';
1783
1791
  const reviewTurn = i === 0 ? `\`${chain.review_turn_id || 'pending'}\`` : '';
1784
1792
  const charter = delegation.charter.replace(/\|/g, '\\|');
1785
- lines.push(`| ${parentRole} | ${parentTurn} | ${outcome} | ${reviewTurn} | \`${delegation.delegation_id}\` \`${delegation.to_role}\` | \`${delegation.child_turn_id || 'pending'}\` | \`${delegation.status}\` | ${charter} |`);
1793
+ const requiredDecisions = (delegation.required_decision_ids || []).join(', ').replace(/\|/g, '\\|') || '';
1794
+ const missingDecisions = (delegation.missing_decision_ids || []).join(', ').replace(/\|/g, '\\|') || '—';
1795
+ lines.push(`| ${parentRole} | ${parentTurn} | ${outcome} | ${reviewTurn} | \`${delegation.delegation_id}\` → \`${delegation.to_role}\` | \`${delegation.child_turn_id || 'pending'}\` | \`${delegation.status}\` | ${requiredDecisions} | ${missingDecisions} | ${charter} |`);
1786
1796
  }
1787
1797
  }
1788
1798
  }
@@ -2392,11 +2402,13 @@ function renderRunHtml(report) {
2392
2402
  `<code>${esc(d.delegation_id)}</code> &rarr; <code>${esc(d.to_role)}</code>`,
2393
2403
  `<code>${esc(d.child_turn_id || 'pending')}</code>`,
2394
2404
  badge(d.status),
2405
+ esc((d.required_decision_ids || []).join(', ') || '\u2014'),
2406
+ esc((d.missing_decision_ids || []).join(', ') || '\u2014'),
2395
2407
  esc(d.charter),
2396
2408
  ]);
2397
2409
  }
2398
2410
  }
2399
- delHtml += htmlTable(['Parent Role', 'Parent Turn', 'Outcome', 'Review Turn', 'Delegation', 'Child Turn', 'Status', 'Charter'], rows);
2411
+ delHtml += htmlTable(['Parent Role', 'Parent Turn', 'Outcome', 'Review Turn', 'Delegation', 'Child Turn', 'Status', 'Required Decisions', 'Missing Decisions', 'Charter'], rows);
2400
2412
  sections.push(`<div class="section">${htmlSection('Delegation Summary', delHtml)}</div>`);
2401
2413
  }
2402
2414
 
@@ -268,6 +268,16 @@
268
268
  "items": { "type": "string", "minLength": 1 },
269
269
  "minItems": 1,
270
270
  "description": "What the delegate must achieve for this delegation to be considered complete."
271
+ },
272
+ "required_decision_ids": {
273
+ "type": "array",
274
+ "items": {
275
+ "type": "string",
276
+ "pattern": "^DEC-\\d+$"
277
+ },
278
+ "minItems": 1,
279
+ "uniqueItems": true,
280
+ "description": "Optional named decisions the delegated child must emit before the parent review turn may advance phase/run lifecycle."
271
281
  }
272
282
  }
273
283
  }
@@ -263,6 +263,16 @@ function validateSchema(tr) {
263
263
  }
264
264
  }
265
265
 
266
+ if ('delegations' in tr) {
267
+ if (!Array.isArray(tr.delegations)) {
268
+ errors.push('delegations must be an array.');
269
+ } else {
270
+ for (let i = 0; i < tr.delegations.length; i++) {
271
+ errors.push(...validateDelegation(tr.delegations[i], i));
272
+ }
273
+ }
274
+ }
275
+
266
276
  errors.push(...collectUnfilledTemplatePlaceholderErrors(tr));
267
277
 
268
278
  return errors;
@@ -382,6 +392,54 @@ function validateObjection(obj, index) {
382
392
  return errors;
383
393
  }
384
394
 
395
+ function validateDelegation(del, index) {
396
+ const errors = [];
397
+ const prefix = `delegations[${index}]`;
398
+
399
+ if (del === null || typeof del !== 'object' || Array.isArray(del)) {
400
+ return [`${prefix} must be an object.`];
401
+ }
402
+ if (typeof del.id !== 'string' || !/^del-\d{3}$/.test(del.id)) {
403
+ errors.push(`${prefix}.id must match pattern del-NNN.`);
404
+ }
405
+ if (typeof del.to_role !== 'string' || !/^[a-z0-9_-]+$/.test(del.to_role)) {
406
+ errors.push(`${prefix}.to_role must match pattern ^[a-z0-9_-]+$.`);
407
+ }
408
+ if (typeof del.charter !== 'string' || !del.charter.trim()) {
409
+ errors.push(`${prefix}.charter must be a non-empty string.`);
410
+ }
411
+ if (!Array.isArray(del.acceptance_contract) || del.acceptance_contract.length === 0) {
412
+ errors.push(`${prefix}.acceptance_contract must be a non-empty array.`);
413
+ } else {
414
+ for (let i = 0; i < del.acceptance_contract.length; i++) {
415
+ if (typeof del.acceptance_contract[i] !== 'string' || !del.acceptance_contract[i].trim()) {
416
+ errors.push(`${prefix}.acceptance_contract[${i}] must be a non-empty string.`);
417
+ }
418
+ }
419
+ }
420
+ if (del.required_decision_ids !== undefined) {
421
+ if (!Array.isArray(del.required_decision_ids) || del.required_decision_ids.length === 0) {
422
+ errors.push(`${prefix}.required_decision_ids must be a non-empty array when provided.`);
423
+ } else {
424
+ const seen = new Set();
425
+ for (let i = 0; i < del.required_decision_ids.length; i++) {
426
+ const id = del.required_decision_ids[i];
427
+ if (typeof id !== 'string' || !/^DEC-\d+$/.test(id)) {
428
+ errors.push(`${prefix}.required_decision_ids[${i}] must match pattern DEC-NNN.`);
429
+ continue;
430
+ }
431
+ if (seen.has(id)) {
432
+ errors.push(`${prefix}.required_decision_ids contains duplicate "${id}".`);
433
+ continue;
434
+ }
435
+ seen.add(id);
436
+ }
437
+ }
438
+ }
439
+
440
+ return errors;
441
+ }
442
+
385
443
  // ── Stage B: Assignment Validation ───────────────────────────────────────────
386
444
 
387
445
  function validateAssignment(tr, state) {
@@ -563,6 +621,7 @@ function validateProtocol(tr, state, config) {
563
621
 
564
622
  const role = config.roles?.[tr.role];
565
623
  const writeAuthority = role?.write_authority;
624
+ const activeTurn = getActiveTurn(state) || state?.current_turn || null;
566
625
 
567
626
  // Challenge requirement: review_only roles MUST raise at least one objection
568
627
  if (config.rules?.challenge_required !== false) {
@@ -628,7 +687,6 @@ function validateProtocol(tr, state, config) {
628
687
  }
629
688
 
630
689
  // No recursive delegation: if this turn is a delegation review, it cannot delegate further
631
- const activeTurn = state?.active_turns ? Object.values(state.active_turns)[0] : null;
632
690
  if (activeTurn?.delegation_context) {
633
691
  errors.push('Delegation review turns cannot contain further delegations.');
634
692
  }
@@ -663,6 +721,26 @@ function validateProtocol(tr, state, config) {
663
721
  }
664
722
  }
665
723
 
724
+ if (activeTurn?.delegation_review) {
725
+ const unmetDecisionContracts = (activeTurn.delegation_review.results || [])
726
+ .filter((result) => Array.isArray(result?.missing_decision_ids) && result.missing_decision_ids.length > 0);
727
+ if (unmetDecisionContracts.length > 0) {
728
+ const detail = unmetDecisionContracts
729
+ .map((result) => `${result.delegation_id}: ${result.missing_decision_ids.join(', ')}`)
730
+ .join('; ');
731
+ if (tr.phase_transition_request) {
732
+ errors.push(
733
+ `Delegation review cannot request phase transition while required child decisions are missing: ${detail}.`
734
+ );
735
+ }
736
+ if (tr.run_completion_request) {
737
+ errors.push(
738
+ `Delegation review cannot request run completion while required child decisions are missing: ${detail}.`
739
+ );
740
+ }
741
+ }
742
+ }
743
+
666
744
  return { errors, warnings };
667
745
  }
668
746