agentxchain 2.145.0 → 2.147.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/dashboard/app.js +3 -0
- package/dashboard/components/notifications.js +127 -0
- package/dashboard/index.html +1 -0
- package/package.json +1 -1
- package/scripts/publish-npm.sh +16 -0
- package/scripts/release-downstream-truth.sh +16 -8
- package/scripts/sync-homebrew.sh +14 -1
- package/scripts/verify-post-publish.sh +55 -4
- package/src/commands/init.js +66 -31
- package/src/commands/reissue-turn.js +16 -0
- package/src/commands/reject-turn.js +14 -1
- package/src/commands/restart.js +33 -3
- package/src/commands/resume.js +78 -66
- package/src/commands/run.js +67 -10
- package/src/commands/schedule.js +34 -7
- package/src/commands/status.js +38 -5
- package/src/commands/step.js +117 -34
- package/src/lib/adapters/api-proxy-adapter.js +8 -0
- package/src/lib/adapters/local-cli-adapter.js +131 -13
- package/src/lib/adapters/manual-adapter.js +9 -10
- package/src/lib/adapters/mcp-adapter.js +3 -5
- package/src/lib/adapters/remote-agent-adapter.js +3 -5
- package/src/lib/config.js +4 -1
- package/src/lib/continuous-run.js +71 -6
- package/src/lib/dashboard/actions.js +9 -3
- package/src/lib/dashboard/bridge-server.js +11 -0
- package/src/lib/dashboard/notifications-reader.js +91 -0
- package/src/lib/dashboard/state-reader.js +16 -4
- package/src/lib/dispatch-bundle.js +1 -1
- package/src/lib/dispatch-progress.js +5 -3
- package/src/lib/governed-state.js +355 -13
- package/src/lib/intake.js +10 -1
- package/src/lib/normalized-config.js +51 -1
- package/src/lib/recent-event-summary.js +12 -0
- package/src/lib/run-events.js +4 -0
- package/src/lib/run-loop.js +67 -2
- package/src/lib/runner-interface.js +1 -0
- package/src/lib/schema.js +7 -0
- package/src/lib/schemas/agentxchain-config.schema.json +15 -1
- package/src/lib/staged-result-proof.js +43 -0
- package/src/lib/stale-turn-watchdog.js +308 -34
- package/src/lib/turn-result-shape.js +38 -0
- package/src/lib/turn-result-validator.js +4 -1
package/dashboard/app.js
CHANGED
|
@@ -15,6 +15,7 @@ import { render as renderCrossRepo } from './components/cross-repo.js';
|
|
|
15
15
|
import { render as renderDelegations } from './components/delegations.js';
|
|
16
16
|
import { render as renderBlockers } from './components/blockers.js';
|
|
17
17
|
import { render as renderArtifacts } from './components/artifacts.js';
|
|
18
|
+
import { render as renderNotifications } from './components/notifications.js';
|
|
18
19
|
import { render as renderMission } from './components/mission.js';
|
|
19
20
|
import { render as renderChain } from './components/chain.js';
|
|
20
21
|
import { render as renderRunHistory } from './components/run-history.js';
|
|
@@ -31,6 +32,7 @@ const VIEWS = {
|
|
|
31
32
|
delegations: { fetch: ['state', 'history'], render: renderDelegations },
|
|
32
33
|
ledger: { fetch: ['state', 'ledger', 'coordinatorState', 'coordinatorLedger', 'repoDecisionsSummary'], render: renderLedger },
|
|
33
34
|
hooks: { fetch: ['audit', 'annotations', 'coordinatorAudit', 'coordinatorAnnotations'], render: renderHooks },
|
|
35
|
+
notifications: { fetch: ['notifications'], render: renderNotifications },
|
|
34
36
|
blocked: { fetch: ['state', 'audit', 'coordinatorState', 'coordinatorAudit', 'coordinatorBlockers', 'coordinatorRepoStatusRows', 'gateActions'], render: renderBlocked },
|
|
35
37
|
gate: { fetch: ['state', 'history', 'coordinatorState', 'coordinatorHistory', 'coordinatorBarriers', 'gateActions'], render: renderGate },
|
|
36
38
|
initiative: { fetch: ['coordinatorState', 'coordinatorBarriers', 'barrierLedger', 'coordinatorBlockers', 'coordinatorRepoStatusRows'], render: renderInitiative },
|
|
@@ -62,6 +64,7 @@ const API_MAP = {
|
|
|
62
64
|
coordinatorBlockers: '/api/coordinator/blockers',
|
|
63
65
|
coordinatorRepoStatusRows: '/api/coordinator/repo-status',
|
|
64
66
|
workflowKitArtifacts: '/api/workflow-kit-artifacts',
|
|
67
|
+
notifications: '/api/notifications',
|
|
65
68
|
missions: '/api/missions',
|
|
66
69
|
plans: '/api/plans',
|
|
67
70
|
chainReports: '/api/chain-reports',
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
function esc(str) {
|
|
2
|
+
if (str == null) return '';
|
|
3
|
+
return String(str)
|
|
4
|
+
.replace(/&/g, '&')
|
|
5
|
+
.replace(/</g, '<')
|
|
6
|
+
.replace(/>/g, '>')
|
|
7
|
+
.replace(/"/g, '"')
|
|
8
|
+
.replace(/'/g, ''');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function badge(label, color = 'var(--text-dim)') {
|
|
12
|
+
return `<span class="badge" style="color:${color};border-color:${color}">${esc(label)}</span>`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function formatResult(entry) {
|
|
16
|
+
if (entry?.delivered) return badge('delivered', 'var(--green)');
|
|
17
|
+
if (entry?.timed_out) return badge('timed out', 'var(--yellow)');
|
|
18
|
+
return badge('failed', 'var(--red)');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function renderWebhookRow(webhook) {
|
|
22
|
+
return `<tr>
|
|
23
|
+
<td class="mono">${esc(webhook.name)}</td>
|
|
24
|
+
<td>${esc(webhook.timeout_ms)}</td>
|
|
25
|
+
<td>${esc(webhook.event_count)}</td>
|
|
26
|
+
<td><span class="mono">${esc((webhook.events || []).join(', '))}</span></td>
|
|
27
|
+
</tr>`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function renderAuditRow(entry) {
|
|
31
|
+
const rowStyle = entry?.delivered ? '' : ' style="border-left:3px solid var(--red)"';
|
|
32
|
+
const statusCode = entry?.status_code == null ? '—' : String(entry.status_code);
|
|
33
|
+
const duration = entry?.duration_ms == null ? '—' : `${entry.duration_ms}ms`;
|
|
34
|
+
return `<tr${rowStyle}>
|
|
35
|
+
<td class="mono">${esc(entry?.emitted_at || '—')}</td>
|
|
36
|
+
<td><span class="mono">${esc(entry?.event_type || '—')}</span></td>
|
|
37
|
+
<td class="mono">${esc(entry?.notification_name || '—')}</td>
|
|
38
|
+
<td>${formatResult(entry)}</td>
|
|
39
|
+
<td>${esc(statusCode)}</td>
|
|
40
|
+
<td>${esc(duration)}</td>
|
|
41
|
+
<td>${esc(entry?.message || '—')}</td>
|
|
42
|
+
</tr>`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function render({ notifications }) {
|
|
46
|
+
if (!notifications) {
|
|
47
|
+
return `<div class="placeholder"><h2>Notifications</h2><p>No notification data available.</p></div>`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (notifications.ok === false) {
|
|
51
|
+
const hint = notifications.code === 'config_missing'
|
|
52
|
+
? ' Run <code>agentxchain init --governed</code> to get started.'
|
|
53
|
+
: '';
|
|
54
|
+
return `<div class="placeholder"><h2>Notifications</h2><p>${esc(notifications.error || 'Failed to load notification data.')}${hint}</p></div>`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const recent = Array.isArray(notifications.recent) ? notifications.recent : [];
|
|
58
|
+
const webhooks = Array.isArray(notifications.webhooks) ? notifications.webhooks : [];
|
|
59
|
+
const summary = notifications.summary || {};
|
|
60
|
+
|
|
61
|
+
if (!notifications.configured && recent.length === 0) {
|
|
62
|
+
return `<div class="placeholder"><h2>Notifications</h2><p>No <code>notifications.webhooks</code> are configured and no delivery audit entries exist yet.</p></div>`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let html = `<div class="notifications-view"><div class="run-header"><div class="run-meta">`;
|
|
66
|
+
html += notifications.configured
|
|
67
|
+
? badge(`${webhooks.length} webhook${webhooks.length === 1 ? '' : 's'} configured`, 'var(--green)')
|
|
68
|
+
: badge('not currently configured', 'var(--yellow)');
|
|
69
|
+
html += badge(`${summary.total_attempts || 0} attempts`, 'var(--accent)');
|
|
70
|
+
if ((summary.failed || 0) > 0) {
|
|
71
|
+
html += badge(`${summary.failed} failed`, 'var(--red)');
|
|
72
|
+
}
|
|
73
|
+
if ((summary.timed_out || 0) > 0) {
|
|
74
|
+
html += badge(`${summary.timed_out} timed out`, 'var(--yellow)');
|
|
75
|
+
}
|
|
76
|
+
if (notifications.approval_sla?.enabled) {
|
|
77
|
+
html += badge(`approval SLA: ${(notifications.approval_sla.reminder_after_seconds || []).join(', ')}s`, 'var(--accent)');
|
|
78
|
+
}
|
|
79
|
+
html += `</div></div>`;
|
|
80
|
+
|
|
81
|
+
if (webhooks.length > 0) {
|
|
82
|
+
html += `<div class="section"><h3>Notification Targets</h3>
|
|
83
|
+
<table class="data-table">
|
|
84
|
+
<thead>
|
|
85
|
+
<tr>
|
|
86
|
+
<th>Name</th>
|
|
87
|
+
<th>Timeout</th>
|
|
88
|
+
<th>Events</th>
|
|
89
|
+
<th>Subscribed Event Types</th>
|
|
90
|
+
</tr>
|
|
91
|
+
</thead>
|
|
92
|
+
<tbody>${webhooks.map(renderWebhookRow).join('')}</tbody>
|
|
93
|
+
</table>
|
|
94
|
+
</div>`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
html += `<div class="section"><h3>Delivery Summary</h3>
|
|
98
|
+
<p><strong>Delivered:</strong> ${esc(summary.delivered || 0)}<br>
|
|
99
|
+
<strong>Failed:</strong> ${esc(summary.failed || 0)}<br>
|
|
100
|
+
<strong>Last emitted:</strong> ${esc(summary.last_emitted_at || '—')}<br>
|
|
101
|
+
<strong>Last failure:</strong> ${esc(summary.last_failure_at || '—')}</p>
|
|
102
|
+
</div>`;
|
|
103
|
+
|
|
104
|
+
if (recent.length === 0) {
|
|
105
|
+
html += `<div class="section"><h3>Recent Delivery Attempts</h3><p style="color:var(--text-dim)">No notification deliveries recorded yet.</p></div>`;
|
|
106
|
+
} else {
|
|
107
|
+
html += `<div class="section"><h3>Recent Delivery Attempts</h3>
|
|
108
|
+
<table class="data-table">
|
|
109
|
+
<thead>
|
|
110
|
+
<tr>
|
|
111
|
+
<th>Emitted</th>
|
|
112
|
+
<th>Event</th>
|
|
113
|
+
<th>Target</th>
|
|
114
|
+
<th>Result</th>
|
|
115
|
+
<th>Status</th>
|
|
116
|
+
<th>Duration</th>
|
|
117
|
+
<th>Message</th>
|
|
118
|
+
</tr>
|
|
119
|
+
</thead>
|
|
120
|
+
<tbody>${recent.map(renderAuditRow).join('')}</tbody>
|
|
121
|
+
</table>
|
|
122
|
+
</div>`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
html += `</div>`;
|
|
126
|
+
return html;
|
|
127
|
+
}
|
package/dashboard/index.html
CHANGED
|
@@ -401,6 +401,7 @@
|
|
|
401
401
|
<a href="#delegations">Delegations</a>
|
|
402
402
|
<a href="#ledger">Decisions</a>
|
|
403
403
|
<a href="#hooks">Hooks</a>
|
|
404
|
+
<a href="#notifications">Notifications</a>
|
|
404
405
|
<a href="#blocked">Blocked</a>
|
|
405
406
|
<a href="#gate">Gates</a>
|
|
406
407
|
<a href="#blockers">Blockers</a>
|
package/package.json
CHANGED
package/scripts/publish-npm.sh
CHANGED
|
@@ -84,6 +84,22 @@ NEW_VERSION="$(node -e "console.log(JSON.parse(require('fs').readFileSync('packa
|
|
|
84
84
|
echo "New version: ${NEW_VERSION}"
|
|
85
85
|
echo ""
|
|
86
86
|
|
|
87
|
+
# Publish-gate enforcement: WAYS-OF-WORKING section 9 prohibits bypassing the
|
|
88
|
+
# publish gate with manual npm publish. Even though this script is a documented
|
|
89
|
+
# non-canonical helper (see RELEASE_CUT_SPEC.md section 6), it must not drop
|
|
90
|
+
# below the same release-boundary proof surface (claim-reality-preflight,
|
|
91
|
+
# beta-tester scenarios, release-docs-content, release-preflight) that the
|
|
92
|
+
# canonical publish-npm-on-tag.yml workflow runs via
|
|
93
|
+
# `release-preflight.sh --publish-gate`. Set ALLOW_PUBLISH_GATE_BYPASS=1 only
|
|
94
|
+
# for dry-run/debug paths that have manually established proof (for example,
|
|
95
|
+
# a release-preflight.sh --publish-gate run the operator just watched pass).
|
|
96
|
+
if [[ "${ALLOW_PUBLISH_GATE_BYPASS:-0}" != "1" ]]; then
|
|
97
|
+
echo "Running release-preflight.sh --publish-gate before npm publish..."
|
|
98
|
+
bash scripts/release-preflight.sh --publish-gate --target-version "${NEW_VERSION}"
|
|
99
|
+
else
|
|
100
|
+
echo "WARNING: ALLOW_PUBLISH_GATE_BYPASS=1 set — skipping publish-gate. The operator owns claim-reality/beta-tester proof manually."
|
|
101
|
+
fi
|
|
102
|
+
|
|
87
103
|
echo "Publishing to npm..."
|
|
88
104
|
if [[ -n "${NPM_TOKEN:-}" ]]; then
|
|
89
105
|
npm publish --access public --//registry.npmjs.org/:_authToken="${NPM_TOKEN}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Release downstream truth — run after all downstream surfaces are updated.
|
|
3
|
-
# Verifies: GitHub release
|
|
3
|
+
# Verifies: GitHub release is published on the expected tag URL, Homebrew tap SHA and URL match registry tarball.
|
|
4
4
|
# Usage: bash scripts/release-downstream-truth.sh --target-version <semver>
|
|
5
5
|
set -uo pipefail
|
|
6
6
|
|
|
@@ -91,22 +91,30 @@ echo "[1/3] GitHub release"
|
|
|
91
91
|
if ! command -v gh >/dev/null 2>&1; then
|
|
92
92
|
fail "gh CLI not available — cannot verify GitHub release"
|
|
93
93
|
else
|
|
94
|
-
|
|
94
|
+
GH_READY=false
|
|
95
|
+
EXPECTED_GH_URL="https://github.com/shivamtiwari93/agentXchain.dev/releases/tag/v${TARGET_VERSION}"
|
|
95
96
|
for attempt in $(seq 1 "$RETRY_ATTEMPTS"); do
|
|
96
97
|
GH_TAG="$(gh release view "v${TARGET_VERSION}" --json tagName -q '.tagName' 2>/dev/null || true)"
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
GH_DRAFT="$(gh release view "v${TARGET_VERSION}" --json isDraft -q '.isDraft' 2>/dev/null || true)"
|
|
99
|
+
GH_URL="$(gh release view "v${TARGET_VERSION}" --json url -q '.url' 2>/dev/null || true)"
|
|
100
|
+
GH_PUBLISHED_AT="$(gh release view "v${TARGET_VERSION}" --json publishedAt -q '.publishedAt' 2>/dev/null || true)"
|
|
101
|
+
if [[ "$GH_TAG" == "v${TARGET_VERSION}" ]] \
|
|
102
|
+
&& [[ "$GH_DRAFT" == "false" ]] \
|
|
103
|
+
&& [[ "$GH_URL" == "$EXPECTED_GH_URL" ]] \
|
|
104
|
+
&& [[ -n "$GH_PUBLISHED_AT" ]] \
|
|
105
|
+
&& [[ "$GH_PUBLISHED_AT" != "null" ]]; then
|
|
106
|
+
GH_READY=true
|
|
99
107
|
break
|
|
100
108
|
fi
|
|
101
109
|
if [[ "$attempt" -lt "$RETRY_ATTEMPTS" ]]; then
|
|
102
|
-
echo " INFO: GitHub release not
|
|
110
|
+
echo " INFO: GitHub release not ready (attempt ${attempt}/${RETRY_ATTEMPTS}); retrying in ${RETRY_DELAY_SECONDS}s..."
|
|
103
111
|
sleep "$RETRY_DELAY_SECONDS"
|
|
104
112
|
fi
|
|
105
113
|
done
|
|
106
|
-
if $
|
|
107
|
-
pass "GitHub release v${TARGET_VERSION}
|
|
114
|
+
if $GH_READY; then
|
|
115
|
+
pass "GitHub release v${TARGET_VERSION} is published on the tagged release URL"
|
|
108
116
|
else
|
|
109
|
-
fail "GitHub release v${TARGET_VERSION} not
|
|
117
|
+
fail "GitHub release v${TARGET_VERSION} is not fully published (tag=${GH_TAG:-<missing>} draft=${GH_DRAFT:-<missing>} url=${GH_URL:-<missing>} publishedAt=${GH_PUBLISHED_AT:-<missing>})"
|
|
110
118
|
fi
|
|
111
119
|
fi
|
|
112
120
|
|
package/scripts/sync-homebrew.sh
CHANGED
|
@@ -244,5 +244,18 @@ fi
|
|
|
244
244
|
|
|
245
245
|
echo ""
|
|
246
246
|
echo "====================================="
|
|
247
|
-
echo "SYNC COMPLETE — Homebrew formula updated to ${PACKAGE_NAME}@${TARGET_VERSION}."
|
|
247
|
+
echo "SYNC STEP COMPLETE — Homebrew formula updated to ${PACKAGE_NAME}@${TARGET_VERSION}."
|
|
248
|
+
echo ""
|
|
249
|
+
echo "This is the Phase 2 -> Phase 3 transition step only. It does NOT prove"
|
|
250
|
+
echo "the public npx install path resolves, and it does NOT prove the canonical"
|
|
251
|
+
echo "tap / GitHub Release / repo-mirror downstream truth is consistent."
|
|
252
|
+
echo ""
|
|
253
|
+
echo "Do NOT declare the release complete from this script's exit code alone."
|
|
254
|
+
echo "Complete the release by running ONE of:"
|
|
255
|
+
echo " - bash cli/scripts/verify-post-publish.sh --target-version ${TARGET_VERSION}"
|
|
256
|
+
echo " (manual/operator path; includes npx smoke + repo-mirror SHA proof + full test suite)"
|
|
257
|
+
echo " - bash cli/scripts/release-downstream-truth.sh --target-version ${TARGET_VERSION}"
|
|
258
|
+
echo " (CI-equivalent path; requires release-postflight.sh to have already run the npx smoke)"
|
|
259
|
+
echo ""
|
|
260
|
+
echo "See DEC-VERIFY-POST-PUBLISH-NPX-001 and DEC-HOMEBREW-SYNC-LOOPHOLE-CLOSE-001."
|
|
248
261
|
exit 0
|
|
@@ -20,6 +20,8 @@ cd "$CLI_DIR"
|
|
|
20
20
|
TARGET_VERSION=""
|
|
21
21
|
|
|
22
22
|
FORMULA_PATH="${CLI_DIR}/homebrew/agentxchain.rb"
|
|
23
|
+
PACKAGE_NAME="$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json','utf8')).name)")"
|
|
24
|
+
PACKAGE_BIN_NAME="$(node -e "const pkg = JSON.parse(require('fs').readFileSync('package.json','utf8')); if (typeof pkg.bin === 'string') { console.log(pkg.name); process.exit(0); } const names = Object.keys(pkg.bin || {}); if (names.length !== 1) { console.error('package.json bin must declare exactly one entry'); process.exit(1); } console.log(names[0]);")"
|
|
23
25
|
|
|
24
26
|
formula_url() {
|
|
25
27
|
local formula_path="$1"
|
|
@@ -31,6 +33,29 @@ formula_sha() {
|
|
|
31
33
|
grep -E '^\s*sha256\s+"' "$formula_path" | sed 's/.*sha256 *"\([a-f0-9]*\)".*/\1/' || true
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
run_npx_smoke() {
|
|
37
|
+
local smoke_root
|
|
38
|
+
local smoke_npmrc
|
|
39
|
+
|
|
40
|
+
smoke_root="$(mktemp -d "${TMPDIR:-/tmp}/agentxchain-verify-post-publish.XXXXXX")"
|
|
41
|
+
mkdir -p "${smoke_root}/home" "${smoke_root}/cache" "${smoke_root}/npm-cache" "${smoke_root}/workspace"
|
|
42
|
+
smoke_npmrc="${smoke_root}/.npmrc"
|
|
43
|
+
echo "registry=https://registry.npmjs.org/" > "$smoke_npmrc"
|
|
44
|
+
|
|
45
|
+
(
|
|
46
|
+
cd "${smoke_root}/workspace" || exit 1
|
|
47
|
+
env -u NODE_AUTH_TOKEN \
|
|
48
|
+
HOME="${smoke_root}/home" \
|
|
49
|
+
XDG_CACHE_HOME="${smoke_root}/cache" \
|
|
50
|
+
NPM_CONFIG_CACHE="${smoke_root}/npm-cache" \
|
|
51
|
+
NPM_CONFIG_USERCONFIG="${smoke_npmrc}" \
|
|
52
|
+
npx --yes -p "${PACKAGE_NAME}@${TARGET_VERSION}" -c "${PACKAGE_BIN_NAME} --version"
|
|
53
|
+
)
|
|
54
|
+
local status=$?
|
|
55
|
+
rm -rf "$smoke_root"
|
|
56
|
+
return "$status"
|
|
57
|
+
}
|
|
58
|
+
|
|
34
59
|
while [[ $# -gt 0 ]]; do
|
|
35
60
|
case "$1" in
|
|
36
61
|
--target-version)
|
|
@@ -57,7 +82,7 @@ echo "============================================="
|
|
|
57
82
|
echo ""
|
|
58
83
|
|
|
59
84
|
# Step 1: Verify npm serves the version
|
|
60
|
-
echo "[1/
|
|
85
|
+
echo "[1/5] Checking npm registry..."
|
|
61
86
|
NPM_VERSION="$(npm view "agentxchain@${TARGET_VERSION}" version 2>/dev/null || echo "")"
|
|
62
87
|
if [[ "$NPM_VERSION" != "$TARGET_VERSION" ]]; then
|
|
63
88
|
echo " FAIL: npm does not serve agentxchain@${TARGET_VERSION} (got: '${NPM_VERSION}')"
|
|
@@ -67,7 +92,7 @@ fi
|
|
|
67
92
|
echo " OK: npm serves v${TARGET_VERSION}"
|
|
68
93
|
|
|
69
94
|
# Step 2: Sync the repo mirror to the published tarball
|
|
70
|
-
echo "[2/
|
|
95
|
+
echo "[2/5] Syncing repo mirror to published tarball..."
|
|
71
96
|
bash "${SCRIPT_DIR}/sync-homebrew.sh" --target-version "$TARGET_VERSION"
|
|
72
97
|
echo " OK: repo mirror synced"
|
|
73
98
|
|
|
@@ -109,8 +134,34 @@ if [[ "$FORMULA_SHA" != "$TARBALL_SHA" ]]; then
|
|
|
109
134
|
fi
|
|
110
135
|
echo " OK: repo mirror formula SHA256 matches registry tarball"
|
|
111
136
|
|
|
112
|
-
# Step 4:
|
|
113
|
-
echo "[4/5]
|
|
137
|
+
# Step 4: Recheck the public npx path against the published registry version
|
|
138
|
+
echo "[4/5] Verifying public npx install path..."
|
|
139
|
+
NPX_OUTPUT="$(run_npx_smoke 2>&1 || true)"
|
|
140
|
+
NPX_VERSION="$(printf '%s\n' "$NPX_OUTPUT" | awk -v expected="${TARGET_VERSION}" '
|
|
141
|
+
{
|
|
142
|
+
line=$0
|
|
143
|
+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
|
|
144
|
+
if (line == expected) {
|
|
145
|
+
print line
|
|
146
|
+
found=1
|
|
147
|
+
exit
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
END {
|
|
151
|
+
if (!found) {
|
|
152
|
+
exit 1
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
' || true)"
|
|
156
|
+
if [[ "$NPX_VERSION" != "$TARGET_VERSION" ]]; then
|
|
157
|
+
echo " FAIL: public npx path did not report ${TARGET_VERSION}"
|
|
158
|
+
printf '%s\n' "$NPX_OUTPUT"
|
|
159
|
+
exit 1
|
|
160
|
+
fi
|
|
161
|
+
echo " OK: public npx path resolves and reports v${TARGET_VERSION}"
|
|
162
|
+
|
|
163
|
+
# Step 5: Run the full test suite WITHOUT the preflight skip
|
|
164
|
+
echo "[5/5] Running full test suite (no preflight skip)..."
|
|
114
165
|
echo " This verifies the broader Homebrew mirror contract passes with the real SHA."
|
|
115
166
|
npm test
|
|
116
167
|
echo " OK: full test suite green"
|
package/src/commands/init.js
CHANGED
|
@@ -103,6 +103,68 @@ const DEFAULT_GOVERNED_LOCAL_DEV_RUNTIME = Object.freeze({
|
|
|
103
103
|
prompt_transport: 'stdin',
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
+
const GOVERNED_GITIGNORE_LINES = Object.freeze([
|
|
107
|
+
'.env',
|
|
108
|
+
'.agentxchain/staging/',
|
|
109
|
+
'.agentxchain/dispatch/',
|
|
110
|
+
'.agentxchain/transactions/',
|
|
111
|
+
'.agentxchain/state.json',
|
|
112
|
+
'.agentxchain/session.json',
|
|
113
|
+
'.agentxchain/history.jsonl',
|
|
114
|
+
'.agentxchain/decision-ledger.jsonl',
|
|
115
|
+
'.agentxchain/repo-decisions.jsonl',
|
|
116
|
+
'.agentxchain/lock.json',
|
|
117
|
+
'.agentxchain/hook-audit.jsonl',
|
|
118
|
+
'.agentxchain/hook-annotations.jsonl',
|
|
119
|
+
'.agentxchain/run-history.jsonl',
|
|
120
|
+
'.agentxchain/events.jsonl',
|
|
121
|
+
'.agentxchain/notification-audit.jsonl',
|
|
122
|
+
'.agentxchain/schedule-state.json',
|
|
123
|
+
'.agentxchain/schedule-daemon.json',
|
|
124
|
+
'.agentxchain/continuous-session.json',
|
|
125
|
+
'.agentxchain/human-escalations.jsonl',
|
|
126
|
+
'.agentxchain/sla-reminders.json',
|
|
127
|
+
'.agentxchain/SESSION_RECOVERY.md',
|
|
128
|
+
'.agentxchain/migration-report.md',
|
|
129
|
+
'.agentxchain/intake/',
|
|
130
|
+
'.agentxchain/missions/',
|
|
131
|
+
'.agentxchain/multirepo/',
|
|
132
|
+
'.agentxchain/reviews/',
|
|
133
|
+
'.agentxchain/reports/',
|
|
134
|
+
'.agentxchain/proposed/',
|
|
135
|
+
'TALK.md',
|
|
136
|
+
'HUMAN_TASKS.md',
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
const GOVERNED_GITIGNORE_CONTENT = [
|
|
140
|
+
'# AgentXchain — secrets',
|
|
141
|
+
'.env',
|
|
142
|
+
'',
|
|
143
|
+
'# AgentXchain — transient execution artifacts (never commit)',
|
|
144
|
+
'.agentxchain/staging/',
|
|
145
|
+
'.agentxchain/dispatch/',
|
|
146
|
+
'.agentxchain/transactions/',
|
|
147
|
+
'',
|
|
148
|
+
'# AgentXchain — framework-owned state (gitignored by default in fresh scaffolds)',
|
|
149
|
+
'# These files remain durable on disk and in export/restore, but defaulting them',
|
|
150
|
+
'# out of raw `git status` reduces operator noise. Existing tracked copies still',
|
|
151
|
+
'# appear dirty until the repo explicitly untracks them.',
|
|
152
|
+
...GOVERNED_GITIGNORE_LINES.slice(4),
|
|
153
|
+
].join('\n') + '\n';
|
|
154
|
+
|
|
155
|
+
function ensureGitignoreEntries(gitignorePath, content, requiredEntries) {
|
|
156
|
+
if (!existsSync(gitignorePath)) {
|
|
157
|
+
writeFileSync(gitignorePath, content);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const existingIgnore = readFileSync(gitignorePath, 'utf8');
|
|
161
|
+
const existingLines = new Set(existingIgnore.split(/\r?\n/));
|
|
162
|
+
const missing = requiredEntries.filter(entry => !existingLines.has(entry));
|
|
163
|
+
if (missing.length === 0) return;
|
|
164
|
+
const prefix = existingIgnore.endsWith('\n') || existingIgnore.length === 0 ? '' : '\n';
|
|
165
|
+
writeFileSync(gitignorePath, existingIgnore + prefix + missing.join('\n') + '\n');
|
|
166
|
+
}
|
|
167
|
+
|
|
106
168
|
const GOVERNED_RUNTIMES = {
|
|
107
169
|
'manual-pm': { type: 'manual' },
|
|
108
170
|
'manual-dev': { type: 'manual' },
|
|
@@ -833,28 +895,10 @@ export function scaffoldGoverned(dir, projectName, projectId, templateId = 'gene
|
|
|
833
895
|
// TALK.md
|
|
834
896
|
writeFileSync(join(dir, 'TALK.md'), `# ${projectName} — Team Talk File\n\nCanonical human-readable handoff log for all agents.\n\n---\n\n`);
|
|
835
897
|
|
|
836
|
-
// .gitignore additions with inline comments so
|
|
898
|
+
// .gitignore additions with inline comments so fresh governed repos keep
|
|
899
|
+
// framework-owned runtime state out of raw git status by default.
|
|
837
900
|
const gitignorePath = join(dir, '.gitignore');
|
|
838
|
-
|
|
839
|
-
'# AgentXchain — secrets',
|
|
840
|
-
'.env',
|
|
841
|
-
'',
|
|
842
|
-
'# AgentXchain — transient execution artifacts (never commit)',
|
|
843
|
-
'.agentxchain/staging/',
|
|
844
|
-
'.agentxchain/dispatch/',
|
|
845
|
-
'.agentxchain/transactions/',
|
|
846
|
-
].join('\n') + '\n';
|
|
847
|
-
const requiredPaths = ['.env', '.agentxchain/staging/', '.agentxchain/dispatch/', '.agentxchain/transactions/'];
|
|
848
|
-
if (!existsSync(gitignorePath)) {
|
|
849
|
-
writeFileSync(gitignorePath, gitignoreContent);
|
|
850
|
-
} else {
|
|
851
|
-
const existingIgnore = readFileSync(gitignorePath, 'utf8');
|
|
852
|
-
const missing = requiredPaths.filter(entry => !existingIgnore.split(/\r?\n/).includes(entry));
|
|
853
|
-
if (missing.length > 0) {
|
|
854
|
-
const prefix = existingIgnore.endsWith('\n') ? '' : '\n';
|
|
855
|
-
writeFileSync(gitignorePath, existingIgnore + prefix + missing.join('\n') + '\n');
|
|
856
|
-
}
|
|
857
|
-
}
|
|
901
|
+
ensureGitignoreEntries(gitignorePath, GOVERNED_GITIGNORE_CONTENT, GOVERNED_GITIGNORE_LINES);
|
|
858
902
|
|
|
859
903
|
return { config, state, scaffoldWorkflowKitConfig };
|
|
860
904
|
}
|
|
@@ -1251,16 +1295,7 @@ export async function initCommand(opts) {
|
|
|
1251
1295
|
writeFileSync(join(dir, 'HUMAN_TASKS.md'), '# Human Tasks\n\n(Agents append tasks here when they need human action.)\n');
|
|
1252
1296
|
const gitignorePath = join(dir, '.gitignore');
|
|
1253
1297
|
const requiredIgnores = ['.env', '.agentxchain-trigger.json', '.agentxchain-prompts/', '.agentxchain-workspaces/', '.agentxchain-watch.pid', '.agentxchain-autonudge.state'];
|
|
1254
|
-
|
|
1255
|
-
writeFileSync(gitignorePath, requiredIgnores.join('\n') + '\n');
|
|
1256
|
-
} else {
|
|
1257
|
-
const existingIgnore = readFileSync(gitignorePath, 'utf8');
|
|
1258
|
-
const missing = requiredIgnores.filter(entry => !existingIgnore.split(/\r?\n/).includes(entry));
|
|
1259
|
-
if (missing.length > 0) {
|
|
1260
|
-
const prefix = existingIgnore.endsWith('\n') ? '' : '\n';
|
|
1261
|
-
writeFileSync(gitignorePath, existingIgnore + prefix + missing.join('\n') + '\n');
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1298
|
+
ensureGitignoreEntries(gitignorePath, requiredIgnores.join('\n') + '\n', requiredIgnores);
|
|
1264
1299
|
|
|
1265
1300
|
// .planning/ structure
|
|
1266
1301
|
mkdirSync(join(dir, '.planning', 'research'), { recursive: true });
|
|
@@ -18,8 +18,10 @@ import {
|
|
|
18
18
|
getActiveTurns,
|
|
19
19
|
getActiveTurn,
|
|
20
20
|
reissueTurn,
|
|
21
|
+
transitionActiveTurnLifecycle,
|
|
21
22
|
} from '../lib/governed-state.js';
|
|
22
23
|
import { writeDispatchBundle } from '../lib/dispatch-bundle.js';
|
|
24
|
+
import { finalizeDispatchManifest } from '../lib/dispatch-manifest.js';
|
|
23
25
|
|
|
24
26
|
export async function reissueTurnCommand(opts) {
|
|
25
27
|
const context = loadProjectContext();
|
|
@@ -91,6 +93,20 @@ export async function reissueTurnCommand(opts) {
|
|
|
91
93
|
console.log(chalk.red(`Turn reissued but dispatch bundle failed: ${bundleResult.error}`));
|
|
92
94
|
process.exit(1);
|
|
93
95
|
}
|
|
96
|
+
// BUG-51 follow-up: every command that writes a dispatch bundle for an
|
|
97
|
+
// active turn must finalize the manifest so adapter-side
|
|
98
|
+
// `verifyDispatchManifestForAdapter` enforcement matches fresh dispatches.
|
|
99
|
+
// Reissue does not run after_dispatch hooks, so finalization happens
|
|
100
|
+
// immediately after writeDispatchBundle.
|
|
101
|
+
const manifestResult = finalizeDispatchManifest(root, result.newTurn.turn_id, {
|
|
102
|
+
run_id: result.state.run_id,
|
|
103
|
+
role: result.newTurn.assigned_role,
|
|
104
|
+
});
|
|
105
|
+
if (!manifestResult.ok) {
|
|
106
|
+
console.log(chalk.red(`Turn reissued but dispatch manifest failed: ${manifestResult.error}`));
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
transitionActiveTurnLifecycle(root, result.newTurn.turn_id, 'dispatched');
|
|
94
110
|
|
|
95
111
|
// Print summary
|
|
96
112
|
console.log('');
|
|
@@ -2,9 +2,10 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { loadProjectContext, loadProjectState } from '../lib/config.js';
|
|
5
|
-
import { getActiveTurns, rejectGovernedTurn } from '../lib/governed-state.js';
|
|
5
|
+
import { getActiveTurns, rejectGovernedTurn, transitionActiveTurnLifecycle } from '../lib/governed-state.js';
|
|
6
6
|
import { validateStagedTurnResult } from '../lib/turn-result-validator.js';
|
|
7
7
|
import { writeDispatchBundle } from '../lib/dispatch-bundle.js';
|
|
8
|
+
import { finalizeDispatchManifest } from '../lib/dispatch-manifest.js';
|
|
8
9
|
import { deriveRecoveryDescriptor } from '../lib/blocked-state.js';
|
|
9
10
|
import { getDispatchTurnDir, getTurnStagingResultPath } from '../lib/turn-paths.js';
|
|
10
11
|
|
|
@@ -51,6 +52,18 @@ export async function rejectTurnCommand(opts) {
|
|
|
51
52
|
console.log(chalk.red(`Turn rejected but dispatch bundle rewrite failed: ${bundleResult.error}`));
|
|
52
53
|
process.exit(1);
|
|
53
54
|
}
|
|
55
|
+
// BUG-51 follow-up: finalize the manifest so adapter verification matches
|
|
56
|
+
// fresh dispatches. reject-for-retry does not run after_dispatch hooks,
|
|
57
|
+
// so finalize immediately after writeDispatchBundle.
|
|
58
|
+
const manifestResult = finalizeDispatchManifest(root, validation.turn.turn_id, {
|
|
59
|
+
run_id: result.state.run_id,
|
|
60
|
+
role: validation.turn.assigned_role,
|
|
61
|
+
});
|
|
62
|
+
if (!manifestResult.ok) {
|
|
63
|
+
console.log(chalk.red(`Turn rejected but dispatch manifest failed: ${manifestResult.error}`));
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
transitionActiveTurnLifecycle(root, validation.turn.turn_id, 'dispatched');
|
|
54
67
|
printDispatchBundleWarnings(bundleResult);
|
|
55
68
|
}
|
|
56
69
|
|
package/src/commands/restart.js
CHANGED
|
@@ -13,21 +13,25 @@
|
|
|
13
13
|
import chalk from 'chalk';
|
|
14
14
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
15
15
|
import { join, dirname } from 'path';
|
|
16
|
-
import { loadProjectContext } from '../lib/config.js';
|
|
16
|
+
import { loadProjectContext, loadProjectState } from '../lib/config.js';
|
|
17
17
|
import {
|
|
18
18
|
assignGovernedTurn,
|
|
19
19
|
getActiveTurns,
|
|
20
20
|
getActiveTurnCount,
|
|
21
21
|
reactivateGovernedRun,
|
|
22
22
|
detectStateBundleDesync,
|
|
23
|
+
normalizeGovernedStateShape,
|
|
24
|
+
reconcileApprovalPausesWithConfig,
|
|
25
|
+
reconcileRecoveryActionsWithConfig,
|
|
26
|
+
transitionActiveTurnLifecycle,
|
|
23
27
|
STATE_PATH,
|
|
24
28
|
HISTORY_PATH,
|
|
25
29
|
LEDGER_PATH,
|
|
26
30
|
} from '../lib/governed-state.js';
|
|
27
31
|
import { writeDispatchBundle } from '../lib/dispatch-bundle.js';
|
|
32
|
+
import { finalizeDispatchManifest } from '../lib/dispatch-manifest.js';
|
|
28
33
|
import { getDispatchTurnDir } from '../lib/turn-paths.js';
|
|
29
34
|
import { consumeNextApprovedIntent } from '../lib/intake.js';
|
|
30
|
-
import { loadProjectState } from '../lib/config.js';
|
|
31
35
|
import { deriveRecoveryDescriptor } from '../lib/blocked-state.js';
|
|
32
36
|
import { deriveRecommendedContinuityAction } from '../lib/continuity-status.js';
|
|
33
37
|
import { readSessionCheckpoint, writeSessionCheckpoint, captureBaselineRef, SESSION_PATH } from '../lib/session-checkpoint.js';
|
|
@@ -178,7 +182,20 @@ export async function restartCommand(opts) {
|
|
|
178
182
|
process.exit(1);
|
|
179
183
|
}
|
|
180
184
|
|
|
181
|
-
|
|
185
|
+
let state;
|
|
186
|
+
try {
|
|
187
|
+
const parsed = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
188
|
+
const normalized = normalizeGovernedStateShape(parsed);
|
|
189
|
+
const reconciledApprovals = reconcileApprovalPausesWithConfig(normalized.state, config);
|
|
190
|
+
const reconciledRecovery = reconcileRecoveryActionsWithConfig(reconciledApprovals.state, config);
|
|
191
|
+
state = reconciledRecovery.state;
|
|
192
|
+
if (normalized.changed || reconciledApprovals.changed || reconciledRecovery.changed) {
|
|
193
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
194
|
+
}
|
|
195
|
+
} catch {
|
|
196
|
+
console.log(chalk.red('No valid governed state.json found.'));
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
182
199
|
|
|
183
200
|
// Load checkpoint (optional — restart can work without it, just with less context)
|
|
184
201
|
const checkpoint = readSessionCheckpoint(root);
|
|
@@ -389,6 +406,19 @@ export async function restartCommand(opts) {
|
|
|
389
406
|
console.log(chalk.dim('Run `agentxchain reissue-turn` to reissue with a fresh bundle.'));
|
|
390
407
|
process.exit(1);
|
|
391
408
|
}
|
|
409
|
+
// BUG-51 follow-up: finalize the dispatch manifest so adapter-side
|
|
410
|
+
// verification matches fresh dispatches via `run`/`step`/`resume`.
|
|
411
|
+
// restart does not run after_dispatch hooks here, so finalize
|
|
412
|
+
// immediately after writeDispatchBundle.
|
|
413
|
+
const manifestResult = finalizeDispatchManifest(root, turnId, {
|
|
414
|
+
run_id: assignedState.run_id,
|
|
415
|
+
role: assignedRole,
|
|
416
|
+
});
|
|
417
|
+
if (!manifestResult.ok) {
|
|
418
|
+
console.log(chalk.red(`Turn assigned but dispatch manifest failed: ${manifestResult.error}`));
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
transitionActiveTurnLifecycle(root, turnId, 'dispatched');
|
|
392
422
|
for (const bw of bundleResult.warnings || []) {
|
|
393
423
|
console.log(chalk.yellow(`Dispatch bundle warning: ${bw}`));
|
|
394
424
|
}
|