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
|
@@ -85,7 +85,7 @@ fi
|
|
|
85
85
|
echo ""
|
|
86
86
|
|
|
87
87
|
# 1. Clean working tree
|
|
88
|
-
echo "[1/
|
|
88
|
+
echo "[1/7] Git status"
|
|
89
89
|
if git diff --quiet HEAD 2>/dev/null && [ -z "$(git ls-files --others --exclude-standard 2>/dev/null)" ]; then
|
|
90
90
|
pass "Working tree is clean"
|
|
91
91
|
else
|
|
@@ -97,7 +97,7 @@ else
|
|
|
97
97
|
fi
|
|
98
98
|
|
|
99
99
|
# 2. Dependencies
|
|
100
|
-
echo "[2/
|
|
100
|
+
echo "[2/7] Dependencies"
|
|
101
101
|
if run_and_capture NPM_CI_OUTPUT npm ci --ignore-scripts; then
|
|
102
102
|
pass "npm ci succeeded"
|
|
103
103
|
else
|
|
@@ -107,7 +107,7 @@ fi
|
|
|
107
107
|
|
|
108
108
|
# 3. Tests
|
|
109
109
|
if [[ "$PUBLISH_GATE" -eq 1 ]]; then
|
|
110
|
-
echo "[3/
|
|
110
|
+
echo "[3/7] Release-gate tests (targeted subset)"
|
|
111
111
|
# In publish-gate mode, run only release-critical tests to avoid CI hangs.
|
|
112
112
|
# The full test suite is a pre-tag responsibility, not a publish-time gate.
|
|
113
113
|
GATE_TESTS=(
|
|
@@ -142,7 +142,7 @@ if [[ "$PUBLISH_GATE" -eq 1 ]]; then
|
|
|
142
142
|
fi
|
|
143
143
|
fi
|
|
144
144
|
else
|
|
145
|
-
echo "[3/
|
|
145
|
+
echo "[3/7] Test suite"
|
|
146
146
|
# Install MCP example deps — tests start example servers as subprocesses
|
|
147
147
|
for example_dir in "${CLI_DIR}/../examples/mcp-echo-agent" "${CLI_DIR}/../examples/mcp-http-echo-agent"; do
|
|
148
148
|
if [[ -f "${example_dir}/package.json" && ! -d "${example_dir}/node_modules" ]]; then
|
|
@@ -189,7 +189,7 @@ else
|
|
|
189
189
|
fi
|
|
190
190
|
|
|
191
191
|
# 4. CHANGELOG has target version
|
|
192
|
-
echo "[4/
|
|
192
|
+
echo "[4/7] CHANGELOG"
|
|
193
193
|
if grep -Fxq "## ${TARGET_VERSION}" CHANGELOG.md 2>/dev/null; then
|
|
194
194
|
pass "CHANGELOG.md contains ${TARGET_VERSION} entry"
|
|
195
195
|
else
|
|
@@ -197,7 +197,7 @@ else
|
|
|
197
197
|
fi
|
|
198
198
|
|
|
199
199
|
# 5. Package version
|
|
200
|
-
echo "[5/
|
|
200
|
+
echo "[5/7] Package version"
|
|
201
201
|
PKG_VERSION=$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json','utf8')).version)")
|
|
202
202
|
echo " Current version: ${PKG_VERSION}"
|
|
203
203
|
if [ "$PKG_VERSION" = "${TARGET_VERSION}" ]; then
|
|
@@ -210,8 +210,23 @@ else
|
|
|
210
210
|
fi
|
|
211
211
|
fi
|
|
212
212
|
|
|
213
|
-
# 6.
|
|
214
|
-
echo "[6/
|
|
213
|
+
# 6. Release-alignment surfaces (shared manifest)
|
|
214
|
+
echo "[6/7] Release alignment (shared manifest)"
|
|
215
|
+
ALIGNMENT_SCRIPT="${SCRIPT_DIR}/check-release-alignment.mjs"
|
|
216
|
+
if [[ -f "$ALIGNMENT_SCRIPT" ]]; then
|
|
217
|
+
if run_and_capture ALIGNMENT_OUTPUT node "$ALIGNMENT_SCRIPT" --scope current --target-version "$TARGET_VERSION"; then
|
|
218
|
+
ALIGNED_COUNT="$(printf '%s\n' "$ALIGNMENT_OUTPUT" | awk -F'[,)]' '/surfaces/ { for (i=1;i<=NF;i++) if ($i ~ /[0-9]+ surfaces/) { gsub(/[^0-9]/,"",$i); print $i; exit } }')"
|
|
219
|
+
pass "Release alignment OK (${ALIGNED_COUNT:-all} surfaces)"
|
|
220
|
+
else
|
|
221
|
+
fail "Release alignment failed"
|
|
222
|
+
printf '%s\n' "$ALIGNMENT_OUTPUT" | head -20
|
|
223
|
+
fi
|
|
224
|
+
else
|
|
225
|
+
warn "check-release-alignment.mjs not found — skipping manifest validation"
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
# 7. Pack dry-run
|
|
229
|
+
echo "[7/7] npm pack --dry-run"
|
|
215
230
|
if run_and_capture PACK_OUTPUT npm pack --dry-run; then
|
|
216
231
|
pass "npm pack --dry-run succeeded"
|
|
217
232
|
PACK_SIZE_LINE="$(printf '%s\n' "$PACK_OUTPUT" | awk '/total files:/ { print; found=1 } END { if (!found) exit 1 }')"
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agentxchain chain — operator-facing read surface for chain reports.
|
|
3
|
+
*
|
|
4
|
+
* Surfaces chain report metadata so operators can inspect lights-out
|
|
5
|
+
* run-chaining history without opening raw JSON files.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { findProjectRoot } from '../lib/config.js';
|
|
10
|
+
import {
|
|
11
|
+
loadAllChainReports,
|
|
12
|
+
loadChainReport,
|
|
13
|
+
loadLatestChainReport,
|
|
14
|
+
} from '../lib/chain-reports.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* agentxchain chain latest — show the most recent chain report.
|
|
18
|
+
*
|
|
19
|
+
* @param {object} opts - { json?: boolean, dir?: string }
|
|
20
|
+
*/
|
|
21
|
+
export async function chainLatestCommand(opts) {
|
|
22
|
+
const root = findProjectRoot(opts.dir || process.cwd());
|
|
23
|
+
if (!root) {
|
|
24
|
+
console.error(chalk.red('No AgentXchain project found. Run this inside a governed project.'));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const report = loadLatestChainReport(root);
|
|
29
|
+
if (!report) {
|
|
30
|
+
console.log(chalk.dim('No chain reports found.'));
|
|
31
|
+
console.log(chalk.dim(' Run `agentxchain run --chain` to enable auto-chaining.'));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (opts.json) {
|
|
36
|
+
console.log(JSON.stringify(report, null, 2));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
renderChainReport(report);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* agentxchain chain list — list all chain reports.
|
|
45
|
+
*
|
|
46
|
+
* @param {object} opts - { json?: boolean, limit?: number, dir?: string }
|
|
47
|
+
*/
|
|
48
|
+
export async function chainListCommand(opts) {
|
|
49
|
+
const root = findProjectRoot(opts.dir || process.cwd());
|
|
50
|
+
if (!root) {
|
|
51
|
+
console.error(chalk.red('No AgentXchain project found. Run this inside a governed project.'));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const reports = loadAllChainReports(root);
|
|
56
|
+
const limit = opts.limit ? parseInt(opts.limit, 10) : 20;
|
|
57
|
+
const limited = reports.slice(0, limit);
|
|
58
|
+
|
|
59
|
+
if (opts.json) {
|
|
60
|
+
console.log(JSON.stringify(limited, null, 2));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (limited.length === 0) {
|
|
65
|
+
console.log(chalk.dim('No chain reports found.'));
|
|
66
|
+
console.log(chalk.dim(' Run `agentxchain run --chain` to enable auto-chaining.'));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Table header
|
|
71
|
+
const header = [
|
|
72
|
+
pad('#', 4),
|
|
73
|
+
pad('Chain ID', 16),
|
|
74
|
+
pad('Runs', 6),
|
|
75
|
+
pad('Turns', 7),
|
|
76
|
+
pad('Terminal Reason', 28),
|
|
77
|
+
pad('Duration', 12),
|
|
78
|
+
pad('Started', 22),
|
|
79
|
+
].join(' ');
|
|
80
|
+
|
|
81
|
+
console.log(chalk.bold(header));
|
|
82
|
+
console.log(chalk.dim('─'.repeat(header.length)));
|
|
83
|
+
|
|
84
|
+
limited.forEach((report, i) => {
|
|
85
|
+
const idx = String(i + 1);
|
|
86
|
+
const chainId = report.chain_id || '—';
|
|
87
|
+
const runs = String(report.runs?.length || 0);
|
|
88
|
+
const turns = String(report.total_turns || 0);
|
|
89
|
+
const terminal = formatTerminalReason(report.terminal_reason);
|
|
90
|
+
const duration = report.total_duration_ms != null
|
|
91
|
+
? formatDuration(report.total_duration_ms)
|
|
92
|
+
: '—';
|
|
93
|
+
const started = report.started_at
|
|
94
|
+
? new Date(report.started_at).toLocaleString()
|
|
95
|
+
: '—';
|
|
96
|
+
|
|
97
|
+
console.log([
|
|
98
|
+
pad(idx, 4),
|
|
99
|
+
pad(chainId, 16),
|
|
100
|
+
pad(runs, 6),
|
|
101
|
+
pad(turns, 7),
|
|
102
|
+
pad(terminal, 28),
|
|
103
|
+
pad(duration, 12),
|
|
104
|
+
pad(started, 22),
|
|
105
|
+
].join(' '));
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
console.log(chalk.dim(`\n${limited.length} chain(s) shown${reports.length > limit ? ` (${reports.length} total)` : ''}`));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* agentxchain chain show <chain_id> — show a specific chain report.
|
|
113
|
+
*
|
|
114
|
+
* @param {string} chainId
|
|
115
|
+
* @param {object} opts - { json?: boolean, dir?: string }
|
|
116
|
+
*/
|
|
117
|
+
export async function chainShowCommand(chainId, opts) {
|
|
118
|
+
const root = findProjectRoot(opts.dir || process.cwd());
|
|
119
|
+
if (!root) {
|
|
120
|
+
console.error(chalk.red('No AgentXchain project found. Run this inside a governed project.'));
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const report = loadChainReport(root, chainId);
|
|
125
|
+
if (!report) {
|
|
126
|
+
console.error(chalk.red(`Chain report not found: ${chainId}`));
|
|
127
|
+
console.log(chalk.dim(' Use `agentxchain chain list` to see available chain reports.'));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (opts.json) {
|
|
132
|
+
console.log(JSON.stringify(report, null, 2));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
renderChainReport(report);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ── Rendering ─────────────────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
function renderChainReport(report) {
|
|
142
|
+
console.log(chalk.bold(`Chain Report: ${report.chain_id}`));
|
|
143
|
+
console.log('');
|
|
144
|
+
console.log(` Total runs: ${report.runs?.length || 0}`);
|
|
145
|
+
console.log(` Total turns: ${report.total_turns || 0}`);
|
|
146
|
+
console.log(` Duration: ${formatDuration(report.total_duration_ms || 0)}`);
|
|
147
|
+
console.log(` Terminal: ${formatTerminalReason(report.terminal_reason)}`);
|
|
148
|
+
console.log(` Started: ${report.started_at || '—'}`);
|
|
149
|
+
console.log(` Completed: ${report.completed_at || '—'}`);
|
|
150
|
+
console.log('');
|
|
151
|
+
|
|
152
|
+
if (!report.runs || report.runs.length === 0) {
|
|
153
|
+
console.log(chalk.dim(' No runs recorded.'));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Run table header
|
|
158
|
+
const runHeader = [
|
|
159
|
+
pad('#', 4),
|
|
160
|
+
pad('Run ID', 14),
|
|
161
|
+
pad('Status', 12),
|
|
162
|
+
pad('Trigger', 14),
|
|
163
|
+
pad('Turns', 7),
|
|
164
|
+
pad('Duration', 12),
|
|
165
|
+
pad('Parent', 14),
|
|
166
|
+
pad('Ctx', 40),
|
|
167
|
+
].join(' ');
|
|
168
|
+
|
|
169
|
+
console.log(chalk.bold(' Runs:'));
|
|
170
|
+
console.log(` ${chalk.dim(runHeader)}`);
|
|
171
|
+
console.log(` ${chalk.dim('─'.repeat(runHeader.length))}`);
|
|
172
|
+
|
|
173
|
+
report.runs.forEach((run, i) => {
|
|
174
|
+
const idx = String(i + 1);
|
|
175
|
+
const runId = (run.run_id || '—').slice(0, 12);
|
|
176
|
+
const status = formatStatus(run.status);
|
|
177
|
+
const trigger = run.provenance_trigger || '—';
|
|
178
|
+
const turns = String(run.turns || 0);
|
|
179
|
+
const duration = run.duration_ms != null ? formatDuration(run.duration_ms) : '—';
|
|
180
|
+
const parent = run.parent_run_id ? run.parent_run_id.slice(0, 12) : '—';
|
|
181
|
+
const ctx = formatInheritedContextSummary(run.inherited_context_summary);
|
|
182
|
+
|
|
183
|
+
console.log(` ${[
|
|
184
|
+
pad(idx, 4),
|
|
185
|
+
pad(runId, 14),
|
|
186
|
+
pad(status, 12),
|
|
187
|
+
pad(trigger, 14),
|
|
188
|
+
pad(turns, 7),
|
|
189
|
+
pad(duration, 12),
|
|
190
|
+
pad(parent, 14),
|
|
191
|
+
pad(ctx, 40),
|
|
192
|
+
].join(' ')}`);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function formatInheritedContextSummary(summary) {
|
|
197
|
+
if (!summary) return '—';
|
|
198
|
+
|
|
199
|
+
const parts = [];
|
|
200
|
+
if (summary.parent_roles_used?.length) {
|
|
201
|
+
parts.push(`${summary.parent_roles_used.length} roles`);
|
|
202
|
+
}
|
|
203
|
+
if (summary.parent_phases_completed_count > 0) {
|
|
204
|
+
parts.push(`${summary.parent_phases_completed_count} phases`);
|
|
205
|
+
}
|
|
206
|
+
if (summary.recent_decisions_count > 0) {
|
|
207
|
+
parts.push(`${summary.recent_decisions_count} decisions`);
|
|
208
|
+
}
|
|
209
|
+
if (summary.recent_accepted_turns_count > 0) {
|
|
210
|
+
parts.push(`${summary.recent_accepted_turns_count} turns`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return parts.length > 0 ? parts.join(', ') : '—';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function formatTerminalReason(reason) {
|
|
217
|
+
if (!reason) return '—';
|
|
218
|
+
switch (reason) {
|
|
219
|
+
case 'chain_limit_reached': return chalk.cyan('chain limit reached');
|
|
220
|
+
case 'non_chainable_status': return chalk.yellow('non-chainable status');
|
|
221
|
+
case 'operator_abort': return chalk.red('operator abort');
|
|
222
|
+
case 'parent_validation_failed': return chalk.red('parent validation failed');
|
|
223
|
+
case 'completed': return chalk.green('completed');
|
|
224
|
+
case 'blocked': return chalk.yellow('blocked');
|
|
225
|
+
default: return reason;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function formatStatus(status) {
|
|
230
|
+
if (status === 'completed') return chalk.green('completed');
|
|
231
|
+
if (status === 'blocked') return chalk.yellow('blocked');
|
|
232
|
+
if (status === 'failed') return chalk.red('failed');
|
|
233
|
+
return status || '—';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function formatDuration(ms) {
|
|
237
|
+
if (ms < 1000) return `${ms}ms`;
|
|
238
|
+
const seconds = Math.floor(ms / 1000);
|
|
239
|
+
if (seconds < 60) return `${seconds}s`;
|
|
240
|
+
const minutes = Math.floor(seconds / 60);
|
|
241
|
+
const remainingSeconds = seconds % 60;
|
|
242
|
+
if (minutes < 60) return `${minutes}m ${remainingSeconds}s`;
|
|
243
|
+
const hours = Math.floor(minutes / 60);
|
|
244
|
+
const remainingMinutes = minutes % 60;
|
|
245
|
+
return `${hours}h ${remainingMinutes}m`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function pad(str, width) {
|
|
249
|
+
return String(str).padEnd(width);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ── Data Loading ──────────────────────────────────────────────────────────────
|
package/src/commands/diff.js
CHANGED
|
@@ -215,9 +215,28 @@ function formatValue(value, label = '') {
|
|
|
215
215
|
if (typeof value === 'boolean') return value ? 'yes' : 'no';
|
|
216
216
|
if (label === 'Cost' || label === 'Budget') return `$${value.toFixed(4)}`;
|
|
217
217
|
if (label === 'Duration') return formatDuration(value);
|
|
218
|
+
if (label === 'Blocked reason' && value && typeof value === 'object') {
|
|
219
|
+
return formatBlockedReason(value);
|
|
220
|
+
}
|
|
221
|
+
if (typeof value === 'object') return JSON.stringify(value);
|
|
218
222
|
return String(value);
|
|
219
223
|
}
|
|
220
224
|
|
|
225
|
+
function formatBlockedReason(reason) {
|
|
226
|
+
const category = reason.category || 'unknown';
|
|
227
|
+
const gateAction = reason.gate_action;
|
|
228
|
+
if (category === 'gate_action_failed' && gateAction) {
|
|
229
|
+
const actionLabel = gateAction.action_label || gateAction.command || 'unknown action';
|
|
230
|
+
if (gateAction.timed_out) {
|
|
231
|
+
return `gate_action_failed: ${actionLabel} timed out after ${gateAction.timeout_ms}ms`;
|
|
232
|
+
}
|
|
233
|
+
const exit = gateAction.exit_code != null ? ` (exit ${gateAction.exit_code})` : '';
|
|
234
|
+
return `gate_action_failed: ${actionLabel} failed${exit}`;
|
|
235
|
+
}
|
|
236
|
+
const detail = reason.detail || reason.recovery?.detail || '';
|
|
237
|
+
return detail ? `${category}: ${detail}` : category;
|
|
238
|
+
}
|
|
239
|
+
|
|
221
240
|
function formatDelta(delta, label) {
|
|
222
241
|
if (delta == null || delta === 0) return '';
|
|
223
242
|
if (label === 'Cost' || label === 'Budget') {
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { findProjectRoot } from '../lib/config.js';
|
|
3
|
+
import {
|
|
4
|
+
attachChainToMission,
|
|
5
|
+
buildMissionListSummary,
|
|
6
|
+
buildMissionSnapshot,
|
|
7
|
+
createMission,
|
|
8
|
+
loadLatestMissionArtifact,
|
|
9
|
+
loadLatestMissionSnapshot,
|
|
10
|
+
loadMissionArtifact,
|
|
11
|
+
loadMissionSnapshot,
|
|
12
|
+
} from '../lib/missions.js';
|
|
13
|
+
|
|
14
|
+
export async function missionStartCommand(opts) {
|
|
15
|
+
const root = findProjectRoot(opts.dir || process.cwd());
|
|
16
|
+
if (!root) {
|
|
17
|
+
console.error(chalk.red('No AgentXchain project found. Run this inside a governed project.'));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const title = String(opts.title || '').trim();
|
|
22
|
+
const goal = String(opts.goal || '').trim();
|
|
23
|
+
if (!title) {
|
|
24
|
+
console.error(chalk.red('Mission title is required. Use --title <text>.'));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
if (!goal) {
|
|
28
|
+
console.error(chalk.red('Mission goal is required. Use --goal <text>.'));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const result = createMission(root, {
|
|
33
|
+
missionId: opts.id,
|
|
34
|
+
title,
|
|
35
|
+
goal,
|
|
36
|
+
});
|
|
37
|
+
if (!result.ok) {
|
|
38
|
+
console.error(chalk.red(result.error));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const snapshot = buildMissionSnapshot(root, result.mission);
|
|
43
|
+
if (opts.json) {
|
|
44
|
+
console.log(JSON.stringify(snapshot, null, 2));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log(chalk.green(`Created mission ${snapshot.mission_id}`));
|
|
49
|
+
console.log(chalk.dim(` Goal: ${snapshot.goal}`));
|
|
50
|
+
renderMissionSnapshot(snapshot);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function missionListCommand(opts) {
|
|
54
|
+
const root = findProjectRoot(opts.dir || process.cwd());
|
|
55
|
+
if (!root) {
|
|
56
|
+
console.error(chalk.red('No AgentXchain project found. Run this inside a governed project.'));
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const limit = opts.limit ? parseInt(opts.limit, 10) : 20;
|
|
61
|
+
const missions = buildMissionListSummary(root, limit);
|
|
62
|
+
|
|
63
|
+
if (opts.json) {
|
|
64
|
+
console.log(JSON.stringify(missions, null, 2));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (missions.length === 0) {
|
|
69
|
+
console.log(chalk.dim('No missions found.'));
|
|
70
|
+
console.log(chalk.dim(' Run `agentxchain mission start --title "..." --goal "..."` to create one.'));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const header = [
|
|
75
|
+
pad('#', 4),
|
|
76
|
+
pad('Mission ID', 28),
|
|
77
|
+
pad('Status', 18),
|
|
78
|
+
pad('Chains', 8),
|
|
79
|
+
pad('Runs', 7),
|
|
80
|
+
pad('Turns', 7),
|
|
81
|
+
pad('Decisions', 10),
|
|
82
|
+
pad('Updated', 22),
|
|
83
|
+
'Title',
|
|
84
|
+
].join(' ');
|
|
85
|
+
|
|
86
|
+
console.log(chalk.bold(header));
|
|
87
|
+
console.log(chalk.dim('─'.repeat(header.length)));
|
|
88
|
+
|
|
89
|
+
missions.forEach((mission, index) => {
|
|
90
|
+
console.log([
|
|
91
|
+
pad(String(index + 1), 4),
|
|
92
|
+
pad(mission.mission_id || '—', 28),
|
|
93
|
+
pad(formatMissionStatus(mission.derived_status), 18),
|
|
94
|
+
pad(String(mission.chain_count || 0), 8),
|
|
95
|
+
pad(String(mission.total_runs || 0), 7),
|
|
96
|
+
pad(String(mission.total_turns || 0), 7),
|
|
97
|
+
pad(String(mission.active_repo_decisions_count || 0), 10),
|
|
98
|
+
pad(formatTimestamp(mission.updated_at), 22),
|
|
99
|
+
mission.title || '—',
|
|
100
|
+
].join(' '));
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
console.log(chalk.dim(`\n${missions.length} mission(s) shown`));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function missionShowCommand(missionId, opts) {
|
|
107
|
+
const root = findProjectRoot(opts.dir || process.cwd());
|
|
108
|
+
if (!root) {
|
|
109
|
+
console.error(chalk.red('No AgentXchain project found. Run this inside a governed project.'));
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const snapshot = missionId
|
|
114
|
+
? loadMissionSnapshot(root, missionId)
|
|
115
|
+
: loadLatestMissionSnapshot(root);
|
|
116
|
+
if (!snapshot) {
|
|
117
|
+
if (missionId) {
|
|
118
|
+
console.error(chalk.red(`Mission not found: ${missionId}`));
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
console.log(chalk.dim('No missions found.'));
|
|
122
|
+
console.log(chalk.dim(' Run `agentxchain mission start --title "..." --goal "..."` to create one.'));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (opts.json) {
|
|
127
|
+
console.log(JSON.stringify(snapshot, null, 2));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
renderMissionSnapshot(snapshot);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function missionAttachChainCommand(chainId, opts) {
|
|
135
|
+
const root = findProjectRoot(opts.dir || process.cwd());
|
|
136
|
+
if (!root) {
|
|
137
|
+
console.error(chalk.red('No AgentXchain project found. Run this inside a governed project.'));
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const mission = opts.mission
|
|
142
|
+
? loadMissionArtifact(root, opts.mission)
|
|
143
|
+
: loadLatestMissionArtifact(root);
|
|
144
|
+
if (!mission) {
|
|
145
|
+
console.error(chalk.red('No mission found to attach to.'));
|
|
146
|
+
console.error(chalk.dim(' Use `agentxchain mission start --title "..." --goal "..."` first.'));
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const result = attachChainToMission(root, mission.mission_id, chainId || 'latest');
|
|
151
|
+
if (!result.ok) {
|
|
152
|
+
console.error(chalk.red(result.error));
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const snapshot = buildMissionSnapshot(root, result.mission);
|
|
157
|
+
if (opts.json) {
|
|
158
|
+
console.log(JSON.stringify(snapshot, null, 2));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
console.log(chalk.green(`Attached ${result.chain.chain_id} to ${snapshot.mission_id}`));
|
|
163
|
+
renderMissionSnapshot(snapshot);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function renderMissionSnapshot(snapshot) {
|
|
167
|
+
console.log(chalk.bold(`Mission: ${snapshot.mission_id}`));
|
|
168
|
+
console.log('');
|
|
169
|
+
console.log(` Title: ${snapshot.title || '—'}`);
|
|
170
|
+
console.log(` Goal: ${snapshot.goal || '—'}`);
|
|
171
|
+
console.log(` Status: ${formatMissionStatus(snapshot.derived_status)}`);
|
|
172
|
+
console.log(` Chains: ${snapshot.chain_count || 0}`);
|
|
173
|
+
console.log(` Total runs: ${snapshot.total_runs || 0}`);
|
|
174
|
+
console.log(` Total turns: ${snapshot.total_turns || 0}`);
|
|
175
|
+
console.log(` Active repo decisions: ${snapshot.active_repo_decisions_count || 0}`);
|
|
176
|
+
console.log(` Latest chain: ${snapshot.latest_chain_id || '—'}`);
|
|
177
|
+
console.log(` Latest terminal: ${snapshot.latest_terminal_reason || '—'}`);
|
|
178
|
+
console.log(` Created: ${snapshot.created_at || '—'}`);
|
|
179
|
+
console.log(` Updated: ${snapshot.updated_at || '—'}`);
|
|
180
|
+
|
|
181
|
+
if (snapshot.missing_chain_ids?.length) {
|
|
182
|
+
console.log(` Missing chains: ${snapshot.missing_chain_ids.join(', ')}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!snapshot.chains || snapshot.chains.length === 0) {
|
|
186
|
+
console.log('');
|
|
187
|
+
console.log(chalk.dim(' No chains attached.'));
|
|
188
|
+
console.log(chalk.dim(' Use `agentxchain mission attach-chain latest` after a chained run.'));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const header = [
|
|
193
|
+
pad('#', 4),
|
|
194
|
+
pad('Chain ID', 16),
|
|
195
|
+
pad('Runs', 6),
|
|
196
|
+
pad('Turns', 7),
|
|
197
|
+
pad('Terminal', 26),
|
|
198
|
+
pad('Started', 22),
|
|
199
|
+
].join(' ');
|
|
200
|
+
|
|
201
|
+
console.log('');
|
|
202
|
+
console.log(chalk.bold(' Chains:'));
|
|
203
|
+
console.log(` ${chalk.dim(header)}`);
|
|
204
|
+
console.log(` ${chalk.dim('─'.repeat(header.length))}`);
|
|
205
|
+
|
|
206
|
+
snapshot.chains.forEach((chain, index) => {
|
|
207
|
+
console.log(` ${[
|
|
208
|
+
pad(String(index + 1), 4),
|
|
209
|
+
pad(chain.chain_id || '—', 16),
|
|
210
|
+
pad(String(chain.runs?.length || 0), 6),
|
|
211
|
+
pad(String(chain.total_turns || 0), 7),
|
|
212
|
+
pad(formatTerminal(chain.terminal_reason), 26),
|
|
213
|
+
pad(formatTimestamp(chain.started_at), 22),
|
|
214
|
+
].join(' ')}`);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function formatTerminal(reason) {
|
|
219
|
+
if (!reason) return '—';
|
|
220
|
+
if (reason === 'chain_limit_reached') return 'chain limit reached';
|
|
221
|
+
if (reason === 'non_chainable_status') return 'non-chainable status';
|
|
222
|
+
return reason.replace(/_/g, ' ');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function formatMissionStatus(status) {
|
|
226
|
+
if (!status) return '—';
|
|
227
|
+
switch (status) {
|
|
228
|
+
case 'planned':
|
|
229
|
+
return chalk.blue('planned');
|
|
230
|
+
case 'progressing':
|
|
231
|
+
return chalk.green('progressing');
|
|
232
|
+
case 'needs_attention':
|
|
233
|
+
return chalk.yellow('needs_attention');
|
|
234
|
+
case 'degraded':
|
|
235
|
+
return chalk.red('degraded');
|
|
236
|
+
default:
|
|
237
|
+
return status;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function formatTimestamp(value) {
|
|
242
|
+
if (!value) return '—';
|
|
243
|
+
try {
|
|
244
|
+
return new Date(value).toLocaleString();
|
|
245
|
+
} catch {
|
|
246
|
+
return value;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function pad(value, width) {
|
|
251
|
+
return String(value).padEnd(width);
|
|
252
|
+
}
|