agentxchain 2.110.0 → 2.112.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/bin/agentxchain.js +70 -0
- package/dashboard/app.js +6 -0
- package/dashboard/components/chain.js +200 -0
- package/dashboard/components/mission.js +177 -0
- package/dashboard/index.html +2 -0
- package/package.json +2 -1
- package/scripts/check-release-alignment.mjs +66 -0
- package/scripts/release-bump.sh +8 -59
- package/scripts/release-preflight.sh +23 -8
- package/src/commands/chain.js +252 -0
- package/src/commands/diff.js +19 -0
- package/src/commands/mission.js +252 -0
- package/src/commands/run.js +112 -97
- package/src/lib/chain-reports.js +54 -0
- package/src/lib/dashboard/bridge-server.js +16 -0
- package/src/lib/dashboard/chain-report-reader.js +15 -0
- package/src/lib/dashboard/file-watcher.js +13 -11
- package/src/lib/dashboard/mission-reader.js +14 -0
- package/src/lib/dashboard/state-reader.js +15 -1
- package/src/lib/export-diff.js +10 -1
- package/src/lib/missions.js +195 -0
- package/src/lib/release-alignment.js +336 -0
- package/src/lib/run-chain.js +296 -0
package/bin/agentxchain.js
CHANGED
|
@@ -121,6 +121,8 @@ import { diffCommand } from '../src/commands/diff.js';
|
|
|
121
121
|
import { eventsCommand } from '../src/commands/events.js';
|
|
122
122
|
import { connectorCheckCommand } from '../src/commands/connector.js';
|
|
123
123
|
import { scheduleDaemonCommand, scheduleListCommand, scheduleRunDueCommand, scheduleStatusCommand } from '../src/commands/schedule.js';
|
|
124
|
+
import { chainLatestCommand, chainListCommand, chainShowCommand } from '../src/commands/chain.js';
|
|
125
|
+
import { missionAttachChainCommand, missionListCommand, missionShowCommand, missionStartCommand } from '../src/commands/mission.js';
|
|
124
126
|
|
|
125
127
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
126
128
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
@@ -392,6 +394,69 @@ program
|
|
|
392
394
|
.option('-d, --dir <path>', 'Project directory')
|
|
393
395
|
.action(eventsCommand);
|
|
394
396
|
|
|
397
|
+
const chainCmd = program
|
|
398
|
+
.command('chain')
|
|
399
|
+
.description('Inspect run-chaining history and reports');
|
|
400
|
+
|
|
401
|
+
chainCmd
|
|
402
|
+
.command('latest')
|
|
403
|
+
.description('Show the most recent chain report')
|
|
404
|
+
.option('-j, --json', 'Output as JSON')
|
|
405
|
+
.option('-d, --dir <path>', 'Project directory')
|
|
406
|
+
.action(chainLatestCommand);
|
|
407
|
+
|
|
408
|
+
chainCmd
|
|
409
|
+
.command('list')
|
|
410
|
+
.description('List all chain reports')
|
|
411
|
+
.option('-j, --json', 'Output as JSON')
|
|
412
|
+
.option('-l, --limit <n>', 'Max chain reports to show (default: 20)')
|
|
413
|
+
.option('-d, --dir <path>', 'Project directory')
|
|
414
|
+
.action(chainListCommand);
|
|
415
|
+
|
|
416
|
+
chainCmd
|
|
417
|
+
.command('show <chain_id>')
|
|
418
|
+
.description('Show a specific chain report by ID')
|
|
419
|
+
.option('-j, --json', 'Output as JSON')
|
|
420
|
+
.option('-d, --dir <path>', 'Project directory')
|
|
421
|
+
.action(chainShowCommand);
|
|
422
|
+
|
|
423
|
+
const missionCmd = program
|
|
424
|
+
.command('mission')
|
|
425
|
+
.description('Group chained runs under a single-repo long-horizon mission');
|
|
426
|
+
|
|
427
|
+
missionCmd
|
|
428
|
+
.command('start')
|
|
429
|
+
.description('Create a durable mission artifact for a single repo')
|
|
430
|
+
.requiredOption('--title <text>', 'Mission title')
|
|
431
|
+
.requiredOption('--goal <text>', 'Mission goal')
|
|
432
|
+
.option('--id <mission_id>', 'Override the derived mission ID')
|
|
433
|
+
.option('-j, --json', 'Output as JSON')
|
|
434
|
+
.option('-d, --dir <path>', 'Project directory')
|
|
435
|
+
.action(missionStartCommand);
|
|
436
|
+
|
|
437
|
+
missionCmd
|
|
438
|
+
.command('list')
|
|
439
|
+
.description('List mission artifacts newest first')
|
|
440
|
+
.option('-l, --limit <n>', 'Max missions to show (default: 20)')
|
|
441
|
+
.option('-j, --json', 'Output as JSON')
|
|
442
|
+
.option('-d, --dir <path>', 'Project directory')
|
|
443
|
+
.action(missionListCommand);
|
|
444
|
+
|
|
445
|
+
missionCmd
|
|
446
|
+
.command('show [mission_id]')
|
|
447
|
+
.description('Show one mission, or the latest mission when no ID is provided')
|
|
448
|
+
.option('-j, --json', 'Output as JSON')
|
|
449
|
+
.option('-d, --dir <path>', 'Project directory')
|
|
450
|
+
.action(missionShowCommand);
|
|
451
|
+
|
|
452
|
+
missionCmd
|
|
453
|
+
.command('attach-chain [chain_id]')
|
|
454
|
+
.description('Attach a chain report to a mission (default: latest chain on latest mission)')
|
|
455
|
+
.option('-m, --mission <mission_id>', 'Explicit mission ID (defaults to latest mission)')
|
|
456
|
+
.option('-j, --json', 'Output as JSON')
|
|
457
|
+
.option('-d, --dir <path>', 'Project directory')
|
|
458
|
+
.action(missionAttachChainCommand);
|
|
459
|
+
|
|
395
460
|
program
|
|
396
461
|
.command('validate')
|
|
397
462
|
.description('Validate project protocol artifacts')
|
|
@@ -516,6 +581,11 @@ program
|
|
|
516
581
|
.option('--continue-from <run_id>', 'Continue from a prior terminal run (sets trigger=continuation)')
|
|
517
582
|
.option('--recover-from <run_id>', 'Recover from a prior blocked run (sets trigger=recovery)')
|
|
518
583
|
.option('--inherit-context', 'Inherit read-only summary context from the parent run (requires --continue-from or --recover-from)')
|
|
584
|
+
.option('--chain', 'Auto-chain runs: when a run completes, start a continuation automatically')
|
|
585
|
+
.option('--max-chains <n>', 'Maximum continuation runs in chain mode (default: 5)', parseInt)
|
|
586
|
+
.option('--chain-on <statuses>', 'Comma-separated terminal statuses that trigger chaining (default: completed)')
|
|
587
|
+
.option('--chain-cooldown <seconds>', 'Seconds to wait between chained runs (default: 5)', parseInt)
|
|
588
|
+
.option('--mission <mission_id>', 'Bind chained runs to a mission (use "latest" for most recent mission)')
|
|
519
589
|
.action(runCommand);
|
|
520
590
|
|
|
521
591
|
program
|
package/dashboard/app.js
CHANGED
|
@@ -15,6 +15,8 @@ import { render as renderCrossRepo } from './components/cross-repo.js';
|
|
|
15
15
|
import { render as renderDelegations } from './components/delegations.js';
|
|
16
16
|
import { render as renderBlockers } from './components/blockers.js';
|
|
17
17
|
import { render as renderArtifacts } from './components/artifacts.js';
|
|
18
|
+
import { render as renderMission } from './components/mission.js';
|
|
19
|
+
import { render as renderChain } from './components/chain.js';
|
|
18
20
|
import { render as renderRunHistory } from './components/run-history.js';
|
|
19
21
|
import { render as renderTimeouts } from './components/timeouts.js';
|
|
20
22
|
import { render as renderCoordinatorTimeouts } from './components/coordinator-timeouts.js';
|
|
@@ -35,6 +37,8 @@ const VIEWS = {
|
|
|
35
37
|
'cross-repo': { fetch: ['coordinatorState', 'coordinatorHistory'], render: renderCrossRepo },
|
|
36
38
|
blockers: { fetch: ['coordinatorBlockers'], render: renderBlockers },
|
|
37
39
|
artifacts: { fetch: ['workflowKitArtifacts'], render: renderArtifacts },
|
|
40
|
+
mission: { fetch: ['missions'], render: renderMission },
|
|
41
|
+
chain: { fetch: ['chainReports'], render: renderChain },
|
|
38
42
|
'run-history': { fetch: ['runHistory'], render: renderRunHistory },
|
|
39
43
|
timeouts: { fetch: ['timeouts'], render: renderTimeouts },
|
|
40
44
|
'coordinator-timeouts': { fetch: ['coordinatorTimeouts'], render: renderCoordinatorTimeouts },
|
|
@@ -58,6 +62,8 @@ const API_MAP = {
|
|
|
58
62
|
coordinatorBlockers: '/api/coordinator/blockers',
|
|
59
63
|
coordinatorRepoStatusRows: '/api/coordinator/repo-status',
|
|
60
64
|
workflowKitArtifacts: '/api/workflow-kit-artifacts',
|
|
65
|
+
missions: '/api/missions',
|
|
66
|
+
chainReports: '/api/chain-reports',
|
|
61
67
|
connectors: '/api/connectors',
|
|
62
68
|
runHistory: '/api/run-history',
|
|
63
69
|
timeouts: '/api/timeouts',
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain view — renders run-chaining visibility from /api/chain-reports.
|
|
3
|
+
*
|
|
4
|
+
* Pure render function: takes snapshot data from the bridge server and returns
|
|
5
|
+
* HTML for latest-chain lineage plus recent chain-session history.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
function esc(str) {
|
|
9
|
+
if (str == null) return '';
|
|
10
|
+
return String(str)
|
|
11
|
+
.replace(/&/g, '&')
|
|
12
|
+
.replace(/</g, '<')
|
|
13
|
+
.replace(/>/g, '>')
|
|
14
|
+
.replace(/"/g, '"')
|
|
15
|
+
.replace(/'/g, ''');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function badge(label, color = 'var(--text-dim)') {
|
|
19
|
+
return `<span class="badge" style="color:${color};border-color:${color}">${esc(label)}</span>`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function formatStatus(status) {
|
|
23
|
+
switch (status) {
|
|
24
|
+
case 'completed':
|
|
25
|
+
return badge('completed', 'var(--green)');
|
|
26
|
+
case 'blocked':
|
|
27
|
+
return badge('blocked', 'var(--yellow)');
|
|
28
|
+
case 'failed':
|
|
29
|
+
return badge('failed', 'var(--red)');
|
|
30
|
+
default:
|
|
31
|
+
return badge(status || 'unknown', 'var(--text-dim)');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function formatTerminalReason(reason) {
|
|
36
|
+
switch (reason) {
|
|
37
|
+
case 'chain_limit_reached':
|
|
38
|
+
return badge('chain limit reached', '#38bdf8');
|
|
39
|
+
case 'non_chainable_status':
|
|
40
|
+
return badge('non-chainable status', 'var(--yellow)');
|
|
41
|
+
case 'operator_abort':
|
|
42
|
+
return badge('operator abort', 'var(--red)');
|
|
43
|
+
case 'parent_validation_failed':
|
|
44
|
+
return badge('parent validation failed', 'var(--red)');
|
|
45
|
+
default:
|
|
46
|
+
return badge(reason || 'unknown', 'var(--text-dim)');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function formatDuration(ms) {
|
|
51
|
+
if (ms == null) return '—';
|
|
52
|
+
if (ms < 1000) return `${ms}ms`;
|
|
53
|
+
const seconds = Math.floor(ms / 1000);
|
|
54
|
+
if (seconds < 60) return `${seconds}s`;
|
|
55
|
+
const minutes = Math.floor(seconds / 60);
|
|
56
|
+
const remainingSeconds = seconds % 60;
|
|
57
|
+
if (minutes < 60) return `${minutes}m ${remainingSeconds}s`;
|
|
58
|
+
const hours = Math.floor(minutes / 60);
|
|
59
|
+
const remainingMinutes = minutes % 60;
|
|
60
|
+
return `${hours}h ${remainingMinutes}m`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function formatDate(iso) {
|
|
64
|
+
if (!iso) return '—';
|
|
65
|
+
try {
|
|
66
|
+
const date = new Date(iso);
|
|
67
|
+
return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })
|
|
68
|
+
+ ' ' + date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
|
|
69
|
+
} catch {
|
|
70
|
+
return esc(iso);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function truncateId(value, len = 12) {
|
|
75
|
+
if (!value) return '—';
|
|
76
|
+
return value.length > len ? `${value.slice(0, len)}…` : value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function formatContextSummary(summary) {
|
|
80
|
+
if (!summary) return '—';
|
|
81
|
+
|
|
82
|
+
const parts = [];
|
|
83
|
+
if (Array.isArray(summary.parent_roles_used) && summary.parent_roles_used.length > 0) {
|
|
84
|
+
parts.push(`${summary.parent_roles_used.length} roles`);
|
|
85
|
+
}
|
|
86
|
+
if (summary.parent_phases_completed_count > 0) {
|
|
87
|
+
parts.push(`${summary.parent_phases_completed_count} phases`);
|
|
88
|
+
}
|
|
89
|
+
if (summary.recent_decisions_count > 0) {
|
|
90
|
+
parts.push(`${summary.recent_decisions_count} decisions`);
|
|
91
|
+
}
|
|
92
|
+
if (summary.recent_accepted_turns_count > 0) {
|
|
93
|
+
parts.push(`${summary.recent_accepted_turns_count} turns`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return parts.length > 0 ? esc(parts.join(', ')) : '—';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function renderLatestRunsTable(report) {
|
|
100
|
+
if (!Array.isArray(report?.runs) || report.runs.length === 0) {
|
|
101
|
+
return `<p class="section-subtitle">No runs recorded for this chain.</p>`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let html = `<div class="section"><h3>Latest Chain Lineage</h3>
|
|
105
|
+
<table class="data-table">
|
|
106
|
+
<thead>
|
|
107
|
+
<tr>
|
|
108
|
+
<th>#</th>
|
|
109
|
+
<th>Run ID</th>
|
|
110
|
+
<th>Status</th>
|
|
111
|
+
<th>Trigger</th>
|
|
112
|
+
<th>Turns</th>
|
|
113
|
+
<th>Duration</th>
|
|
114
|
+
<th>Parent</th>
|
|
115
|
+
<th>Inherited Context</th>
|
|
116
|
+
</tr>
|
|
117
|
+
</thead>
|
|
118
|
+
<tbody>`;
|
|
119
|
+
|
|
120
|
+
report.runs.forEach((run, index) => {
|
|
121
|
+
html += `<tr>
|
|
122
|
+
<td style="color:var(--text-dim)">${index + 1}</td>
|
|
123
|
+
<td class="mono" title="${esc(run.run_id || '')}">${esc(truncateId(run.run_id))}</td>
|
|
124
|
+
<td>${formatStatus(run.status)}</td>
|
|
125
|
+
<td>${esc(run.provenance_trigger || '—')}</td>
|
|
126
|
+
<td>${run.turns ?? '—'}</td>
|
|
127
|
+
<td>${formatDuration(run.duration_ms)}</td>
|
|
128
|
+
<td class="mono" title="${esc(run.parent_run_id || '')}">${esc(truncateId(run.parent_run_id))}</td>
|
|
129
|
+
<td>${formatContextSummary(run.inherited_context_summary)}</td>
|
|
130
|
+
</tr>`;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
html += '</tbody></table></div>';
|
|
134
|
+
return html;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function renderRecentChainsTable(reports) {
|
|
138
|
+
let html = `<div class="section"><h3>Recent Chain Sessions</h3>
|
|
139
|
+
<table class="data-table">
|
|
140
|
+
<thead>
|
|
141
|
+
<tr>
|
|
142
|
+
<th>#</th>
|
|
143
|
+
<th>Chain ID</th>
|
|
144
|
+
<th>Runs</th>
|
|
145
|
+
<th>Turns</th>
|
|
146
|
+
<th>Terminal</th>
|
|
147
|
+
<th>Duration</th>
|
|
148
|
+
<th>Started</th>
|
|
149
|
+
</tr>
|
|
150
|
+
</thead>
|
|
151
|
+
<tbody>`;
|
|
152
|
+
|
|
153
|
+
reports.forEach((report, index) => {
|
|
154
|
+
html += `<tr>
|
|
155
|
+
<td style="color:var(--text-dim)">${index + 1}</td>
|
|
156
|
+
<td class="mono" title="${esc(report.chain_id || '')}">${esc(truncateId(report.chain_id, 14))}</td>
|
|
157
|
+
<td>${report.runs?.length || 0}</td>
|
|
158
|
+
<td>${report.total_turns ?? '—'}</td>
|
|
159
|
+
<td>${formatTerminalReason(report.terminal_reason)}</td>
|
|
160
|
+
<td>${formatDuration(report.total_duration_ms)}</td>
|
|
161
|
+
<td>${formatDate(report.started_at)}</td>
|
|
162
|
+
</tr>`;
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
html += '</tbody></table></div>';
|
|
166
|
+
return html;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function render({ chainReports }) {
|
|
170
|
+
if (!chainReports || typeof chainReports !== 'object') {
|
|
171
|
+
return `<div class="placeholder"><h2>Chain</h2><p>No chain data available. Run a chained governed session to populate this view.</p></div>`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const reports = Array.isArray(chainReports.reports) ? chainReports.reports : [];
|
|
175
|
+
const latest = chainReports.latest || reports[0] || null;
|
|
176
|
+
|
|
177
|
+
if (!latest || reports.length === 0) {
|
|
178
|
+
return `<div class="placeholder"><h2>Chain</h2><p>No chain reports found. Run <code>agentxchain run --chain</code> to record automatic continuation lineage.</p></div>`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let html = `<div class="chain-view"><div class="run-header"><div class="run-meta">`;
|
|
182
|
+
html += `<span class="turn-count">latest chain ${esc(latest.chain_id || '—')}</span>`;
|
|
183
|
+
html += badge(`${latest.runs?.length || 0} runs`, '#38bdf8');
|
|
184
|
+
html += badge(`${latest.total_turns || 0} turns`, 'var(--green)');
|
|
185
|
+
html += formatTerminalReason(latest.terminal_reason);
|
|
186
|
+
html += `</div></div>`;
|
|
187
|
+
|
|
188
|
+
html += `<div class="section"><h3>Latest Chain Summary</h3><dl class="detail-list">`;
|
|
189
|
+
html += `<dt>Started</dt><dd>${esc(latest.started_at || '—')}</dd>`;
|
|
190
|
+
html += `<dt>Completed</dt><dd>${esc(latest.completed_at || '—')}</dd>`;
|
|
191
|
+
html += `<dt>Total Duration</dt><dd>${formatDuration(latest.total_duration_ms)}</dd>`;
|
|
192
|
+
html += `<dt>Total Turns</dt><dd>${latest.total_turns ?? '—'}</dd>`;
|
|
193
|
+
html += `<dt>Terminal Reason</dt><dd>${esc(latest.terminal_reason || '—')}</dd>`;
|
|
194
|
+
html += `</dl></div>`;
|
|
195
|
+
|
|
196
|
+
html += renderLatestRunsTable(latest);
|
|
197
|
+
html += renderRecentChainsTable(reports);
|
|
198
|
+
html += '</div>';
|
|
199
|
+
return html;
|
|
200
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
function esc(str) {
|
|
2
|
+
if (str == null) return '';
|
|
3
|
+
return String(str)
|
|
4
|
+
.replace(/&/g, '&')
|
|
5
|
+
.replace(/</g, '<')
|
|
6
|
+
.replace(/>/g, '>')
|
|
7
|
+
.replace(/"/g, '"')
|
|
8
|
+
.replace(/'/g, ''');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function badge(label, color = 'var(--text-dim)') {
|
|
12
|
+
return `<span class="badge" style="color:${color};border-color:${color}">${esc(label)}</span>`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function formatMissionStatus(status) {
|
|
16
|
+
switch (status) {
|
|
17
|
+
case 'progressing':
|
|
18
|
+
return badge('progressing', 'var(--green)');
|
|
19
|
+
case 'planned':
|
|
20
|
+
return badge('planned', '#38bdf8');
|
|
21
|
+
case 'needs_attention':
|
|
22
|
+
return badge('needs attention', 'var(--yellow)');
|
|
23
|
+
case 'degraded':
|
|
24
|
+
return badge('degraded', 'var(--red)');
|
|
25
|
+
default:
|
|
26
|
+
return badge(status || 'unknown', 'var(--text-dim)');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function formatTerminalReason(reason) {
|
|
31
|
+
switch (reason) {
|
|
32
|
+
case 'chain_limit_reached':
|
|
33
|
+
return badge('chain limit reached', '#38bdf8');
|
|
34
|
+
case 'non_chainable_status':
|
|
35
|
+
return badge('non-chainable status', 'var(--yellow)');
|
|
36
|
+
case 'operator_abort':
|
|
37
|
+
return badge('operator abort', 'var(--red)');
|
|
38
|
+
case 'parent_validation_failed':
|
|
39
|
+
return badge('parent validation failed', 'var(--red)');
|
|
40
|
+
case 'completed':
|
|
41
|
+
return badge('completed', 'var(--green)');
|
|
42
|
+
default:
|
|
43
|
+
return badge(reason || 'none', 'var(--text-dim)');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function formatDate(iso) {
|
|
48
|
+
if (!iso) return '—';
|
|
49
|
+
try {
|
|
50
|
+
const date = new Date(iso);
|
|
51
|
+
return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })
|
|
52
|
+
+ ' ' + date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
|
|
53
|
+
} catch {
|
|
54
|
+
return esc(iso);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function truncateId(value, len = 14) {
|
|
59
|
+
if (!value) return '—';
|
|
60
|
+
return value.length > len ? `${value.slice(0, len)}…` : value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function renderAttachedChains(latest) {
|
|
64
|
+
if (!Array.isArray(latest?.chains) || latest.chains.length === 0) {
|
|
65
|
+
return `<div class="section"><h3>Attached Chains</h3><p class="section-subtitle">No chain reports are attached to this mission yet.</p></div>`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let html = `<div class="section"><h3>Attached Chains</h3>
|
|
69
|
+
<table class="data-table">
|
|
70
|
+
<thead>
|
|
71
|
+
<tr>
|
|
72
|
+
<th>#</th>
|
|
73
|
+
<th>Chain ID</th>
|
|
74
|
+
<th>Runs</th>
|
|
75
|
+
<th>Turns</th>
|
|
76
|
+
<th>Terminal</th>
|
|
77
|
+
<th>Started</th>
|
|
78
|
+
</tr>
|
|
79
|
+
</thead>
|
|
80
|
+
<tbody>`;
|
|
81
|
+
|
|
82
|
+
latest.chains.forEach((chain, index) => {
|
|
83
|
+
html += `<tr>
|
|
84
|
+
<td style="color:var(--text-dim)">${index + 1}</td>
|
|
85
|
+
<td class="mono" title="${esc(chain.chain_id || '')}">${esc(truncateId(chain.chain_id))}</td>
|
|
86
|
+
<td>${chain.runs?.length || 0}</td>
|
|
87
|
+
<td>${chain.total_turns ?? '—'}</td>
|
|
88
|
+
<td>${formatTerminalReason(chain.terminal_reason)}</td>
|
|
89
|
+
<td>${formatDate(chain.started_at)}</td>
|
|
90
|
+
</tr>`;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
html += '</tbody></table></div>';
|
|
94
|
+
return html;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function renderRecentMissions(missions) {
|
|
98
|
+
let html = `<div class="section"><h3>Recent Missions</h3>
|
|
99
|
+
<table class="data-table">
|
|
100
|
+
<thead>
|
|
101
|
+
<tr>
|
|
102
|
+
<th>#</th>
|
|
103
|
+
<th>Mission ID</th>
|
|
104
|
+
<th>Status</th>
|
|
105
|
+
<th>Chains</th>
|
|
106
|
+
<th>Runs</th>
|
|
107
|
+
<th>Turns</th>
|
|
108
|
+
<th>Repo Decisions</th>
|
|
109
|
+
<th>Latest Terminal</th>
|
|
110
|
+
<th>Updated</th>
|
|
111
|
+
<th>Title</th>
|
|
112
|
+
</tr>
|
|
113
|
+
</thead>
|
|
114
|
+
<tbody>`;
|
|
115
|
+
|
|
116
|
+
missions.forEach((mission, index) => {
|
|
117
|
+
html += `<tr>
|
|
118
|
+
<td style="color:var(--text-dim)">${index + 1}</td>
|
|
119
|
+
<td class="mono" title="${esc(mission.mission_id || '')}">${esc(truncateId(mission.mission_id, 18))}</td>
|
|
120
|
+
<td>${formatMissionStatus(mission.derived_status)}</td>
|
|
121
|
+
<td>${mission.chain_count ?? 0}</td>
|
|
122
|
+
<td>${mission.total_runs ?? 0}</td>
|
|
123
|
+
<td>${mission.total_turns ?? 0}</td>
|
|
124
|
+
<td>${mission.active_repo_decisions_count ?? 0}</td>
|
|
125
|
+
<td>${formatTerminalReason(mission.latest_terminal_reason)}</td>
|
|
126
|
+
<td>${formatDate(mission.updated_at || mission.created_at)}</td>
|
|
127
|
+
<td>${esc(mission.title || '—')}</td>
|
|
128
|
+
</tr>`;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
html += '</tbody></table></div>';
|
|
132
|
+
return html;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function render({ missions }) {
|
|
136
|
+
if (!missions || typeof missions !== 'object') {
|
|
137
|
+
return `<div class="placeholder"><h2>Mission</h2><p>No mission data available. Create a mission and bind a chain to populate this view.</p></div>`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const missionList = Array.isArray(missions.missions) ? missions.missions : [];
|
|
141
|
+
const latest = missions.latest || missionList[0] || null;
|
|
142
|
+
|
|
143
|
+
if (!latest || missionList.length === 0) {
|
|
144
|
+
return `<div class="placeholder"><h2>Mission</h2><p>No missions found. Run <code>agentxchain mission start --title "..." --goal "..."</code> and then <code>agentxchain run --chain --mission latest</code> to track long-horizon work here.</p></div>`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const missingChains = Array.isArray(latest.missing_chain_ids) ? latest.missing_chain_ids : [];
|
|
148
|
+
|
|
149
|
+
let html = `<div class="mission-view"><div class="run-header"><div class="run-meta">`;
|
|
150
|
+
html += `<span class="turn-count">latest mission ${esc(latest.mission_id || '—')}</span>`;
|
|
151
|
+
html += formatMissionStatus(latest.derived_status);
|
|
152
|
+
html += badge(`${latest.chain_count || 0} chains`, '#38bdf8');
|
|
153
|
+
html += badge(`${latest.total_turns || 0} turns`, 'var(--green)');
|
|
154
|
+
html += badge(`${latest.active_repo_decisions_count || 0} repo decisions`, 'var(--yellow)');
|
|
155
|
+
html += `</div></div>`;
|
|
156
|
+
|
|
157
|
+
html += `<div class="section"><h3>Latest Mission Summary</h3><dl class="detail-list">`;
|
|
158
|
+
html += `<dt>Title</dt><dd>${esc(latest.title || '—')}</dd>`;
|
|
159
|
+
html += `<dt>Goal</dt><dd>${esc(latest.goal || '—')}</dd>`;
|
|
160
|
+
html += `<dt>Updated</dt><dd>${esc(latest.updated_at || latest.created_at || '—')}</dd>`;
|
|
161
|
+
html += `<dt>Latest Chain</dt><dd class="mono">${esc(latest.latest_chain_id || '—')}</dd>`;
|
|
162
|
+
html += `<dt>Latest Terminal</dt><dd>${esc(latest.latest_terminal_reason || '—')}</dd>`;
|
|
163
|
+
html += `<dt>Total Runs</dt><dd>${latest.total_runs ?? 0}</dd>`;
|
|
164
|
+
html += `<dt>Attached Chains</dt><dd>${latest.attached_chain_count ?? 0}</dd>`;
|
|
165
|
+
html += `<dt>Missing Chains</dt><dd>${missingChains.length}</dd>`;
|
|
166
|
+
html += `<dt>Active Repo Decisions</dt><dd>${latest.active_repo_decisions_count ?? 0}</dd>`;
|
|
167
|
+
html += `</dl></div>`;
|
|
168
|
+
|
|
169
|
+
if (missingChains.length > 0) {
|
|
170
|
+
html += `<div class="section"><h3>Missing Chain References</h3><p class="section-subtitle">This mission is degraded until the missing chain reports are restored.</p><p class="mono">${esc(missingChains.join(', '))}</p></div>`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
html += renderAttachedChains(latest);
|
|
174
|
+
html += renderRecentMissions(missionList);
|
|
175
|
+
html += '</div>';
|
|
176
|
+
return html;
|
|
177
|
+
}
|
package/dashboard/index.html
CHANGED
|
@@ -405,6 +405,8 @@
|
|
|
405
405
|
<a href="#gate">Gates</a>
|
|
406
406
|
<a href="#blockers">Blockers</a>
|
|
407
407
|
<a href="#artifacts">Artifacts</a>
|
|
408
|
+
<a href="#mission">Mission</a>
|
|
409
|
+
<a href="#chain">Chain</a>
|
|
408
410
|
<a href="#run-history">Run History</a>
|
|
409
411
|
<a href="#timeouts">Timeouts</a>
|
|
410
412
|
<a href="#coordinator-timeouts">Coordinator Timeouts</a>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentxchain",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.112.0",
|
|
4
4
|
"description": "CLI for AgentXchain — governed multi-agent software delivery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"test:node": "node --test test/*.test.js",
|
|
28
28
|
"preflight:release": "bash scripts/release-preflight.sh",
|
|
29
29
|
"preflight:release:strict": "bash scripts/release-preflight.sh --strict",
|
|
30
|
+
"check:release-alignment": "node scripts/check-release-alignment.mjs",
|
|
30
31
|
"postflight:release": "bash scripts/release-postflight.sh",
|
|
31
32
|
"postflight:downstream": "bash scripts/release-downstream-truth.sh",
|
|
32
33
|
"bump:release": "bash scripts/release-bump.sh",
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import {
|
|
7
|
+
RELEASE_ALIGNMENT_SCOPES,
|
|
8
|
+
validateReleaseAlignment,
|
|
9
|
+
} from '../src/lib/release-alignment.js';
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const REPO_ROOT = join(__dirname, '..', '..');
|
|
13
|
+
|
|
14
|
+
function usage() {
|
|
15
|
+
console.error('Usage: node cli/scripts/check-release-alignment.mjs [--target-version <semver>] [--scope prebump|current] [--json]');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let targetVersion = null;
|
|
19
|
+
let scope = RELEASE_ALIGNMENT_SCOPES.CURRENT;
|
|
20
|
+
let json = false;
|
|
21
|
+
|
|
22
|
+
for (let index = 2; index < process.argv.length; index += 1) {
|
|
23
|
+
const arg = process.argv[index];
|
|
24
|
+
if (arg === '--target-version') {
|
|
25
|
+
targetVersion = process.argv[index + 1] || null;
|
|
26
|
+
if (!targetVersion) {
|
|
27
|
+
console.error('Error: --target-version requires a semver argument');
|
|
28
|
+
usage();
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
index += 1;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (arg === '--scope') {
|
|
35
|
+
scope = process.argv[index + 1] || '';
|
|
36
|
+
if (!Object.values(RELEASE_ALIGNMENT_SCOPES).includes(scope)) {
|
|
37
|
+
console.error(`Error: invalid --scope "${scope}"`);
|
|
38
|
+
usage();
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
index += 1;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (arg === '--json') {
|
|
45
|
+
json = true;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
console.error(`Error: unknown argument "${arg}"`);
|
|
49
|
+
usage();
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const result = validateReleaseAlignment(REPO_ROOT, { targetVersion, scope });
|
|
54
|
+
|
|
55
|
+
if (json) {
|
|
56
|
+
console.log(JSON.stringify(result, null, 2));
|
|
57
|
+
} else if (result.ok) {
|
|
58
|
+
console.log(`Release alignment OK for ${result.targetVersion} (${result.scope}, ${result.checkedSurfaceCount} surfaces).`);
|
|
59
|
+
} else {
|
|
60
|
+
console.error(`Release alignment FAILED for ${result.targetVersion} (${result.scope}, ${result.errors.length} issue(s)).`);
|
|
61
|
+
for (const error of result.errors) {
|
|
62
|
+
console.error(`- [${error.surface_id}] ${error.message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
process.exit(result.ok ? 0 : 1);
|
package/scripts/release-bump.sh
CHANGED
|
@@ -143,74 +143,23 @@ if git rev-parse "v${TARGET_VERSION}" >/dev/null 2>&1; then
|
|
|
143
143
|
fi
|
|
144
144
|
echo " OK: tag v${TARGET_VERSION} does not exist"
|
|
145
145
|
|
|
146
|
-
# 4. Pre-bump
|
|
147
|
-
# Ensures all
|
|
146
|
+
# 4. Pre-bump release-alignment guard
|
|
147
|
+
# Ensures all manual target-version surfaces already reference the target version
|
|
148
148
|
# BEFORE the bump commit is created. This catches stale drift that would
|
|
149
149
|
# otherwise only be discovered after minting local release identities.
|
|
150
150
|
#
|
|
151
151
|
# NOTE: Homebrew mirror formula and README are NOT checked here. They are
|
|
152
|
-
# auto-aligned in step
|
|
153
|
-
# post-publish
|
|
154
|
-
echo "[4/
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
CHANGELOG_TOP=$(grep -m1 -E '^## [0-9]+\.[0-9]+\.[0-9]+$' "${REPO_ROOT}/cli/CHANGELOG.md" 2>/dev/null | sed 's/^## //' || true)
|
|
159
|
-
if [[ "$CHANGELOG_TOP" != "$TARGET_VERSION" ]]; then
|
|
160
|
-
SURFACE_ERRORS+=("CHANGELOG.md top heading is '${CHANGELOG_TOP:-missing}', expected '${TARGET_VERSION}'")
|
|
161
|
-
fi
|
|
162
|
-
|
|
163
|
-
# 4b. Release notes page exists
|
|
164
|
-
RELEASE_DOC_ID="v${TARGET_VERSION//./-}"
|
|
165
|
-
RELEASE_DOC_PATH="website-v2/docs/releases/${RELEASE_DOC_ID}.mdx"
|
|
166
|
-
if [[ ! -f "${REPO_ROOT}/${RELEASE_DOC_PATH}" ]]; then
|
|
167
|
-
SURFACE_ERRORS+=("release notes page missing: ${RELEASE_DOC_PATH}")
|
|
168
|
-
fi
|
|
169
|
-
|
|
170
|
-
# 4c. Docs sidebar auto-generates releases from dirName (release doc existence is sufficient)
|
|
171
|
-
if ! grep -q "dirName.*releases" "${REPO_ROOT}/website-v2/sidebars.ts" 2>/dev/null; then
|
|
172
|
-
SURFACE_ERRORS+=("sidebars.ts does not auto-generate releases (missing dirName: 'releases')")
|
|
173
|
-
fi
|
|
174
|
-
|
|
175
|
-
# 4d. Homepage hero badge shows target version
|
|
176
|
-
if ! grep -q "v${TARGET_VERSION}" "${REPO_ROOT}/website-v2/src/pages/index.tsx" 2>/dev/null; then
|
|
177
|
-
SURFACE_ERRORS+=("homepage index.tsx does not contain 'v${TARGET_VERSION}'")
|
|
178
|
-
fi
|
|
179
|
-
|
|
180
|
-
# 4e. Conformance capabilities version
|
|
181
|
-
CAPS_VERSION=$(node -e "try{console.log(JSON.parse(require('fs').readFileSync('${REPO_ROOT}/.agentxchain-conformance/capabilities.json','utf8')).version)}catch{console.log('missing')}" 2>/dev/null || echo "missing")
|
|
182
|
-
if [[ "$CAPS_VERSION" != "$TARGET_VERSION" ]]; then
|
|
183
|
-
SURFACE_ERRORS+=("capabilities.json version is '${CAPS_VERSION}', expected '${TARGET_VERSION}'")
|
|
184
|
-
fi
|
|
185
|
-
|
|
186
|
-
# 4f. Protocol implementor guide example
|
|
187
|
-
if ! grep -q "\"version\": \"${TARGET_VERSION}\"" "${REPO_ROOT}/website-v2/docs/protocol-implementor-guide.mdx" 2>/dev/null; then
|
|
188
|
-
SURFACE_ERRORS+=("protocol-implementor-guide.mdx does not contain '\"version\": \"${TARGET_VERSION}\"'")
|
|
189
|
-
fi
|
|
190
|
-
|
|
191
|
-
# 4g. Launch evidence report title
|
|
192
|
-
ESCAPED_VERSION="${TARGET_VERSION//./\\.}"
|
|
193
|
-
if ! grep -qE "^# Launch Evidence Report — AgentXchain v${ESCAPED_VERSION}" "${REPO_ROOT}/.planning/LAUNCH_EVIDENCE_REPORT.md" 2>/dev/null; then
|
|
194
|
-
SURFACE_ERRORS+=("LAUNCH_EVIDENCE_REPORT.md title does not carry v${TARGET_VERSION}")
|
|
195
|
-
fi
|
|
196
|
-
|
|
197
|
-
# 4h. llms.txt must list the current release notes route
|
|
198
|
-
CURRENT_RELEASE_ROUTE="/docs/releases/${RELEASE_DOC_ID}"
|
|
199
|
-
if ! grep -q "${CURRENT_RELEASE_ROUTE}" "${REPO_ROOT}/website-v2/static/llms.txt" 2>/dev/null; then
|
|
200
|
-
SURFACE_ERRORS+=("website-v2/static/llms.txt does not list '${CURRENT_RELEASE_ROUTE}'")
|
|
201
|
-
fi
|
|
202
|
-
|
|
203
|
-
# 4i. sitemap.xml is now auto-generated by Docusaurus at build time — no static file check needed
|
|
204
|
-
|
|
205
|
-
if [[ "${#SURFACE_ERRORS[@]}" -gt 0 ]]; then
|
|
206
|
-
echo "FAIL: ${#SURFACE_ERRORS[@]} version-surface(s) not aligned to ${TARGET_VERSION}:" >&2
|
|
207
|
-
printf ' - %s\n' "${SURFACE_ERRORS[@]}" >&2
|
|
152
|
+
# auto-aligned in step 6 because the registry tarball URL is deterministic but
|
|
153
|
+
# the registry SHA256 is inherently post-publish truth.
|
|
154
|
+
echo "[4/10] Verifying release alignment for ${TARGET_VERSION}..."
|
|
155
|
+
if node "${CLI_DIR}/scripts/check-release-alignment.mjs" --target-version "${TARGET_VERSION}" --scope prebump; then
|
|
156
|
+
:
|
|
157
|
+
else
|
|
208
158
|
echo "" >&2
|
|
209
159
|
echo "Fix these surfaces before running release-bump. The bump script refuses to" >&2
|
|
210
160
|
echo "create release identity when governed surfaces are stale." >&2
|
|
211
161
|
exit 1
|
|
212
162
|
fi
|
|
213
|
-
echo " OK: all 8 governed version surfaces reference ${TARGET_VERSION}"
|
|
214
163
|
|
|
215
164
|
# 5. Normalize release-note sidebar ordering
|
|
216
165
|
echo "[5/10] Normalizing release-note sidebar positions..."
|