agentxchain 2.41.0 → 2.43.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 +1 -1
- package/scripts/release-bump.sh +14 -1
- package/src/lib/turn-result-validator.js +113 -12
package/package.json
CHANGED
package/scripts/release-bump.sh
CHANGED
|
@@ -56,6 +56,8 @@ ALLOWED_RELEASE_PATHS=(
|
|
|
56
56
|
".agentxchain-conformance/capabilities.json"
|
|
57
57
|
"website-v2/docs/protocol-implementor-guide.mdx"
|
|
58
58
|
".planning/LAUNCH_EVIDENCE_REPORT.md"
|
|
59
|
+
"website-v2/static/llms.txt"
|
|
60
|
+
"website-v2/static/sitemap.xml"
|
|
59
61
|
"cli/homebrew/agentxchain.rb"
|
|
60
62
|
"cli/homebrew/README.md"
|
|
61
63
|
)
|
|
@@ -168,6 +170,17 @@ if ! grep -qE "^# Launch Evidence Report — AgentXchain v${ESCAPED_VERSION}" "$
|
|
|
168
170
|
SURFACE_ERRORS+=("LAUNCH_EVIDENCE_REPORT.md title does not carry v${TARGET_VERSION}")
|
|
169
171
|
fi
|
|
170
172
|
|
|
173
|
+
# 4h. llms.txt must list the current release notes route
|
|
174
|
+
CURRENT_RELEASE_ROUTE="/docs/releases/${RELEASE_DOC_ID}"
|
|
175
|
+
if ! grep -q "${CURRENT_RELEASE_ROUTE}" "${REPO_ROOT}/website-v2/static/llms.txt" 2>/dev/null; then
|
|
176
|
+
SURFACE_ERRORS+=("website-v2/static/llms.txt does not list '${CURRENT_RELEASE_ROUTE}'")
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# 4i. sitemap.xml must list the current release notes route
|
|
180
|
+
if ! grep -q "${CURRENT_RELEASE_ROUTE}" "${REPO_ROOT}/website-v2/static/sitemap.xml" 2>/dev/null; then
|
|
181
|
+
SURFACE_ERRORS+=("website-v2/static/sitemap.xml does not list '${CURRENT_RELEASE_ROUTE}'")
|
|
182
|
+
fi
|
|
183
|
+
|
|
171
184
|
if [[ "${#SURFACE_ERRORS[@]}" -gt 0 ]]; then
|
|
172
185
|
echo "FAIL: ${#SURFACE_ERRORS[@]} version-surface(s) not aligned to ${TARGET_VERSION}:" >&2
|
|
173
186
|
printf ' - %s\n' "${SURFACE_ERRORS[@]}" >&2
|
|
@@ -176,7 +189,7 @@ if [[ "${#SURFACE_ERRORS[@]}" -gt 0 ]]; then
|
|
|
176
189
|
echo "create release identity when governed surfaces are stale." >&2
|
|
177
190
|
exit 1
|
|
178
191
|
fi
|
|
179
|
-
echo " OK: all
|
|
192
|
+
echo " OK: all 9 governed version surfaces reference ${TARGET_VERSION}"
|
|
180
193
|
|
|
181
194
|
# 5. Auto-align Homebrew mirror to target version
|
|
182
195
|
# The formula URL and README version/tarball are updated automatically.
|
|
@@ -79,6 +79,7 @@ export function validateStagedTurnResult(root, state, config, opts = {}) {
|
|
|
79
79
|
const activeTurn = getActiveTurn(state) || state.current_turn;
|
|
80
80
|
if (activeTurn) {
|
|
81
81
|
const roleKey = activeTurn.assigned_role || activeTurn.role;
|
|
82
|
+
normContext.assignedRole = roleKey;
|
|
82
83
|
const roleConfig = config?.roles?.[roleKey];
|
|
83
84
|
if (roleConfig) {
|
|
84
85
|
normContext.writeAuthority = roleConfig.write_authority;
|
|
@@ -562,6 +563,23 @@ export function normalizeTurnResult(tr, config, context = {}) {
|
|
|
562
563
|
}
|
|
563
564
|
|
|
564
565
|
const normalized = { ...tr };
|
|
566
|
+
const routing = config?.routing;
|
|
567
|
+
const phaseNames = routing ? Object.keys(routing) : [];
|
|
568
|
+
const currentPhase = context.phase;
|
|
569
|
+
const currentPhaseIndex = currentPhase ? phaseNames.indexOf(currentPhase) : -1;
|
|
570
|
+
const isKnownPhase = currentPhaseIndex >= 0;
|
|
571
|
+
const isTerminalPhase = isKnownPhase && currentPhaseIndex === phaseNames.length - 1;
|
|
572
|
+
const nextPhase = isKnownPhase && currentPhaseIndex + 1 < phaseNames.length
|
|
573
|
+
? phaseNames[currentPhaseIndex + 1]
|
|
574
|
+
: null;
|
|
575
|
+
const allowedNextRoles = isKnownPhase ? (routing?.[currentPhase]?.allowed_next_roles || []) : [];
|
|
576
|
+
const assignedRole = context.assignedRole || normalized.role || null;
|
|
577
|
+
const isReviewOnly = context.writeAuthority === 'review_only';
|
|
578
|
+
|
|
579
|
+
const pickAllowedRoleFallback = () => {
|
|
580
|
+
if (allowedNextRoles.length === 0) return null;
|
|
581
|
+
return allowedNextRoles.find((role) => role !== assignedRole) || allowedNextRoles[0] || null;
|
|
582
|
+
};
|
|
565
583
|
|
|
566
584
|
// ── Rule 0: infer missing status only when intent is unambiguous ──────
|
|
567
585
|
if (!('status' in normalized)) {
|
|
@@ -604,7 +622,6 @@ export function normalizeTurnResult(tr, config, context = {}) {
|
|
|
604
622
|
}
|
|
605
623
|
|
|
606
624
|
// ── Rule 2: exit-gate-as-phase auto-correction ────────────────────────
|
|
607
|
-
const routing = config?.routing;
|
|
608
625
|
const gates = config?.gates;
|
|
609
626
|
if (
|
|
610
627
|
typeof normalized.phase_transition_request === 'string' &&
|
|
@@ -643,33 +660,117 @@ export function normalizeTurnResult(tr, config, context = {}) {
|
|
|
643
660
|
}
|
|
644
661
|
}
|
|
645
662
|
|
|
646
|
-
// ── Rule 3: review_only
|
|
663
|
+
// ── Rule 3: review_only needs_human → lifecycle correction ──────────
|
|
664
|
+
// review_only roles cannot perform work that genuinely requires human
|
|
665
|
+
// intervention. If the model says "needs_human" with an affirmative,
|
|
666
|
+
// non-blocker reason, correct to the appropriate lifecycle signal:
|
|
667
|
+
// terminal phase → run_completion_request, non-terminal → phase_transition.
|
|
647
668
|
if (
|
|
648
669
|
context.writeAuthority === 'review_only' &&
|
|
649
670
|
context.phase &&
|
|
650
671
|
routing &&
|
|
651
672
|
normalized.status === 'needs_human' &&
|
|
652
|
-
normalized.run_completion_request !== false
|
|
673
|
+
normalized.run_completion_request !== false &&
|
|
674
|
+
typeof normalized.needs_human_reason === 'string'
|
|
653
675
|
) {
|
|
654
|
-
const
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
const isBlocker = blockerSignals.test(reason);
|
|
662
|
-
if (isAffirmative && !isBlocker) {
|
|
676
|
+
const reason = normalized.needs_human_reason.toLowerCase();
|
|
677
|
+
const affirmativeSignals = /\b(approv|ship|release|sign.?off|no.?block|ready|pass|good|accept|green.?light|proceed|move.?forward|complet|done|lgtm|satisf|recommend)\b/i;
|
|
678
|
+
const blockerSignals = /\b(critical|security|fail|block|cannot|must.?fix|regression|vulnerab|reject|unsafe|broken)\b/i;
|
|
679
|
+
const isAffirmative = affirmativeSignals.test(reason);
|
|
680
|
+
const isBlocker = blockerSignals.test(reason);
|
|
681
|
+
if (isAffirmative && !isBlocker) {
|
|
682
|
+
if (isTerminalPhase) {
|
|
663
683
|
corrections.push(
|
|
664
684
|
`status: corrected review_only terminal "needs_human" to run_completion_request — reason indicated ship readiness ("${normalized.needs_human_reason.slice(0, 80)}"), not a genuine blocker`
|
|
665
685
|
);
|
|
666
686
|
normalized.status = 'completed';
|
|
667
687
|
normalized.run_completion_request = true;
|
|
668
688
|
delete normalized.needs_human_reason;
|
|
689
|
+
} else if (nextPhase) {
|
|
690
|
+
corrections.push(
|
|
691
|
+
`status: corrected review_only "needs_human" to phase_transition_request "${nextPhase}" — reason indicated forward progress ("${normalized.needs_human_reason.slice(0, 80)}"), not a genuine blocker`
|
|
692
|
+
);
|
|
693
|
+
normalized.status = 'completed';
|
|
694
|
+
normalized.phase_transition_request = nextPhase;
|
|
695
|
+
delete normalized.needs_human_reason;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// ── Rule 4: infer missing lifecycle signal for completed turns ────────
|
|
701
|
+
if (
|
|
702
|
+
isKnownPhase &&
|
|
703
|
+
isReviewOnly &&
|
|
704
|
+
normalized.status === 'completed' &&
|
|
705
|
+
normalized.run_completion_request == null &&
|
|
706
|
+
!normalized.phase_transition_request
|
|
707
|
+
) {
|
|
708
|
+
if (isTerminalPhase) {
|
|
709
|
+
normalized.run_completion_request = true;
|
|
710
|
+
corrections.push(
|
|
711
|
+
`run_completion_request: inferred true for completed terminal phase "${currentPhase}"`
|
|
712
|
+
);
|
|
713
|
+
} else if (nextPhase) {
|
|
714
|
+
normalized.phase_transition_request = nextPhase;
|
|
715
|
+
corrections.push(
|
|
716
|
+
`phase_transition_request: inferred next phase "${nextPhase}" for completed phase "${currentPhase}"`
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// ── Rule 5: correct invalid or non-forward lifecycle requests ─────────
|
|
722
|
+
if (
|
|
723
|
+
isKnownPhase &&
|
|
724
|
+
isReviewOnly &&
|
|
725
|
+
normalized.status === 'completed' &&
|
|
726
|
+
typeof normalized.phase_transition_request === 'string' &&
|
|
727
|
+
!normalized.run_completion_request
|
|
728
|
+
) {
|
|
729
|
+
const requested = normalized.phase_transition_request;
|
|
730
|
+
const requestedIndex = phaseNames.indexOf(requested);
|
|
731
|
+
const invalidPhase = !(requested in (routing || {}));
|
|
732
|
+
const notForward = requestedIndex >= 0 && requestedIndex <= currentPhaseIndex;
|
|
733
|
+
|
|
734
|
+
if (invalidPhase || notForward) {
|
|
735
|
+
if (nextPhase) {
|
|
736
|
+
normalized.phase_transition_request = nextPhase;
|
|
737
|
+
corrections.push(
|
|
738
|
+
`phase_transition_request: corrected "${requested}" to forward phase "${nextPhase}"`
|
|
739
|
+
);
|
|
740
|
+
} else if (isTerminalPhase) {
|
|
741
|
+
normalized.phase_transition_request = null;
|
|
742
|
+
normalized.run_completion_request = true;
|
|
743
|
+
corrections.push(
|
|
744
|
+
`phase_transition_request: corrected terminal/non-forward "${requested}" to run_completion_request: true`
|
|
745
|
+
);
|
|
669
746
|
}
|
|
670
747
|
}
|
|
671
748
|
}
|
|
672
749
|
|
|
750
|
+
// ── Rule 6: repair routing-illegal next-role signals only when safe ───
|
|
751
|
+
if (isReviewOnly && normalized.run_completion_request === true && !normalized.phase_transition_request) {
|
|
752
|
+
if (normalized.proposed_next_role !== 'human') {
|
|
753
|
+
normalized.proposed_next_role = 'human';
|
|
754
|
+
corrections.push('proposed_next_role: corrected to "human" for run completion');
|
|
755
|
+
}
|
|
756
|
+
} else if (
|
|
757
|
+
isKnownPhase &&
|
|
758
|
+
isReviewOnly &&
|
|
759
|
+
normalized.status === 'completed' &&
|
|
760
|
+
typeof normalized.proposed_next_role === 'string' &&
|
|
761
|
+
normalized.proposed_next_role !== 'human' &&
|
|
762
|
+
allowedNextRoles.length > 0 &&
|
|
763
|
+
!allowedNextRoles.includes(normalized.proposed_next_role)
|
|
764
|
+
) {
|
|
765
|
+
const fallback = pickAllowedRoleFallback();
|
|
766
|
+
if (fallback) {
|
|
767
|
+
corrections.push(
|
|
768
|
+
`proposed_next_role: corrected "${normalized.proposed_next_role}" to allowed role "${fallback}"`
|
|
769
|
+
);
|
|
770
|
+
normalized.proposed_next_role = fallback;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
673
774
|
return { normalized, corrections };
|
|
674
775
|
}
|
|
675
776
|
|