agentxchain 2.104.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 +12 -6
- package/bin/agentxchain.js +5 -5
- 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/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/history.js +21 -3
- 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/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/role.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { loadProjectContext } from '../lib/config.js';
|
|
3
|
+
import {
|
|
4
|
+
getRoleRuntimeCapabilityContract,
|
|
5
|
+
summarizeRuntimeCapabilityContract,
|
|
6
|
+
} from '../lib/runtime-capabilities.js';
|
|
3
7
|
|
|
4
8
|
export function roleCommand(subcommand, roleId, opts) {
|
|
5
9
|
const context = loadProjectContext();
|
|
@@ -8,25 +12,26 @@ export function roleCommand(subcommand, roleId, opts) {
|
|
|
8
12
|
process.exit(1);
|
|
9
13
|
}
|
|
10
14
|
|
|
11
|
-
const {
|
|
15
|
+
const { config, version } = context;
|
|
12
16
|
|
|
13
17
|
if (version !== 4) {
|
|
14
18
|
console.log(chalk.red(' Not a governed AgentXchain project (requires v4 config).'));
|
|
15
19
|
process.exit(1);
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
const roles =
|
|
22
|
+
const roles = config.roles || {};
|
|
23
|
+
const runtimes = config.runtimes || {};
|
|
19
24
|
const roleIds = Object.keys(roles);
|
|
20
25
|
|
|
21
26
|
if (subcommand === 'show') {
|
|
22
|
-
return showRole(roleId, roles, roleIds, opts);
|
|
27
|
+
return showRole(roleId, roles, runtimes, roleIds, opts);
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
// Default: list
|
|
26
|
-
return listRoles(roles, roleIds, opts);
|
|
31
|
+
return listRoles(roles, runtimes, roleIds, opts);
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
function listRoles(roles, roleIds, opts) {
|
|
34
|
+
function listRoles(roles, runtimes, roleIds, opts) {
|
|
30
35
|
if (roleIds.length === 0) {
|
|
31
36
|
if (opts.json) {
|
|
32
37
|
console.log('[]');
|
|
@@ -38,14 +43,17 @@ function listRoles(roles, roleIds, opts) {
|
|
|
38
43
|
|
|
39
44
|
if (opts.json) {
|
|
40
45
|
const output = roleIds.map((id) => {
|
|
46
|
+
const runtimeId = roles[id].runtime_id;
|
|
47
|
+
const runtime = runtimes[runtimeId];
|
|
41
48
|
const entry = {
|
|
42
49
|
id,
|
|
43
50
|
title: roles[id].title,
|
|
44
51
|
mandate: roles[id].mandate,
|
|
45
52
|
write_authority: roles[id].write_authority,
|
|
46
|
-
runtime:
|
|
53
|
+
runtime: runtimeId,
|
|
54
|
+
runtime_contract: runtime ? getRoleRuntimeCapabilityContract(id, roles[id], runtime).runtime_contract : null,
|
|
47
55
|
};
|
|
48
|
-
if (typeof roles[id]
|
|
56
|
+
if (typeof roles[id]?.decision_authority === 'number') {
|
|
49
57
|
entry.decision_authority = roles[id].decision_authority;
|
|
50
58
|
}
|
|
51
59
|
return entry;
|
|
@@ -57,19 +65,22 @@ function listRoles(roles, roleIds, opts) {
|
|
|
57
65
|
console.log(chalk.bold(`\n Roles (${roleIds.length}):\n`));
|
|
58
66
|
for (const id of roleIds) {
|
|
59
67
|
const r = roles[id];
|
|
68
|
+
const runtime = runtimes[r.runtime_id];
|
|
69
|
+
const contract = runtime ? getRoleRuntimeCapabilityContract(id, r, runtime) : null;
|
|
60
70
|
const authority = r.write_authority === 'authoritative'
|
|
61
71
|
? chalk.green(r.write_authority)
|
|
62
72
|
: r.write_authority === 'proposed'
|
|
63
73
|
? chalk.yellow(r.write_authority)
|
|
64
74
|
: chalk.dim(r.write_authority);
|
|
65
|
-
const decAuth = typeof r
|
|
66
|
-
|
|
75
|
+
const decAuth = typeof r?.decision_authority === 'number' ? chalk.dim(` dec:${r.decision_authority}`) : '';
|
|
76
|
+
const capabilitySuffix = contract ? chalk.dim(` {${summarizeRuntimeCapabilityContract(contract.runtime_contract)}}`) : '';
|
|
77
|
+
console.log(` ${chalk.cyan(id)} — ${r.title} [${authority}${decAuth}] → ${chalk.dim(r.runtime_id)}${capabilitySuffix}`);
|
|
67
78
|
}
|
|
68
79
|
console.log('');
|
|
69
80
|
console.log(chalk.dim(' Usage: agentxchain role show <role_id>\n'));
|
|
70
81
|
}
|
|
71
82
|
|
|
72
|
-
function showRole(roleId, roles, roleIds, opts) {
|
|
83
|
+
function showRole(roleId, roles, runtimes, roleIds, opts) {
|
|
73
84
|
if (!roleId) {
|
|
74
85
|
console.log(chalk.red(' Missing role ID.'));
|
|
75
86
|
console.log(chalk.dim(` Usage: agentxchain role show <role_id>`));
|
|
@@ -86,6 +97,8 @@ function showRole(roleId, roles, roleIds, opts) {
|
|
|
86
97
|
}
|
|
87
98
|
|
|
88
99
|
const r = roles[roleId];
|
|
100
|
+
const runtime = runtimes[r.runtime_id];
|
|
101
|
+
const capability = runtime ? getRoleRuntimeCapabilityContract(roleId, r, runtime) : null;
|
|
89
102
|
|
|
90
103
|
if (opts.json) {
|
|
91
104
|
const entry = {
|
|
@@ -93,9 +106,19 @@ function showRole(roleId, roles, roleIds, opts) {
|
|
|
93
106
|
title: r.title,
|
|
94
107
|
mandate: r.mandate,
|
|
95
108
|
write_authority: r.write_authority,
|
|
96
|
-
runtime: r.
|
|
109
|
+
runtime: r.runtime_id,
|
|
110
|
+
runtime_contract: capability?.runtime_contract || null,
|
|
111
|
+
effective_runtime_contract: capability
|
|
112
|
+
? {
|
|
113
|
+
role_id: capability.role_id,
|
|
114
|
+
role_write_authority: capability.role_write_authority,
|
|
115
|
+
effective_write_path: capability.effective_write_path,
|
|
116
|
+
workflow_artifact_ownership: capability.workflow_artifact_ownership,
|
|
117
|
+
notes: capability.notes,
|
|
118
|
+
}
|
|
119
|
+
: null,
|
|
97
120
|
};
|
|
98
|
-
if (typeof r
|
|
121
|
+
if (typeof r?.decision_authority === 'number') {
|
|
99
122
|
entry.decision_authority = r.decision_authority;
|
|
100
123
|
}
|
|
101
124
|
console.log(JSON.stringify(entry, null, 2));
|
|
@@ -112,9 +135,23 @@ function showRole(roleId, roles, roleIds, opts) {
|
|
|
112
135
|
console.log(` Title: ${r.title}`);
|
|
113
136
|
console.log(` Mandate: ${r.mandate}`);
|
|
114
137
|
console.log(` Authority: ${authority}`);
|
|
115
|
-
if (typeof r
|
|
138
|
+
if (typeof r?.decision_authority === 'number') {
|
|
116
139
|
console.log(` Decision: ${r.decision_authority}`);
|
|
117
140
|
}
|
|
118
|
-
console.log(` Runtime: ${chalk.dim(r.
|
|
141
|
+
console.log(` Runtime: ${chalk.dim(r.runtime_id)}`);
|
|
142
|
+
if (capability) {
|
|
143
|
+
const base = capability.runtime_contract;
|
|
144
|
+
console.log(` Transport: ${base.transport}`);
|
|
145
|
+
console.log(` Writes: ${base.can_write_files}`);
|
|
146
|
+
console.log(` Review: ${base.review_only_behavior}`);
|
|
147
|
+
console.log(` Proposals: ${base.proposal_support}`);
|
|
148
|
+
console.log(` Binary: ${base.requires_local_binary ? 'yes' : 'no'}`);
|
|
149
|
+
console.log(` owned_by: ${base.workflow_artifact_ownership}`);
|
|
150
|
+
console.log(` Effective: ${capability.effective_write_path}`);
|
|
151
|
+
console.log(` Ownership: ${capability.workflow_artifact_ownership}`);
|
|
152
|
+
for (const note of capability.notes) {
|
|
153
|
+
console.log(` Note: ${note}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
119
156
|
console.log('');
|
|
120
157
|
}
|
package/src/commands/run.js
CHANGED
|
@@ -35,6 +35,7 @@ import { deriveRecoveryDescriptor } from '../lib/blocked-state.js';
|
|
|
35
35
|
import { summarizeRunProvenance } from '../lib/run-provenance.js';
|
|
36
36
|
import { resolveGovernedRole } from '../lib/role-resolution.js';
|
|
37
37
|
import { buildInheritedContext } from '../lib/run-context-inheritance.js';
|
|
38
|
+
import { shouldSuggestManualQaFallback } from '../lib/manual-qa-fallback.js';
|
|
38
39
|
import {
|
|
39
40
|
getDispatchAssignmentPath,
|
|
40
41
|
getDispatchContextPath,
|
|
@@ -56,7 +57,7 @@ export async function runCommand(opts) {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
export async function executeGovernedRun(context, opts = {}) {
|
|
59
|
-
const { root, config
|
|
60
|
+
const { root, config } = context;
|
|
60
61
|
const log = opts.log || console.log;
|
|
61
62
|
|
|
62
63
|
if (config.protocol_mode !== 'governed') {
|
|
@@ -315,11 +316,11 @@ export async function executeGovernedRun(context, opts = {}) {
|
|
|
315
316
|
|
|
316
317
|
// Adapter failure
|
|
317
318
|
if (!adapterResult.ok) {
|
|
318
|
-
if (
|
|
319
|
+
if (shouldSuggestManualQaFallback({
|
|
319
320
|
roleId,
|
|
320
321
|
runtimeId,
|
|
321
322
|
classified: adapterResult.classified,
|
|
322
|
-
|
|
323
|
+
config: cfg,
|
|
323
324
|
})) {
|
|
324
325
|
qaMissingCredentialsFallback = {
|
|
325
326
|
roleId,
|
|
@@ -438,7 +439,7 @@ export async function executeGovernedRun(context, opts = {}) {
|
|
|
438
439
|
|
|
439
440
|
// Recovery guidance for blocked/rejected states
|
|
440
441
|
if (result.state && (result.stop_reason === 'blocked' || result.stop_reason === 'reject_exhausted' || result.stop_reason === 'dispatch_error')) {
|
|
441
|
-
const recovery = deriveRecoveryDescriptor(result.state);
|
|
442
|
+
const recovery = deriveRecoveryDescriptor(result.state, config);
|
|
442
443
|
if (recovery) {
|
|
443
444
|
log('');
|
|
444
445
|
log(chalk.yellow(` Recovery: ${recovery.typed_reason}`));
|
|
@@ -514,13 +515,6 @@ function promptUser(question) {
|
|
|
514
515
|
});
|
|
515
516
|
}
|
|
516
517
|
|
|
517
|
-
function shouldPrintManualQaFallback({ roleId, runtimeId, classified, rawConfig }) {
|
|
518
|
-
return classified?.error_class === 'missing_credentials'
|
|
519
|
-
&& roleId === 'qa'
|
|
520
|
-
&& runtimeId === 'api-qa'
|
|
521
|
-
&& rawConfig?.runtimes?.['manual-qa']?.type === 'manual';
|
|
522
|
-
}
|
|
523
|
-
|
|
524
518
|
function printManualQaFallback(log = console.log) {
|
|
525
519
|
log('');
|
|
526
520
|
log(chalk.dim(' No-key QA fallback:'));
|
package/src/commands/status.js
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
2
3
|
import { existsSync } from 'fs';
|
|
3
4
|
import { join } from 'path';
|
|
4
|
-
import { loadConfig, loadLock, loadProjectContext, loadProjectState, loadState } from '../lib/config.js';
|
|
5
|
-
import {
|
|
5
|
+
import { findProjectRoot, loadConfig, loadLock, loadProjectContext, loadProjectState, loadState } from '../lib/config.js';
|
|
6
|
+
import {
|
|
7
|
+
deriveGovernedRunNextActions,
|
|
8
|
+
deriveRecoveryDescriptor,
|
|
9
|
+
deriveRuntimeBlockedGuidance,
|
|
10
|
+
} from '../lib/blocked-state.js';
|
|
6
11
|
import { getActiveTurn, getActiveTurnCount, getActiveTurns } from '../lib/governed-state.js';
|
|
7
12
|
import { getContinuityStatus } from '../lib/continuity-status.js';
|
|
8
13
|
import { getConnectorHealth } from '../lib/connector-health.js';
|
|
14
|
+
import { readRepoDecisions, summarizeRepoDecisions } from '../lib/repo-decisions.js';
|
|
9
15
|
import { deriveWorkflowKitArtifacts } from '../lib/workflow-kit-artifacts.js';
|
|
10
16
|
import { evaluateTimeouts } from '../lib/timeout-evaluator.js';
|
|
11
17
|
import { summarizeRunProvenance } from '../lib/run-provenance.js';
|
|
18
|
+
import { readRecentRunEventSummary } from '../lib/recent-event-summary.js';
|
|
19
|
+
import { deriveConflictedTurnResolutionActions } from '../lib/conflict-actions.js';
|
|
12
20
|
import { getDashboardPid, getDashboardSession } from './dashboard.js';
|
|
13
21
|
|
|
14
22
|
export async function statusCommand(opts) {
|
|
15
|
-
const context =
|
|
23
|
+
const context = loadStatusContext();
|
|
16
24
|
if (!context) {
|
|
17
25
|
console.log(chalk.red('No agentxchain.json found. Run `agentxchain init` first.'));
|
|
18
26
|
process.exit(1);
|
|
@@ -79,11 +87,42 @@ export async function statusCommand(opts) {
|
|
|
79
87
|
console.log('');
|
|
80
88
|
}
|
|
81
89
|
|
|
90
|
+
function loadStatusContext(dir = process.cwd()) {
|
|
91
|
+
const context = loadProjectContext(dir);
|
|
92
|
+
if (context) return context;
|
|
93
|
+
|
|
94
|
+
const root = findProjectRoot(dir);
|
|
95
|
+
if (!root) return null;
|
|
96
|
+
|
|
97
|
+
let rawConfig;
|
|
98
|
+
try {
|
|
99
|
+
rawConfig = JSON.parse(readFileSync(join(root, 'agentxchain.json'), 'utf8'));
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (rawConfig?.protocol_mode !== 'governed') {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
root,
|
|
110
|
+
rawConfig,
|
|
111
|
+
config: rawConfig,
|
|
112
|
+
version: 4,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
82
116
|
function renderGovernedStatus(context, opts) {
|
|
83
117
|
const { root, config, version } = context;
|
|
84
118
|
const state = loadProjectState(root, config);
|
|
85
119
|
const continuity = getContinuityStatus(root, state);
|
|
86
120
|
const connectorHealth = getConnectorHealth(root, config, state);
|
|
121
|
+
const recovery = deriveRecoveryDescriptor(state, config);
|
|
122
|
+
const runtimeGuidance = deriveRuntimeBlockedGuidance(state, config);
|
|
123
|
+
const nextActions = deriveGovernedRunNextActions(state, config);
|
|
124
|
+
const recentEventSummary = readRecentRunEventSummary(root);
|
|
125
|
+
const repoDecisionSummary = summarizeRepoDecisions(readRepoDecisions(root), config);
|
|
87
126
|
|
|
88
127
|
const workflowKitArtifacts = deriveWorkflowKitArtifacts(root, config, state);
|
|
89
128
|
|
|
@@ -106,8 +145,13 @@ function renderGovernedStatus(context, opts) {
|
|
|
106
145
|
provenance: state?.provenance || null,
|
|
107
146
|
inherited_context: state?.inherited_context || null,
|
|
108
147
|
repo_decisions: state?.repo_decisions || null,
|
|
148
|
+
repo_decision_summary: repoDecisionSummary,
|
|
109
149
|
continuity,
|
|
150
|
+
recovery,
|
|
151
|
+
runtime_guidance: runtimeGuidance,
|
|
152
|
+
next_actions: nextActions,
|
|
110
153
|
connector_health: connectorHealth,
|
|
154
|
+
recent_event_summary: recentEventSummary,
|
|
111
155
|
workflow_kit_artifacts: workflowKitArtifacts,
|
|
112
156
|
dashboard_session: dashboardSessionObj,
|
|
113
157
|
}, null, 2));
|
|
@@ -134,8 +178,12 @@ function renderGovernedStatus(context, opts) {
|
|
|
134
178
|
if (state?.inherited_context?.parent_run_id) {
|
|
135
179
|
console.log(` ${chalk.dim('Inherits:')} ${chalk.magenta(`parent ${state.inherited_context.parent_run_id} (${state.inherited_context.parent_status || 'unknown'})`)}`);
|
|
136
180
|
}
|
|
137
|
-
if (
|
|
138
|
-
console.log(` ${chalk.dim('Repo decisions:')} ${chalk.yellow(
|
|
181
|
+
if (repoDecisionSummary) {
|
|
182
|
+
console.log(` ${chalk.dim('Repo decisions:')} ${chalk.yellow(formatRepoDecisionHeadline(repoDecisionSummary))}`);
|
|
183
|
+
const carryoverDetail = formatRepoDecisionCarryover(repoDecisionSummary);
|
|
184
|
+
if (carryoverDetail) {
|
|
185
|
+
console.log(` ${chalk.dim('Carryover:')} ${carryoverDetail}`);
|
|
186
|
+
}
|
|
139
187
|
}
|
|
140
188
|
if (state?.accepted_integration_ref) {
|
|
141
189
|
console.log(` ${chalk.dim('Accepted:')} ${state.accepted_integration_ref}`);
|
|
@@ -144,6 +192,7 @@ function renderGovernedStatus(context, opts) {
|
|
|
144
192
|
|
|
145
193
|
renderContinuityStatus(continuity, state);
|
|
146
194
|
renderConnectorHealthStatus(connectorHealth);
|
|
195
|
+
renderRecentEventSummary(recentEventSummary);
|
|
147
196
|
|
|
148
197
|
const activeTurnCount = getActiveTurnCount(state);
|
|
149
198
|
const activeTurns = getActiveTurns(state);
|
|
@@ -171,14 +220,15 @@ function renderGovernedStatus(context, opts) {
|
|
|
171
220
|
const cs = turn.conflict_state;
|
|
172
221
|
const files = cs.conflict_error?.conflicting_files || [];
|
|
173
222
|
const count = cs.detection_count || 1;
|
|
223
|
+
const [reassignAction, mergeAction] = deriveConflictedTurnResolutionActions(turn.turn_id);
|
|
174
224
|
console.log(` ${chalk.dim('Conflict:')} ${files.length} file(s) — detection #${count}`);
|
|
175
225
|
if (cs.conflict_error?.overlap_ratio != null) {
|
|
176
226
|
console.log(` ${chalk.dim('Overlap:')} ${(cs.conflict_error.overlap_ratio * 100).toFixed(0)}%`);
|
|
177
227
|
}
|
|
178
228
|
const suggestion = cs.conflict_error?.suggested_resolution || 'reject_and_reassign';
|
|
179
229
|
console.log(` ${chalk.dim('Suggested:')} ${suggestion}`);
|
|
180
|
-
console.log(` ${chalk.dim('Resolve:')} ${chalk.cyan(
|
|
181
|
-
console.log(` ${chalk.dim(' or:')} ${chalk.cyan(
|
|
230
|
+
console.log(` ${chalk.dim('Resolve:')} ${chalk.cyan(reassignAction.command)}`);
|
|
231
|
+
console.log(` ${chalk.dim(' or:')} ${chalk.cyan(mergeAction.command)}`);
|
|
182
232
|
}
|
|
183
233
|
}
|
|
184
234
|
} else if (singleActiveTurn) {
|
|
@@ -199,9 +249,10 @@ function renderGovernedStatus(context, opts) {
|
|
|
199
249
|
if (singleActiveTurn.status === 'conflicted' && singleActiveTurn.conflict_state) {
|
|
200
250
|
const cs = singleActiveTurn.conflict_state;
|
|
201
251
|
const files = cs.conflict_error?.conflicting_files || [];
|
|
252
|
+
const [reassignAction, mergeAction] = deriveConflictedTurnResolutionActions(singleActiveTurn.turn_id);
|
|
202
253
|
console.log(` ${chalk.dim('Conflict:')} ${chalk.red(`${files.length} file(s) conflicting`)} — detection #${cs.detection_count || 1}`);
|
|
203
|
-
console.log(` ${chalk.dim('Resolve:')} ${chalk.cyan(
|
|
204
|
-
console.log(` ${chalk.dim(' or:')} ${chalk.cyan(
|
|
254
|
+
console.log(` ${chalk.dim('Resolve:')} ${chalk.cyan(reassignAction.command)}`);
|
|
255
|
+
console.log(` ${chalk.dim(' or:')} ${chalk.cyan(mergeAction.command)}`);
|
|
205
256
|
}
|
|
206
257
|
} else {
|
|
207
258
|
console.log(` ${chalk.dim('Turn:')} ${chalk.yellow('No active turn')}`);
|
|
@@ -231,7 +282,6 @@ function renderGovernedStatus(context, opts) {
|
|
|
231
282
|
if (state?.blocked_on) {
|
|
232
283
|
console.log('');
|
|
233
284
|
if (state.status === 'blocked') {
|
|
234
|
-
const recovery = deriveRecoveryDescriptor(state, config);
|
|
235
285
|
const detail = recovery?.detail || state.blocked_on;
|
|
236
286
|
console.log(` ${chalk.dim('Blocked:')} ${chalk.red.bold('BLOCKED')} — ${detail}`);
|
|
237
287
|
} else if (state.blocked_on.startsWith('human_approval:')) {
|
|
@@ -246,7 +296,6 @@ function renderGovernedStatus(context, opts) {
|
|
|
246
296
|
renderLastGateFailure(state.last_gate_failure, config);
|
|
247
297
|
}
|
|
248
298
|
|
|
249
|
-
const recovery = deriveRecoveryDescriptor(state, config);
|
|
250
299
|
if (recovery) {
|
|
251
300
|
console.log('');
|
|
252
301
|
console.log(` ${chalk.dim('Reason:')} ${recovery.typed_reason}`);
|
|
@@ -258,18 +307,26 @@ function renderGovernedStatus(context, opts) {
|
|
|
258
307
|
}
|
|
259
308
|
}
|
|
260
309
|
|
|
310
|
+
if (runtimeGuidance.length > 0) {
|
|
311
|
+
console.log('');
|
|
312
|
+
console.log(` ${chalk.dim('Runtime guidance:')}`);
|
|
313
|
+
for (const entry of runtimeGuidance) {
|
|
314
|
+
console.log(` ${chalk.yellow('•')} ${entry.code} — ${chalk.cyan(entry.command)}`);
|
|
315
|
+
console.log(` ${chalk.dim(`${entry.role_id} owns ${entry.artifact_path} at ${entry.phase}/${entry.gate_id}`)}`);
|
|
316
|
+
console.log(` ${chalk.dim(entry.reason)}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
261
320
|
if (state?.pending_phase_transition) {
|
|
262
321
|
const pt = state.pending_phase_transition;
|
|
263
322
|
console.log(` ${chalk.dim('Pending:')} ${formatGovernedPhase(pt.from)} → ${formatGovernedPhase(pt.to)}`);
|
|
264
323
|
console.log(` ${chalk.dim('Gate:')} ${pt.gate} (requires human approval)`);
|
|
265
|
-
console.log(` ${chalk.dim('Action:')} Run ${chalk.cyan('agentxchain approve-transition')} to advance`);
|
|
266
324
|
}
|
|
267
325
|
|
|
268
326
|
if (state?.pending_run_completion) {
|
|
269
327
|
const pc = state.pending_run_completion;
|
|
270
328
|
console.log(` ${chalk.dim('Pending:')} ${chalk.bold('Run Completion')}`);
|
|
271
329
|
console.log(` ${chalk.dim('Gate:')} ${pc.gate} (requires human approval)`);
|
|
272
|
-
console.log(` ${chalk.dim('Action:')} Run ${chalk.cyan('agentxchain approve-completion')} to finalize`);
|
|
273
330
|
}
|
|
274
331
|
|
|
275
332
|
if (state?.status === 'completed') {
|
|
@@ -459,6 +516,81 @@ function renderWorkflowKitArtifactsSection(wkData) {
|
|
|
459
516
|
}
|
|
460
517
|
}
|
|
461
518
|
|
|
519
|
+
function renderRecentEventSummary(summary) {
|
|
520
|
+
if (!summary) return;
|
|
521
|
+
|
|
522
|
+
const label = summary.freshness === 'recent'
|
|
523
|
+
? chalk.green('recent')
|
|
524
|
+
: summary.freshness === 'quiet'
|
|
525
|
+
? chalk.yellow('quiet')
|
|
526
|
+
: summary.freshness === 'unknown'
|
|
527
|
+
? chalk.yellow('unknown timing')
|
|
528
|
+
: chalk.dim('none recorded');
|
|
529
|
+
const countLabel = summary.freshness === 'no_events'
|
|
530
|
+
? null
|
|
531
|
+
: `${summary.recent_count || 0} in last ${summary.window_minutes || 15}m`;
|
|
532
|
+
|
|
533
|
+
console.log(` ${chalk.dim('Recent events:')} ${label}${countLabel ? chalk.dim(` (${countLabel})`) : ''}`);
|
|
534
|
+
if (summary.latest_event) {
|
|
535
|
+
console.log(` ${chalk.dim('Latest:')} ${summary.latest_event.summary || summary.latest_event.event_type || 'unknown_event'}`);
|
|
536
|
+
console.log(` ${chalk.dim('When:')} ${summary.latest_event.timestamp || 'unknown'}`);
|
|
537
|
+
}
|
|
538
|
+
console.log('');
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function formatRepoDecisionHeadline(summary) {
|
|
542
|
+
if (!summary) return 'none';
|
|
543
|
+
const parts = [`${summary.active_count} active`];
|
|
544
|
+
if (summary.overridden_count > 0) {
|
|
545
|
+
parts.push(`${summary.overridden_count} overridden`);
|
|
546
|
+
}
|
|
547
|
+
return parts.join(', ');
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function formatRepoDecisionCarryover(summary) {
|
|
551
|
+
if (!summary?.operator_summary) {
|
|
552
|
+
return '';
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const { operator_summary: operatorSummary, active_count: activeCount } = summary;
|
|
556
|
+
const parts = [];
|
|
557
|
+
|
|
558
|
+
if (activeCount === 0) {
|
|
559
|
+
parts.push('no active cross-run constraints remain');
|
|
560
|
+
} else if (Array.isArray(operatorSummary.active_categories) && operatorSummary.active_categories.length > 0) {
|
|
561
|
+
parts.push(`categories: ${operatorSummary.active_categories.join(', ')}`);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (typeof operatorSummary.highest_active_authority_level === 'number') {
|
|
565
|
+
const roleLabel = operatorSummary.highest_active_authority_role
|
|
566
|
+
? ` (${operatorSummary.highest_active_authority_role})`
|
|
567
|
+
: '';
|
|
568
|
+
parts.push(`highest authority: ${operatorSummary.highest_active_authority_level}${roleLabel}`);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (operatorSummary.superseding_active_count > 0) {
|
|
572
|
+
parts.push(pluralizeRepoDecisionCount(
|
|
573
|
+
operatorSummary.superseding_active_count,
|
|
574
|
+
'active superseding earlier decision',
|
|
575
|
+
'active superseding earlier decisions',
|
|
576
|
+
));
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (operatorSummary.overridden_with_successor_count > 0) {
|
|
580
|
+
parts.push(pluralizeRepoDecisionCount(
|
|
581
|
+
operatorSummary.overridden_with_successor_count,
|
|
582
|
+
'overridden with recorded successor',
|
|
583
|
+
'overridden with recorded successors',
|
|
584
|
+
));
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return parts.join('; ');
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function pluralizeRepoDecisionCount(count, singular, plural) {
|
|
591
|
+
return `${count} ${count === 1 ? singular : plural}`;
|
|
592
|
+
}
|
|
593
|
+
|
|
462
594
|
function renderLastGateFailure(failure, config) {
|
|
463
595
|
const entryRole = config?.routing?.[failure.phase]?.entry_role || null;
|
|
464
596
|
const suggestedCommand = entryRole ? `agentxchain step --role ${entryRole}` : 'agentxchain step --role <role>';
|
package/src/commands/step.js
CHANGED
|
@@ -62,9 +62,11 @@ import {
|
|
|
62
62
|
} from '../lib/turn-paths.js';
|
|
63
63
|
import { dispatchApiProxy } from '../lib/adapters/api-proxy-adapter.js';
|
|
64
64
|
import { deriveRecoveryDescriptor } from '../lib/blocked-state.js';
|
|
65
|
+
import { deriveConflictedTurnResolutionActions } from '../lib/conflict-actions.js';
|
|
65
66
|
import { runHooks } from '../lib/hook-runner.js';
|
|
66
67
|
import { finalizeDispatchManifest, verifyDispatchManifest } from '../lib/dispatch-manifest.js';
|
|
67
68
|
import { resolveGovernedRole } from '../lib/role-resolution.js';
|
|
69
|
+
import { shouldSuggestManualQaFallback } from '../lib/manual-qa-fallback.js';
|
|
68
70
|
|
|
69
71
|
export async function stepCommand(opts) {
|
|
70
72
|
const context = loadProjectContext();
|
|
@@ -73,7 +75,7 @@ export async function stepCommand(opts) {
|
|
|
73
75
|
process.exit(1);
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
const { root, config
|
|
78
|
+
const { root, config } = context;
|
|
77
79
|
|
|
78
80
|
if (config.protocol_mode !== 'governed') {
|
|
79
81
|
console.log(chalk.red('The step command is only available for governed projects.'));
|
|
@@ -132,11 +134,12 @@ export async function stepCommand(opts) {
|
|
|
132
134
|
|
|
133
135
|
// If the target turn is conflicted, print recovery paths instead of resuming
|
|
134
136
|
if (targetTurn.status === 'conflicted') {
|
|
137
|
+
const [reassignAction, mergeAction] = deriveConflictedTurnResolutionActions(targetTurn.turn_id);
|
|
135
138
|
console.log(chalk.yellow(`Turn ${targetTurn.turn_id} is conflicted. Resolve the conflict before resuming.`));
|
|
136
139
|
console.log('');
|
|
137
140
|
console.log(chalk.dim('Recovery options:'));
|
|
138
|
-
console.log(` ${chalk.cyan(
|
|
139
|
-
console.log(` ${chalk.cyan(
|
|
141
|
+
console.log(` ${chalk.cyan(reassignAction.command)} — ${reassignAction.description}`);
|
|
142
|
+
console.log(` ${chalk.cyan(mergeAction.command)} — ${mergeAction.description}`);
|
|
140
143
|
process.exit(1);
|
|
141
144
|
}
|
|
142
145
|
|
|
@@ -166,13 +169,13 @@ export async function stepCommand(opts) {
|
|
|
166
169
|
|
|
167
170
|
if (!skipAssignment) {
|
|
168
171
|
if (state.pending_phase_transition || state.pending_run_completion) {
|
|
169
|
-
printRecoverySummary(state, 'This run is awaiting approval.');
|
|
172
|
+
printRecoverySummary(state, 'This run is awaiting approval.', config);
|
|
170
173
|
process.exit(1);
|
|
171
174
|
}
|
|
172
175
|
|
|
173
176
|
if (state.status === 'blocked' && activeCount > 0) {
|
|
174
177
|
if (!opts.resume) {
|
|
175
|
-
printRecoverySummary(state, 'This run is blocked on a retained turn.');
|
|
178
|
+
printRecoverySummary(state, 'This run is blocked on a retained turn.', config);
|
|
176
179
|
process.exit(1);
|
|
177
180
|
}
|
|
178
181
|
|
|
@@ -199,11 +202,12 @@ export async function stepCommand(opts) {
|
|
|
199
202
|
|
|
200
203
|
// If the target turn is conflicted, print recovery paths
|
|
201
204
|
if (targetTurn.status === 'conflicted') {
|
|
205
|
+
const [reassignAction, mergeAction] = deriveConflictedTurnResolutionActions(targetTurn.turn_id);
|
|
202
206
|
console.log(chalk.yellow(`Turn ${targetTurn.turn_id} is conflicted. Resolve the conflict before resuming.`));
|
|
203
207
|
console.log('');
|
|
204
208
|
console.log(chalk.dim('Recovery options:'));
|
|
205
|
-
console.log(` ${chalk.cyan(
|
|
206
|
-
console.log(` ${chalk.cyan(
|
|
209
|
+
console.log(` ${chalk.cyan(reassignAction.command)} — ${reassignAction.description}`);
|
|
210
|
+
console.log(` ${chalk.cyan(mergeAction.command)} — ${mergeAction.description}`);
|
|
207
211
|
process.exit(1);
|
|
208
212
|
}
|
|
209
213
|
|
|
@@ -291,7 +295,7 @@ export async function stepCommand(opts) {
|
|
|
291
295
|
const assignResult = assignGovernedTurn(root, config, roleId);
|
|
292
296
|
if (!assignResult.ok) {
|
|
293
297
|
if (assignResult.error_code?.startsWith('hook_') || assignResult.error_code === 'hook_blocked') {
|
|
294
|
-
printAssignmentHookFailure(assignResult, roleId);
|
|
298
|
+
printAssignmentHookFailure(assignResult, roleId, config);
|
|
295
299
|
}
|
|
296
300
|
console.log(chalk.red(`Failed to assign turn: ${assignResult.error}`));
|
|
297
301
|
process.exit(1);
|
|
@@ -357,6 +361,7 @@ export async function stepCommand(opts) {
|
|
|
357
361
|
turnId: turn.turn_id,
|
|
358
362
|
roleId,
|
|
359
363
|
action: `Fix or reconfigure the hook, then rerun agentxchain step --resume${turn.turn_id ? ` --turn ${turn.turn_id}` : ''}`,
|
|
364
|
+
config,
|
|
360
365
|
});
|
|
361
366
|
process.exit(1);
|
|
362
367
|
}
|
|
@@ -430,12 +435,12 @@ export async function stepCommand(opts) {
|
|
|
430
435
|
console.log(chalk.dim(` Retry trace: ${apiResult.retry_trace_path}`));
|
|
431
436
|
}
|
|
432
437
|
|
|
433
|
-
if (
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
) {
|
|
438
|
+
if (shouldSuggestManualQaFallback({
|
|
439
|
+
roleId,
|
|
440
|
+
runtimeId,
|
|
441
|
+
classified: apiResult.classified,
|
|
442
|
+
config,
|
|
443
|
+
})) {
|
|
439
444
|
console.log(chalk.dim(' No-key QA fallback:'));
|
|
440
445
|
console.log(chalk.dim(' - Edit agentxchain.json and change roles.qa.runtime from "api-qa" to "manual-qa"'));
|
|
441
446
|
console.log(chalk.dim(' - Then rerun: agentxchain step --resume'));
|
|
@@ -737,6 +742,7 @@ export async function stepCommand(opts) {
|
|
|
737
742
|
turnId: turn.turn_id,
|
|
738
743
|
roleId,
|
|
739
744
|
action: `Fix or reconfigure the hook, then rerun agentxchain step --resume${turn.turn_id ? ` --turn ${turn.turn_id}` : ''}`,
|
|
745
|
+
config,
|
|
740
746
|
});
|
|
741
747
|
process.exit(1);
|
|
742
748
|
}
|
|
@@ -769,6 +775,7 @@ export async function stepCommand(opts) {
|
|
|
769
775
|
turnId: turn.turn_id,
|
|
770
776
|
roleId,
|
|
771
777
|
action: `Fix or reconfigure the hook, then rerun agentxchain step --resume${turn.turn_id ? ` --turn ${turn.turn_id}` : ''}`,
|
|
778
|
+
config,
|
|
772
779
|
});
|
|
773
780
|
process.exit(1);
|
|
774
781
|
}
|
|
@@ -779,14 +786,14 @@ export async function stepCommand(opts) {
|
|
|
779
786
|
const acceptResult = acceptGovernedTurn(root, config, { turnId: turn.turn_id });
|
|
780
787
|
if (!acceptResult.ok) {
|
|
781
788
|
if (acceptResult.accepted && acceptResult.error_code?.startsWith('hook_')) {
|
|
782
|
-
printAcceptedHookFailure(acceptResult);
|
|
789
|
+
printAcceptedHookFailure(acceptResult, config);
|
|
783
790
|
} else {
|
|
784
791
|
console.log(chalk.red(`Acceptance failed: ${acceptResult.error}`));
|
|
785
792
|
}
|
|
786
793
|
process.exit(1);
|
|
787
794
|
}
|
|
788
795
|
|
|
789
|
-
printAcceptSummary(acceptResult);
|
|
796
|
+
printAcceptSummary(acceptResult, config);
|
|
790
797
|
} else {
|
|
791
798
|
// Reject and potentially retry
|
|
792
799
|
console.log(chalk.yellow('Validation failed:'));
|
|
@@ -808,7 +815,7 @@ export async function stepCommand(opts) {
|
|
|
808
815
|
}
|
|
809
816
|
|
|
810
817
|
if (rejectResult.escalated) {
|
|
811
|
-
printEscalationSummary(rejectResult);
|
|
818
|
+
printEscalationSummary(rejectResult, config);
|
|
812
819
|
} else {
|
|
813
820
|
console.log(chalk.yellow('Turn rejected for retry.'));
|
|
814
821
|
console.log(` Attempt: ${rejectResult.state?.current_turn?.attempt}`);
|
|
@@ -887,8 +894,8 @@ function blockStepForHookIssue(root, state, turn, { hookResults, phase, defaultD
|
|
|
887
894
|
};
|
|
888
895
|
}
|
|
889
896
|
|
|
890
|
-
function printLifecycleHookFailure(title, result, { turnId, roleId, action }) {
|
|
891
|
-
const recovery = deriveRecoveryDescriptor(result.state);
|
|
897
|
+
function printLifecycleHookFailure(title, result, { turnId, roleId, action, config }) {
|
|
898
|
+
const recovery = deriveRecoveryDescriptor(result.state, config);
|
|
892
899
|
const hookName = result.hookResults?.blocker?.hook_name
|
|
893
900
|
|| result.hookResults?.results?.find((entry) => entry.hook_name)?.hook_name
|
|
894
901
|
|| '(unknown)';
|
|
@@ -935,8 +942,8 @@ function resolveTargetRole(opts, state, config) {
|
|
|
935
942
|
return resolved.roleId;
|
|
936
943
|
}
|
|
937
944
|
|
|
938
|
-
function printRecoverySummary(state, heading) {
|
|
939
|
-
const recovery = deriveRecoveryDescriptor(state);
|
|
945
|
+
function printRecoverySummary(state, heading, config) {
|
|
946
|
+
const recovery = deriveRecoveryDescriptor(state, config);
|
|
940
947
|
console.log(chalk.yellow(heading));
|
|
941
948
|
if (!recovery) {
|
|
942
949
|
return;
|
|
@@ -1007,8 +1014,8 @@ function printAssignmentWarnings(assignResult) {
|
|
|
1007
1014
|
}
|
|
1008
1015
|
}
|
|
1009
1016
|
|
|
1010
|
-
function printAssignmentHookFailure(result, roleId) {
|
|
1011
|
-
const recovery = deriveRecoveryDescriptor(result.state);
|
|
1017
|
+
function printAssignmentHookFailure(result, roleId, config) {
|
|
1018
|
+
const recovery = deriveRecoveryDescriptor(result.state, config);
|
|
1012
1019
|
const hookName = result.hookResults?.blocker?.hook_name
|
|
1013
1020
|
|| result.hookResults?.results?.find((entry) => entry.hook_name)?.hook_name
|
|
1014
1021
|
|| '(unknown)';
|
|
@@ -1034,8 +1041,8 @@ function printAssignmentHookFailure(result, roleId) {
|
|
|
1034
1041
|
console.log('');
|
|
1035
1042
|
}
|
|
1036
1043
|
|
|
1037
|
-
function printAcceptedHookFailure(result) {
|
|
1038
|
-
const recovery = deriveRecoveryDescriptor(result.state);
|
|
1044
|
+
function printAcceptedHookFailure(result, config) {
|
|
1045
|
+
const recovery = deriveRecoveryDescriptor(result.state, config);
|
|
1039
1046
|
const hookName = result.hookResults?.results?.find((entry) => entry.hook_name)?.hook_name || '(unknown)';
|
|
1040
1047
|
|
|
1041
1048
|
console.log('');
|
|
@@ -1058,9 +1065,9 @@ function printAcceptedHookFailure(result) {
|
|
|
1058
1065
|
console.log('');
|
|
1059
1066
|
}
|
|
1060
1067
|
|
|
1061
|
-
function printAcceptSummary(result) {
|
|
1068
|
+
function printAcceptSummary(result, config) {
|
|
1062
1069
|
const accepted = result.accepted;
|
|
1063
|
-
const recovery = deriveRecoveryDescriptor(result.state);
|
|
1070
|
+
const recovery = deriveRecoveryDescriptor(result.state, config);
|
|
1064
1071
|
console.log('');
|
|
1065
1072
|
console.log(chalk.green(' Turn Accepted'));
|
|
1066
1073
|
console.log(chalk.dim(' ' + '-'.repeat(44)));
|
|
@@ -1104,8 +1111,8 @@ function printAcceptSummary(result) {
|
|
|
1104
1111
|
console.log('');
|
|
1105
1112
|
}
|
|
1106
1113
|
|
|
1107
|
-
function printEscalationSummary(result) {
|
|
1108
|
-
const recovery = deriveRecoveryDescriptor(result.state);
|
|
1114
|
+
function printEscalationSummary(result, config) {
|
|
1115
|
+
const recovery = deriveRecoveryDescriptor(result.state, config);
|
|
1109
1116
|
console.log('');
|
|
1110
1117
|
console.log(chalk.red(' Turn Escalated'));
|
|
1111
1118
|
console.log(chalk.dim(' ' + '-'.repeat(44)));
|