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/README.md
CHANGED
|
@@ -15,6 +15,7 @@ Legacy IDE-window coordination is still shipped as a compatibility mode for team
|
|
|
15
15
|
- [CLI reference](https://agentxchain.dev/docs/cli/)
|
|
16
16
|
- [Lights-Out Scheduling](https://agentxchain.dev/docs/lights-out-scheduling/)
|
|
17
17
|
- [Templates](https://agentxchain.dev/docs/templates/)
|
|
18
|
+
- [Automation Patterns](https://agentxchain.dev/docs/automation-patterns/)
|
|
18
19
|
- [Export schema reference](https://agentxchain.dev/docs/export-schema/)
|
|
19
20
|
- [Adapter reference](https://agentxchain.dev/docs/adapters/)
|
|
20
21
|
- [Protocol v7](https://agentxchain.dev/docs/protocol/)
|
|
@@ -107,6 +108,7 @@ Built-in governed templates:
|
|
|
107
108
|
- `cli-tool`: command surface, platform support, distribution checklist
|
|
108
109
|
- `library`: public API, compatibility policy, release and adoption checklist
|
|
109
110
|
- `web-app`: user flows, UI acceptance, browser support
|
|
111
|
+
- `full-local-cli`: human-gated automation pattern with PM, Dev, QA, and Director all on authoritative `local_cli`
|
|
110
112
|
- `enterprise-app`: enterprise planning artifacts plus blueprint-backed `architect` and `security_reviewer` phases
|
|
111
113
|
|
|
112
114
|
Inspect the shipped template surfaces instead of inferring them from docs:
|
package/bin/agentxchain.js
CHANGED
|
@@ -71,7 +71,9 @@ import { unblockCommand } from '../src/commands/unblock.js';
|
|
|
71
71
|
import { injectCommand } from '../src/commands/inject.js';
|
|
72
72
|
import { escalateCommand } from '../src/commands/escalate.js';
|
|
73
73
|
import { acceptTurnCommand } from '../src/commands/accept-turn.js';
|
|
74
|
+
import { checkpointTurnCommand } from '../src/commands/checkpoint-turn.js';
|
|
74
75
|
import { rejectTurnCommand } from '../src/commands/reject-turn.js';
|
|
76
|
+
import { reissueTurnCommand } from '../src/commands/reissue-turn.js';
|
|
75
77
|
import { proposalListCommand, proposalDiffCommand, proposalApplyCommand, proposalRejectCommand } from '../src/commands/proposal.js';
|
|
76
78
|
import { stepCommand } from '../src/commands/step.js';
|
|
77
79
|
import { runCommand } from '../src/commands/run.js';
|
|
@@ -124,7 +126,7 @@ import { eventsCommand } from '../src/commands/events.js';
|
|
|
124
126
|
import { connectorCheckCommand, connectorValidateCommand } from '../src/commands/connector.js';
|
|
125
127
|
import { scheduleDaemonCommand, scheduleListCommand, scheduleRunDueCommand, scheduleStatusCommand } from '../src/commands/schedule.js';
|
|
126
128
|
import { chainLatestCommand, chainListCommand, chainShowCommand } from '../src/commands/chain.js';
|
|
127
|
-
import { missionAttachChainCommand, missionListCommand, missionPlanApproveCommand, missionPlanAutopilotCommand, missionPlanCommand, missionPlanLaunchCommand, missionPlanListCommand, missionPlanShowCommand, missionShowCommand, missionStartCommand } from '../src/commands/mission.js';
|
|
129
|
+
import { missionAttachChainCommand, missionBindCoordinatorCommand, missionListCommand, missionPlanApproveCommand, missionPlanAutopilotCommand, missionPlanCommand, missionPlanLaunchCommand, missionPlanListCommand, missionPlanShowCommand, missionShowCommand, missionStartCommand } from '../src/commands/mission.js';
|
|
128
130
|
|
|
129
131
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
130
132
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
@@ -142,7 +144,7 @@ program
|
|
|
142
144
|
.option('-y, --yes', 'Skip guided prompts, use defaults')
|
|
143
145
|
.option('--governed', 'Create a governed project (orchestrator-owned state)')
|
|
144
146
|
.option('--dir <path>', 'Scaffold target directory. Use "." for in-place bootstrap.')
|
|
145
|
-
.option('--template <id>', 'Governed scaffold template: generic, api-service, cli-tool, library, web-app, enterprise-app')
|
|
147
|
+
.option('--template <id>', 'Governed scaffold template: generic, api-service, cli-tool, library, web-app, full-local-cli, enterprise-app')
|
|
146
148
|
.option('--dev-command <parts...>', 'Governed local-dev command parts. Include {prompt} for argv prompt delivery.')
|
|
147
149
|
.option('--dev-prompt-transport <mode>', 'Governed local-dev prompt transport: argv, stdin, dispatch_bundle_only')
|
|
148
150
|
.option('--goal <text>', 'Project goal — persisted in config and rendered in every dispatch bundle')
|
|
@@ -445,10 +447,23 @@ missionCmd
|
|
|
445
447
|
.option('--constraint <text>', 'Add a constraint to the planner when using --plan (repeatable)', collectOption, [])
|
|
446
448
|
.option('--role-hint <role>', 'Hint available roles to the planner when using --plan (repeatable)', collectOption, [])
|
|
447
449
|
.option('--planner-output-file <path>', 'Read planner JSON output from a file instead of calling the configured planner')
|
|
450
|
+
.option('--multi', 'Create a multi-repo mission with coordinator initialization')
|
|
451
|
+
.option('--coordinator-config <path>', 'Path to agentxchain-multi.json (required with --multi)')
|
|
452
|
+
.option('--coordinator-workspace <path>', 'Coordinator workspace path (defaults to project root)')
|
|
448
453
|
.option('-j, --json', 'Output as JSON')
|
|
449
454
|
.option('-d, --dir <path>', 'Project directory')
|
|
450
455
|
.action(missionStartCommand);
|
|
451
456
|
|
|
457
|
+
missionCmd
|
|
458
|
+
.command('bind-coordinator [mission_id]')
|
|
459
|
+
.description('Bind an existing coordinator super_run to a mission')
|
|
460
|
+
.requiredOption('--super-run-id <id>', 'Coordinator super_run_id to bind')
|
|
461
|
+
.option('--coordinator-config <path>', 'Path to agentxchain-multi.json')
|
|
462
|
+
.option('--coordinator-workspace <path>', 'Coordinator workspace path')
|
|
463
|
+
.option('-j, --json', 'Output as JSON')
|
|
464
|
+
.option('-d, --dir <path>', 'Project directory')
|
|
465
|
+
.action(missionBindCoordinatorCommand);
|
|
466
|
+
|
|
452
467
|
missionCmd
|
|
453
468
|
.command('list')
|
|
454
469
|
.description('List mission artifacts newest first')
|
|
@@ -625,6 +640,7 @@ program
|
|
|
625
640
|
.description('Resume a governed project: initialize or continue a run and assign the next turn')
|
|
626
641
|
.option('--role <role>', 'Override the target role (default: phase entry role)')
|
|
627
642
|
.option('--turn <id>', 'Target a specific retained turn when multiple exist')
|
|
643
|
+
.option('--no-intent', 'Do not bind the next queued approved/planned intake intent to the next turn')
|
|
628
644
|
.action(resumeCommand);
|
|
629
645
|
|
|
630
646
|
program
|
|
@@ -636,7 +652,7 @@ program
|
|
|
636
652
|
.command('inject <description>')
|
|
637
653
|
.description('Inject a priority work item into the intake queue (composed record + triage + approve)')
|
|
638
654
|
.option('--priority <level>', 'Priority level (p0, p1, p2, p3)', 'p0')
|
|
639
|
-
.option('--template <id>', 'Governed template (generic, api-service, cli-tool, library, web-app, enterprise-app)', 'generic')
|
|
655
|
+
.option('--template <id>', 'Governed template (generic, api-service, cli-tool, library, web-app, full-local-cli, enterprise-app)', 'generic')
|
|
640
656
|
.option('--charter <text>', 'Delivery charter (defaults to description)')
|
|
641
657
|
.option('--acceptance <text>', 'Comma-separated acceptance criteria')
|
|
642
658
|
.option('--approver <name>', 'Approver identity', 'human')
|
|
@@ -657,9 +673,16 @@ program
|
|
|
657
673
|
.command('accept-turn')
|
|
658
674
|
.description('Accept the currently staged governed turn result')
|
|
659
675
|
.option('--turn <id>', 'Target a specific active turn when multiple turns exist')
|
|
676
|
+
.option('--checkpoint', 'Checkpoint the accepted turn to git immediately after acceptance')
|
|
660
677
|
.option('--resolution <mode>', 'Conflict resolution mode for conflicted turns (standard, human_merge)', 'standard')
|
|
661
678
|
.action(acceptTurnCommand);
|
|
662
679
|
|
|
680
|
+
program
|
|
681
|
+
.command('checkpoint-turn')
|
|
682
|
+
.description('Checkpoint the latest accepted turn into git so the next writable turn has a clean baseline')
|
|
683
|
+
.option('--turn <id>', 'Checkpoint a specific accepted turn from history')
|
|
684
|
+
.action(checkpointTurnCommand);
|
|
685
|
+
|
|
663
686
|
program
|
|
664
687
|
.command('reject-turn')
|
|
665
688
|
.description('Reject the current governed turn result and retry or escalate')
|
|
@@ -668,14 +691,23 @@ program
|
|
|
668
691
|
.option('--reassign', 'Immediately re-dispatch a conflicted turn with conflict context')
|
|
669
692
|
.action(rejectTurnCommand);
|
|
670
693
|
|
|
694
|
+
program
|
|
695
|
+
.command('reissue-turn')
|
|
696
|
+
.description('Invalidate an active turn and reissue it against current repo state (baseline drift recovery)')
|
|
697
|
+
.option('--turn <id>', 'Target a specific active turn when multiple turns exist')
|
|
698
|
+
.option('--reason <reason>', 'Reason for the reissue (e.g., baseline drift, runtime rebinding)')
|
|
699
|
+
.action(reissueTurnCommand);
|
|
700
|
+
|
|
671
701
|
program
|
|
672
702
|
.command('step')
|
|
673
703
|
.description('Run a single governed turn: assign, dispatch, wait, validate, accept/reject')
|
|
674
704
|
.option('--role <role>', 'Override the target role (default: phase entry role)')
|
|
675
705
|
.option('--resume', 'Resume waiting for an already-active turn')
|
|
676
706
|
.option('--turn <id>', 'Target a specific active turn (required with --resume when multiple turns exist)')
|
|
707
|
+
.option('--no-intent', 'Do not bind the next queued approved/planned intake intent to the next turn')
|
|
677
708
|
.option('--poll <seconds>', 'Polling interval for manual adapter in seconds', '2')
|
|
678
709
|
.option('--verbose', 'Stream local_cli subprocess output while the turn is running')
|
|
710
|
+
.option('--stream', 'Stream live subprocess output to terminal (alias for --verbose)')
|
|
679
711
|
.option('--auto-reject', 'Auto-reject and retry on validation failure')
|
|
680
712
|
.action(stepCommand);
|
|
681
713
|
|
|
@@ -703,6 +735,8 @@ program
|
|
|
703
735
|
.option('--triage-approval <mode>', 'Triage policy for vision-derived intents: auto or human (default: config or auto)')
|
|
704
736
|
.option('--max-idle-cycles <n>', 'Stop after N consecutive idle cycles with no derivable work (default: 3)', parseInt)
|
|
705
737
|
.option('--session-budget <usd>', 'Cumulative session-level budget cap in USD for continuous mode', parseFloat)
|
|
738
|
+
.option('--auto-checkpoint', 'Auto-commit accepted writable turns after acceptance')
|
|
739
|
+
.option('--no-auto-checkpoint', 'Disable automatic checkpointing after accepted writable turns')
|
|
706
740
|
.action(runCommand);
|
|
707
741
|
|
|
708
742
|
program
|
|
@@ -911,7 +945,7 @@ intakeCmd
|
|
|
911
945
|
.description('Triage a detected intent — set priority, template, charter, and acceptance')
|
|
912
946
|
.option('--intent <id>', 'Intent ID to triage')
|
|
913
947
|
.option('--priority <level>', 'Priority level (p0, p1, p2, p3)')
|
|
914
|
-
.option('--template <id>', 'Governed template (generic, api-service, cli-tool, library, web-app, enterprise-app)')
|
|
948
|
+
.option('--template <id>', 'Governed template (generic, api-service, cli-tool, library, web-app, full-local-cli, enterprise-app)')
|
|
915
949
|
.option('--charter <text>', 'Delivery charter text')
|
|
916
950
|
.option('--acceptance <text>', 'Comma-separated acceptance criteria')
|
|
917
951
|
.option('--suppress', 'Suppress the intent instead of triaging')
|
package/package.json
CHANGED
|
@@ -19,6 +19,18 @@ cd "$CLI_DIR"
|
|
|
19
19
|
|
|
20
20
|
TARGET_VERSION=""
|
|
21
21
|
|
|
22
|
+
FORMULA_PATH="${CLI_DIR}/homebrew/agentxchain.rb"
|
|
23
|
+
|
|
24
|
+
formula_url() {
|
|
25
|
+
local formula_path="$1"
|
|
26
|
+
grep -E '^\s*url\s+"' "$formula_path" | sed 's/.*url *"\([^"]*\)".*/\1/' || true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
formula_sha() {
|
|
30
|
+
local formula_path="$1"
|
|
31
|
+
grep -E '^\s*sha256\s+"' "$formula_path" | sed 's/.*sha256 *"\([a-f0-9]*\)".*/\1/' || true
|
|
32
|
+
}
|
|
33
|
+
|
|
22
34
|
while [[ $# -gt 0 ]]; do
|
|
23
35
|
case "$1" in
|
|
24
36
|
--target-version)
|
|
@@ -59,18 +71,56 @@ echo "[2/4] Syncing repo mirror to published tarball..."
|
|
|
59
71
|
bash "${SCRIPT_DIR}/sync-homebrew.sh" --target-version "$TARGET_VERSION"
|
|
60
72
|
echo " OK: repo mirror synced"
|
|
61
73
|
|
|
62
|
-
# Step 3:
|
|
63
|
-
echo "[3/
|
|
64
|
-
|
|
74
|
+
# Step 3: Explicitly prove the repo mirror now matches registry truth
|
|
75
|
+
echo "[3/5] Verifying repo mirror against registry tarball..."
|
|
76
|
+
TARBALL_URL="$(npm view "agentxchain@${TARGET_VERSION}" dist.tarball 2>/dev/null || echo "")"
|
|
77
|
+
if [[ -z "$TARBALL_URL" ]]; then
|
|
78
|
+
echo " FAIL: npm did not return dist.tarball for agentxchain@${TARGET_VERSION}"
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
TARBALL_SHA="$(curl -sL "$TARBALL_URL" | shasum -a 256 | awk '{print $1}')"
|
|
83
|
+
if [[ -z "$TARBALL_SHA" ]] || [[ ${#TARBALL_SHA} -ne 64 ]]; then
|
|
84
|
+
echo " FAIL: could not compute valid SHA256 from registry tarball"
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if [[ ! -f "$FORMULA_PATH" ]]; then
|
|
89
|
+
echo " FAIL: Homebrew formula not found at ${FORMULA_PATH}"
|
|
90
|
+
exit 1
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
FORMULA_URL="$(formula_url "$FORMULA_PATH")"
|
|
94
|
+
FORMULA_SHA="$(formula_sha "$FORMULA_PATH")"
|
|
95
|
+
|
|
96
|
+
if [[ "$FORMULA_URL" != "$TARBALL_URL" ]]; then
|
|
97
|
+
echo " FAIL: repo mirror formula URL does not match registry tarball"
|
|
98
|
+
echo " formula: ${FORMULA_URL:-<missing>}"
|
|
99
|
+
echo " registry: ${TARBALL_URL}"
|
|
100
|
+
exit 1
|
|
101
|
+
fi
|
|
102
|
+
echo " OK: repo mirror formula URL matches registry tarball"
|
|
103
|
+
|
|
104
|
+
if [[ "$FORMULA_SHA" != "$TARBALL_SHA" ]]; then
|
|
105
|
+
echo " FAIL: repo mirror formula SHA256 does not match registry tarball"
|
|
106
|
+
echo " formula: ${FORMULA_SHA:-<missing>}"
|
|
107
|
+
echo " registry: ${TARBALL_SHA}"
|
|
108
|
+
exit 1
|
|
109
|
+
fi
|
|
110
|
+
echo " OK: repo mirror formula SHA256 matches registry tarball"
|
|
111
|
+
|
|
112
|
+
# Step 4: Run the full test suite WITHOUT the preflight skip
|
|
113
|
+
echo "[4/5] Running full test suite (no preflight skip)..."
|
|
114
|
+
echo " This verifies the broader Homebrew mirror contract passes with the real SHA."
|
|
65
115
|
npm test
|
|
66
116
|
echo " OK: full test suite green"
|
|
67
117
|
|
|
68
|
-
# Step
|
|
118
|
+
# Step 5: Summary
|
|
69
119
|
echo ""
|
|
70
120
|
echo "============================================="
|
|
71
121
|
echo "Post-publish verification PASSED."
|
|
72
122
|
echo " - npm: agentxchain@${TARGET_VERSION} live"
|
|
73
|
-
echo " - repo mirror: SHA
|
|
123
|
+
echo " - repo mirror: formula URL and SHA match the published tarball"
|
|
74
124
|
echo " - test suite: green without preflight skip"
|
|
75
125
|
echo ""
|
|
76
126
|
echo "Main is now in Phase 3 (post-sync). Commit and push the mirror update."
|
|
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { loadProjectContext } from '../lib/config.js';
|
|
3
3
|
import { acceptGovernedTurn } from '../lib/governed-state.js';
|
|
4
4
|
import { deriveRecoveryDescriptor } from '../lib/blocked-state.js';
|
|
5
|
+
import { checkpointAcceptedTurn } from '../lib/turn-checkpoint.js';
|
|
5
6
|
|
|
6
7
|
export async function acceptTurnCommand(opts = {}) {
|
|
7
8
|
const context = loadProjectContext();
|
|
@@ -166,6 +167,19 @@ export async function acceptTurnCommand(opts = {}) {
|
|
|
166
167
|
if (accepted?.cost?.usd != null) {
|
|
167
168
|
console.log(` ${chalk.dim('Cost:')} $${formatUsd(accepted.cost.usd)}`);
|
|
168
169
|
}
|
|
170
|
+
if (opts.checkpoint) {
|
|
171
|
+
const checkpoint = checkpointAcceptedTurn(root, { turnId });
|
|
172
|
+
if (!checkpoint.ok) {
|
|
173
|
+
console.log(` ${chalk.yellow('Checkpoint:')} accepted but checkpoint failed`);
|
|
174
|
+
console.log(` ${chalk.dim('Action:')} ${checkpoint.error}`);
|
|
175
|
+
console.log(` ${chalk.dim('Retry:')} agentxchain checkpoint-turn --turn ${turnId}`);
|
|
176
|
+
console.log('');
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
if (!checkpoint.skipped) {
|
|
180
|
+
console.log(` ${chalk.dim('Checkpoint:')} ${checkpoint.checkpoint_sha}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
169
183
|
if (accepted?.verification_replay) {
|
|
170
184
|
const verifiedAt = accepted.verification_replay.verified_at
|
|
171
185
|
? ` at ${accepted.verification_replay.verified_at}`
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { loadProjectContext } from '../lib/config.js';
|
|
3
|
+
import { checkpointAcceptedTurn } from '../lib/turn-checkpoint.js';
|
|
4
|
+
|
|
5
|
+
export async function checkpointTurnCommand(opts = {}) {
|
|
6
|
+
const context = loadProjectContext();
|
|
7
|
+
if (!context) {
|
|
8
|
+
console.log(chalk.red('No agentxchain.json found. Run `agentxchain init` first.'));
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { root, config } = context;
|
|
13
|
+
if (config.protocol_mode !== 'governed') {
|
|
14
|
+
console.log(chalk.red('The checkpoint-turn command is only available for governed projects.'));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const result = checkpointAcceptedTurn(root, { turnId: opts.turn });
|
|
19
|
+
if (!result.ok) {
|
|
20
|
+
console.log(chalk.red(result.error || 'Failed to checkpoint accepted turn.'));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (result.already_checkpointed) {
|
|
25
|
+
console.log(chalk.yellow(`Turn ${result.turn.turn_id} already has checkpoint ${result.checkpoint_sha}.`));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (result.skipped) {
|
|
30
|
+
console.log(chalk.dim(result.reason));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(chalk.green(`Checkpointed ${result.turn.turn_id} at ${result.checkpoint_sha}.`));
|
|
35
|
+
}
|
|
@@ -27,7 +27,11 @@ function printText(result, exitCode) {
|
|
|
27
27
|
console.log('');
|
|
28
28
|
|
|
29
29
|
for (const connector of result.connectors) {
|
|
30
|
-
const badge = connector.level === 'pass'
|
|
30
|
+
const badge = connector.level === 'pass'
|
|
31
|
+
? chalk.green('PASS')
|
|
32
|
+
: connector.level === 'warn'
|
|
33
|
+
? chalk.yellow('WARN')
|
|
34
|
+
: chalk.red('FAIL');
|
|
31
35
|
console.log(` ${badge} ${connector.runtime_id} (${connector.type})`);
|
|
32
36
|
console.log(` ${chalk.dim('Target:')} ${connector.target}`);
|
|
33
37
|
console.log(` ${chalk.dim('Probe:')} ${connector.probe_kind}`);
|
|
@@ -44,12 +48,22 @@ function printText(result, exitCode) {
|
|
|
44
48
|
console.log(` ${chalk.dim('Time:')} ${connector.latency_ms}ms`);
|
|
45
49
|
}
|
|
46
50
|
console.log(` ${chalk.dim('Detail:')} ${connector.detail}`);
|
|
51
|
+
if (Array.isArray(connector.authority_warnings) && connector.authority_warnings.length > 0) {
|
|
52
|
+
for (const warning of connector.authority_warnings) {
|
|
53
|
+
console.log(` ${chalk.yellow('⚠')} ${warning.detail}`);
|
|
54
|
+
if (warning.fix) {
|
|
55
|
+
console.log(` ${chalk.dim('Fix:')} ${warning.fix}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
47
59
|
}
|
|
48
60
|
|
|
49
61
|
console.log('');
|
|
50
62
|
const summary = result.overall === 'pass'
|
|
51
63
|
? chalk.green(` ✓ ${result.pass_count}/${result.connectors.length} connectors passed`)
|
|
52
|
-
:
|
|
64
|
+
: result.overall === 'warn'
|
|
65
|
+
? chalk.yellow(` ⚠ ${result.warn_count} connector warning(s), ${result.pass_count} passed`)
|
|
66
|
+
: chalk.red(` ${result.fail_count} connector failure(s), ${result.pass_count} passed`);
|
|
53
67
|
console.log(summary);
|
|
54
68
|
console.log('');
|
|
55
69
|
process.exit(exitCode);
|
|
@@ -110,6 +124,7 @@ export async function connectorCheckCommand(runtimeId, options = {}) {
|
|
|
110
124
|
overall: result.overall,
|
|
111
125
|
timeout_ms: result.timeout_ms,
|
|
112
126
|
pass_count: result.pass_count,
|
|
127
|
+
warn_count: result.warn_count,
|
|
113
128
|
fail_count: result.fail_count,
|
|
114
129
|
connectors: result.connectors,
|
|
115
130
|
};
|
package/src/commands/doctor.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from 'fs';
|
|
|
2
2
|
import { execFileSync, execSync } from 'child_process';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
-
import { loadConfig, loadLock, findProjectRoot } from '../lib/config.js';
|
|
5
|
+
import { loadConfig, loadLock, findProjectRoot, loadProjectState } from '../lib/config.js';
|
|
6
6
|
import { validateProject } from '../lib/validation.js';
|
|
7
7
|
import { runAdmissionControl } from '../lib/admission-control.js';
|
|
8
8
|
import { getWatchPid } from './watch.js';
|
|
@@ -10,12 +10,16 @@ import { getDashboardPid, getDashboardSession } from './dashboard.js';
|
|
|
10
10
|
import { loadNormalizedConfig, detectConfigVersion } from '../lib/normalized-config.js';
|
|
11
11
|
import { readDaemonState, evaluateDaemonStatus } from '../lib/run-schedule.js';
|
|
12
12
|
import { getGovernedVersionSurface, formatGovernedVersionLabel } from '../lib/protocol-version.js';
|
|
13
|
+
import { getCliVersionHealth } from '../lib/cli-version.js';
|
|
13
14
|
import { PLUGIN_MANIFEST_FILE } from '../lib/plugins.js';
|
|
14
15
|
import {
|
|
15
16
|
getRoleRuntimeCapabilityContract,
|
|
16
17
|
summarizeRoleRuntimeCapability,
|
|
17
18
|
summarizeRuntimeCapabilityContract,
|
|
18
19
|
} from '../lib/runtime-capabilities.js';
|
|
20
|
+
import { detectActiveTurnBindingDrift, detectStateBundleDesync } from '../lib/governed-state.js';
|
|
21
|
+
import { findPendingApprovedIntents } from '../lib/intake.js';
|
|
22
|
+
import { checkCleanBaseline } from '../lib/repo-observer.js';
|
|
19
23
|
|
|
20
24
|
export async function doctorCommand(opts = {}) {
|
|
21
25
|
const root = findProjectRoot(process.cwd());
|
|
@@ -55,6 +59,9 @@ export async function doctorCommand(opts = {}) {
|
|
|
55
59
|
|
|
56
60
|
function governedDoctor(root, rawConfig, opts) {
|
|
57
61
|
const checks = [];
|
|
62
|
+
const cliVersionHealth = getCliVersionHealth();
|
|
63
|
+
|
|
64
|
+
checks.push(buildCliVersionCheck(cliVersionHealth));
|
|
58
65
|
|
|
59
66
|
// 1. Config validation
|
|
60
67
|
const configResult = loadNormalizedConfig(rawConfig, root);
|
|
@@ -113,6 +120,92 @@ function governedDoctor(root, rawConfig, opts) {
|
|
|
113
120
|
checks.push({ id: 'state_health', name: 'State health', level: 'warn', detail: 'No state file yet (first run pending)' });
|
|
114
121
|
}
|
|
115
122
|
|
|
123
|
+
// 5b. Active turn binding drift (B-7: detect runtime rebinding mid-run)
|
|
124
|
+
if (normalized && existsSync(join(root, '.agentxchain', 'state.json'))) {
|
|
125
|
+
try {
|
|
126
|
+
const stateData = loadProjectState(root, normalized);
|
|
127
|
+
const drifts = detectActiveTurnBindingDrift(stateData, normalized);
|
|
128
|
+
if (drifts.length > 0) {
|
|
129
|
+
const driftSummary = drifts.map(d => {
|
|
130
|
+
const parts = [];
|
|
131
|
+
if (d.runtime_changed) parts.push(`runtime ${d.old_runtime} → ${d.new_runtime}`);
|
|
132
|
+
if (d.authority_changed) parts.push(`authority ${d.old_authority} → ${d.new_authority}`);
|
|
133
|
+
return `${d.turn_id} (${d.role_id}): ${parts.join(', ')}`;
|
|
134
|
+
}).join('; ');
|
|
135
|
+
checks.push({
|
|
136
|
+
id: 'binding_drift',
|
|
137
|
+
name: 'Active turn binding',
|
|
138
|
+
level: 'warn',
|
|
139
|
+
detail: `Stale binding: ${driftSummary}. Run: agentxchain reissue-turn`,
|
|
140
|
+
drifts,
|
|
141
|
+
});
|
|
142
|
+
} else if (Object.keys(stateData?.active_turns || {}).length > 0) {
|
|
143
|
+
checks.push({
|
|
144
|
+
id: 'binding_drift',
|
|
145
|
+
name: 'Active turn binding',
|
|
146
|
+
level: 'pass',
|
|
147
|
+
detail: 'Active turns match current config bindings',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
// State couldn't be loaded — skip drift check
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 5c. BUG-18: State/bundle integrity — active turns must have dispatch bundles
|
|
156
|
+
if (normalized && existsSync(join(root, '.agentxchain', 'state.json'))) {
|
|
157
|
+
try {
|
|
158
|
+
const stateData = loadProjectState(root, normalized);
|
|
159
|
+
const desync = detectStateBundleDesync(root, stateData);
|
|
160
|
+
if (!desync.ok) {
|
|
161
|
+
const desyncSummary = desync.desynced
|
|
162
|
+
.map(d => `${d.turn_id} (${d.role}): missing ${d.expected_path}`)
|
|
163
|
+
.join('; ');
|
|
164
|
+
checks.push({
|
|
165
|
+
id: 'bundle_integrity',
|
|
166
|
+
name: 'Dispatch bundle integrity',
|
|
167
|
+
level: 'fail',
|
|
168
|
+
detail: `Ghost turn(s): ${desyncSummary}. Run: agentxchain reissue-turn`,
|
|
169
|
+
desynced: desync.desynced,
|
|
170
|
+
});
|
|
171
|
+
} else if (Object.keys(stateData?.active_turns || {}).length > 0) {
|
|
172
|
+
checks.push({
|
|
173
|
+
id: 'bundle_integrity',
|
|
174
|
+
name: 'Dispatch bundle integrity',
|
|
175
|
+
level: 'pass',
|
|
176
|
+
detail: 'All active turns have dispatch bundles on disk',
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
180
|
+
// State couldn't be loaded — skip integrity check
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 5d. Clean working tree pre-flight for authoritative/proposed roles
|
|
185
|
+
if (normalized?.roles) {
|
|
186
|
+
const writableRoles = Object.entries(normalized.roles)
|
|
187
|
+
.filter(([, role]) => role.write_authority === 'authoritative' || role.write_authority === 'proposed')
|
|
188
|
+
.map(([id]) => id);
|
|
189
|
+
if (writableRoles.length > 0) {
|
|
190
|
+
const cleanCheck = checkCleanBaseline(root, 'authoritative');
|
|
191
|
+
if (cleanCheck.clean) {
|
|
192
|
+
checks.push({
|
|
193
|
+
id: 'clean_baseline',
|
|
194
|
+
name: 'Clean working tree',
|
|
195
|
+
level: 'pass',
|
|
196
|
+
detail: 'Working tree is clean for authoritative/proposed turns',
|
|
197
|
+
});
|
|
198
|
+
} else {
|
|
199
|
+
checks.push({
|
|
200
|
+
id: 'clean_baseline',
|
|
201
|
+
name: 'Clean working tree',
|
|
202
|
+
level: 'warn',
|
|
203
|
+
detail: `${cleanCheck.reason} Writable roles: ${writableRoles.join(', ')}`,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
116
209
|
// 6. Schedule health (only when schedules configured)
|
|
117
210
|
const schedules = normalized?.schedules;
|
|
118
211
|
const hasSchedules = schedules && typeof schedules === 'object' && Object.keys(schedules).length > 0;
|
|
@@ -258,6 +351,20 @@ function governedDoctor(root, rawConfig, opts) {
|
|
|
258
351
|
}
|
|
259
352
|
}
|
|
260
353
|
|
|
354
|
+
// 11. Pending intake intents (BUG-15 — informational)
|
|
355
|
+
{
|
|
356
|
+
const pendingIntents = findPendingApprovedIntents(root);
|
|
357
|
+
if (pendingIntents.length > 0) {
|
|
358
|
+
const summary = pendingIntents.map(pi => `[${pi.priority}] ${pi.intent_id}`).join(', ');
|
|
359
|
+
checks.push({
|
|
360
|
+
id: 'pending_intents',
|
|
361
|
+
name: 'Pending intents',
|
|
362
|
+
level: 'info',
|
|
363
|
+
detail: `${pendingIntents.length} approved intent(s) in queue, next turn will consume them: ${summary}`,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
261
368
|
// Compute summary
|
|
262
369
|
const failCount = checks.filter(c => c.level === 'fail').length;
|
|
263
370
|
const warnCount = checks.filter(c => c.level === 'warn').length;
|
|
@@ -270,6 +377,10 @@ function governedDoctor(root, rawConfig, opts) {
|
|
|
270
377
|
project: projectId,
|
|
271
378
|
...versionSurface,
|
|
272
379
|
config_version: versionSurface.config_generation,
|
|
380
|
+
cli_version: cliVersionHealth.current_version,
|
|
381
|
+
docs_min_cli_version: cliVersionHealth.docs_min_cli_version,
|
|
382
|
+
cli_version_status: cliVersionHealth.status,
|
|
383
|
+
cli_version_source: cliVersionHealth.source,
|
|
273
384
|
overall,
|
|
274
385
|
connector_probe_recommended: connectorProbe.recommended,
|
|
275
386
|
connector_probe_runtime_ids: connectorProbe.runtimeIds,
|
|
@@ -312,6 +423,11 @@ function governedDoctor(root, rawConfig, opts) {
|
|
|
312
423
|
} else {
|
|
313
424
|
console.log(chalk.red(` Not ready: ${failCount} failure${failCount > 1 ? 's' : ''}, ${warnCount} warning${warnCount > 1 ? 's' : ''}.`));
|
|
314
425
|
}
|
|
426
|
+
if (cliVersionHealth.stale) {
|
|
427
|
+
console.log(chalk.yellow(' Fix: npm install -g agentxchain@latest'));
|
|
428
|
+
console.log(chalk.yellow(' brew upgrade agentxchain'));
|
|
429
|
+
console.log(chalk.yellow(' npx --yes -p agentxchain@latest -c "agentxchain doctor"'));
|
|
430
|
+
}
|
|
315
431
|
if (failCount === 0 && connectorProbe.recommended) {
|
|
316
432
|
console.log(chalk.dim(` Next: ${connectorProbe.detail}`));
|
|
317
433
|
}
|
|
@@ -341,6 +457,33 @@ function attachRuntimeContract(baseCheck, rtId, rt, boundRoleEntries) {
|
|
|
341
457
|
};
|
|
342
458
|
}
|
|
343
459
|
|
|
460
|
+
function buildCliVersionCheck(cliVersionHealth) {
|
|
461
|
+
if (cliVersionHealth.status === 'stale') {
|
|
462
|
+
return {
|
|
463
|
+
id: 'cli_version',
|
|
464
|
+
name: 'CLI version',
|
|
465
|
+
level: 'warn',
|
|
466
|
+
detail: `${cliVersionHealth.detail} Fix with npm install -g agentxchain@latest, brew upgrade agentxchain, or npx --yes -p agentxchain@latest -c "agentxchain doctor".`,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (cliVersionHealth.status === 'ok') {
|
|
471
|
+
return {
|
|
472
|
+
id: 'cli_version',
|
|
473
|
+
name: 'CLI version',
|
|
474
|
+
level: 'pass',
|
|
475
|
+
detail: cliVersionHealth.detail,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return {
|
|
480
|
+
id: 'cli_version',
|
|
481
|
+
name: 'CLI version',
|
|
482
|
+
level: 'info',
|
|
483
|
+
detail: cliVersionHealth.detail,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
344
487
|
function checkRuntimeReachable(rtId, rt, boundRoleEntries = []) {
|
|
345
488
|
const base = { id: `runtime_${rtId}`, name: `Runtime: ${rtId}` };
|
|
346
489
|
|
|
@@ -449,8 +592,10 @@ function legacyDoctor(root, opts) {
|
|
|
449
592
|
|
|
450
593
|
const { config } = result;
|
|
451
594
|
const lock = loadLock(root);
|
|
595
|
+
const cliVersionHealth = getCliVersionHealth();
|
|
452
596
|
const checks = [];
|
|
453
597
|
|
|
598
|
+
checks.push(buildCliVersionCheck(cliVersionHealth));
|
|
454
599
|
checks.push(checkFile('agentxchain.json', existsSync(join(root, 'agentxchain.json')), 'Project config exists'));
|
|
455
600
|
checks.push(checkFile('lock.json', !!lock, 'Lock file exists'));
|
|
456
601
|
checks.push(checkBinary('cursor', 'Cursor CLI available (optional for non-macOS launch)'));
|
|
@@ -489,6 +634,11 @@ function legacyDoctor(root, opts) {
|
|
|
489
634
|
} else {
|
|
490
635
|
console.log(chalk.red(` Not ready: ${failCount} blocking issue(s).`));
|
|
491
636
|
}
|
|
637
|
+
if (cliVersionHealth.stale) {
|
|
638
|
+
console.log(chalk.yellow(' Fix: npm install -g agentxchain@latest'));
|
|
639
|
+
console.log(chalk.yellow(' brew upgrade agentxchain'));
|
|
640
|
+
console.log(chalk.yellow(' npx --yes -p agentxchain@latest -c "agentxchain doctor"'));
|
|
641
|
+
}
|
|
492
642
|
console.log('');
|
|
493
643
|
}
|
|
494
644
|
|
package/src/commands/events.js
CHANGED
|
@@ -61,12 +61,16 @@ function printEvent(evt) {
|
|
|
61
61
|
const runId = evt.run_id ? evt.run_id.slice(0, 12) : '—';
|
|
62
62
|
const phase = evt.phase || '—';
|
|
63
63
|
const turnInfo = evt.turn?.role_id ? ` [${evt.turn.role_id}]` : '';
|
|
64
|
+
const intentInfo = evt.intent_id ? ` intent=${evt.intent_id}` : '';
|
|
64
65
|
const conflictDetail = evt.event_type === 'turn_conflicted'
|
|
65
66
|
? ` — ${formatConflictDetail(evt)}`
|
|
66
67
|
: '';
|
|
67
68
|
const rejectionDetail = evt.event_type === 'turn_rejected' && evt.payload?.reason
|
|
68
69
|
? ` — ${evt.payload.reason}${evt.payload.failed_stage ? ` (${evt.payload.failed_stage})` : ''}`
|
|
69
70
|
: '';
|
|
71
|
+
const acceptanceFailedDetail = evt.event_type === 'acceptance_failed' && evt.payload?.reason
|
|
72
|
+
? ` — ${evt.payload.reason}${evt.payload.stage ? ` (${evt.payload.stage})` : ''}`
|
|
73
|
+
: '';
|
|
70
74
|
const phaseTransitionDetail = evt.event_type === 'phase_entered' && evt.payload?.from && evt.payload?.to
|
|
71
75
|
? ` ${evt.payload.from} → ${evt.payload.to}${evt.payload.trigger ? ` (${evt.payload.trigger})` : ''}`
|
|
72
76
|
: '';
|
|
@@ -78,7 +82,7 @@ function printEvent(evt) {
|
|
|
78
82
|
: evt.event_type === 'human_escalation_resolved' && evt.payload?.escalation_id
|
|
79
83
|
? ` ${evt.payload.escalation_id} via ${evt.payload.resolved_via || '?'}`
|
|
80
84
|
: '';
|
|
81
|
-
console.log(`${chalk.dim(ts)} ${type} ${chalk.cyan(runId)} ${phase}${turnInfo}${conflictDetail}${rejectionDetail}${phaseTransitionDetail}${gateFailedDetail}${humanEscalationDetail}`);
|
|
85
|
+
console.log(`${chalk.dim(ts)} ${type} ${chalk.cyan(runId)} ${phase}${turnInfo}${intentInfo}${conflictDetail}${rejectionDetail}${acceptanceFailedDetail}${phaseTransitionDetail}${gateFailedDetail}${humanEscalationDetail}`);
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
function formatConflictDetail(evt) {
|
|
@@ -116,6 +120,8 @@ function colorEventType(type) {
|
|
|
116
120
|
turn_dispatched: chalk.blue,
|
|
117
121
|
turn_accepted: chalk.green,
|
|
118
122
|
turn_rejected: chalk.yellow,
|
|
123
|
+
acceptance_failed: chalk.red.bold,
|
|
124
|
+
turn_reissued: chalk.cyan,
|
|
119
125
|
turn_conflicted: chalk.redBright,
|
|
120
126
|
phase_entered: chalk.magenta,
|
|
121
127
|
escalation_raised: chalk.red.bold,
|