agentxchain 2.2.0 → 2.4.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.
@@ -0,0 +1,53 @@
1
+ import chalk from 'chalk';
2
+ import { findProjectRoot } from '../lib/config.js';
3
+ import { startIntent } from '../lib/intake.js';
4
+
5
+ export async function intakeStartCommand(opts) {
6
+ const root = findProjectRoot(process.cwd());
7
+ if (!root) {
8
+ if (opts.json) {
9
+ console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
10
+ } else {
11
+ console.log(chalk.red('agentxchain.json not found'));
12
+ }
13
+ process.exit(2);
14
+ }
15
+
16
+ if (!opts.intent) {
17
+ const msg = '--intent <id> is required';
18
+ if (opts.json) {
19
+ console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
20
+ } else {
21
+ console.log(chalk.red(msg));
22
+ }
23
+ process.exit(1);
24
+ }
25
+
26
+ const result = startIntent(root, opts.intent, {
27
+ role: opts.role || undefined,
28
+ });
29
+
30
+ if (opts.json) {
31
+ console.log(JSON.stringify(result, null, 2));
32
+ } else if (result.ok) {
33
+ console.log('');
34
+ console.log(chalk.green(` Started intent ${result.intent.intent_id}`));
35
+ console.log(` Run: ${result.run_id}`);
36
+ console.log(` Turn: ${result.turn_id}`);
37
+ console.log(` Role: ${result.role}`);
38
+ console.log(chalk.dim(` Status: planned \u2192 executing`));
39
+ console.log('');
40
+ } else if (result.missing) {
41
+ console.log('');
42
+ console.log(chalk.red(` Cannot start intent ${opts.intent}`));
43
+ console.log(chalk.red(' Recorded planning artifacts are missing on disk:'));
44
+ for (const m of result.missing) {
45
+ console.log(chalk.red(` ${m}`));
46
+ }
47
+ console.log('');
48
+ } else {
49
+ console.log(chalk.red(` ${result.error}`));
50
+ }
51
+
52
+ process.exit(result.exitCode);
53
+ }
@@ -0,0 +1,113 @@
1
+ import chalk from 'chalk';
2
+ import { findProjectRoot } from '../lib/config.js';
3
+ import { intakeStatus } from '../lib/intake.js';
4
+
5
+ export async function intakeStatusCommand(opts) {
6
+ const root = findProjectRoot(process.cwd());
7
+ if (!root) {
8
+ if (opts.json) {
9
+ console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
10
+ } else {
11
+ console.log(chalk.red('agentxchain.json not found'));
12
+ }
13
+ process.exit(2);
14
+ }
15
+
16
+ const result = intakeStatus(root, opts.intent || null);
17
+
18
+ if (opts.json) {
19
+ console.log(JSON.stringify(result, null, 2));
20
+ process.exit(result.exitCode);
21
+ }
22
+
23
+ if (!result.ok) {
24
+ console.log(chalk.red(` ${result.error}`));
25
+ process.exit(result.exitCode);
26
+ }
27
+
28
+ // Detail mode: single intent
29
+ if (result.intent) {
30
+ printIntentDetail(result.intent, result.event);
31
+ process.exit(0);
32
+ }
33
+
34
+ // List mode: summary
35
+ printSummary(result.summary);
36
+ process.exit(0);
37
+ }
38
+
39
+ function printSummary(summary) {
40
+ console.log('');
41
+ console.log(chalk.bold(' AgentXchain Intake Status'));
42
+ console.log(chalk.dim(' ' + '─'.repeat(44)));
43
+ console.log(` ${chalk.dim('Events:')} ${summary.total_events}`);
44
+
45
+ const statusParts = Object.entries(summary.by_status)
46
+ .map(([s, c]) => `${s}: ${c}`)
47
+ .join(', ');
48
+ console.log(` ${chalk.dim('Intents:')} ${summary.total_intents} (${statusParts || 'none'})`);
49
+ console.log('');
50
+
51
+ if (summary.intents.length > 0) {
52
+ console.log(chalk.dim(' Recent Intents:'));
53
+ for (const i of summary.intents.slice(0, 20)) {
54
+ const pri = i.priority ? i.priority.padEnd(3) : '---';
55
+ const tpl = (i.template || '---').padEnd(12);
56
+ const st = statusColor(i.status);
57
+ console.log(` ${chalk.dim(i.intent_id)} ${pri} ${tpl} ${st} ${chalk.dim(i.updated_at)}`);
58
+ }
59
+ }
60
+
61
+ console.log('');
62
+ }
63
+
64
+ function printIntentDetail(intent, event) {
65
+ console.log('');
66
+ console.log(chalk.bold(` Intent: ${intent.intent_id}`));
67
+ console.log(chalk.dim(' ' + '─'.repeat(44)));
68
+ console.log(` ${chalk.dim('Status:')} ${statusColor(intent.status)}`);
69
+ console.log(` ${chalk.dim('Event:')} ${intent.event_id}`);
70
+ console.log(` ${chalk.dim('Priority:')} ${intent.priority || '—'}`);
71
+ console.log(` ${chalk.dim('Template:')} ${intent.template || '—'}`);
72
+ console.log(` ${chalk.dim('Charter:')} ${intent.charter || '—'}`);
73
+ console.log(` ${chalk.dim('Created:')} ${intent.created_at}`);
74
+ console.log(` ${chalk.dim('Updated:')} ${intent.updated_at}`);
75
+
76
+ if (intent.acceptance_contract && intent.acceptance_contract.length > 0) {
77
+ console.log('');
78
+ console.log(chalk.dim(' Acceptance Contract:'));
79
+ for (const a of intent.acceptance_contract) {
80
+ console.log(` - ${a}`);
81
+ }
82
+ }
83
+
84
+ if (intent.history && intent.history.length > 0) {
85
+ console.log('');
86
+ console.log(chalk.dim(' History:'));
87
+ for (const h of intent.history) {
88
+ const from = h.from || '(new)';
89
+ console.log(` ${chalk.dim(h.at)} ${from} → ${h.to} ${chalk.dim(h.reason)}`);
90
+ }
91
+ }
92
+
93
+ if (event) {
94
+ console.log('');
95
+ console.log(chalk.dim(' Source Event:'));
96
+ console.log(` ${chalk.dim('Source:')} ${event.source}`);
97
+ console.log(` ${chalk.dim('Signal:')} ${JSON.stringify(event.signal)}`);
98
+ }
99
+
100
+ console.log('');
101
+ }
102
+
103
+ function statusColor(status) {
104
+ switch (status) {
105
+ case 'detected': return chalk.yellow(status);
106
+ case 'triaged': return chalk.cyan(status);
107
+ case 'approved': return chalk.green(status);
108
+ case 'planned': return chalk.green(status);
109
+ case 'suppressed': return chalk.dim(status);
110
+ case 'rejected': return chalk.red(status);
111
+ default: return status;
112
+ }
113
+ }
@@ -0,0 +1,54 @@
1
+ import chalk from 'chalk';
2
+ import { findProjectRoot } from '../lib/config.js';
3
+ import { triageIntent } from '../lib/intake.js';
4
+
5
+ export async function intakeTriageCommand(opts) {
6
+ const root = findProjectRoot(process.cwd());
7
+ if (!root) {
8
+ if (opts.json) {
9
+ console.log(JSON.stringify({ ok: false, error: 'agentxchain.json not found' }, null, 2));
10
+ } else {
11
+ console.log(chalk.red('agentxchain.json not found'));
12
+ }
13
+ process.exit(2);
14
+ }
15
+
16
+ if (!opts.intent) {
17
+ const msg = '--intent <id> is required';
18
+ if (opts.json) {
19
+ console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
20
+ } else {
21
+ console.log(chalk.red(msg));
22
+ }
23
+ process.exit(1);
24
+ }
25
+
26
+ const fields = {
27
+ suppress: opts.suppress || false,
28
+ reject: opts.reject || false,
29
+ reason: opts.reason || null,
30
+ priority: opts.priority || null,
31
+ template: opts.template || null,
32
+ charter: opts.charter || null,
33
+ acceptance_contract: opts.acceptance
34
+ ? opts.acceptance.split(',').map(s => s.trim()).filter(Boolean)
35
+ : [],
36
+ };
37
+
38
+ const result = triageIntent(root, opts.intent, fields);
39
+
40
+ if (opts.json) {
41
+ console.log(JSON.stringify(result, null, 2));
42
+ } else if (result.ok) {
43
+ console.log('');
44
+ console.log(chalk.green(` Intent ${result.intent.intent_id} → ${result.intent.status}`));
45
+ if (result.intent.priority) {
46
+ console.log(chalk.dim(` Priority: ${result.intent.priority} Template: ${result.intent.template}`));
47
+ }
48
+ console.log('');
49
+ } else {
50
+ console.log(chalk.red(` ${result.error}`));
51
+ }
52
+
53
+ process.exit(result.exitCode);
54
+ }
@@ -17,6 +17,7 @@
17
17
  * - local_cli: implemented via subprocess dispatch + staged turn result
18
18
  * - api_proxy: implemented for synchronous review-only turns and stages
19
19
  * provider-backed JSON before validation/acceptance
20
+ * - mcp: implemented for synchronous MCP stdio tool dispatch
20
21
  */
21
22
 
22
23
  import chalk from 'chalk';
@@ -45,6 +46,7 @@ import {
45
46
  saveDispatchLogs,
46
47
  resolvePromptTransport,
47
48
  } from '../lib/adapters/local-cli-adapter.js';
49
+ import { dispatchMcp } from '../lib/adapters/mcp-adapter.js';
48
50
  import {
49
51
  getDispatchAssignmentPath,
50
52
  getDispatchContextPath,
@@ -423,12 +425,64 @@ export async function stepCommand(opts) {
423
425
  console.log(chalk.dim(` Tokens: ${apiResult.usage.input_tokens || 0} in / ${apiResult.usage.output_tokens || 0} out`));
424
426
  }
425
427
  console.log('');
428
+ } else if (runtimeType === 'mcp') {
429
+ console.log(chalk.cyan(`Dispatching to MCP stdio: ${runtime?.command || '(unknown)'}`));
430
+ console.log(chalk.dim(`Turn: ${turn.turn_id} Role: ${roleId} Phase: ${state.phase} Tool: ${runtime?.tool_name || 'agentxchain_turn'}`));
431
+
432
+ const mcpResult = await dispatchMcp(root, state, config, {
433
+ signal: controller.signal,
434
+ onStatus: (msg) => console.log(chalk.dim(` ${msg}`)),
435
+ onStderr: opts.verbose ? (text) => process.stderr.write(chalk.yellow(text)) : undefined,
436
+ verifyManifest: true,
437
+ });
438
+
439
+ if (mcpResult.logs?.length) {
440
+ saveDispatchLogs(root, turn.turn_id, mcpResult.logs);
441
+ }
442
+
443
+ if (mcpResult.aborted) {
444
+ console.log('');
445
+ console.log(chalk.yellow('Aborted. Turn remains assigned.'));
446
+ console.log(chalk.dim('Resume later with: agentxchain step --resume'));
447
+ console.log(chalk.dim('Or accept/reject manually: agentxchain accept-turn / reject-turn'));
448
+ process.exit(0);
449
+ }
450
+
451
+ if (!mcpResult.ok) {
452
+ const blocked = markRunBlocked(root, {
453
+ blockedOn: 'dispatch:mcp_failure',
454
+ category: 'dispatch_error',
455
+ recovery: {
456
+ typed_reason: 'dispatch_error',
457
+ owner: 'human',
458
+ recovery_action: 'Resolve the MCP dispatch issue, then run agentxchain step --resume',
459
+ turn_retained: true,
460
+ detail: mcpResult.error,
461
+ },
462
+ turnId: turn.turn_id,
463
+ hooksConfig,
464
+ });
465
+ if (blocked.ok) {
466
+ state = blocked.state;
467
+ }
468
+
469
+ console.log('');
470
+ console.log(chalk.red(`MCP dispatch failed: ${mcpResult.error}`));
471
+ console.log(chalk.dim('The turn remains assigned. You can:'));
472
+ console.log(chalk.dim(' - Fix the MCP server/runtime and retry: agentxchain step --resume'));
473
+ console.log(chalk.dim(' - Complete manually: edit .agentxchain/staging/turn-result.json'));
474
+ console.log(chalk.dim(' - Reject: agentxchain reject-turn --reason "mcp dispatch failed"'));
475
+ process.exit(1);
476
+ }
477
+
478
+ console.log(chalk.green(`MCP tool completed${mcpResult.toolName ? ` (${mcpResult.toolName})` : ''}. Staged result detected.`));
479
+ console.log('');
426
480
  }
427
481
 
428
482
  // ── Phase 3: Wait for turn completion ─────────────────────────────────────
429
483
 
430
- if (runtimeType === 'api_proxy') {
431
- // api_proxy is synchronous — result already staged in Phase 2
484
+ if (runtimeType === 'api_proxy' || runtimeType === 'mcp') {
485
+ // api_proxy and mcp are synchronous — result already staged in Phase 2
432
486
  } else if (runtimeType === 'local_cli') {
433
487
  // ── Local CLI adapter: spawn subprocess ──
434
488
  const transport = runtime ? resolvePromptTransport(runtime) : 'dispatch_bundle_only';
@@ -0,0 +1,159 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import chalk from 'chalk';
4
+ import { CONFIG_FILE, findProjectRoot } from '../lib/config.js';
5
+ import {
6
+ validateGovernedProjectTemplate,
7
+ validateGovernedTemplateRegistry,
8
+ validateProjectPlanningArtifacts,
9
+ validateAcceptanceHintCompletion,
10
+ } from '../lib/governed-templates.js';
11
+
12
+ function loadProjectTemplateValidation() {
13
+ const root = findProjectRoot();
14
+ if (!root) {
15
+ return {
16
+ present: false,
17
+ root: null,
18
+ template: null,
19
+ source: null,
20
+ ok: true,
21
+ errors: [],
22
+ warnings: [],
23
+ };
24
+ }
25
+
26
+ const configPath = join(root, CONFIG_FILE);
27
+ let parsed;
28
+ try {
29
+ parsed = JSON.parse(readFileSync(configPath, 'utf8'));
30
+ } catch (err) {
31
+ return {
32
+ present: true,
33
+ root,
34
+ template: null,
35
+ source: 'agentxchain.json',
36
+ ok: false,
37
+ errors: [`Failed to parse ${CONFIG_FILE}: ${err.message}`],
38
+ warnings: [],
39
+ };
40
+ }
41
+
42
+ const projectValidation = validateGovernedProjectTemplate(parsed.template);
43
+ return {
44
+ present: true,
45
+ root,
46
+ ...projectValidation,
47
+ };
48
+ }
49
+
50
+ export function templateValidateCommand(opts = {}) {
51
+ const registry = validateGovernedTemplateRegistry();
52
+ const project = loadProjectTemplateValidation();
53
+
54
+ // Planning artifact completeness check
55
+ let planningArtifacts = null;
56
+ if (project.present && project.ok && project.root) {
57
+ planningArtifacts = validateProjectPlanningArtifacts(project.root, project.template);
58
+ }
59
+
60
+ // Acceptance hint completion check
61
+ let acceptanceHints = null;
62
+ if (project.present && project.ok && project.root) {
63
+ acceptanceHints = validateAcceptanceHintCompletion(project.root, project.template);
64
+ }
65
+
66
+ const errors = [
67
+ ...registry.errors,
68
+ ...project.errors,
69
+ ...(planningArtifacts?.errors || []),
70
+ ];
71
+ const warnings = [
72
+ ...registry.warnings,
73
+ ...project.warnings,
74
+ ...(planningArtifacts?.warnings || []),
75
+ ...(acceptanceHints?.warnings || []),
76
+ ];
77
+ const ok = errors.length === 0;
78
+
79
+ const payload = {
80
+ ok,
81
+ registry,
82
+ project,
83
+ planning_artifacts: planningArtifacts,
84
+ acceptance_hints: acceptanceHints,
85
+ errors,
86
+ warnings,
87
+ };
88
+
89
+ if (opts.json) {
90
+ console.log(JSON.stringify(payload, null, 2));
91
+ if (!ok) process.exit(1);
92
+ return;
93
+ }
94
+
95
+ console.log('');
96
+ console.log(chalk.bold(' AgentXchain Template Validate'));
97
+ console.log(chalk.dim(' ' + '─'.repeat(44)));
98
+ console.log('');
99
+
100
+ if (ok) {
101
+ console.log(chalk.green(' ✓ Template validation passed.'));
102
+ } else {
103
+ console.log(chalk.red(` ✗ Template validation failed (${errors.length} errors).`));
104
+ }
105
+
106
+ console.log('');
107
+ console.log(` ${chalk.dim('Registry:')} ${registry.ok ? chalk.green('OK') : chalk.red('FAIL')} (${registry.registered_ids.length} registered, ${registry.manifest_ids.length} manifests)`);
108
+
109
+ if (project.present) {
110
+ const sourceLabel = project.source === 'implicit_default'
111
+ ? 'implicit default'
112
+ : project.source;
113
+ console.log(` ${chalk.dim('Project:')} ${project.ok ? chalk.green('OK') : chalk.red('FAIL')} (${project.template} via ${sourceLabel})`);
114
+ if (project.root && existsSync(project.root)) {
115
+ console.log(` ${chalk.dim('Root:')} ${project.root}`);
116
+ }
117
+ if (planningArtifacts) {
118
+ const total = planningArtifacts.expected.length;
119
+ const found = planningArtifacts.present.length;
120
+ if (total === 0) {
121
+ console.log(` ${chalk.dim('Planning:')} ${chalk.green('OK')} (no template artifacts required)`);
122
+ } else if (planningArtifacts.ok) {
123
+ console.log(` ${chalk.dim('Planning:')} ${chalk.green('OK')} (${found}/${total} present)`);
124
+ } else {
125
+ console.log(` ${chalk.dim('Planning:')} ${chalk.red('FAIL')} (${planningArtifacts.missing.length}/${total} missing: ${planningArtifacts.missing.join(', ')})`);
126
+ }
127
+ }
128
+ if (acceptanceHints) {
129
+ if (acceptanceHints.total === 0) {
130
+ console.log(` ${chalk.dim('Acceptance:')} ${chalk.green('OK')} (no template hints defined)`);
131
+ } else if (acceptanceHints.unchecked === 0) {
132
+ console.log(` ${chalk.dim('Acceptance:')} ${chalk.green('OK')} (${acceptanceHints.checked}/${acceptanceHints.total} checked)`);
133
+ } else {
134
+ console.log(` ${chalk.dim('Acceptance:')} ${chalk.yellow('WARN')} (${acceptanceHints.unchecked}/${acceptanceHints.total} unchecked)`);
135
+ }
136
+ }
137
+ } else {
138
+ console.log(` ${chalk.dim('Project:')} ${chalk.dim('No project detected; registry-only validation')}`);
139
+ }
140
+
141
+ if (errors.length > 0) {
142
+ console.log('');
143
+ console.log(chalk.red(' Errors:'));
144
+ for (const error of errors) {
145
+ console.log(` - ${error}`);
146
+ }
147
+ }
148
+
149
+ if (warnings.length > 0) {
150
+ console.log('');
151
+ console.log(chalk.yellow(' Warnings:'));
152
+ for (const warning of warnings) {
153
+ console.log(` - ${warning}`);
154
+ }
155
+ }
156
+
157
+ console.log('');
158
+ if (!ok) process.exit(1);
159
+ }
@@ -57,13 +57,18 @@ function printProtocolReport(report) {
57
57
  : tier.status === 'skipped'
58
58
  ? chalk.yellow('skipped')
59
59
  : chalk.red(tier.status);
60
- console.log(` ${tierKey}: ${label} (${tier.fixtures_passed}/${tier.fixtures_run} passed)`);
60
+ const niCount = tier.fixtures_not_implemented || 0;
61
+ const niSuffix = niCount > 0 ? chalk.yellow(`, ${niCount} not implemented`) : '';
62
+ console.log(` ${tierKey}: ${label} (${tier.fixtures_passed}/${tier.fixtures_run} passed${niSuffix})`);
61
63
 
64
+ for (const ni of tier.not_implemented || []) {
65
+ console.log(chalk.yellow(` ○ ${ni.fixture_id}: ${ni.message}`));
66
+ }
62
67
  for (const failure of tier.failures || []) {
63
- console.log(chalk.red(` - ${failure.fixture_id}: ${failure.message}`));
68
+ console.log(chalk.red(` ${failure.fixture_id}: ${failure.message}`));
64
69
  }
65
70
  for (const error of tier.errors || []) {
66
- console.log(chalk.red(` - ${error.fixture_id}: ${error.message}`));
71
+ console.log(chalk.red(` ${error.fixture_id}: ${error.message}`));
67
72
  }
68
73
  }
69
74