agentxchain 2.128.0 → 2.130.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 +2 -0
- package/bin/agentxchain.js +38 -4
- package/package.json +1 -1
- package/scripts/verify-post-publish.sh +55 -5
- package/src/commands/accept-turn.js +14 -0
- package/src/commands/checkpoint-turn.js +35 -0
- package/src/commands/connector.js +17 -2
- package/src/commands/doctor.js +151 -1
- package/src/commands/events.js +7 -1
- package/src/commands/init.js +42 -11
- package/src/commands/inject.js +1 -1
- package/src/commands/mission.js +803 -7
- package/src/commands/reissue-turn.js +122 -0
- package/src/commands/reject-turn.js +60 -6
- package/src/commands/restart.js +81 -10
- package/src/commands/resume.js +20 -9
- package/src/commands/run.js +13 -0
- package/src/commands/status.js +58 -4
- package/src/commands/step.js +49 -10
- package/src/commands/validate.js +78 -20
- package/src/lib/cli-version.js +106 -0
- package/src/lib/connector-probe.js +146 -5
- package/src/lib/continuous-run.js +22 -87
- package/src/lib/coordinator-dispatch.js +25 -0
- package/src/lib/dispatch-bundle.js +39 -0
- package/src/lib/governed-state.js +624 -11
- package/src/lib/governed-templates.js +1 -0
- package/src/lib/intake.js +233 -77
- package/src/lib/mission-plans.js +510 -6
- package/src/lib/missions.js +65 -6
- package/src/lib/normalized-config.js +50 -15
- package/src/lib/repo-observer.js +8 -2
- package/src/lib/run-events.js +5 -0
- package/src/lib/run-loop.js +25 -0
- package/src/lib/runner-interface.js +2 -0
- package/src/lib/session-checkpoint.js +18 -2
- package/src/lib/turn-checkpoint.js +221 -0
- package/src/templates/governed/full-local-cli.json +71 -0
package/src/lib/intake.js
CHANGED
|
@@ -27,6 +27,8 @@ const INTENT_ID_RE = /^intent_\d+_[0-9a-f]{4}$/;
|
|
|
27
27
|
// intent files, but current first-party intake writers do not transition into it.
|
|
28
28
|
const S1_STATES = new Set(['detected', 'triaged', 'approved', 'planned', 'executing', 'blocked', 'completed', 'failed', 'suppressed', 'rejected']);
|
|
29
29
|
const TERMINAL_STATES = new Set(['suppressed', 'rejected', 'completed', 'failed']);
|
|
30
|
+
const DISPATCHABLE_STATUSES = new Set(['planned', 'approved']);
|
|
31
|
+
const PRIORITY_RANK = { p0: 0, p1: 1, p2: 2, p3: 3 };
|
|
30
32
|
|
|
31
33
|
const VALID_TRANSITIONS = {
|
|
32
34
|
detected: ['triaged', 'suppressed'],
|
|
@@ -502,6 +504,196 @@ export function intakeStatus(root, intentId) {
|
|
|
502
504
|
return { ok: true, summary, exitCode: 0 };
|
|
503
505
|
}
|
|
504
506
|
|
|
507
|
+
export function findNextDispatchableIntent(root) {
|
|
508
|
+
const dirs = intakeDirs(root);
|
|
509
|
+
if (!existsSync(dirs.intents)) {
|
|
510
|
+
return { ok: false, error: 'no intents directory' };
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const intents = readJsonDir(dirs.intents)
|
|
514
|
+
.filter((intent) => intent && DISPATCHABLE_STATUSES.has(intent.status));
|
|
515
|
+
|
|
516
|
+
if (intents.length === 0) {
|
|
517
|
+
return { ok: false, error: 'no dispatchable intents' };
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const approved = intents.filter((intent) => intent.status === 'approved');
|
|
521
|
+
const candidates = approved.length > 0 ? approved : intents;
|
|
522
|
+
|
|
523
|
+
candidates.sort((a, b) => {
|
|
524
|
+
const aPriority = PRIORITY_RANK[a.priority] ?? Number.MAX_SAFE_INTEGER;
|
|
525
|
+
const bPriority = PRIORITY_RANK[b.priority] ?? Number.MAX_SAFE_INTEGER;
|
|
526
|
+
if (aPriority !== bPriority) return aPriority - bPriority;
|
|
527
|
+
|
|
528
|
+
const aTime = Date.parse(a.approved_at || a.planned_at || a.created_at || a.updated_at || 0);
|
|
529
|
+
const bTime = Date.parse(b.approved_at || b.planned_at || b.created_at || b.updated_at || 0);
|
|
530
|
+
if (Number.isFinite(aTime) && Number.isFinite(bTime) && aTime !== bTime) {
|
|
531
|
+
return aTime - bTime;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return String(a.intent_id || '').localeCompare(String(b.intent_id || ''));
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
const intent = candidates[0];
|
|
538
|
+
return {
|
|
539
|
+
ok: true,
|
|
540
|
+
intentId: intent.intent_id,
|
|
541
|
+
status: intent.status,
|
|
542
|
+
priority: intent.priority || null,
|
|
543
|
+
charter: intent.charter || null,
|
|
544
|
+
acceptance_count: Array.isArray(intent.acceptance_contract) ? intent.acceptance_contract.length : 0,
|
|
545
|
+
intent,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Return all approved-but-unconsumed intents sorted by priority (BUG-15).
|
|
551
|
+
* Used by `status` to surface the pending intent queue.
|
|
552
|
+
*/
|
|
553
|
+
export function findPendingApprovedIntents(root) {
|
|
554
|
+
const dirs = intakeDirs(root);
|
|
555
|
+
if (!existsSync(dirs.intents)) return [];
|
|
556
|
+
|
|
557
|
+
return readJsonDir(dirs.intents)
|
|
558
|
+
.filter((intent) => intent && intent.status === 'approved')
|
|
559
|
+
.sort((a, b) => {
|
|
560
|
+
const aPriority = PRIORITY_RANK[a.priority] ?? Number.MAX_SAFE_INTEGER;
|
|
561
|
+
const bPriority = PRIORITY_RANK[b.priority] ?? Number.MAX_SAFE_INTEGER;
|
|
562
|
+
if (aPriority !== bPriority) return aPriority - bPriority;
|
|
563
|
+
const aTime = Date.parse(a.approved_at || a.created_at || 0);
|
|
564
|
+
const bTime = Date.parse(b.approved_at || b.created_at || 0);
|
|
565
|
+
if (Number.isFinite(aTime) && Number.isFinite(bTime) && aTime !== bTime) return aTime - bTime;
|
|
566
|
+
return String(a.intent_id || '').localeCompare(String(b.intent_id || ''));
|
|
567
|
+
})
|
|
568
|
+
.map((intent) => ({
|
|
569
|
+
intent_id: intent.intent_id,
|
|
570
|
+
priority: intent.priority || 'p0',
|
|
571
|
+
charter: intent.charter || intent.description || null,
|
|
572
|
+
acceptance_count: Array.isArray(intent.acceptance_contract) ? intent.acceptance_contract.length : 0,
|
|
573
|
+
approved_at: intent.approved_at || null,
|
|
574
|
+
}));
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Unified intent consumption entry point (BUG-16).
|
|
579
|
+
* Both manual (resume/step --resume) and continuous/scheduler paths should call
|
|
580
|
+
* this single function to consume the next approved intent.
|
|
581
|
+
*
|
|
582
|
+
* @param {string} root
|
|
583
|
+
* @param {{ role?: string, writeDispatchBundle?: boolean, allowTerminalRestart?: boolean, provenance?: object }} options
|
|
584
|
+
* @returns {{ ok: boolean, intentId?: string, intent?: object, error?: string }}
|
|
585
|
+
*/
|
|
586
|
+
export function consumeNextApprovedIntent(root, options = {}) {
|
|
587
|
+
const queued = findNextDispatchableIntent(root);
|
|
588
|
+
if (!queued.ok) {
|
|
589
|
+
return { ok: false, error: queued.error || 'no dispatchable intents' };
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const prepared = prepareIntentForDispatch(root, queued.intentId, {
|
|
593
|
+
role: options.role,
|
|
594
|
+
writeDispatchBundle: options.writeDispatchBundle ?? false,
|
|
595
|
+
allowTerminalRestart: options.allowTerminalRestart ?? false,
|
|
596
|
+
provenance: options.provenance,
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
if (!prepared.ok) {
|
|
600
|
+
return { ok: false, error: prepared.error, intentId: queued.intentId };
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return {
|
|
604
|
+
ok: true,
|
|
605
|
+
intentId: queued.intentId,
|
|
606
|
+
status: queued.status,
|
|
607
|
+
priority: queued.priority,
|
|
608
|
+
charter: queued.charter,
|
|
609
|
+
intent: prepared.intent,
|
|
610
|
+
run_id: prepared.run_id,
|
|
611
|
+
turn_id: prepared.turn_id,
|
|
612
|
+
role: prepared.role,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export function prepareIntentForDispatch(root, intentId, options = {}) {
|
|
617
|
+
const loadedIntent = readIntent(root, intentId);
|
|
618
|
+
if (!loadedIntent.ok) {
|
|
619
|
+
return loadedIntent;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const startingStatus = loadedIntent.intent.status;
|
|
623
|
+
let intent = loadedIntent.intent;
|
|
624
|
+
let planned = false;
|
|
625
|
+
|
|
626
|
+
if (intent.status === 'approved') {
|
|
627
|
+
const plannedResult = planIntent(root, intent.intent_id, {
|
|
628
|
+
projectName: options.projectName,
|
|
629
|
+
force: options.forcePlan === true,
|
|
630
|
+
});
|
|
631
|
+
if (!plannedResult.ok) {
|
|
632
|
+
return {
|
|
633
|
+
ok: false,
|
|
634
|
+
error: `plan failed: ${plannedResult.error}`,
|
|
635
|
+
intent_status: intent.status,
|
|
636
|
+
exitCode: plannedResult.exitCode || 1,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
intent = plannedResult.intent;
|
|
640
|
+
planned = true;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (intent.status === 'planned') {
|
|
644
|
+
const startResult = startIntent(root, intent.intent_id, {
|
|
645
|
+
allowTerminalRestart: options.allowTerminalRestart === true,
|
|
646
|
+
provenance: options.provenance,
|
|
647
|
+
role: options.role || undefined,
|
|
648
|
+
writeDispatchBundle: options.writeDispatchBundle,
|
|
649
|
+
});
|
|
650
|
+
if (!startResult.ok) {
|
|
651
|
+
return {
|
|
652
|
+
ok: false,
|
|
653
|
+
error: `start failed: ${startResult.error}`,
|
|
654
|
+
intent_status: intent.status,
|
|
655
|
+
exitCode: startResult.exitCode || 1,
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
return {
|
|
659
|
+
ok: true,
|
|
660
|
+
intent_id: intent.intent_id,
|
|
661
|
+
starting_status: startingStatus,
|
|
662
|
+
final_status: startResult.intent.status,
|
|
663
|
+
planned,
|
|
664
|
+
started: true,
|
|
665
|
+
run_id: startResult.run_id,
|
|
666
|
+
turn_id: startResult.turn_id,
|
|
667
|
+
role: startResult.role,
|
|
668
|
+
intent: startResult.intent,
|
|
669
|
+
exitCode: 0,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (intent.status === 'executing') {
|
|
674
|
+
return {
|
|
675
|
+
ok: true,
|
|
676
|
+
intent_id: intent.intent_id,
|
|
677
|
+
starting_status: startingStatus,
|
|
678
|
+
final_status: intent.status,
|
|
679
|
+
planned,
|
|
680
|
+
started: false,
|
|
681
|
+
run_id: intent.target_run || null,
|
|
682
|
+
turn_id: intent.target_turn || null,
|
|
683
|
+
role: options.role || null,
|
|
684
|
+
intent,
|
|
685
|
+
exitCode: 0,
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
return {
|
|
690
|
+
ok: false,
|
|
691
|
+
error: `intent ${intentId} is in unsupported status "${intent.status}" for dispatch preparation`,
|
|
692
|
+
intent_status: intent.status,
|
|
693
|
+
exitCode: 1,
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
505
697
|
// ---------------------------------------------------------------------------
|
|
506
698
|
// Approve
|
|
507
699
|
// ---------------------------------------------------------------------------
|
|
@@ -750,7 +942,9 @@ export function startIntent(root, intentId, options = {}) {
|
|
|
750
942
|
}
|
|
751
943
|
|
|
752
944
|
// Assign governed turn
|
|
753
|
-
const assignResult = assignGovernedTurn(root, config, roleId.role
|
|
945
|
+
const assignResult = assignGovernedTurn(root, config, roleId.role, {
|
|
946
|
+
intakeContext,
|
|
947
|
+
});
|
|
754
948
|
if (!assignResult.ok) {
|
|
755
949
|
return { ok: false, error: `turn assignment failed: ${assignResult.error}`, exitCode: 1 };
|
|
756
950
|
}
|
|
@@ -763,24 +957,18 @@ export function startIntent(root, intentId, options = {}) {
|
|
|
763
957
|
return { ok: false, error: 'turn assignment succeeded but turn not found in state', exitCode: 1 };
|
|
764
958
|
}
|
|
765
959
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
960
|
+
if (options.writeDispatchBundle !== false) {
|
|
961
|
+
const bundleResult = writeDispatchBundle(root, state, config);
|
|
962
|
+
if (!bundleResult.ok) {
|
|
963
|
+
return { ok: false, error: `dispatch bundle failed: ${bundleResult.error}`, exitCode: 1 };
|
|
964
|
+
}
|
|
771
965
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
966
|
+
finalizeDispatchManifest(root, assignedTurn.turn_id, {
|
|
967
|
+
run_id: state.run_id,
|
|
968
|
+
role: assignedTurn.assigned_role,
|
|
969
|
+
});
|
|
776
970
|
}
|
|
777
971
|
|
|
778
|
-
// Finalize dispatch manifest
|
|
779
|
-
finalizeDispatchManifest(root, assignedTurn.turn_id, {
|
|
780
|
-
run_id: state.run_id,
|
|
781
|
-
role: assignedTurn.assigned_role,
|
|
782
|
-
});
|
|
783
|
-
|
|
784
972
|
// Update intent: planned → executing
|
|
785
973
|
const now = nowISO();
|
|
786
974
|
intent.status = 'executing';
|
|
@@ -967,6 +1155,18 @@ export function resolveIntent(root, intentId) {
|
|
|
967
1155
|
|
|
968
1156
|
const { intent, intentPath, dirs } = loadedIntent;
|
|
969
1157
|
|
|
1158
|
+
if (intent.status === 'completed') {
|
|
1159
|
+
return {
|
|
1160
|
+
ok: true,
|
|
1161
|
+
intent,
|
|
1162
|
+
previous_status: 'completed',
|
|
1163
|
+
new_status: 'completed',
|
|
1164
|
+
run_outcome: 'completed',
|
|
1165
|
+
no_change: true,
|
|
1166
|
+
exitCode: 0,
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
|
|
970
1170
|
if (intent.status !== 'executing' && intent.status !== 'blocked') {
|
|
971
1171
|
return {
|
|
972
1172
|
ok: false,
|
|
@@ -1426,83 +1626,39 @@ export function consumePreemptionMarker(root, options = {}) {
|
|
|
1426
1626
|
};
|
|
1427
1627
|
}
|
|
1428
1628
|
|
|
1429
|
-
const
|
|
1430
|
-
|
|
1431
|
-
let planned = false;
|
|
1432
|
-
let started = false;
|
|
1433
|
-
|
|
1434
|
-
if (intent.status === 'approved') {
|
|
1435
|
-
const plannedResult = planIntent(root, intent.intent_id, {
|
|
1436
|
-
projectName: options.projectName,
|
|
1437
|
-
force: options.forcePlan === true,
|
|
1438
|
-
});
|
|
1439
|
-
if (!plannedResult.ok) {
|
|
1440
|
-
return {
|
|
1441
|
-
ok: false,
|
|
1442
|
-
error: `failed to plan injected intent ${intent.intent_id}: ${plannedResult.error}`,
|
|
1443
|
-
marker,
|
|
1444
|
-
intent_status: intent.status,
|
|
1445
|
-
exitCode: plannedResult.exitCode || 1,
|
|
1446
|
-
};
|
|
1447
|
-
}
|
|
1448
|
-
intent = plannedResult.intent;
|
|
1449
|
-
planned = true;
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
if (intent.status === 'planned') {
|
|
1453
|
-
const startResult = startIntent(root, intent.intent_id, {
|
|
1454
|
-
role: options.role || undefined,
|
|
1455
|
-
});
|
|
1456
|
-
if (!startResult.ok) {
|
|
1457
|
-
return {
|
|
1458
|
-
ok: false,
|
|
1459
|
-
error: `failed to start injected intent ${intent.intent_id}: ${startResult.error}`,
|
|
1460
|
-
marker,
|
|
1461
|
-
intent_status: intent.status,
|
|
1462
|
-
exitCode: startResult.exitCode || 1,
|
|
1463
|
-
};
|
|
1464
|
-
}
|
|
1465
|
-
clearPreemptionMarker(root);
|
|
1629
|
+
const prepared = prepareIntentForDispatch(root, marker.intent_id, options);
|
|
1630
|
+
if (!prepared.ok) {
|
|
1466
1631
|
return {
|
|
1467
|
-
|
|
1632
|
+
...prepared,
|
|
1633
|
+
error: `failed to prepare injected intent ${marker.intent_id}: ${prepared.error}`,
|
|
1468
1634
|
marker,
|
|
1469
|
-
intent_id: intent.intent_id,
|
|
1470
|
-
starting_status: startingStatus,
|
|
1471
|
-
final_status: startResult.intent.status,
|
|
1472
|
-
planned,
|
|
1473
|
-
started: true,
|
|
1474
|
-
run_id: startResult.run_id,
|
|
1475
|
-
turn_id: startResult.turn_id,
|
|
1476
|
-
role: startResult.role,
|
|
1477
|
-
intent: startResult.intent,
|
|
1478
|
-
exitCode: 0,
|
|
1479
1635
|
};
|
|
1480
1636
|
}
|
|
1481
1637
|
|
|
1482
|
-
if (
|
|
1638
|
+
if (prepared.final_status === 'executing') {
|
|
1483
1639
|
clearPreemptionMarker(root);
|
|
1484
1640
|
return {
|
|
1485
1641
|
ok: true,
|
|
1486
1642
|
marker,
|
|
1487
|
-
intent_id:
|
|
1488
|
-
starting_status:
|
|
1489
|
-
final_status:
|
|
1490
|
-
planned,
|
|
1491
|
-
started:
|
|
1492
|
-
run_id:
|
|
1493
|
-
turn_id:
|
|
1494
|
-
role:
|
|
1495
|
-
intent,
|
|
1643
|
+
intent_id: prepared.intent_id,
|
|
1644
|
+
starting_status: prepared.starting_status,
|
|
1645
|
+
final_status: prepared.final_status,
|
|
1646
|
+
planned: prepared.planned,
|
|
1647
|
+
started: prepared.started,
|
|
1648
|
+
run_id: prepared.run_id,
|
|
1649
|
+
turn_id: prepared.turn_id,
|
|
1650
|
+
role: prepared.role,
|
|
1651
|
+
intent: prepared.intent,
|
|
1496
1652
|
exitCode: 0,
|
|
1497
1653
|
};
|
|
1498
1654
|
}
|
|
1499
1655
|
|
|
1500
1656
|
return {
|
|
1501
1657
|
ok: false,
|
|
1502
|
-
error: `cannot consume preemption marker from intent status "${
|
|
1658
|
+
error: `cannot consume preemption marker from intent status "${prepared.final_status}"`,
|
|
1503
1659
|
marker,
|
|
1504
|
-
intent_id:
|
|
1505
|
-
intent_status:
|
|
1660
|
+
intent_id: prepared.intent_id,
|
|
1661
|
+
intent_status: prepared.final_status,
|
|
1506
1662
|
exitCode: 1,
|
|
1507
1663
|
};
|
|
1508
1664
|
}
|