agentxchain 2.103.0 → 2.105.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/README.md +13 -7
- package/bin/agentxchain.js +16 -8
- package/dashboard/app.js +111 -7
- package/dashboard/components/blocked.js +95 -11
- package/dashboard/components/blockers.js +85 -86
- package/dashboard/components/coordinator-timeouts.js +13 -0
- package/dashboard/components/cross-repo.js +17 -12
- package/dashboard/components/gate.js +31 -11
- package/dashboard/components/initiative.js +173 -78
- package/dashboard/components/ledger.js +28 -0
- package/dashboard/components/live-status.js +39 -0
- package/dashboard/components/run-history.js +76 -1
- package/dashboard/components/timeline.js +5 -1
- package/dashboard/index.html +21 -0
- package/dashboard/live-observer.js +91 -0
- package/package.json +1 -1
- package/scripts/release-bump.sh +26 -3
- package/scripts/release-preflight.sh +82 -38
- package/src/commands/accept-turn.js +3 -3
- package/src/commands/decisions.js +98 -29
- package/src/commands/diff.js +27 -4
- package/src/commands/doctor.js +48 -16
- package/src/commands/generate.js +126 -1
- package/src/commands/history.js +21 -3
- package/src/commands/init.js +15 -97
- package/src/commands/multi.js +223 -54
- package/src/commands/phase.js +11 -13
- package/src/commands/reject-turn.js +1 -1
- package/src/commands/restart.js +28 -11
- package/src/commands/resume.js +6 -6
- package/src/commands/role.js +51 -14
- package/src/commands/run.js +5 -11
- package/src/commands/status.js +145 -13
- package/src/commands/step.js +36 -29
- package/src/lib/admission-control.js +14 -12
- package/src/lib/blocked-state.js +150 -0
- package/src/lib/conflict-actions.js +17 -0
- package/src/lib/context-section-parser.js +2 -0
- package/src/lib/continuity-status.js +1 -1
- package/src/lib/coordinator-blocker-presentation.js +127 -0
- package/src/lib/coordinator-event-narrative.js +43 -0
- package/src/lib/coordinator-gate-approval.js +98 -0
- package/src/lib/coordinator-gate-evaluation-presentation.js +57 -0
- package/src/lib/coordinator-next-actions.js +128 -0
- package/src/lib/coordinator-pending-gate-presentation.js +79 -0
- package/src/lib/coordinator-presentation-detail.js +11 -0
- package/src/lib/coordinator-repo-snapshots.js +53 -0
- package/src/lib/coordinator-repo-status-presentation.js +134 -0
- package/src/lib/dashboard/actions.js +105 -29
- package/src/lib/dashboard/bridge-server.js +7 -0
- package/src/lib/dashboard/coordinator-blockers.js +17 -0
- package/src/lib/dashboard/coordinator-repo-status.js +50 -0
- package/src/lib/dashboard/coordinator-timeout-status.js +34 -11
- package/src/lib/dashboard/state-reader.js +36 -1
- package/src/lib/dispatch-bundle.js +23 -0
- package/src/lib/export-diff.js +70 -38
- package/src/lib/export-verifier.js +3 -0
- package/src/lib/history-diff-summary.js +249 -0
- package/src/lib/manual-qa-fallback.js +18 -0
- package/src/lib/normalized-config.js +27 -22
- package/src/lib/planning-artifacts.js +131 -0
- package/src/lib/recent-event-summary.js +132 -0
- package/src/lib/repo-decisions.js +69 -28
- package/src/lib/report.js +353 -145
- package/src/lib/run-diff.js +4 -0
- package/src/lib/runtime-capabilities.js +222 -0
package/src/commands/multi.js
CHANGED
|
@@ -11,7 +11,16 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import chalk from 'chalk';
|
|
14
|
+
import { getCoordinatorBlockerDetails } from '../lib/coordinator-blocker-presentation.js';
|
|
15
|
+
import {
|
|
16
|
+
normalizeCoordinatorGateApprovalFailure,
|
|
17
|
+
normalizeCoordinatorGateApprovalSuccess,
|
|
18
|
+
} from '../lib/coordinator-gate-approval.js';
|
|
14
19
|
import { loadCoordinatorConfig } from '../lib/coordinator-config.js';
|
|
20
|
+
import { deriveCoordinatorNextActions } from '../lib/coordinator-next-actions.js';
|
|
21
|
+
import { getCoordinatorPendingGateDetails } from '../lib/coordinator-pending-gate-presentation.js';
|
|
22
|
+
import { buildCoordinatorRepoStatusRows } from '../lib/coordinator-repo-status-presentation.js';
|
|
23
|
+
import { collectCoordinatorRepoSnapshots } from '../lib/coordinator-repo-snapshots.js';
|
|
15
24
|
import {
|
|
16
25
|
initializeCoordinatorRun,
|
|
17
26
|
loadCoordinatorState,
|
|
@@ -99,9 +108,18 @@ export async function multiStatusCommand(options) {
|
|
|
99
108
|
|
|
100
109
|
const status = getCoordinatorStatus(workspacePath);
|
|
101
110
|
const barriers = readBarriers(workspacePath);
|
|
111
|
+
const configResult = loadCoordinatorConfig(workspacePath);
|
|
112
|
+
const repoRows = buildCoordinatorRepoStatusRows({
|
|
113
|
+
config: configResult.ok ? configResult.config : null,
|
|
114
|
+
coordinatorRepoRuns: status.repo_runs || {},
|
|
115
|
+
});
|
|
116
|
+
const nextActions = deriveCoordinatorCliNextActions(
|
|
117
|
+
status,
|
|
118
|
+
configResult.ok ? configResult.config : null,
|
|
119
|
+
);
|
|
102
120
|
|
|
103
121
|
if (options.json) {
|
|
104
|
-
console.log(JSON.stringify({ ...status, barriers }, null, 2));
|
|
122
|
+
console.log(JSON.stringify({ ...status, barriers, next_actions: nextActions }, null, 2));
|
|
105
123
|
return;
|
|
106
124
|
}
|
|
107
125
|
|
|
@@ -129,14 +147,13 @@ export async function multiStatusCommand(options) {
|
|
|
129
147
|
console.log(`Blocked: ${chalk.red.bold('BLOCKED')} — ${reason}`);
|
|
130
148
|
}
|
|
131
149
|
|
|
132
|
-
// Pending gate
|
|
150
|
+
// Pending gate details
|
|
133
151
|
if (status.pending_gate) {
|
|
134
|
-
|
|
135
|
-
const fromTo = pg.from && pg.to ? ` ${pg.from} → ${pg.to}` : '';
|
|
136
|
-
console.log(`Pending Gate: ${pg.gate} (${pg.gate_type})${fromTo}`);
|
|
137
|
-
console.log(`Action: Run ${chalk.cyan('agentxchain multi approve-gate')} to advance`);
|
|
152
|
+
printCoordinatorPendingGate(status.pending_gate);
|
|
138
153
|
}
|
|
139
154
|
|
|
155
|
+
printCoordinatorNextActions(nextActions);
|
|
156
|
+
|
|
140
157
|
// Completed state
|
|
141
158
|
if (status.status === 'completed') {
|
|
142
159
|
console.log('');
|
|
@@ -148,9 +165,13 @@ export async function multiStatusCommand(options) {
|
|
|
148
165
|
|
|
149
166
|
console.log('');
|
|
150
167
|
console.log('Repos:');
|
|
151
|
-
for (const
|
|
152
|
-
const phase =
|
|
153
|
-
|
|
168
|
+
for (const row of repoRows) {
|
|
169
|
+
const phase = row.phase ? ` [${row.phase}]` : '';
|
|
170
|
+
const details = [
|
|
171
|
+
`run: ${row.run_id || 'unknown'}`,
|
|
172
|
+
...row.details.map((detail) => `${detail.label}: ${detail.value}`),
|
|
173
|
+
];
|
|
174
|
+
console.log(` ${row.repo_id}: ${row.status || 'unknown'}${phase} (${details.join('; ')})`);
|
|
154
175
|
}
|
|
155
176
|
|
|
156
177
|
// Phase gate status
|
|
@@ -183,6 +204,121 @@ function formatCoordinatorStatus(status) {
|
|
|
183
204
|
}
|
|
184
205
|
}
|
|
185
206
|
|
|
207
|
+
function deriveCoordinatorCliNextActions(statusLike, config) {
|
|
208
|
+
const repos = config ? collectCoordinatorRepoSnapshots(config) : [];
|
|
209
|
+
return deriveCoordinatorNextActions({
|
|
210
|
+
status: statusLike?.status ?? null,
|
|
211
|
+
blockedReason: statusLike?.blocked_reason ?? null,
|
|
212
|
+
pendingGate: statusLike?.pending_gate ?? null,
|
|
213
|
+
repos,
|
|
214
|
+
coordinatorRepoRuns: statusLike?.repo_runs || {},
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function printCoordinatorNextActions(nextActions, write = console.log) {
|
|
219
|
+
if (!Array.isArray(nextActions) || nextActions.length === 0) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
write('');
|
|
224
|
+
write('Next Actions:');
|
|
225
|
+
for (const [index, action] of nextActions.entries()) {
|
|
226
|
+
write(` ${index + 1}. ${action.command}`);
|
|
227
|
+
if (action.reason) {
|
|
228
|
+
write(` ${action.reason}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function printCoordinatorPendingGate(pendingGate, write = console.log) {
|
|
234
|
+
const details = getCoordinatorPendingGateDetails({ pendingGate });
|
|
235
|
+
if (details.length === 0) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
write('Pending Gate:');
|
|
240
|
+
for (const detail of details) {
|
|
241
|
+
write(` ${detail.label}: ${detail.value}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function printCoordinatorGateApprovalFailure(failure, write = console.error) {
|
|
246
|
+
write('');
|
|
247
|
+
write('Coordinator Gate Approval Failed');
|
|
248
|
+
write('');
|
|
249
|
+
if (failure.gate_type) {
|
|
250
|
+
write(` Gate Type: ${failure.gate_type}`);
|
|
251
|
+
}
|
|
252
|
+
if (failure.gate) {
|
|
253
|
+
write(` Gate: ${failure.gate}`);
|
|
254
|
+
}
|
|
255
|
+
if (failure.hook_name) {
|
|
256
|
+
write(` Hook: ${failure.hook_name}`);
|
|
257
|
+
} else if (failure.hook_phase) {
|
|
258
|
+
write(` Hook: ${failure.hook_phase}`);
|
|
259
|
+
}
|
|
260
|
+
write(` Error: ${failure.error || 'Coordinator gate approval failed'}`);
|
|
261
|
+
if (failure.recovery_summary?.typed_reason) {
|
|
262
|
+
write(` Reason: ${failure.recovery_summary.typed_reason}`);
|
|
263
|
+
}
|
|
264
|
+
if (failure.recovery_summary?.owner) {
|
|
265
|
+
write(` Owner: ${failure.recovery_summary.owner}`);
|
|
266
|
+
}
|
|
267
|
+
if (failure.recovery_summary?.recovery_action) {
|
|
268
|
+
write(` Action: ${failure.recovery_summary.recovery_action}`);
|
|
269
|
+
}
|
|
270
|
+
if (failure.recovery_summary?.detail) {
|
|
271
|
+
write(` Detail: ${failure.recovery_summary.detail}`);
|
|
272
|
+
}
|
|
273
|
+
printCoordinatorNextActions(failure.next_actions, write);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function normalizeCoordinatorBlockerForPresentation(blocker) {
|
|
277
|
+
if (!blocker || typeof blocker !== 'object' || Array.isArray(blocker)) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (blocker.code === 'repo_run_id_mismatch') {
|
|
282
|
+
return {
|
|
283
|
+
code: blocker.code,
|
|
284
|
+
repo_id: blocker.repo_id ?? null,
|
|
285
|
+
expected_run_id: blocker.expected_run_id ?? null,
|
|
286
|
+
actual_run_id: blocker.actual_run_id ?? null,
|
|
287
|
+
message: blocker.message || null,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (blocker.type === 'run_id_mismatch') {
|
|
292
|
+
return {
|
|
293
|
+
code: 'repo_run_id_mismatch',
|
|
294
|
+
repo_id: blocker.repo_id ?? null,
|
|
295
|
+
expected_run_id: blocker.coordinator_run_id ?? blocker.expected_run_id ?? null,
|
|
296
|
+
actual_run_id: blocker.repo_run_id ?? blocker.actual_run_id ?? null,
|
|
297
|
+
message: blocker.detail || blocker.message || null,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
code: blocker.code || blocker.type || null,
|
|
303
|
+
message: blocker.message || blocker.detail || null,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function printCoordinatorBlockerDetails(blockers, write = console.error) {
|
|
308
|
+
for (const blocker of Array.isArray(blockers) ? blockers : []) {
|
|
309
|
+
const normalized = normalizeCoordinatorBlockerForPresentation(blocker);
|
|
310
|
+
if (!normalized?.message) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const codeTag = normalized.code ? `[${normalized.code}] ` : '';
|
|
315
|
+
write(` - ${codeTag}${normalized.message}`);
|
|
316
|
+
for (const detail of getCoordinatorBlockerDetails(normalized)) {
|
|
317
|
+
write(` ${detail.label}: ${detail.value}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
186
322
|
// ── multi step ─────────────────────────────────────────────────────────────
|
|
187
323
|
|
|
188
324
|
export async function multiStepCommand(options) {
|
|
@@ -214,14 +350,21 @@ export async function multiStepCommand(options) {
|
|
|
214
350
|
// Fire on_escalation hook (advisory — cannot block, only notifies)
|
|
215
351
|
fireEscalationHook(workspacePath, configResult.config, state, state.blocked_reason || 'unknown reason');
|
|
216
352
|
console.error(`Coordinator is blocked: ${state.blocked_reason || 'unknown reason'}`);
|
|
217
|
-
|
|
353
|
+
printCoordinatorNextActions(
|
|
354
|
+
deriveCoordinatorCliNextActions(state, configResult.config),
|
|
355
|
+
console.error,
|
|
356
|
+
);
|
|
218
357
|
process.exitCode = 1;
|
|
219
358
|
return;
|
|
220
359
|
}
|
|
221
360
|
|
|
222
361
|
if (state.pending_gate) {
|
|
223
|
-
console.error(
|
|
224
|
-
console.error
|
|
362
|
+
console.error('Coordinator has a pending gate.');
|
|
363
|
+
printCoordinatorPendingGate(state.pending_gate, console.error);
|
|
364
|
+
printCoordinatorNextActions(
|
|
365
|
+
deriveCoordinatorCliNextActions(state, configResult.config),
|
|
366
|
+
console.error,
|
|
367
|
+
);
|
|
225
368
|
process.exitCode = 1;
|
|
226
369
|
return;
|
|
227
370
|
}
|
|
@@ -235,14 +378,7 @@ export async function multiStepCommand(options) {
|
|
|
235
378
|
// Fire on_escalation for the blocked resync
|
|
236
379
|
fireEscalationHook(workspacePath, configResult.config, state, resync.blocked_reason || 'resync failure');
|
|
237
380
|
console.error(`Coordinator resync entered blocked state: ${resync.blocked_reason || 'unknown reason'}`);
|
|
238
|
-
|
|
239
|
-
const codeTag = mismatch.code ? `[${mismatch.code}] ` : '';
|
|
240
|
-
console.error(` - ${codeTag}${mismatch.message}`);
|
|
241
|
-
if (mismatch.code === 'repo_run_id_mismatch') {
|
|
242
|
-
console.error(` expected: ${mismatch.expected_run_id}`);
|
|
243
|
-
console.error(` actual: ${mismatch.actual_run_id}`);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
381
|
+
printCoordinatorBlockerDetails(resync.mismatch_details, console.error);
|
|
246
382
|
process.exitCode = 1;
|
|
247
383
|
return;
|
|
248
384
|
}
|
|
@@ -308,7 +444,10 @@ export async function multiStepCommand(options) {
|
|
|
308
444
|
} else {
|
|
309
445
|
console.log(`Completion gate requested: ${gate.payload.gate}`);
|
|
310
446
|
}
|
|
311
|
-
|
|
447
|
+
const updatedState = loadCoordinatorState(workspacePath) || state;
|
|
448
|
+
printCoordinatorNextActions(
|
|
449
|
+
deriveCoordinatorCliNextActions(updatedState, configResult.config),
|
|
450
|
+
);
|
|
312
451
|
return;
|
|
313
452
|
}
|
|
314
453
|
|
|
@@ -318,14 +457,7 @@ export async function multiStepCommand(options) {
|
|
|
318
457
|
}
|
|
319
458
|
if (gate.blockers.length > 0) {
|
|
320
459
|
console.error(`Coordinator ${gate.type === 'phase_transition' ? 'phase' : 'completion'} gate is not ready:`);
|
|
321
|
-
|
|
322
|
-
const codeTag = blocker.code ? `[${blocker.code}] ` : '';
|
|
323
|
-
console.error(` - ${codeTag}${blocker.message}`);
|
|
324
|
-
if (blocker.code === 'repo_run_id_mismatch') {
|
|
325
|
-
console.error(` expected: ${blocker.expected_run_id}`);
|
|
326
|
-
console.error(` actual: ${blocker.actual_run_id}`);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
460
|
+
printCoordinatorBlockerDetails(gate.blockers, console.error);
|
|
329
461
|
}
|
|
330
462
|
process.exitCode = 1;
|
|
331
463
|
return;
|
|
@@ -465,6 +597,8 @@ export async function multiResumeCommand(options) {
|
|
|
465
597
|
return;
|
|
466
598
|
}
|
|
467
599
|
|
|
600
|
+
const nextActions = deriveCoordinatorCliNextActions(result.state, configResult.config);
|
|
601
|
+
|
|
468
602
|
if (options.json) {
|
|
469
603
|
console.log(JSON.stringify({
|
|
470
604
|
ok: true,
|
|
@@ -472,6 +606,8 @@ export async function multiResumeCommand(options) {
|
|
|
472
606
|
resumed_status: result.resumed_status,
|
|
473
607
|
blocked_reason: result.blocked_reason,
|
|
474
608
|
pending_gate: result.state?.pending_gate || null,
|
|
609
|
+
next_action: nextActions[0]?.command ?? null,
|
|
610
|
+
next_actions: nextActions,
|
|
475
611
|
resync: result.resync,
|
|
476
612
|
}, null, 2));
|
|
477
613
|
return;
|
|
@@ -479,11 +615,10 @@ export async function multiResumeCommand(options) {
|
|
|
479
615
|
|
|
480
616
|
console.log(`Coordinator resumed: ${result.resumed_status}`);
|
|
481
617
|
console.log(`Previous block: ${result.blocked_reason}`);
|
|
482
|
-
if (result.
|
|
483
|
-
|
|
484
|
-
} else {
|
|
485
|
-
console.log('Next action: agentxchain multi step');
|
|
618
|
+
if (result.state?.pending_gate) {
|
|
619
|
+
printCoordinatorPendingGate(result.state.pending_gate);
|
|
486
620
|
}
|
|
621
|
+
printCoordinatorNextActions(nextActions);
|
|
487
622
|
}
|
|
488
623
|
|
|
489
624
|
// ── multi approve-gate ─────────────────────────────────────────────────────
|
|
@@ -523,22 +658,34 @@ export async function multiApproveGateCommand(options) {
|
|
|
523
658
|
if (gateHook.blocked) {
|
|
524
659
|
const blocker = gateHook.verdicts.find(v => v.verdict === 'block');
|
|
525
660
|
const reason = blocker?.message || 'before_gate hook blocked approval';
|
|
526
|
-
|
|
661
|
+
const failure = normalizeCoordinatorGateApprovalFailure({
|
|
662
|
+
state,
|
|
663
|
+
config: configResult.config,
|
|
664
|
+
code: 'hook_blocked',
|
|
665
|
+
error: reason,
|
|
666
|
+
hookName: blocker?.hook_name || null,
|
|
667
|
+
hookPhase: 'before_gate',
|
|
668
|
+
});
|
|
669
|
+
printCoordinatorGateApprovalFailure(failure);
|
|
527
670
|
if (options.json) {
|
|
528
|
-
console.log(JSON.stringify(
|
|
671
|
+
console.log(JSON.stringify(failure, null, 2));
|
|
529
672
|
}
|
|
530
673
|
process.exitCode = 1;
|
|
531
674
|
return;
|
|
532
675
|
}
|
|
533
676
|
|
|
534
677
|
if (!gateHook.ok) {
|
|
535
|
-
|
|
678
|
+
const failure = normalizeCoordinatorGateApprovalFailure({
|
|
679
|
+
state,
|
|
680
|
+
config: configResult.config,
|
|
681
|
+
code: 'hook_failed',
|
|
682
|
+
error: gateHook.error || 'unknown hook failure',
|
|
683
|
+
hookName: gateHook.results?.find((entry) => entry?.hook_name)?.hook_name || null,
|
|
684
|
+
hookPhase: 'before_gate',
|
|
685
|
+
});
|
|
686
|
+
printCoordinatorGateApprovalFailure(failure);
|
|
536
687
|
if (options.json) {
|
|
537
|
-
console.log(JSON.stringify(
|
|
538
|
-
blocked: true,
|
|
539
|
-
hook_phase: 'before_gate',
|
|
540
|
-
reason: gateHook.error || 'unknown hook failure',
|
|
541
|
-
}, null, 2));
|
|
688
|
+
console.log(JSON.stringify(failure, null, 2));
|
|
542
689
|
}
|
|
543
690
|
process.exitCode = 1;
|
|
544
691
|
return;
|
|
@@ -558,21 +705,32 @@ export async function multiApproveGateCommand(options) {
|
|
|
558
705
|
}
|
|
559
706
|
|
|
560
707
|
if (!result.ok) {
|
|
561
|
-
|
|
708
|
+
const failure = normalizeCoordinatorGateApprovalFailure({
|
|
709
|
+
state,
|
|
710
|
+
config: configResult.config,
|
|
711
|
+
code: 'approval_failed',
|
|
712
|
+
error: result.error || 'Coordinator gate approval failed',
|
|
713
|
+
});
|
|
714
|
+
printCoordinatorGateApprovalFailure(failure);
|
|
715
|
+
if (options.json) {
|
|
716
|
+
console.log(JSON.stringify(failure, null, 2));
|
|
717
|
+
}
|
|
562
718
|
process.exitCode = 1;
|
|
563
719
|
return;
|
|
564
720
|
}
|
|
565
721
|
|
|
722
|
+
const success = normalizeCoordinatorGateApprovalSuccess({
|
|
723
|
+
result: { ...result, config: configResult.config },
|
|
724
|
+
gateType,
|
|
725
|
+
});
|
|
726
|
+
|
|
566
727
|
if (options.json) {
|
|
567
|
-
console.log(JSON.stringify(
|
|
728
|
+
console.log(JSON.stringify(success, null, 2));
|
|
568
729
|
return;
|
|
569
730
|
}
|
|
570
731
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
} else {
|
|
574
|
-
console.log('Run completion approved. Coordinator run is now complete.');
|
|
575
|
-
}
|
|
732
|
+
console.log(success.message);
|
|
733
|
+
printCoordinatorNextActions(success.next_actions);
|
|
576
734
|
}
|
|
577
735
|
|
|
578
736
|
// ── multi resync ───────────────────────────────────────────────────────────
|
|
@@ -614,9 +772,7 @@ export async function multiResyncCommand(options) {
|
|
|
614
772
|
console.log(JSON.stringify({ diverged: true, mismatches: divergence.mismatches }, null, 2));
|
|
615
773
|
} else {
|
|
616
774
|
console.log(`Divergence detected (${divergence.mismatches.length} mismatch(es)):`);
|
|
617
|
-
|
|
618
|
-
console.log(` [${m.type}] ${m.detail}`);
|
|
619
|
-
}
|
|
775
|
+
printCoordinatorBlockerDetails(divergence.mismatches, console.log);
|
|
620
776
|
console.log('');
|
|
621
777
|
console.log('Run without --dry-run to resync.');
|
|
622
778
|
}
|
|
@@ -625,9 +781,17 @@ export async function multiResyncCommand(options) {
|
|
|
625
781
|
|
|
626
782
|
// Step 2: Resync
|
|
627
783
|
const result = resyncFromRepoAuthority(workspacePath, state, configResult.config);
|
|
784
|
+
const updatedState = loadCoordinatorState(workspacePath) || state;
|
|
785
|
+
const nextActions = deriveCoordinatorCliNextActions(updatedState, configResult.config);
|
|
628
786
|
|
|
629
787
|
if (options.json) {
|
|
630
|
-
console.log(JSON.stringify(
|
|
788
|
+
console.log(JSON.stringify({
|
|
789
|
+
...result,
|
|
790
|
+
status: updatedState.status ?? null,
|
|
791
|
+
pending_gate: updatedState.pending_gate ?? null,
|
|
792
|
+
next_action: nextActions[0]?.command ?? null,
|
|
793
|
+
next_actions: nextActions,
|
|
794
|
+
}, null, 2));
|
|
631
795
|
return;
|
|
632
796
|
}
|
|
633
797
|
|
|
@@ -642,9 +806,14 @@ export async function multiResyncCommand(options) {
|
|
|
642
806
|
console.log(` ${bc.barrier_id}: ${bc.previous_status} → ${bc.new_status}`);
|
|
643
807
|
}
|
|
644
808
|
}
|
|
809
|
+
if (updatedState.pending_gate) {
|
|
810
|
+
printCoordinatorPendingGate(updatedState.pending_gate);
|
|
811
|
+
}
|
|
812
|
+
printCoordinatorNextActions(nextActions);
|
|
645
813
|
} else {
|
|
646
|
-
console.error(
|
|
647
|
-
console.error
|
|
814
|
+
console.error(`Coordinator resync entered blocked state: ${result.blocked_reason || 'unknown reason'}`);
|
|
815
|
+
printCoordinatorBlockerDetails(result.mismatch_details, console.error);
|
|
816
|
+
printCoordinatorNextActions(nextActions, console.error);
|
|
648
817
|
process.exitCode = 1;
|
|
649
818
|
}
|
|
650
819
|
}
|
package/src/commands/phase.js
CHANGED
|
@@ -12,7 +12,7 @@ export function phaseCommand(subcommand, phaseId, opts) {
|
|
|
12
12
|
process.exit(1);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
const { root, config,
|
|
15
|
+
const { root, config, version } = context;
|
|
16
16
|
if (version !== 4 || config.protocol_mode !== 'governed') {
|
|
17
17
|
console.log(chalk.red(' Not a governed AgentXchain project (requires v4 config).'));
|
|
18
18
|
process.exit(1);
|
|
@@ -27,14 +27,14 @@ export function phaseCommand(subcommand, phaseId, opts) {
|
|
|
27
27
|
const state = loadProjectState(root, config);
|
|
28
28
|
|
|
29
29
|
if (subcommand === 'show') {
|
|
30
|
-
return showPhase(phaseId, { root, config,
|
|
30
|
+
return showPhase(phaseId, { root, config, state, phaseIds, opts });
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
return listPhases({ root, config,
|
|
33
|
+
return listPhases({ root, config, state, phaseIds, opts });
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function listPhases({ root, config,
|
|
37
|
-
const phases = phaseIds.map((phaseId) => buildPhaseRecord(root, config,
|
|
36
|
+
function listPhases({ root, config, state, phaseIds, opts }) {
|
|
37
|
+
const phases = phaseIds.map((phaseId) => buildPhaseRecord(root, config, state, phaseId));
|
|
38
38
|
|
|
39
39
|
if (opts.json) {
|
|
40
40
|
console.log(JSON.stringify({
|
|
@@ -56,7 +56,7 @@ function listPhases({ root, config, rawConfig, state, phaseIds, opts }) {
|
|
|
56
56
|
console.log(chalk.dim(' Usage: agentxchain phase show <phase>\n'));
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
function showPhase(requestedPhaseId, { root, config,
|
|
59
|
+
function showPhase(requestedPhaseId, { root, config, state, phaseIds, opts }) {
|
|
60
60
|
const phaseId = requestedPhaseId || state?.phase || phaseIds[0];
|
|
61
61
|
if (!config.routing?.[phaseId]) {
|
|
62
62
|
console.log(chalk.red(` Unknown phase: ${phaseId}`));
|
|
@@ -64,7 +64,7 @@ function showPhase(requestedPhaseId, { root, config, rawConfig, state, phaseIds,
|
|
|
64
64
|
process.exit(1);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
const phase = buildPhaseRecord(root, config,
|
|
67
|
+
const phase = buildPhaseRecord(root, config, state, phaseId);
|
|
68
68
|
|
|
69
69
|
if (opts.json) {
|
|
70
70
|
console.log(JSON.stringify(phase, null, 2));
|
|
@@ -112,16 +112,14 @@ function showPhase(requestedPhaseId, { root, config, rawConfig, state, phaseIds,
|
|
|
112
112
|
console.log('');
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
function buildPhaseRecord(root, config,
|
|
115
|
+
function buildPhaseRecord(root, config, state, phaseId) {
|
|
116
116
|
const route = config.routing?.[phaseId] || {};
|
|
117
117
|
const normalizedPhaseKit = config.workflow_kit?.phases?.[phaseId] || null;
|
|
118
|
-
const
|
|
119
|
-
const rawPhaseKit = rawWorkflowKit?.phases?.[phaseId] || null;
|
|
120
|
-
const hasExplicitWorkflowKit = rawWorkflowKit !== undefined && rawWorkflowKit !== null;
|
|
118
|
+
const hasExplicitWorkflowKit = config.workflow_kit?._explicit === true;
|
|
121
119
|
|
|
122
120
|
const workflowSource = !hasExplicitWorkflowKit
|
|
123
121
|
? 'default'
|
|
124
|
-
:
|
|
122
|
+
: normalizedPhaseKit
|
|
125
123
|
? 'explicit'
|
|
126
124
|
: 'not_declared';
|
|
127
125
|
|
|
@@ -152,7 +150,7 @@ function buildPhaseRecord(root, config, rawConfig, state, phaseId) {
|
|
|
152
150
|
max_concurrent_turns: getMaxConcurrentTurns(config, phaseId),
|
|
153
151
|
workflow_kit: {
|
|
154
152
|
source: workflowSource,
|
|
155
|
-
template: typeof
|
|
153
|
+
template: typeof normalizedPhaseKit?.template === 'string' ? normalizedPhaseKit.template : null,
|
|
156
154
|
artifacts,
|
|
157
155
|
},
|
|
158
156
|
};
|
|
@@ -76,7 +76,7 @@ export async function rejectTurnCommand(opts) {
|
|
|
76
76
|
console.log('');
|
|
77
77
|
|
|
78
78
|
if (result.escalated) {
|
|
79
|
-
const recovery = deriveRecoveryDescriptor(result.state);
|
|
79
|
+
const recovery = deriveRecoveryDescriptor(result.state, config);
|
|
80
80
|
if (recovery) {
|
|
81
81
|
console.log(` ${chalk.dim('Reason:')} ${recovery.typed_reason}`);
|
|
82
82
|
console.log(` ${chalk.dim('Owner:')} ${recovery.owner}`);
|
package/src/commands/restart.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import chalk from 'chalk';
|
|
14
14
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
15
15
|
import { join, dirname } from 'path';
|
|
16
|
-
import { loadProjectContext
|
|
16
|
+
import { loadProjectContext } from '../lib/config.js';
|
|
17
17
|
import {
|
|
18
18
|
assignGovernedTurn,
|
|
19
19
|
getActiveTurns,
|
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
HISTORY_PATH,
|
|
24
24
|
LEDGER_PATH,
|
|
25
25
|
} from '../lib/governed-state.js';
|
|
26
|
+
import { deriveRecoveryDescriptor } from '../lib/blocked-state.js';
|
|
27
|
+
import { deriveRecommendedContinuityAction } from '../lib/continuity-status.js';
|
|
26
28
|
import { readSessionCheckpoint, writeSessionCheckpoint, captureBaselineRef, SESSION_PATH } from '../lib/session-checkpoint.js';
|
|
27
29
|
|
|
28
30
|
/**
|
|
@@ -30,6 +32,7 @@ import { readSessionCheckpoint, writeSessionCheckpoint, captureBaselineRef, SESS
|
|
|
30
32
|
* so a new agent session can orient quickly.
|
|
31
33
|
*/
|
|
32
34
|
function generateRecoveryReport(root, state, checkpoint, driftWarnings = []) {
|
|
35
|
+
const continuityAction = deriveRecommendedContinuityAction(state);
|
|
33
36
|
const lines = [
|
|
34
37
|
'# Session Recovery Report',
|
|
35
38
|
'',
|
|
@@ -112,7 +115,7 @@ function generateRecoveryReport(root, state, checkpoint, driftWarnings = []) {
|
|
|
112
115
|
`- **To**: ${pt.to}`,
|
|
113
116
|
`- **Gate**: ${pt.gate}`,
|
|
114
117
|
`- **Requested by**: ${pt.requested_by_turn || 'unknown'}`,
|
|
115
|
-
`- **Action**: Run \`
|
|
118
|
+
`- **Action**: Run \`${continuityAction.recommended_command}\` to continue`,
|
|
116
119
|
'',
|
|
117
120
|
);
|
|
118
121
|
}
|
|
@@ -124,7 +127,7 @@ function generateRecoveryReport(root, state, checkpoint, driftWarnings = []) {
|
|
|
124
127
|
'',
|
|
125
128
|
`- **Gate**: ${pc.gate}`,
|
|
126
129
|
`- **Requested by**: ${pc.requested_by_turn || 'unknown'}`,
|
|
127
|
-
`- **Action**: Run \`
|
|
130
|
+
`- **Action**: Run \`${continuityAction.recommended_command}\` to continue`,
|
|
128
131
|
'',
|
|
129
132
|
);
|
|
130
133
|
}
|
|
@@ -139,10 +142,9 @@ function generateRecoveryReport(root, state, checkpoint, driftWarnings = []) {
|
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
lines.push('## Next Steps', '');
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
lines.push('A run completion is pending approval. Run `agentxchain approve-completion` to finalize.', '');
|
|
145
|
+
if (continuityAction.recommended_command && !continuityAction.restart_recommended) {
|
|
146
|
+
const detail = continuityAction.recommended_detail ? ` Detail: ${continuityAction.recommended_detail}.` : '';
|
|
147
|
+
lines.push(`Run \`${continuityAction.recommended_command}\` next.${detail}`, '');
|
|
146
148
|
} else {
|
|
147
149
|
lines.push('The next turn has been assigned. Check the dispatch bundle for context.', '');
|
|
148
150
|
}
|
|
@@ -197,10 +199,17 @@ export async function restartCommand(opts) {
|
|
|
197
199
|
|
|
198
200
|
if (state.status === 'blocked') {
|
|
199
201
|
console.log(chalk.red('Run is blocked. Resolve the blocker first.'));
|
|
200
|
-
|
|
202
|
+
const recovery = deriveRecoveryDescriptor(state, config);
|
|
203
|
+
if (recovery) {
|
|
204
|
+
console.log(chalk.dim(`Reason: ${recovery.typed_reason}`));
|
|
205
|
+
console.log(chalk.dim(`Owner: ${recovery.owner}`));
|
|
206
|
+
console.log(chalk.dim(`Action: ${recovery.recovery_action}`));
|
|
207
|
+
if (recovery.detail) {
|
|
208
|
+
console.log(chalk.dim(`Detail: ${recovery.detail}`));
|
|
209
|
+
}
|
|
210
|
+
} else if (state.blocked_reason) {
|
|
201
211
|
console.log(chalk.dim(`Reason: ${typeof state.blocked_reason === 'string' ? state.blocked_reason : JSON.stringify(state.blocked_reason)}`));
|
|
202
212
|
}
|
|
203
|
-
console.log(chalk.dim('Use `agentxchain step --resume` or resolve the blocker, then try again.'));
|
|
204
213
|
process.exit(1);
|
|
205
214
|
}
|
|
206
215
|
|
|
@@ -227,17 +236,25 @@ export async function restartCommand(opts) {
|
|
|
227
236
|
}
|
|
228
237
|
}
|
|
229
238
|
|
|
239
|
+
const continuityAction = deriveRecommendedContinuityAction(state);
|
|
240
|
+
|
|
230
241
|
// ── Pending gate / completion check ────────────────────────────────────
|
|
231
242
|
if (state.pending_phase_transition) {
|
|
232
243
|
const pt = state.pending_phase_transition;
|
|
233
244
|
console.log(chalk.yellow(`Pending phase transition: ${pt.from} → ${pt.to} (gate: ${pt.gate})`));
|
|
234
|
-
console.log(chalk.dim(
|
|
245
|
+
console.log(chalk.dim(`Run \`${continuityAction.recommended_command}\` next.`));
|
|
246
|
+
if (continuityAction.recommended_detail) {
|
|
247
|
+
console.log(chalk.dim(`Detail: ${continuityAction.recommended_detail}`));
|
|
248
|
+
}
|
|
235
249
|
}
|
|
236
250
|
|
|
237
251
|
if (state.pending_run_completion) {
|
|
238
252
|
const pc = state.pending_run_completion;
|
|
239
253
|
console.log(chalk.yellow(`Pending run completion (gate: ${pc.gate})`));
|
|
240
|
-
console.log(chalk.dim(
|
|
254
|
+
console.log(chalk.dim(`Run \`${continuityAction.recommended_command}\` next.`));
|
|
255
|
+
if (continuityAction.recommended_detail) {
|
|
256
|
+
console.log(chalk.dim(`Detail: ${continuityAction.recommended_detail}`));
|
|
257
|
+
}
|
|
241
258
|
}
|
|
242
259
|
|
|
243
260
|
// Handle abandoned active turns (assigned but never completed)
|
package/src/commands/resume.js
CHANGED
|
@@ -101,7 +101,7 @@ export async function resumeCommand(opts) {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
if (state.pending_phase_transition || state.pending_run_completion) {
|
|
104
|
-
printRecoverySummary(state, 'This run is awaiting approval.');
|
|
104
|
+
printRecoverySummary(state, 'This run is awaiting approval.', config);
|
|
105
105
|
process.exit(1);
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -267,7 +267,7 @@ export async function resumeCommand(opts) {
|
|
|
267
267
|
const assignResult = assignGovernedTurn(root, config, roleId);
|
|
268
268
|
if (!assignResult.ok) {
|
|
269
269
|
if (assignResult.error_code?.startsWith('hook_') || assignResult.error_code === 'hook_blocked') {
|
|
270
|
-
printAssignmentHookFailure(assignResult, roleId);
|
|
270
|
+
printAssignmentHookFailure(assignResult, roleId, config);
|
|
271
271
|
}
|
|
272
272
|
console.log(chalk.red(`Failed to assign turn: ${assignResult.error}`));
|
|
273
273
|
process.exit(1);
|
|
@@ -482,8 +482,8 @@ function printAssignmentWarnings(assignResult) {
|
|
|
482
482
|
}
|
|
483
483
|
}
|
|
484
484
|
|
|
485
|
-
function printRecoverySummary(state, heading) {
|
|
486
|
-
const recovery = deriveRecoveryDescriptor(state);
|
|
485
|
+
function printRecoverySummary(state, heading, config) {
|
|
486
|
+
const recovery = deriveRecoveryDescriptor(state, config);
|
|
487
487
|
console.log(chalk.yellow(heading));
|
|
488
488
|
if (!recovery) {
|
|
489
489
|
return;
|
|
@@ -495,8 +495,8 @@ function printRecoverySummary(state, heading) {
|
|
|
495
495
|
}
|
|
496
496
|
}
|
|
497
497
|
|
|
498
|
-
function printAssignmentHookFailure(result, roleId) {
|
|
499
|
-
const recovery = deriveRecoveryDescriptor(result.state);
|
|
498
|
+
function printAssignmentHookFailure(result, roleId, config) {
|
|
499
|
+
const recovery = deriveRecoveryDescriptor(result.state, config);
|
|
500
500
|
const hookName = result.hookResults?.blocker?.hook_name
|
|
501
501
|
|| result.hookResults?.results?.find((entry) => entry.hook_name)?.hook_name
|
|
502
502
|
|| '(unknown)';
|