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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.154.5",
3
+ "version": "2.154.7",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
- return Boolean(proposed && proposed !== 'human');
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
  }