agentxchain 2.132.0 → 2.134.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.132.0",
3
+ "version": "2.134.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,8 +2,9 @@
2
2
  # Release postflight — run this after publish succeeds.
3
3
  # Verifies: release tag exists, npm registry serves the version, metadata is present,
4
4
  # the published package resolves through npx, the published package can execute its
5
- # CLI entrypoint from the tarball, and runner package exports are importable in a
6
- # clean consumer project.
5
+ # CLI entrypoint from the tarball, runner package exports are importable in a
6
+ # clean consumer project, and the installed CLI can scaffold and validate a fresh
7
+ # governed workspace.
7
8
  # Usage: bash scripts/release-postflight.sh --target-version <semver> [--tag vX.Y.Z]
8
9
  set -uo pipefail
9
10
 
@@ -260,6 +261,64 @@ EOF
260
261
  return "$node_status"
261
262
  }
262
263
 
264
+ run_operator_frontdoor_smoke() {
265
+ if [[ -z "$TARBALL_URL" ]]; then
266
+ echo "registry tarball metadata unavailable for operator front-door smoke" >&2
267
+ return 1
268
+ fi
269
+
270
+ local smoke_root
271
+ local workspace
272
+ local bin_path
273
+ local smoke_npmrc
274
+ local install_status
275
+ local init_output
276
+ local init_status
277
+ local validate_output
278
+ local validate_status
279
+
280
+ smoke_root="$(mktemp -d "${TMPDIR:-/tmp}/agentxchain-operator-postflight.XXXXXX")"
281
+ workspace="${smoke_root}/workspace"
282
+ bin_path="${smoke_root}/bin/${PACKAGE_BIN_NAME}"
283
+ smoke_npmrc="${smoke_root}/.npmrc"
284
+ echo "registry=https://registry.npmjs.org/" > "$smoke_npmrc"
285
+
286
+ env -u NODE_AUTH_TOKEN NPM_CONFIG_USERCONFIG="$smoke_npmrc" \
287
+ npm install --global --prefix "$smoke_root" "$TARBALL_URL" >/dev/null 2>&1
288
+ install_status=$?
289
+ if [[ "$install_status" -ne 0 ]]; then
290
+ rm -rf "$smoke_root"
291
+ return "$install_status"
292
+ fi
293
+
294
+ if [[ ! -x "$bin_path" ]]; then
295
+ echo "installed binary missing at ${bin_path}" >&2
296
+ rm -rf "$smoke_root"
297
+ return 1
298
+ fi
299
+
300
+ init_output="$(
301
+ env -u NODE_AUTH_TOKEN NPM_CONFIG_USERCONFIG="$smoke_npmrc" \
302
+ "$bin_path" init --governed --template cli-tool --goal "Release operator smoke" --dir "$workspace" -y 2>&1
303
+ )"
304
+ init_status=$?
305
+ if [[ "$init_status" -ne 0 ]]; then
306
+ printf '%s\n' "$init_output"
307
+ rm -rf "$smoke_root"
308
+ return "$init_status"
309
+ fi
310
+
311
+ validate_output="$(
312
+ cd "$workspace" || exit 1
313
+ env -u NODE_AUTH_TOKEN NPM_CONFIG_USERCONFIG="$smoke_npmrc" \
314
+ "$bin_path" validate --mode kickoff --json 2>&1
315
+ )"
316
+ validate_status=$?
317
+ printf '%s\n' "$validate_output"
318
+ rm -rf "$smoke_root"
319
+ return "$validate_status"
320
+ }
321
+
263
322
  run_with_retry() {
264
323
  local __output_var="$1"
265
324
  local description="$2"
@@ -313,17 +372,17 @@ run_with_retry() {
313
372
 
314
373
  echo "AgentXchain v${TARGET_VERSION} Release Postflight"
315
374
  echo "====================================="
316
- echo "Checks release truth after publish: tag, registry visibility, metadata, npx smoke, CLI install smoke, and package export smoke."
375
+ echo "Checks release truth after publish: tag, registry visibility, metadata, npx smoke, CLI install smoke, package export smoke, and operator front-door smoke."
317
376
  echo ""
318
377
 
319
- echo "[1/7] Git tag"
378
+ echo "[1/8] Git tag"
320
379
  if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null 2>&1; then
321
380
  pass "Git tag ${TAG} exists locally"
322
381
  else
323
382
  fail "Git tag ${TAG} is missing locally"
324
383
  fi
325
384
 
326
- echo "[2/7] Registry version"
385
+ echo "[2/8] Registry version"
327
386
  if run_with_retry VERSION_OUTPUT "registry version" equals "${TARGET_VERSION}" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" version; then
328
387
  PUBLISHED_VERSION="$(trim_last_line "$VERSION_OUTPUT")"
329
388
  if [[ "$PUBLISHED_VERSION" == "$TARGET_VERSION" ]]; then
@@ -336,7 +395,7 @@ else
336
395
  printf '%s\n' "$VERSION_OUTPUT" | tail -20
337
396
  fi
338
397
 
339
- echo "[3/7] Registry tarball metadata"
398
+ echo "[3/8] Registry tarball metadata"
340
399
  if run_with_retry TARBALL_OUTPUT "registry tarball metadata" nonempty "" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" dist.tarball; then
341
400
  TARBALL_URL="$(trim_last_line "$TARBALL_OUTPUT")"
342
401
  if [[ -n "$TARBALL_URL" ]]; then
@@ -349,7 +408,7 @@ else
349
408
  printf '%s\n' "$TARBALL_OUTPUT" | tail -20
350
409
  fi
351
410
 
352
- echo "[4/7] Registry checksum metadata"
411
+ echo "[4/8] Registry checksum metadata"
353
412
  if run_with_retry INTEGRITY_OUTPUT "registry checksum metadata" nonempty "" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" dist.integrity; then
354
413
  REGISTRY_CHECKSUM="$(trim_last_line "$INTEGRITY_OUTPUT")"
355
414
  fi
@@ -364,7 +423,7 @@ else
364
423
  fail "registry did not return checksum metadata"
365
424
  fi
366
425
 
367
- echo "[5/7] npx smoke"
426
+ echo "[5/8] npx smoke"
368
427
  if run_with_retry NPX_OUTPUT "npx smoke" nonempty "" run_npx_smoke; then
369
428
  NPX_VERSION="$(extract_matching_line "$NPX_OUTPUT" "$TARGET_VERSION" 2>/dev/null || trim_last_line "$NPX_OUTPUT")"
370
429
  if [[ "$NPX_VERSION" == "$TARGET_VERSION" ]]; then
@@ -377,7 +436,7 @@ else
377
436
  printf '%s\n' "$NPX_OUTPUT" | tail -20
378
437
  fi
379
438
 
380
- echo "[6/7] Install smoke"
439
+ echo "[6/8] Install smoke"
381
440
  if run_with_retry EXEC_OUTPUT "install smoke" nonempty "" run_install_smoke; then
382
441
  EXEC_VERSION="$(trim_last_line "$EXEC_OUTPUT")"
383
442
  if [[ "$EXEC_VERSION" == "$TARGET_VERSION" ]]; then
@@ -390,7 +449,7 @@ else
390
449
  printf '%s\n' "$EXEC_OUTPUT" | tail -20
391
450
  fi
392
451
 
393
- echo "[7/7] Package export smoke"
452
+ echo "[7/8] Package export smoke"
394
453
  if run_with_retry RUNNER_EXPORT_OUTPUT "runner export smoke" nonempty "" run_runner_export_smoke; then
395
454
  RUNNER_EXPORT_JSON="$(trim_last_line "$RUNNER_EXPORT_OUTPUT")"
396
455
  RUNNER_EXPORT_VERSION="$(printf '%s' "$RUNNER_EXPORT_JSON" | node --input-type=module -e "process.stdin.setEncoding('utf8'); let raw=''; process.stdin.on('data', (chunk) => raw += chunk); process.stdin.on('end', () => { const parsed = JSON.parse(raw); console.log(parsed.runner_interface_version || ''); });")"
@@ -410,6 +469,20 @@ else
410
469
  printf '%s\n' "$RUNNER_EXPORT_OUTPUT" | tail -20
411
470
  fi
412
471
 
472
+ echo "[8/8] Operator front-door smoke"
473
+ if run_with_retry OPERATOR_OUTPUT "operator front-door smoke" nonempty "" run_operator_frontdoor_smoke; then
474
+ OPERATOR_OK="$(printf '%s' "$OPERATOR_OUTPUT" | node --input-type=module -e "process.stdin.setEncoding('utf8'); let raw=''; process.stdin.on('data', (chunk) => raw += chunk); process.stdin.on('end', () => { const parsed = JSON.parse(raw); console.log(parsed.ok ? 'true' : 'false'); });" 2>/dev/null || true)"
475
+ OPERATOR_PROTOCOL_MODE="$(printf '%s' "$OPERATOR_OUTPUT" | node --input-type=module -e "process.stdin.setEncoding('utf8'); let raw=''; process.stdin.on('data', (chunk) => raw += chunk); process.stdin.on('end', () => { const parsed = JSON.parse(raw); console.log(parsed.protocol_mode || ''); });" 2>/dev/null || true)"
476
+ if [[ "$OPERATOR_OK" == "true" && "$OPERATOR_PROTOCOL_MODE" == "governed" ]]; then
477
+ pass "published CLI scaffolds and validates a governed workspace"
478
+ else
479
+ fail "published operator front-door smoke did not validate a governed workspace"
480
+ fi
481
+ else
482
+ fail "published operator front-door smoke failed"
483
+ printf '%s\n' "$OPERATOR_OUTPUT" | tail -20
484
+ fi
485
+
413
486
  echo ""
414
487
  echo "====================================="
415
488
  echo "Results: ${PASS} passed, ${FAIL} failed"
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { readFileSync } from 'fs';
3
- import { resolve } from 'path';
3
+ import { join, resolve } from 'path';
4
4
  import { findProjectRoot, loadProjectContext } from '../lib/config.js';
5
5
  import {
6
6
  attachChainToMission,
@@ -37,6 +37,7 @@ import { executeChainedRun } from '../lib/run-chain.js';
37
37
  import { executeGovernedRun } from './run.js';
38
38
  import { dispatchCoordinatorTurn, selectAssignmentForWorkstream } from '../lib/coordinator-dispatch.js';
39
39
  import { loadCoordinatorState } from '../lib/coordinator-state.js';
40
+ import { projectRepoAcceptance } from '../lib/coordinator-acceptance.js';
40
41
 
41
42
  export async function missionStartCommand(opts) {
42
43
  const root = findProjectRoot(opts.dir || process.cwd());
@@ -144,6 +145,43 @@ export async function missionStartCommand(opts) {
144
145
  renderMissionSnapshot(snapshot);
145
146
  }
146
147
 
148
+ function loadAcceptedRepoTurn(repoPath, turnId) {
149
+ try {
150
+ const historyPath = join(repoPath, '.agentxchain', 'history.jsonl');
151
+ const content = readFileSync(historyPath, 'utf8').trim();
152
+ if (!content) return null;
153
+ const entries = content
154
+ .split('\n')
155
+ .map((line) => JSON.parse(line))
156
+ .filter((entry) => entry?.turn_id === turnId)
157
+ .filter((entry) => Boolean(entry?.accepted_at) || entry?.status === 'accepted' || entry?.status === 'completed');
158
+ return entries.at(-1) || null;
159
+ } catch {
160
+ return null;
161
+ }
162
+ }
163
+
164
+ function projectAcceptedCoordinatorTurn(workspacePath, coordinatorConfig, repoId, turnId, workstreamId, fallbackState) {
165
+ const acceptedTurn = loadAcceptedRepoTurn(coordinatorConfig.repos?.[repoId]?.resolved_path, turnId);
166
+ if (!acceptedTurn) {
167
+ return { ok: false, error: `Accepted turn ${turnId} not found in repo-local history for ${repoId}.` };
168
+ }
169
+
170
+ const currentState = loadCoordinatorState(workspacePath) || fallbackState;
171
+ if (!currentState) {
172
+ return { ok: false, error: `Coordinator state not found at ${workspacePath}.` };
173
+ }
174
+
175
+ return projectRepoAcceptance(
176
+ workspacePath,
177
+ currentState,
178
+ coordinatorConfig,
179
+ repoId,
180
+ acceptedTurn,
181
+ workstreamId,
182
+ );
183
+ }
184
+
147
185
  export async function missionListCommand(opts) {
148
186
  const root = findProjectRoot(opts.dir || process.cwd());
149
187
  if (!root) {
@@ -578,6 +616,7 @@ export async function missionPlanLaunchCommand(planTarget, opts) {
578
616
  }
579
617
 
580
618
  const executor = opts._executeGovernedRun || executeGovernedRun;
619
+ const childLog = opts.json ? noop : (opts._log || console.log);
581
620
  const repoContext = loadProjectContext(retry.retryResult.repo_path);
582
621
  if (!repoContext) {
583
622
  console.error(chalk.red(`Cannot load project context for retried repo at ${retry.retryResult.repo_path}.`));
@@ -586,6 +625,7 @@ export async function missionPlanLaunchCommand(planTarget, opts) {
586
625
 
587
626
  const runOpts = {
588
627
  autoApprove: !!opts.autoApprove,
628
+ log: childLog,
589
629
  provenance: {
590
630
  trigger: 'manual',
591
631
  created_by: 'operator',
@@ -619,6 +659,20 @@ export async function missionPlanLaunchCommand(planTarget, opts) {
619
659
  process.exit(1);
620
660
  }
621
661
 
662
+ if ((execution?.exitCode ?? 0) === 0) {
663
+ const projection = projectAcceptedCoordinatorTurn(
664
+ mission.coordinator.workspace_path,
665
+ coordinatorConfigResult.config,
666
+ retry.retryResult.repo_id,
667
+ retry.retryResult.reissued_turn_id,
668
+ opts.workstream,
669
+ loadCoordinatorState(mission.coordinator.workspace_path),
670
+ );
671
+ if (!projection.ok) {
672
+ console.error(chalk.yellow(`Coordinator retry projection warning: ${projection.error}`));
673
+ }
674
+ }
675
+
622
676
  const syncedRetry = synchronizeCoordinatorPlanState(root, mission, retry.plan);
623
677
  const retriedPlan = syncedRetry.ok ? syncedRetry.plan : retry.plan;
624
678
  const retriedWorkstream = retriedPlan.workstreams.find((ws) => ws.workstream_id === opts.workstream);
@@ -1385,7 +1439,7 @@ async function dispatchAndExecuteCoordinatorWorkstream(
1385
1439
  root, mission, plan, workstreamId, coordinatorConfig, coordinatorState, opts,
1386
1440
  ) {
1387
1441
  const executor = opts._executeGovernedRun || executeGovernedRun;
1388
- const logger = opts._log || console.log;
1442
+ const logger = opts.json ? noop : (opts._log || console.log);
1389
1443
 
1390
1444
  // 1. Select assignment
1391
1445
  const assignment = selectAssignmentForWorkstream(
@@ -1456,6 +1510,26 @@ async function dispatchAndExecuteCoordinatorWorkstream(
1456
1510
  };
1457
1511
  }
1458
1512
 
1513
+ if ((execution?.exitCode ?? 0) === 0) {
1514
+ const projection = projectAcceptedCoordinatorTurn(
1515
+ mission.coordinator.workspace_path,
1516
+ coordinatorConfig,
1517
+ dispatch.repo_id,
1518
+ dispatch.turn_id,
1519
+ workstreamId,
1520
+ loadCoordinatorState(mission.coordinator.workspace_path) || coordinatorState,
1521
+ );
1522
+ if (!projection.ok) {
1523
+ return {
1524
+ ok: false,
1525
+ status: 'projection_error',
1526
+ repo_id: dispatch.repo_id,
1527
+ turn_id: dispatch.turn_id,
1528
+ error: projection.error,
1529
+ };
1530
+ }
1531
+ }
1532
+
1459
1533
  // 5. Sync plan state from coordinator (updates barriers, accepted repos, failures)
1460
1534
  const synced = synchronizeCoordinatorPlanState(root, mission, launch.plan);
1461
1535
 
@@ -2285,3 +2359,5 @@ function formatTimestamp(value) {
2285
2359
  function pad(value, width) {
2286
2360
  return String(value).padEnd(width);
2287
2361
  }
2362
+
2363
+ function noop() {}
@@ -1,7 +1,7 @@
1
1
  import { appendFileSync, copyFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { loadProjectContext, loadProjectState } from './config.js';
4
- import { assignGovernedTurn, getActiveTurnCount } from './governed-state.js';
4
+ import { assignGovernedTurn, getActiveTurnCount, initializeGovernedRun } from './governed-state.js';
5
5
  import { writeDispatchBundle } from './dispatch-bundle.js';
6
6
  import { getDispatchTurnDir } from './turn-paths.js';
7
7
  import { readBarriers, readCoordinatorHistory, recordCoordinatorDecision } from './coordinator-state.js';
@@ -253,6 +253,17 @@ export function dispatchCoordinatorTurn(workspacePath, state, config, assignment
253
253
  return { ok: false, error: runtime.detail };
254
254
  }
255
255
 
256
+ let repoState = runtime.state;
257
+ if (repoState.status === 'idle' || repoState.status === 'completed') {
258
+ const initResult = initializeGovernedRun(runtime.root, runtime.config, {
259
+ allow_terminal_restart: repoState.status === 'completed',
260
+ });
261
+ if (!initResult.ok) {
262
+ return { ok: false, error: initResult.error };
263
+ }
264
+ repoState = initResult.state;
265
+ }
266
+
256
267
  const assignResult = assignGovernedTurn(runtime.root, runtime.config, assignment.role);
257
268
  if (!assignResult.ok) {
258
269
  return { ok: false, error: assignResult.error };
@@ -12,7 +12,7 @@ import { join } from 'path';
12
12
  import { loadChainReport } from './chain-reports.js';
13
13
  import { writeDispatchBundle } from './dispatch-bundle.js';
14
14
  import { loadProjectContext, loadProjectState } from './config.js';
15
- import { reissueTurn } from './governed-state.js';
15
+ import { reactivateGovernedRun, reissueTurn } from './governed-state.js';
16
16
  import { emitRunEvent } from './run-events.js';
17
17
  import { readBarriers, readCoordinatorHistory, recordCoordinatorDecision } from './coordinator-state.js';
18
18
  import { loadCoordinatorConfig } from './coordinator-config.js';
@@ -1074,7 +1074,19 @@ export function retryCoordinatorWorkstream(root, mission, planId, workstreamId,
1074
1074
  return { ok: false, error: reissued.error };
1075
1075
  }
1076
1076
 
1077
- const bundleResult = writeDispatchBundle(repoTurn.root, reissued.state, repoTurn.config, {
1077
+ let repoStateForBundle = reissued.state;
1078
+ if (reissued.state?.status === 'blocked' || reissued.state?.status === 'paused') {
1079
+ const reactivated = reactivateGovernedRun(repoTurn.root, reissued.state, {
1080
+ via: 'mission plan launch --retry',
1081
+ notificationConfig: repoTurn.config,
1082
+ });
1083
+ if (!reactivated.ok) {
1084
+ return { ok: false, error: `Turn reissued but blocked run could not be reactivated: ${reactivated.error}` };
1085
+ }
1086
+ repoStateForBundle = reactivated.state;
1087
+ }
1088
+
1089
+ const bundleResult = writeDispatchBundle(repoTurn.root, repoStateForBundle, repoTurn.config, {
1078
1090
  turnId: reissued.newTurn.turn_id,
1079
1091
  });
1080
1092
  if (!bundleResult.ok) {