agentxchain 2.154.8 → 2.154.9
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 +45 -17
package/package.json
CHANGED
package/src/commands/resume.js
CHANGED
|
@@ -65,31 +65,59 @@ function entrySatisfiesSyntheticGateVerification(gate, entry) {
|
|
|
65
65
|
return verificationStatus === 'pass' || verificationStatus === 'attested_pass';
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
function
|
|
69
|
-
// Returns
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
// escalation where the agent blocked BEFORE writing gate artifacts
|
|
74
|
-
// (schedule-daemon `needs_decision` fixture).
|
|
68
|
+
function evaluateHumanApprovalGateArtifacts(root, state, config, entry) {
|
|
69
|
+
// Returns the artifact readiness shape for a human-required phase exit gate.
|
|
70
|
+
// A turn can be a valid standing-gate source either by producing one of the
|
|
71
|
+
// required files, or by re-verifying already-complete gate artifacts as the
|
|
72
|
+
// phase entry role before escalating to the human reviewer.
|
|
75
73
|
const phase = state?.phase;
|
|
76
74
|
const gateId = phase ? config?.routing?.[phase]?.exit_gate : null;
|
|
77
|
-
if (!gateId) return false;
|
|
75
|
+
if (!gateId) return { ready: false, contributed: false, entryRole: false };
|
|
78
76
|
const gate = config?.gates?.[gateId];
|
|
79
77
|
if (!gate || !Array.isArray(gate.requires_files) || gate.requires_files.length === 0) {
|
|
80
|
-
return false;
|
|
78
|
+
return { ready: false, contributed: false, entryRole: false };
|
|
81
79
|
}
|
|
82
|
-
if (!gate.requires_human_approval) return false;
|
|
80
|
+
if (!gate.requires_human_approval) return { ready: false, contributed: false, entryRole: false };
|
|
83
81
|
const filesChanged = Array.isArray(entry?.files_changed) ? entry.files_changed : [];
|
|
84
82
|
const required = gate.requires_files.filter((p) => typeof p === 'string' && p.trim());
|
|
85
|
-
if (required.length === 0) return false;
|
|
83
|
+
if (required.length === 0) return { ready: false, contributed: false, entryRole: false };
|
|
86
84
|
const changedSet = new Set(filesChanged.filter((p) => typeof p === 'string'));
|
|
87
85
|
const contributed = required.some((relPath) => changedSet.has(relPath));
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
|
|
86
|
+
const ready = required.every((relPath) => existsSync(join(root, relPath)));
|
|
87
|
+
const entryRole = Boolean(config?.routing?.[phase]?.entry_role)
|
|
88
|
+
&& (entry?.role === config.routing[phase].entry_role || entry?.assigned_role === config.routing[phase].entry_role);
|
|
89
|
+
return { ready, contributed, entryRole };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function turnCanApproveHumanGateFromEscalation(root, state, config, entry, proposed) {
|
|
93
|
+
const artifacts = evaluateHumanApprovalGateArtifacts(root, state, config, entry);
|
|
94
|
+
if (!artifacts.ready) return false;
|
|
95
|
+
if (artifacts.contributed) return true;
|
|
96
|
+
const phase = state?.phase;
|
|
97
|
+
const gateId = phase ? config?.routing?.[phase]?.exit_gate : null;
|
|
98
|
+
const gateApprovalText = [
|
|
99
|
+
entry?.summary,
|
|
100
|
+
entry?.needs_human_reason,
|
|
101
|
+
entry?.verification?.evidence_summary,
|
|
102
|
+
state?.blocked_on,
|
|
103
|
+
state?.blocked_reason?.recovery?.detail,
|
|
104
|
+
...(Array.isArray(entry?.decisions)
|
|
105
|
+
? entry.decisions.flatMap((decision) => [decision?.statement, decision?.rationale])
|
|
106
|
+
: []),
|
|
107
|
+
]
|
|
108
|
+
.filter((value) => typeof value === 'string' && value.trim())
|
|
109
|
+
.join('\n')
|
|
110
|
+
.toLowerCase();
|
|
111
|
+
const referencesHumanGateApproval = Boolean(gateId)
|
|
112
|
+
&& (
|
|
113
|
+
gateApprovalText.includes(gateId.toLowerCase())
|
|
114
|
+
|| (gateApprovalText.includes('gate') && gateApprovalText.includes('human approval'))
|
|
115
|
+
|| (gateApprovalText.includes('gate') && gateApprovalText.includes('human-required'))
|
|
116
|
+
);
|
|
117
|
+
// tusq.dev BUG-52 real shape: PM re-verified already-complete planning
|
|
118
|
+
// artifacts, changed no files by design, and escalated to human because the
|
|
119
|
+
// gate requires human approval. The human unblock is the gate approval.
|
|
120
|
+
return proposed === 'human' && artifacts.entryRole && referencesHumanGateApproval;
|
|
93
121
|
}
|
|
94
122
|
|
|
95
123
|
function latestCompletedTurnWantsPhaseContinuation(root, state, config, opts = {}) {
|
|
@@ -130,7 +158,7 @@ function latestCompletedTurnWantsPhaseContinuation(root, state, config, opts = {
|
|
|
130
158
|
if (
|
|
131
159
|
entry.status === 'needs_human'
|
|
132
160
|
&& entrySatisfiesSyntheticGateVerification(standingGate, entry)
|
|
133
|
-
&&
|
|
161
|
+
&& turnCanApproveHumanGateFromEscalation(root, state, config, entry, proposed)
|
|
134
162
|
) {
|
|
135
163
|
return true;
|
|
136
164
|
}
|