agentxchain 2.154.5 → 2.154.7
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/src/commands/resume.js +69 -1
package/package.json
CHANGED
package/src/commands/resume.js
CHANGED
|
@@ -50,6 +50,48 @@ function hasStandingPendingExitGate(state, config) {
|
|
|
50
50
|
return Boolean(gateId && (state?.phase_gate_status || {})[gateId] === 'pending');
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
function getStandingPendingExitGate(state, config) {
|
|
54
|
+
const phase = state?.phase;
|
|
55
|
+
const gateId = phase ? config?.routing?.[phase]?.exit_gate : null;
|
|
56
|
+
if (!gateId || (state?.phase_gate_status || {})[gateId] !== 'pending') {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return config?.gates?.[gateId] || null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function entrySatisfiesSyntheticGateVerification(gate, entry) {
|
|
63
|
+
if (!gate?.requires_verification_pass) return true;
|
|
64
|
+
const verificationStatus = entry?.verification?.status;
|
|
65
|
+
return verificationStatus === 'pass' || verificationStatus === 'attested_pass';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function turnContributedToHumanApprovalGateArtifacts(root, state, config, entry) {
|
|
69
|
+
// Returns true only when the accepted turn itself produced at least one of
|
|
70
|
+
// the phase exit gate's required_files AND all required_files are present
|
|
71
|
+
// on disk. This distinguishes a PM turn that finished phase work and
|
|
72
|
+
// escalated for final sign-off (BUG-52 third variant) from a generic
|
|
73
|
+
// escalation where the agent blocked BEFORE writing gate artifacts
|
|
74
|
+
// (schedule-daemon `needs_decision` fixture).
|
|
75
|
+
const phase = state?.phase;
|
|
76
|
+
const gateId = phase ? config?.routing?.[phase]?.exit_gate : null;
|
|
77
|
+
if (!gateId) return false;
|
|
78
|
+
const gate = config?.gates?.[gateId];
|
|
79
|
+
if (!gate || !Array.isArray(gate.requires_files) || gate.requires_files.length === 0) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
if (!gate.requires_human_approval) return false;
|
|
83
|
+
const filesChanged = Array.isArray(entry?.files_changed) ? entry.files_changed : [];
|
|
84
|
+
const required = gate.requires_files.filter((p) => typeof p === 'string' && p.trim());
|
|
85
|
+
if (required.length === 0) return false;
|
|
86
|
+
const changedSet = new Set(filesChanged.filter((p) => typeof p === 'string'));
|
|
87
|
+
const contributed = required.some((relPath) => changedSet.has(relPath));
|
|
88
|
+
if (!contributed) return false;
|
|
89
|
+
for (const relPath of required) {
|
|
90
|
+
if (!existsSync(join(root, relPath))) return false;
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
53
95
|
function latestCompletedTurnWantsPhaseContinuation(root, state, config) {
|
|
54
96
|
const turnId = state?.last_completed_turn_id || state?.blocked_reason?.turn_id || null;
|
|
55
97
|
if (!turnId) return false;
|
|
@@ -65,8 +107,34 @@ function latestCompletedTurnWantsPhaseContinuation(root, state, config) {
|
|
|
65
107
|
}
|
|
66
108
|
if (entry?.turn_id !== turnId) continue;
|
|
67
109
|
if (entry.phase_transition_request) return true;
|
|
110
|
+
const standingGate = getStandingPendingExitGate(state, config);
|
|
68
111
|
const proposed = typeof entry.proposed_next_role === 'string' ? entry.proposed_next_role.trim() : '';
|
|
69
|
-
|
|
112
|
+
if (proposed && proposed !== 'human') {
|
|
113
|
+
return entrySatisfiesSyntheticGateVerification(standingGate, entry);
|
|
114
|
+
}
|
|
115
|
+
// Turn 205 extension (refines DEC-BUG52-UNBLOCK-STANDING-GATE-DISCRIMINATOR-001):
|
|
116
|
+
// The realistic BUG-52 third-variant PM shape sets `status: 'needs_human'`,
|
|
117
|
+
// `phase_transition_request: null`, and `proposed_next_role: 'human'` — the
|
|
118
|
+
// PM is escalating specifically for the phase exit gate's human-approval
|
|
119
|
+
// check and has already written the gate's required artifacts to disk.
|
|
120
|
+
// When the current phase's exit gate requires human approval, all of its
|
|
121
|
+
// `requires_files` are present on disk, and any gate-level verification
|
|
122
|
+
// predicate was satisfied by the accepted turn, an `unblock` on that
|
|
123
|
+
// escalation IS the final gate approval and must run the standing-gate
|
|
124
|
+
// reconcile.
|
|
125
|
+
//
|
|
126
|
+
// Distinguished from generic schedule-daemon `needs_decision` escalations
|
|
127
|
+
// (which block BEFORE the agent writes gate artifacts, so `requires_files`
|
|
128
|
+
// are not yet on disk) — those correctly continue to re-dispatch the
|
|
129
|
+
// in-phase role rather than force-advancing the phase.
|
|
130
|
+
if (
|
|
131
|
+
entry.status === 'needs_human'
|
|
132
|
+
&& entrySatisfiesSyntheticGateVerification(standingGate, entry)
|
|
133
|
+
&& turnContributedToHumanApprovalGateArtifacts(root, state, config, entry)
|
|
134
|
+
) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
70
138
|
}
|
|
71
139
|
return false;
|
|
72
140
|
}
|