agentxchain 2.112.0 → 2.113.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 +48 -1
- package/dashboard/app.js +2 -1
- package/dashboard/components/mission.js +180 -1
- package/package.json +1 -1
- package/src/commands/mission.js +529 -1
- package/src/lib/dashboard/bridge-server.js +9 -0
- package/src/lib/dashboard/file-watcher.js +10 -6
- package/src/lib/dashboard/plan-reader.js +108 -0
- package/src/lib/dashboard/state-reader.js +11 -0
- package/src/lib/mission-plans.js +520 -0
- package/src/lib/run-chain.js +5 -3
package/bin/agentxchain.js
CHANGED
|
@@ -122,7 +122,7 @@ 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
124
|
import { chainLatestCommand, chainListCommand, chainShowCommand } from '../src/commands/chain.js';
|
|
125
|
-
import { missionAttachChainCommand, missionListCommand, missionShowCommand, missionStartCommand } from '../src/commands/mission.js';
|
|
125
|
+
import { missionAttachChainCommand, missionListCommand, missionPlanApproveCommand, missionPlanCommand, missionPlanLaunchCommand, missionPlanListCommand, missionPlanShowCommand, missionShowCommand, missionStartCommand } from '../src/commands/mission.js';
|
|
126
126
|
|
|
127
127
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
128
128
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
@@ -457,6 +457,53 @@ missionCmd
|
|
|
457
457
|
.option('-d, --dir <path>', 'Project directory')
|
|
458
458
|
.action(missionAttachChainCommand);
|
|
459
459
|
|
|
460
|
+
const missionPlanCmd = missionCmd
|
|
461
|
+
.command('plan [mission_id]')
|
|
462
|
+
.description('Generate a decomposition plan for a mission (default: latest mission)')
|
|
463
|
+
.option('--constraint <text>', 'Add a constraint to the planner (repeatable)', collectOption, [])
|
|
464
|
+
.option('--role-hint <role>', 'Hint available roles to the planner (repeatable)', collectOption, [])
|
|
465
|
+
.option('-j, --json', 'Output as JSON')
|
|
466
|
+
.option('-d, --dir <path>', 'Project directory')
|
|
467
|
+
.action(missionPlanCommand);
|
|
468
|
+
|
|
469
|
+
missionPlanCmd
|
|
470
|
+
.command('show [plan_id]')
|
|
471
|
+
.description('Show a decomposition plan (default: latest plan)')
|
|
472
|
+
.option('-m, --mission <mission_id>', 'Explicit mission ID')
|
|
473
|
+
.option('-j, --json', 'Output as JSON')
|
|
474
|
+
.option('-d, --dir <path>', 'Project directory')
|
|
475
|
+
.action(missionPlanShowCommand);
|
|
476
|
+
|
|
477
|
+
missionPlanCmd
|
|
478
|
+
.command('approve [plan_id]')
|
|
479
|
+
.description('Approve a decomposition plan (default: latest plan)')
|
|
480
|
+
.option('-m, --mission <mission_id>', 'Explicit mission ID')
|
|
481
|
+
.option('-d, --dir <path>', 'Project directory')
|
|
482
|
+
.action(missionPlanApproveCommand);
|
|
483
|
+
|
|
484
|
+
missionPlanCmd
|
|
485
|
+
.command('launch [plan_id]')
|
|
486
|
+
.description('Launch a workstream from an approved plan (default: latest plan)')
|
|
487
|
+
.requiredOption('-w, --workstream <id>', 'Workstream ID to launch')
|
|
488
|
+
.option('-m, --mission <mission_id>', 'Explicit mission ID')
|
|
489
|
+
.option('--auto-approve', 'Auto-approve run gates while executing the launched workstream')
|
|
490
|
+
.option('-j, --json', 'Output as JSON')
|
|
491
|
+
.option('-d, --dir <path>', 'Project directory')
|
|
492
|
+
.action(missionPlanLaunchCommand);
|
|
493
|
+
|
|
494
|
+
missionPlanCmd
|
|
495
|
+
.command('list')
|
|
496
|
+
.description('List all plans for a mission')
|
|
497
|
+
.option('-m, --mission <mission_id>', 'Explicit mission ID')
|
|
498
|
+
.option('-l, --limit <n>', 'Max plans to show (default: 20)')
|
|
499
|
+
.option('-j, --json', 'Output as JSON')
|
|
500
|
+
.option('-d, --dir <path>', 'Project directory')
|
|
501
|
+
.action(missionPlanListCommand);
|
|
502
|
+
|
|
503
|
+
function collectOption(value, previous) {
|
|
504
|
+
return previous.concat([value]);
|
|
505
|
+
}
|
|
506
|
+
|
|
460
507
|
program
|
|
461
508
|
.command('validate')
|
|
462
509
|
.description('Validate project protocol artifacts')
|
package/dashboard/app.js
CHANGED
|
@@ -37,7 +37,7 @@ const VIEWS = {
|
|
|
37
37
|
'cross-repo': { fetch: ['coordinatorState', 'coordinatorHistory'], render: renderCrossRepo },
|
|
38
38
|
blockers: { fetch: ['coordinatorBlockers'], render: renderBlockers },
|
|
39
39
|
artifacts: { fetch: ['workflowKitArtifacts'], render: renderArtifacts },
|
|
40
|
-
mission: { fetch: ['missions'], render: renderMission },
|
|
40
|
+
mission: { fetch: ['missions', 'plans'], render: renderMission },
|
|
41
41
|
chain: { fetch: ['chainReports'], render: renderChain },
|
|
42
42
|
'run-history': { fetch: ['runHistory'], render: renderRunHistory },
|
|
43
43
|
timeouts: { fetch: ['timeouts'], render: renderTimeouts },
|
|
@@ -63,6 +63,7 @@ const API_MAP = {
|
|
|
63
63
|
coordinatorRepoStatusRows: '/api/coordinator/repo-status',
|
|
64
64
|
workflowKitArtifacts: '/api/workflow-kit-artifacts',
|
|
65
65
|
missions: '/api/missions',
|
|
66
|
+
plans: '/api/plans',
|
|
66
67
|
chainReports: '/api/chain-reports',
|
|
67
68
|
connectors: '/api/connectors',
|
|
68
69
|
runHistory: '/api/run-history',
|
|
@@ -132,7 +132,178 @@ function renderRecentMissions(missions) {
|
|
|
132
132
|
return html;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
function formatPlanStatus(status) {
|
|
136
|
+
switch (status) {
|
|
137
|
+
case 'proposed':
|
|
138
|
+
return badge('proposed', '#38bdf8');
|
|
139
|
+
case 'approved':
|
|
140
|
+
return badge('approved', 'var(--green)');
|
|
141
|
+
case 'superseded':
|
|
142
|
+
return badge('superseded', 'var(--text-dim)');
|
|
143
|
+
case 'needs_attention':
|
|
144
|
+
return badge('needs attention', 'var(--yellow)');
|
|
145
|
+
default:
|
|
146
|
+
return badge(status || 'unknown', 'var(--text-dim)');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function formatLaunchStatus(status) {
|
|
151
|
+
switch (status) {
|
|
152
|
+
case 'ready':
|
|
153
|
+
return badge('ready', 'var(--green)');
|
|
154
|
+
case 'blocked':
|
|
155
|
+
return badge('blocked', 'var(--red)');
|
|
156
|
+
case 'launched':
|
|
157
|
+
return badge('launched', '#38bdf8');
|
|
158
|
+
case 'completed':
|
|
159
|
+
return badge('completed', 'var(--green)');
|
|
160
|
+
case 'needs_attention':
|
|
161
|
+
return badge('needs attention', 'var(--yellow)');
|
|
162
|
+
default:
|
|
163
|
+
return badge(status || 'unknown', 'var(--text-dim)');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function renderLatestPlan(plan) {
|
|
168
|
+
if (!plan) {
|
|
169
|
+
return `<div class="section"><h3>Latest Plan</h3><p class="section-subtitle">No decomposition plan found. Run <code>agentxchain mission plan <mission_id></code> to generate one.</p></div>`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const workstreams = Array.isArray(plan.workstreams) ? plan.workstreams : [];
|
|
173
|
+
const launchRecords = Array.isArray(plan.launch_records) ? plan.launch_records : [];
|
|
174
|
+
const statusCounts = plan.workstream_status_counts || {};
|
|
175
|
+
|
|
176
|
+
let html = `<div class="section"><h3>Latest Plan</h3><dl class="detail-list">`;
|
|
177
|
+
html += `<dt>Plan ID</dt><dd class="mono" title="${esc(plan.plan_id || '')}">${esc(truncateId(plan.plan_id, 30))}</dd>`;
|
|
178
|
+
html += `<dt>Status</dt><dd>${formatPlanStatus(plan.status)}</dd>`;
|
|
179
|
+
html += `<dt>Mission</dt><dd class="mono">${esc(plan.mission_id || '—')}</dd>`;
|
|
180
|
+
html += `<dt>Created</dt><dd>${formatDate(plan.created_at)}</dd>`;
|
|
181
|
+
if (plan.approved_at) {
|
|
182
|
+
html += `<dt>Approved</dt><dd>${formatDate(plan.approved_at)}</dd>`;
|
|
183
|
+
}
|
|
184
|
+
html += `<dt>Workstreams</dt><dd>${plan.workstream_count || 0}</dd>`;
|
|
185
|
+
html += `<dt>Launched</dt><dd>${plan.launch_record_count || 0}</dd>`;
|
|
186
|
+
if (Object.keys(statusCounts).length > 0) {
|
|
187
|
+
const countsStr = Object.entries(statusCounts).map(([k, v]) => `${k}: ${v}`).join(', ');
|
|
188
|
+
html += `<dt>Status Breakdown</dt><dd>${esc(countsStr)}</dd>`;
|
|
189
|
+
}
|
|
190
|
+
if (plan.supersedes_plan_id) {
|
|
191
|
+
html += `<dt>Supersedes</dt><dd class="mono">${esc(truncateId(plan.supersedes_plan_id, 30))}</dd>`;
|
|
192
|
+
}
|
|
193
|
+
html += `</dl></div>`;
|
|
194
|
+
|
|
195
|
+
// Workstreams table
|
|
196
|
+
if (workstreams.length > 0) {
|
|
197
|
+
html += `<div class="section"><h3>Workstreams</h3>
|
|
198
|
+
<table class="data-table">
|
|
199
|
+
<thead>
|
|
200
|
+
<tr>
|
|
201
|
+
<th>#</th>
|
|
202
|
+
<th>ID</th>
|
|
203
|
+
<th>Title</th>
|
|
204
|
+
<th>Status</th>
|
|
205
|
+
<th>Roles</th>
|
|
206
|
+
<th>Phases</th>
|
|
207
|
+
<th>Dependencies</th>
|
|
208
|
+
</tr>
|
|
209
|
+
</thead>
|
|
210
|
+
<tbody>`;
|
|
211
|
+
|
|
212
|
+
workstreams.forEach((ws, index) => {
|
|
213
|
+
const roles = Array.isArray(ws.roles) ? ws.roles.join(', ') : '—';
|
|
214
|
+
const phases = Array.isArray(ws.phases) ? ws.phases.join(', ') : '—';
|
|
215
|
+
const deps = Array.isArray(ws.depends_on) && ws.depends_on.length > 0 ? ws.depends_on.join(', ') : 'none';
|
|
216
|
+
|
|
217
|
+
html += `<tr>
|
|
218
|
+
<td style="color:var(--text-dim)">${index + 1}</td>
|
|
219
|
+
<td class="mono">${esc(ws.workstream_id || '—')}</td>
|
|
220
|
+
<td>${esc(ws.title || '—')}</td>
|
|
221
|
+
<td>${formatLaunchStatus(ws.launch_status)}</td>
|
|
222
|
+
<td>${esc(roles)}</td>
|
|
223
|
+
<td>${esc(phases)}</td>
|
|
224
|
+
<td class="mono">${esc(deps)}</td>
|
|
225
|
+
</tr>`;
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
html += '</tbody></table></div>';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Launch records table
|
|
232
|
+
if (launchRecords.length > 0) {
|
|
233
|
+
html += `<div class="section"><h3>Launch Records</h3>
|
|
234
|
+
<table class="data-table">
|
|
235
|
+
<thead>
|
|
236
|
+
<tr>
|
|
237
|
+
<th>#</th>
|
|
238
|
+
<th>Workstream</th>
|
|
239
|
+
<th>Chain ID</th>
|
|
240
|
+
<th>Status</th>
|
|
241
|
+
<th>Terminal</th>
|
|
242
|
+
<th>Launched</th>
|
|
243
|
+
<th>Completed</th>
|
|
244
|
+
</tr>
|
|
245
|
+
</thead>
|
|
246
|
+
<tbody>`;
|
|
247
|
+
|
|
248
|
+
launchRecords.forEach((lr, index) => {
|
|
249
|
+
html += `<tr>
|
|
250
|
+
<td style="color:var(--text-dim)">${index + 1}</td>
|
|
251
|
+
<td class="mono">${esc(lr.workstream_id || '—')}</td>
|
|
252
|
+
<td class="mono" title="${esc(lr.chain_id || '')}">${esc(truncateId(lr.chain_id))}</td>
|
|
253
|
+
<td>${formatLaunchStatus(lr.status)}</td>
|
|
254
|
+
<td>${esc(lr.terminal_reason || '—')}</td>
|
|
255
|
+
<td>${formatDate(lr.launched_at)}</td>
|
|
256
|
+
<td>${formatDate(lr.completed_at)}</td>
|
|
257
|
+
</tr>`;
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
html += '</tbody></table></div>';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return html;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function renderRecentPlans(plans) {
|
|
267
|
+
if (!Array.isArray(plans) || plans.length <= 1) {
|
|
268
|
+
return '';
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Skip the first (latest) since it's rendered in detail above
|
|
272
|
+
const olderPlans = plans.slice(1);
|
|
273
|
+
if (olderPlans.length === 0) return '';
|
|
274
|
+
|
|
275
|
+
let html = `<div class="section"><h3>Previous Plans</h3>
|
|
276
|
+
<table class="data-table">
|
|
277
|
+
<thead>
|
|
278
|
+
<tr>
|
|
279
|
+
<th>#</th>
|
|
280
|
+
<th>Plan ID</th>
|
|
281
|
+
<th>Mission</th>
|
|
282
|
+
<th>Status</th>
|
|
283
|
+
<th>Workstreams</th>
|
|
284
|
+
<th>Launched</th>
|
|
285
|
+
<th>Created</th>
|
|
286
|
+
</tr>
|
|
287
|
+
</thead>
|
|
288
|
+
<tbody>`;
|
|
289
|
+
|
|
290
|
+
olderPlans.forEach((plan, index) => {
|
|
291
|
+
html += `<tr>
|
|
292
|
+
<td style="color:var(--text-dim)">${index + 1}</td>
|
|
293
|
+
<td class="mono" title="${esc(plan.plan_id || '')}">${esc(truncateId(plan.plan_id, 20))}</td>
|
|
294
|
+
<td class="mono">${esc(truncateId(plan.mission_id, 18))}</td>
|
|
295
|
+
<td>${formatPlanStatus(plan.status)}</td>
|
|
296
|
+
<td>${plan.workstream_count || 0}</td>
|
|
297
|
+
<td>${plan.launch_record_count || 0}</td>
|
|
298
|
+
<td>${formatDate(plan.created_at)}</td>
|
|
299
|
+
</tr>`;
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
html += '</tbody></table></div>';
|
|
303
|
+
return html;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function render({ missions, plans }) {
|
|
136
307
|
if (!missions || typeof missions !== 'object') {
|
|
137
308
|
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
309
|
}
|
|
@@ -171,6 +342,14 @@ export function render({ missions }) {
|
|
|
171
342
|
}
|
|
172
343
|
|
|
173
344
|
html += renderAttachedChains(latest);
|
|
345
|
+
|
|
346
|
+
// Plan visibility
|
|
347
|
+
const planData = plans && typeof plans === 'object' ? plans : null;
|
|
348
|
+
const latestPlan = planData?.latest || null;
|
|
349
|
+
const allPlans = Array.isArray(planData?.plans) ? planData.plans : [];
|
|
350
|
+
html += renderLatestPlan(latestPlan);
|
|
351
|
+
html += renderRecentPlans(allPlans);
|
|
352
|
+
|
|
174
353
|
html += renderRecentMissions(missionList);
|
|
175
354
|
html += '</div>';
|
|
176
355
|
return html;
|