agentxchain 2.129.0 → 2.130.1
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 +10 -0
- package/package.json +3 -2
- package/scripts/release-preflight.sh +18 -5
- package/src/commands/accept-turn.js +14 -0
- package/src/commands/checkpoint-turn.js +35 -0
- package/src/commands/connector.js +1 -0
- package/src/commands/doctor.js +39 -21
- package/src/commands/mission.js +661 -7
- package/src/commands/reject-turn.js +36 -2
- package/src/commands/restart.js +72 -8
- package/src/commands/run.js +13 -0
- package/src/commands/status.js +13 -1
- package/src/lib/connector-probe.js +42 -27
- package/src/lib/connector-validate.js +21 -0
- package/src/lib/continuous-run.js +8 -1
- package/src/lib/coordinator-dispatch.js +25 -0
- package/src/lib/governed-state.js +152 -2
- package/src/lib/intake.js +12 -0
- package/src/lib/mission-plans.js +510 -6
- package/src/lib/missions.js +9 -2
- package/src/lib/repo-observer.js +1 -0
- package/src/lib/run-events.js +1 -0
- package/src/lib/run-loop.js +20 -0
- package/src/lib/runtime-spawn-context.js +163 -0
- package/src/lib/turn-checkpoint.js +221 -0
package/bin/agentxchain.js
CHANGED
|
@@ -71,6 +71,7 @@ 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';
|
|
75
76
|
import { reissueTurnCommand } from '../src/commands/reissue-turn.js';
|
|
76
77
|
import { proposalListCommand, proposalDiffCommand, proposalApplyCommand, proposalRejectCommand } from '../src/commands/proposal.js';
|
|
@@ -672,9 +673,16 @@ program
|
|
|
672
673
|
.command('accept-turn')
|
|
673
674
|
.description('Accept the currently staged governed turn result')
|
|
674
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')
|
|
675
677
|
.option('--resolution <mode>', 'Conflict resolution mode for conflicted turns (standard, human_merge)', 'standard')
|
|
676
678
|
.action(acceptTurnCommand);
|
|
677
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
|
+
|
|
678
686
|
program
|
|
679
687
|
.command('reject-turn')
|
|
680
688
|
.description('Reject the current governed turn result and retry or escalate')
|
|
@@ -727,6 +735,8 @@ program
|
|
|
727
735
|
.option('--triage-approval <mode>', 'Triage policy for vision-derived intents: auto or human (default: config or auto)')
|
|
728
736
|
.option('--max-idle-cycles <n>', 'Stop after N consecutive idle cycles with no derivable work (default: 3)', parseInt)
|
|
729
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')
|
|
730
740
|
.action(runCommand);
|
|
731
741
|
|
|
732
742
|
program
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentxchain",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.130.1",
|
|
4
4
|
"description": "CLI for AgentXchain — governed multi-agent software delivery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"dev": "node bin/agentxchain.js",
|
|
25
25
|
"test": "npm run test:vitest && npm run test:node",
|
|
26
26
|
"test:vitest": "vitest run --reporter=verbose",
|
|
27
|
-
"test:
|
|
27
|
+
"test:beta": "node --test test/beta-tester-scenarios/*.test.js",
|
|
28
|
+
"test:node": "node --test test/*.test.js test/beta-tester-scenarios/*.test.js",
|
|
28
29
|
"preflight:release": "bash scripts/release-preflight.sh",
|
|
29
30
|
"preflight:release:strict": "bash scripts/release-preflight.sh --strict",
|
|
30
31
|
"check:release-alignment": "node scripts/check-release-alignment.mjs",
|
|
@@ -110,24 +110,37 @@ if [[ "$PUBLISH_GATE" -eq 1 ]]; then
|
|
|
110
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_TEST_PATTERNS=(
|
|
114
114
|
test/release-preflight.test.js
|
|
115
115
|
test/release-docs-content.test.js
|
|
116
116
|
test/release-notes-gate.test.js
|
|
117
117
|
test/release-identity-hardening.test.js
|
|
118
118
|
test/normalized-config.test.js
|
|
119
119
|
test/conformance.test.js
|
|
120
|
+
test/beta-tester-scenarios/*.test.js
|
|
120
121
|
)
|
|
121
122
|
GATE_TEST_ARGS=()
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
shopt -s nullglob
|
|
124
|
+
for pattern in "${GATE_TEST_PATTERNS[@]}"; do
|
|
125
|
+
for t in $pattern; do
|
|
124
126
|
GATE_TEST_ARGS+=("$t")
|
|
125
|
-
|
|
127
|
+
done
|
|
126
128
|
done
|
|
129
|
+
shopt -u nullglob
|
|
127
130
|
if [[ ${#GATE_TEST_ARGS[@]} -eq 0 ]]; then
|
|
128
131
|
fail "No release-gate test files found"
|
|
129
132
|
else
|
|
130
|
-
|
|
133
|
+
BETA_TEST_COUNT=0
|
|
134
|
+
for t in "${GATE_TEST_ARGS[@]}"; do
|
|
135
|
+
if [[ "$t" == test/beta-tester-scenarios/*.test.js ]]; then
|
|
136
|
+
BETA_TEST_COUNT=$((BETA_TEST_COUNT + 1))
|
|
137
|
+
fi
|
|
138
|
+
done
|
|
139
|
+
if [[ "$BETA_TEST_COUNT" -eq 0 ]]; then
|
|
140
|
+
fail "No beta-tester scenario tests found for release-gate verification"
|
|
141
|
+
TEST_OUTPUT=""
|
|
142
|
+
TEST_STATUS=1
|
|
143
|
+
elif run_and_capture TEST_OUTPUT env AGENTXCHAIN_RELEASE_TARGET_VERSION="${TARGET_VERSION}" AGENTXCHAIN_RELEASE_PREFLIGHT=1 node --test "${GATE_TEST_ARGS[@]}"; then
|
|
131
144
|
TEST_STATUS=0
|
|
132
145
|
else
|
|
133
146
|
TEST_STATUS=$?
|
|
@@ -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
|
+
}
|
|
@@ -105,6 +105,7 @@ export async function connectorCheckCommand(runtimeId, options = {}) {
|
|
|
105
105
|
const result = await probeConfiguredConnectors(context.config, {
|
|
106
106
|
runtimeId: runtimeId || null,
|
|
107
107
|
timeoutMs,
|
|
108
|
+
root: context.root,
|
|
108
109
|
onProbeStart: options.json ? null : (probeRuntimeId, runtime) => {
|
|
109
110
|
console.log(` ${chalk.dim('…')} Probing ${chalk.bold(probeRuntimeId)} ${chalk.dim(`(${runtime.type})`)}`);
|
|
110
111
|
},
|
package/src/commands/doctor.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'fs';
|
|
2
|
-
import { execFileSync
|
|
2
|
+
import { execFileSync } from 'child_process';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { loadConfig, loadLock, findProjectRoot, loadProjectState } from '../lib/config.js';
|
|
@@ -17,9 +17,10 @@ import {
|
|
|
17
17
|
summarizeRoleRuntimeCapability,
|
|
18
18
|
summarizeRuntimeCapabilityContract,
|
|
19
19
|
} from '../lib/runtime-capabilities.js';
|
|
20
|
-
import { detectActiveTurnBindingDrift } from '../lib/governed-state.js';
|
|
20
|
+
import { detectActiveTurnBindingDrift, detectStateBundleDesync } from '../lib/governed-state.js';
|
|
21
21
|
import { findPendingApprovedIntents } from '../lib/intake.js';
|
|
22
22
|
import { checkCleanBaseline } from '../lib/repo-observer.js';
|
|
23
|
+
import { probeRuntimeSpawnContext } from '../lib/runtime-spawn-context.js';
|
|
23
24
|
|
|
24
25
|
export async function doctorCommand(opts = {}) {
|
|
25
26
|
const root = findProjectRoot(process.cwd());
|
|
@@ -90,7 +91,7 @@ function governedDoctor(root, rawConfig, opts) {
|
|
|
90
91
|
const runtimes = (normalized && normalized.runtimes) || rawConfig.runtimes || {};
|
|
91
92
|
const rolesByRuntime = buildRolesByRuntime(normalized?.roles || {});
|
|
92
93
|
for (const [rtId, rt] of Object.entries(runtimes)) {
|
|
93
|
-
const check = checkRuntimeReachable(rtId, rt, rolesByRuntime[rtId] || []);
|
|
94
|
+
const check = checkRuntimeReachable(root, rtId, rt, rolesByRuntime[rtId] || []);
|
|
94
95
|
checks.push(check);
|
|
95
96
|
}
|
|
96
97
|
const connectorProbe = getConnectorProbeRecommendation(runtimes);
|
|
@@ -152,7 +153,36 @@ function governedDoctor(root, rawConfig, opts) {
|
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
155
|
|
|
155
|
-
// 5c.
|
|
156
|
+
// 5c. BUG-18: State/bundle integrity — active turns must have dispatch bundles
|
|
157
|
+
if (normalized && existsSync(join(root, '.agentxchain', 'state.json'))) {
|
|
158
|
+
try {
|
|
159
|
+
const stateData = loadProjectState(root, normalized);
|
|
160
|
+
const desync = detectStateBundleDesync(root, stateData);
|
|
161
|
+
if (!desync.ok) {
|
|
162
|
+
const desyncSummary = desync.desynced
|
|
163
|
+
.map(d => `${d.turn_id} (${d.role}): missing ${d.expected_path}`)
|
|
164
|
+
.join('; ');
|
|
165
|
+
checks.push({
|
|
166
|
+
id: 'bundle_integrity',
|
|
167
|
+
name: 'Dispatch bundle integrity',
|
|
168
|
+
level: 'fail',
|
|
169
|
+
detail: `Ghost turn(s): ${desyncSummary}. Run: agentxchain reissue-turn`,
|
|
170
|
+
desynced: desync.desynced,
|
|
171
|
+
});
|
|
172
|
+
} else if (Object.keys(stateData?.active_turns || {}).length > 0) {
|
|
173
|
+
checks.push({
|
|
174
|
+
id: 'bundle_integrity',
|
|
175
|
+
name: 'Dispatch bundle integrity',
|
|
176
|
+
level: 'pass',
|
|
177
|
+
detail: 'All active turns have dispatch bundles on disk',
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
} catch {
|
|
181
|
+
// State couldn't be loaded — skip integrity check
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 5d. Clean working tree pre-flight for authoritative/proposed roles
|
|
156
186
|
if (normalized?.roles) {
|
|
157
187
|
const writableRoles = Object.entries(normalized.roles)
|
|
158
188
|
.filter(([, role]) => role.write_authority === 'authoritative' || role.write_authority === 'proposed')
|
|
@@ -455,7 +485,7 @@ function buildCliVersionCheck(cliVersionHealth) {
|
|
|
455
485
|
};
|
|
456
486
|
}
|
|
457
487
|
|
|
458
|
-
function checkRuntimeReachable(rtId, rt, boundRoleEntries = []) {
|
|
488
|
+
function checkRuntimeReachable(root, rtId, rt, boundRoleEntries = []) {
|
|
459
489
|
const base = { id: `runtime_${rtId}`, name: `Runtime: ${rtId}` };
|
|
460
490
|
|
|
461
491
|
if (!rt || !rt.type) {
|
|
@@ -467,14 +497,8 @@ function checkRuntimeReachable(rtId, rt, boundRoleEntries = []) {
|
|
|
467
497
|
return attachRuntimeContract({ ...base, level: 'pass', detail: 'Manual runtime (no binary needed)' }, rtId, rt, boundRoleEntries);
|
|
468
498
|
|
|
469
499
|
case 'local_cli': {
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
try {
|
|
473
|
-
execSync(`command -v ${cmd}`, { stdio: 'ignore' });
|
|
474
|
-
return attachRuntimeContract({ ...base, level: 'pass', detail: `${cmd} binary found` }, rtId, rt, boundRoleEntries);
|
|
475
|
-
} catch {
|
|
476
|
-
return attachRuntimeContract({ ...base, level: 'fail', detail: `${cmd} not found in PATH` }, rtId, rt, boundRoleEntries);
|
|
477
|
-
}
|
|
500
|
+
const probe = probeRuntimeSpawnContext(root, rt, { runtimeId: rtId });
|
|
501
|
+
return attachRuntimeContract({ ...base, level: probe.ok ? 'pass' : 'fail', detail: probe.detail }, rtId, rt, boundRoleEntries);
|
|
478
502
|
}
|
|
479
503
|
|
|
480
504
|
case 'api_proxy': {
|
|
@@ -494,14 +518,8 @@ function checkRuntimeReachable(rtId, rt, boundRoleEntries = []) {
|
|
|
494
518
|
if (transport === 'streamable_http') {
|
|
495
519
|
return attachRuntimeContract({ ...base, level: 'warn', detail: 'Remote MCP endpoint (cannot verify at doctor time)' }, rtId, rt, boundRoleEntries);
|
|
496
520
|
}
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
try {
|
|
500
|
-
execSync(`command -v ${cmd}`, { stdio: 'ignore' });
|
|
501
|
-
return attachRuntimeContract({ ...base, level: 'pass', detail: `${cmd} binary found` }, rtId, rt, boundRoleEntries);
|
|
502
|
-
} catch {
|
|
503
|
-
return attachRuntimeContract({ ...base, level: 'fail', detail: `${cmd} not found in PATH` }, rtId, rt, boundRoleEntries);
|
|
504
|
-
}
|
|
521
|
+
const probe = probeRuntimeSpawnContext(root, rt, { runtimeId: rtId });
|
|
522
|
+
return attachRuntimeContract({ ...base, level: probe.ok ? 'pass' : 'fail', detail: probe.detail }, rtId, rt, boundRoleEntries);
|
|
505
523
|
}
|
|
506
524
|
|
|
507
525
|
case 'remote_agent':
|