agentxchain 2.133.0 → 2.134.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/package.json +1 -1
- package/scripts/release-postflight.sh +83 -10
- package/src/commands/events.js +5 -1
- package/src/commands/mission.js +106 -2
- package/src/commands/status.js +54 -1
- package/src/lib/coordinator-dispatch.js +12 -1
- package/src/lib/coordinator-warnings.js +31 -0
- package/src/lib/dashboard/plan-reader.js +9 -0
- package/src/lib/mission-plans.js +14 -2
- package/src/lib/recent-event-summary.js +5 -0
- package/src/lib/run-events.js +1 -0
package/package.json
CHANGED
|
@@ -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,
|
|
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,
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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"
|
package/src/commands/events.js
CHANGED
|
@@ -88,7 +88,10 @@ function printEvent(evt) {
|
|
|
88
88
|
const coordinatorRetryDetail = evt.event_type === 'coordinator_retry' && evt.payload
|
|
89
89
|
? ` — ws ${evt.payload.workstream_id || '?'} repo ${evt.payload.repo_id || '?'} (retry of ${evt.payload.failed_turn_id || '?'})`
|
|
90
90
|
: '';
|
|
91
|
-
|
|
91
|
+
const projectionWarningDetail = evt.event_type === 'coordinator_retry_projection_warning' && evt.payload
|
|
92
|
+
? ` — ws ${evt.payload.workstream_id || '?'} repo ${evt.payload.repo_id || '?'} (reconciliation required)`
|
|
93
|
+
: '';
|
|
94
|
+
console.log(`${chalk.dim(ts)} ${type} ${chalk.cyan(runId)} ${phase}${turnInfo}${intentInfo}${conflictDetail}${conflictResolvedDetail}${rejectionDetail}${acceptanceFailedDetail}${phaseTransitionDetail}${gateFailedDetail}${humanEscalationDetail}${coordinatorRetryDetail}${projectionWarningDetail}`);
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
function formatConflictDetail(evt) {
|
|
@@ -150,6 +153,7 @@ function colorEventType(type) {
|
|
|
150
153
|
gate_failed: chalk.red,
|
|
151
154
|
budget_exceeded_warn: chalk.yellowBright,
|
|
152
155
|
coordinator_retry: chalk.cyan.bold,
|
|
156
|
+
coordinator_retry_projection_warning: chalk.yellow.bold,
|
|
153
157
|
turn_checkpointed: chalk.green,
|
|
154
158
|
dispatch_progress: chalk.blue.dim,
|
|
155
159
|
};
|
package/src/commands/mission.js
CHANGED
|
@@ -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,8 @@ 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';
|
|
41
|
+
import { emitRunEvent } from '../lib/run-events.js';
|
|
40
42
|
|
|
41
43
|
export async function missionStartCommand(opts) {
|
|
42
44
|
const root = findProjectRoot(opts.dir || process.cwd());
|
|
@@ -144,6 +146,50 @@ export async function missionStartCommand(opts) {
|
|
|
144
146
|
renderMissionSnapshot(snapshot);
|
|
145
147
|
}
|
|
146
148
|
|
|
149
|
+
function loadAcceptedRepoTurn(repoPath, turnId) {
|
|
150
|
+
try {
|
|
151
|
+
const historyPath = join(repoPath, '.agentxchain', 'history.jsonl');
|
|
152
|
+
const content = readFileSync(historyPath, 'utf8').trim();
|
|
153
|
+
if (!content) return null;
|
|
154
|
+
const entries = content
|
|
155
|
+
.split('\n')
|
|
156
|
+
.map((line) => JSON.parse(line))
|
|
157
|
+
.filter((entry) => entry?.turn_id === turnId)
|
|
158
|
+
.filter((entry) => Boolean(entry?.accepted_at) || entry?.status === 'accepted' || entry?.status === 'completed');
|
|
159
|
+
return entries.at(-1) || null;
|
|
160
|
+
} catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function projectAcceptedCoordinatorTurn(workspacePath, coordinatorConfig, repoId, turnId, workstreamId, fallbackState) {
|
|
166
|
+
const acceptedTurn = loadAcceptedRepoTurn(coordinatorConfig.repos?.[repoId]?.resolved_path, turnId);
|
|
167
|
+
if (!acceptedTurn) {
|
|
168
|
+
return { ok: false, error: `Accepted turn ${turnId} not found in repo-local history for ${repoId}.` };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const currentState = loadCoordinatorState(workspacePath) || fallbackState;
|
|
172
|
+
if (!currentState) {
|
|
173
|
+
return { ok: false, error: `Coordinator state not found at ${workspacePath}.` };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return projectRepoAcceptance(
|
|
177
|
+
workspacePath,
|
|
178
|
+
currentState,
|
|
179
|
+
coordinatorConfig,
|
|
180
|
+
repoId,
|
|
181
|
+
acceptedTurn,
|
|
182
|
+
workstreamId,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function buildCoordinatorProjectionWarning(message) {
|
|
187
|
+
return {
|
|
188
|
+
code: 'coordinator_acceptance_projection_incomplete',
|
|
189
|
+
message,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
147
193
|
export async function missionListCommand(opts) {
|
|
148
194
|
const root = findProjectRoot(opts.dir || process.cwd());
|
|
149
195
|
if (!root) {
|
|
@@ -578,6 +624,7 @@ export async function missionPlanLaunchCommand(planTarget, opts) {
|
|
|
578
624
|
}
|
|
579
625
|
|
|
580
626
|
const executor = opts._executeGovernedRun || executeGovernedRun;
|
|
627
|
+
const childLog = opts.json ? noop : (opts._log || console.log);
|
|
581
628
|
const repoContext = loadProjectContext(retry.retryResult.repo_path);
|
|
582
629
|
if (!repoContext) {
|
|
583
630
|
console.error(chalk.red(`Cannot load project context for retried repo at ${retry.retryResult.repo_path}.`));
|
|
@@ -586,6 +633,7 @@ export async function missionPlanLaunchCommand(planTarget, opts) {
|
|
|
586
633
|
|
|
587
634
|
const runOpts = {
|
|
588
635
|
autoApprove: !!opts.autoApprove,
|
|
636
|
+
log: childLog,
|
|
589
637
|
provenance: {
|
|
590
638
|
trigger: 'manual',
|
|
591
639
|
created_by: 'operator',
|
|
@@ -594,6 +642,7 @@ export async function missionPlanLaunchCommand(planTarget, opts) {
|
|
|
594
642
|
};
|
|
595
643
|
|
|
596
644
|
let execution;
|
|
645
|
+
const retryWarnings = [];
|
|
597
646
|
try {
|
|
598
647
|
execution = await executor(repoContext, runOpts);
|
|
599
648
|
} catch (error) {
|
|
@@ -619,6 +668,34 @@ export async function missionPlanLaunchCommand(planTarget, opts) {
|
|
|
619
668
|
process.exit(1);
|
|
620
669
|
}
|
|
621
670
|
|
|
671
|
+
if ((execution?.exitCode ?? 0) === 0) {
|
|
672
|
+
const projection = projectAcceptedCoordinatorTurn(
|
|
673
|
+
mission.coordinator.workspace_path,
|
|
674
|
+
coordinatorConfigResult.config,
|
|
675
|
+
retry.retryResult.repo_id,
|
|
676
|
+
retry.retryResult.reissued_turn_id,
|
|
677
|
+
opts.workstream,
|
|
678
|
+
loadCoordinatorState(mission.coordinator.workspace_path),
|
|
679
|
+
);
|
|
680
|
+
if (!projection.ok) {
|
|
681
|
+
const warning = buildCoordinatorProjectionWarning(projection.error);
|
|
682
|
+
retryWarnings.push(warning);
|
|
683
|
+
console.error(chalk.yellow(`Coordinator retry projection warning: ${warning.message}`));
|
|
684
|
+
emitRunEvent(mission.coordinator.workspace_path, 'coordinator_retry_projection_warning', {
|
|
685
|
+
run_id: mission.coordinator.super_run_id,
|
|
686
|
+
phase: coordinatorConfigResult.config?.workstreams?.[opts.workstream]?.phase || null,
|
|
687
|
+
status: 'active',
|
|
688
|
+
payload: {
|
|
689
|
+
workstream_id: opts.workstream,
|
|
690
|
+
repo_id: retry.retryResult.repo_id,
|
|
691
|
+
reissued_turn_id: retry.retryResult.reissued_turn_id,
|
|
692
|
+
warning_code: warning.code,
|
|
693
|
+
warning_message: warning.message,
|
|
694
|
+
},
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
622
699
|
const syncedRetry = synchronizeCoordinatorPlanState(root, mission, retry.plan);
|
|
623
700
|
const retriedPlan = syncedRetry.ok ? syncedRetry.plan : retry.plan;
|
|
624
701
|
const retriedWorkstream = retriedPlan.workstreams.find((ws) => ws.workstream_id === opts.workstream);
|
|
@@ -643,6 +720,8 @@ export async function missionPlanLaunchCommand(planTarget, opts) {
|
|
|
643
720
|
workstream_status: retriedWorkstream?.launch_status || 'launched',
|
|
644
721
|
launch_record: retriedLaunchRecord,
|
|
645
722
|
exit_code: execution?.exitCode ?? 0,
|
|
723
|
+
warnings: retryWarnings,
|
|
724
|
+
reconciliation_required: retryWarnings.length > 0,
|
|
646
725
|
}, null, 2));
|
|
647
726
|
if ((execution?.exitCode ?? 0) !== 0) {
|
|
648
727
|
process.exit(execution.exitCode);
|
|
@@ -659,6 +738,9 @@ export async function missionPlanLaunchCommand(planTarget, opts) {
|
|
|
659
738
|
console.log(chalk.dim(` Old Turn: ${retry.retryResult.failed_turn_id}`));
|
|
660
739
|
console.log(chalk.dim(` New Turn: ${retry.retryResult.reissued_turn_id}`));
|
|
661
740
|
console.log(chalk.dim(` Workstream: ${retriedWorkstream?.launch_status || 'launched'}`));
|
|
741
|
+
if (retryWarnings.length > 0) {
|
|
742
|
+
console.log(chalk.yellow(` Warning: ${retryWarnings[0].message}`));
|
|
743
|
+
}
|
|
662
744
|
console.log('');
|
|
663
745
|
renderPlan(retriedPlan);
|
|
664
746
|
if ((execution?.exitCode ?? 0) !== 0) {
|
|
@@ -1385,7 +1467,7 @@ async function dispatchAndExecuteCoordinatorWorkstream(
|
|
|
1385
1467
|
root, mission, plan, workstreamId, coordinatorConfig, coordinatorState, opts,
|
|
1386
1468
|
) {
|
|
1387
1469
|
const executor = opts._executeGovernedRun || executeGovernedRun;
|
|
1388
|
-
const logger = opts._log || console.log;
|
|
1470
|
+
const logger = opts.json ? noop : (opts._log || console.log);
|
|
1389
1471
|
|
|
1390
1472
|
// 1. Select assignment
|
|
1391
1473
|
const assignment = selectAssignmentForWorkstream(
|
|
@@ -1456,6 +1538,26 @@ async function dispatchAndExecuteCoordinatorWorkstream(
|
|
|
1456
1538
|
};
|
|
1457
1539
|
}
|
|
1458
1540
|
|
|
1541
|
+
if ((execution?.exitCode ?? 0) === 0) {
|
|
1542
|
+
const projection = projectAcceptedCoordinatorTurn(
|
|
1543
|
+
mission.coordinator.workspace_path,
|
|
1544
|
+
coordinatorConfig,
|
|
1545
|
+
dispatch.repo_id,
|
|
1546
|
+
dispatch.turn_id,
|
|
1547
|
+
workstreamId,
|
|
1548
|
+
loadCoordinatorState(mission.coordinator.workspace_path) || coordinatorState,
|
|
1549
|
+
);
|
|
1550
|
+
if (!projection.ok) {
|
|
1551
|
+
return {
|
|
1552
|
+
ok: false,
|
|
1553
|
+
status: 'projection_error',
|
|
1554
|
+
repo_id: dispatch.repo_id,
|
|
1555
|
+
turn_id: dispatch.turn_id,
|
|
1556
|
+
error: projection.error,
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1459
1561
|
// 5. Sync plan state from coordinator (updates barriers, accepted repos, failures)
|
|
1460
1562
|
const synced = synchronizeCoordinatorPlanState(root, mission, launch.plan);
|
|
1461
1563
|
|
|
@@ -2285,3 +2387,5 @@ function formatTimestamp(value) {
|
|
|
2285
2387
|
function pad(value, width) {
|
|
2286
2388
|
return String(value).padEnd(width);
|
|
2287
2389
|
}
|
|
2390
|
+
|
|
2391
|
+
function noop() {}
|
package/src/commands/status.js
CHANGED
|
@@ -24,6 +24,7 @@ import { getDashboardPid, getDashboardSession } from './dashboard.js';
|
|
|
24
24
|
import { readPreemptionMarker, findPendingApprovedIntents } from '../lib/intake.js';
|
|
25
25
|
import { readContinuousSession } from '../lib/continuous-run.js';
|
|
26
26
|
import { readAllDispatchProgress } from '../lib/dispatch-progress.js';
|
|
27
|
+
import { readCoordinatorWarnings } from '../lib/coordinator-warnings.js';
|
|
27
28
|
|
|
28
29
|
export async function statusCommand(opts) {
|
|
29
30
|
const context = loadStatusContext();
|
|
@@ -111,10 +112,22 @@ function loadStatusContext(dir = process.cwd()) {
|
|
|
111
112
|
return null;
|
|
112
113
|
}
|
|
113
114
|
|
|
115
|
+
const fallbackConfig = {
|
|
116
|
+
...rawConfig,
|
|
117
|
+
files: {
|
|
118
|
+
talk: rawConfig.files?.talk || 'TALK.md',
|
|
119
|
+
history: rawConfig.files?.history || '.agentxchain/history.jsonl',
|
|
120
|
+
state: rawConfig.files?.state || '.agentxchain/state.json',
|
|
121
|
+
log: Object.prototype.hasOwnProperty.call(rawConfig.files || {}, 'log')
|
|
122
|
+
? rawConfig.files.log
|
|
123
|
+
: null,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
114
127
|
return {
|
|
115
128
|
root,
|
|
116
129
|
rawConfig,
|
|
117
|
-
config:
|
|
130
|
+
config: fallbackConfig,
|
|
118
131
|
version: 4,
|
|
119
132
|
};
|
|
120
133
|
}
|
|
@@ -122,6 +135,7 @@ function loadStatusContext(dir = process.cwd()) {
|
|
|
122
135
|
function renderGovernedStatus(context, opts) {
|
|
123
136
|
const { root, config, version } = context;
|
|
124
137
|
const state = loadProjectState(root, config);
|
|
138
|
+
const stateRunId = state?.run_id || readRawStateRunId(root, config);
|
|
125
139
|
const continuity = getContinuityStatus(root, state);
|
|
126
140
|
const connectorHealth = getConnectorHealth(root, config, state);
|
|
127
141
|
const recovery = deriveRecoveryDescriptor(state, config);
|
|
@@ -147,6 +161,9 @@ function renderGovernedStatus(context, opts) {
|
|
|
147
161
|
const activeTurns = getActiveTurns(state);
|
|
148
162
|
const dispatchProgress = filterDispatchProgressForActiveTurns(readAllDispatchProgress(root), activeTurns);
|
|
149
163
|
|
|
164
|
+
// Coordinator warning surfacing — DEC-COORD-RETRY-PROJECTION-EVENT-001
|
|
165
|
+
const coordinatorWarnings = readCoordinatorWarnings(root, { runId: stateRunId || null });
|
|
166
|
+
|
|
150
167
|
if (opts.json) {
|
|
151
168
|
const dashPid = getDashboardPid(root);
|
|
152
169
|
const dashSession = getDashboardSession(root);
|
|
@@ -183,6 +200,7 @@ function renderGovernedStatus(context, opts) {
|
|
|
183
200
|
dashboard_session: dashboardSessionObj,
|
|
184
201
|
binding_drift: detectActiveTurnBindingDrift(state, config),
|
|
185
202
|
bundle_integrity: detectStateBundleDesync(root, state),
|
|
203
|
+
coordinator_warnings: coordinatorWarnings,
|
|
186
204
|
}, null, 2));
|
|
187
205
|
return;
|
|
188
206
|
}
|
|
@@ -284,6 +302,7 @@ function renderGovernedStatus(context, opts) {
|
|
|
284
302
|
renderContinuityStatus(continuity, state);
|
|
285
303
|
renderConnectorHealthStatus(connectorHealth);
|
|
286
304
|
renderRecentEventSummary(recentEventSummary);
|
|
305
|
+
renderCoordinatorWarnings(coordinatorWarnings);
|
|
287
306
|
|
|
288
307
|
// BUG-18: State/bundle integrity check
|
|
289
308
|
const desync = detectStateBundleDesync(root, state);
|
|
@@ -908,3 +927,37 @@ function formatUsd(value) {
|
|
|
908
927
|
if (typeof value !== 'number' || Number.isNaN(value)) return '0.00';
|
|
909
928
|
return value.toFixed(2);
|
|
910
929
|
}
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* Read coordinator retry projection warnings from the event log.
|
|
933
|
+
* Returns a structured summary for both JSON and CLI output.
|
|
934
|
+
*/
|
|
935
|
+
function renderCoordinatorWarnings(warnings) {
|
|
936
|
+
if (!warnings || warnings.count === 0) return;
|
|
937
|
+
console.log(chalk.yellow.bold(` ⚠ Coordinator reconciliation required (${warnings.count} projection warning${warnings.count !== 1 ? 's' : ''})`));
|
|
938
|
+
for (const w of warnings.warnings) {
|
|
939
|
+
const wsLabel = w.workstream_id ? `ws:${w.workstream_id}` : '';
|
|
940
|
+
const repoLabel = w.repo_id ? `repo:${w.repo_id}` : '';
|
|
941
|
+
const parts = [wsLabel, repoLabel].filter(Boolean).join(' ');
|
|
942
|
+
console.log(` ${chalk.yellow('●')} ${parts} — ${w.warning_code}`);
|
|
943
|
+
}
|
|
944
|
+
console.log(chalk.dim(' Run `agentxchain mission plan show latest --json` to force plan sync and verify.'));
|
|
945
|
+
console.log('');
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
function readRawStateRunId(root, config) {
|
|
949
|
+
const relPath = config?.files?.state || '.agentxchain/state.json';
|
|
950
|
+
const filePath = join(root, relPath);
|
|
951
|
+
if (!existsSync(filePath)) {
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
try {
|
|
956
|
+
const parsed = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
957
|
+
return typeof parsed?.run_id === 'string' && parsed.run_id.trim().length > 0
|
|
958
|
+
? parsed.run_id.trim()
|
|
959
|
+
: null;
|
|
960
|
+
} catch {
|
|
961
|
+
return null;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
@@ -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 };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { readRunEvents } from './run-events.js';
|
|
2
|
+
|
|
3
|
+
function normalizeCoordinatorWarning(event) {
|
|
4
|
+
return {
|
|
5
|
+
event_id: event.event_id,
|
|
6
|
+
timestamp: event.timestamp,
|
|
7
|
+
run_id: event.run_id || null,
|
|
8
|
+
workstream_id: event.payload?.workstream_id || null,
|
|
9
|
+
repo_id: event.payload?.repo_id || null,
|
|
10
|
+
reissued_turn_id: event.payload?.reissued_turn_id || null,
|
|
11
|
+
warning_code: event.payload?.warning_code || 'coordinator_acceptance_projection_incomplete',
|
|
12
|
+
warning_message: event.payload?.warning_message || null,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function readCoordinatorWarnings(root, { runId = null } = {}) {
|
|
17
|
+
const events = readRunEvents(root, { type: 'coordinator_retry_projection_warning' });
|
|
18
|
+
const filtered = runId
|
|
19
|
+
? events.filter((event) => event.run_id === runId)
|
|
20
|
+
: events;
|
|
21
|
+
|
|
22
|
+
if (filtered.length === 0) {
|
|
23
|
+
return { count: 0, reconciliation_required: false, warnings: [] };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
count: filtered.length,
|
|
28
|
+
reconciliation_required: true,
|
|
29
|
+
warnings: filtered.map(normalizeCoordinatorWarning),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
import { buildPlanProgressSummary, loadAllPlans } from '../mission-plans.js';
|
|
9
9
|
import { loadAllMissionArtifacts } from '../missions.js';
|
|
10
|
+
import { loadCoordinatorState } from '../coordinator-state.js';
|
|
11
|
+
import { readCoordinatorWarnings } from '../coordinator-warnings.js';
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Build a dashboard-ready plan snapshot across all missions.
|
|
@@ -49,12 +51,19 @@ export function readPlanSnapshot(workspacePath, { limit, missionId } = {}) {
|
|
|
49
51
|
latestSummary = buildPlanSummary(latest);
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
// Surface coordinator projection warnings for dashboard consumers
|
|
55
|
+
const coordinatorState = loadCoordinatorState(workspacePath);
|
|
56
|
+
const coordinatorWarnings = readCoordinatorWarnings(workspacePath, {
|
|
57
|
+
runId: coordinatorState?.super_run_id || null,
|
|
58
|
+
});
|
|
59
|
+
|
|
52
60
|
return {
|
|
53
61
|
ok: true,
|
|
54
62
|
status: 200,
|
|
55
63
|
body: {
|
|
56
64
|
latest: latestSummary,
|
|
57
65
|
plans: plans.map(buildPlanSummary),
|
|
66
|
+
coordinator_warnings: coordinatorWarnings,
|
|
58
67
|
},
|
|
59
68
|
};
|
|
60
69
|
}
|
package/src/lib/mission-plans.js
CHANGED
|
@@ -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
|
-
|
|
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) {
|
|
@@ -47,6 +47,11 @@ function describeEvent(eventType, entry) {
|
|
|
47
47
|
const retryRepo = trimToNull(entry.payload?.repo_id);
|
|
48
48
|
return `${prefix}${eventType}${wsId ? ` ${wsId}` : ''}${retryRepo ? ` (${retryRepo})` : ''}`;
|
|
49
49
|
}
|
|
50
|
+
case 'coordinator_retry_projection_warning': {
|
|
51
|
+
const wsIdWarn = trimToNull(entry.payload?.workstream_id);
|
|
52
|
+
const warnRepo = trimToNull(entry.payload?.repo_id);
|
|
53
|
+
return `${prefix}${eventType}${wsIdWarn ? ` ${wsIdWarn}` : ''}${warnRepo ? ` (${warnRepo})` : ''} — reconciliation required`;
|
|
54
|
+
}
|
|
50
55
|
case 'turn_checkpointed':
|
|
51
56
|
return `${prefix}${eventType}${roleId ? ` [${roleId}]` : ''}`;
|
|
52
57
|
case 'dispatch_progress':
|