agentxchain 2.12.0 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ Legacy IDE-window coordination is still shipped as a compatibility mode for team
|
|
|
14
14
|
- [Adapter reference](https://agentxchain.dev/docs/adapters/)
|
|
15
15
|
- [Protocol spec (v6)](https://agentxchain.dev/docs/protocol/)
|
|
16
16
|
- [Protocol reference](https://agentxchain.dev/docs/protocol-reference/)
|
|
17
|
+
- [Build your own runner](https://agentxchain.dev/docs/build-your-own-runner/)
|
|
17
18
|
- [Why governed multi-agent delivery matters](https://agentxchain.dev/why/)
|
|
18
19
|
|
|
19
20
|
## Install
|
|
@@ -91,6 +92,19 @@ agentxchain approve-completion
|
|
|
91
92
|
|
|
92
93
|
Default governed scaffolding configures QA as `api_proxy` with `ANTHROPIC_API_KEY`. For a provider-free walkthrough, switch the QA runtime to `manual` before the QA step. If you override the dev runtime, either include `{prompt}` for argv delivery or set `--dev-prompt-transport` explicitly.
|
|
93
94
|
|
|
95
|
+
### Multi-repo coordination
|
|
96
|
+
|
|
97
|
+
For initiatives spanning multiple governed repos, use the coordinator to add cross-repo sequencing and shared gates:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npx agentxchain init --governed --template api-service --dir repos/backend -y
|
|
101
|
+
npx agentxchain init --governed --template web-app --dir repos/frontend -y
|
|
102
|
+
agentxchain multi init
|
|
103
|
+
agentxchain multi step --repo backend --role pm
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
See the [multi-repo quickstart](https://agentxchain.dev/docs/quickstart#multi-repo-cold-start) for the full cold-start walkthrough.
|
|
107
|
+
|
|
94
108
|
### Migrate a legacy project
|
|
95
109
|
|
|
96
110
|
```bash
|
package/package.json
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentxchain",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.0",
|
|
4
4
|
"description": "CLI for AgentXchain — governed multi-agent software delivery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"agentxchain": "./bin/agentxchain.js"
|
|
8
8
|
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./bin/agentxchain.js",
|
|
11
|
+
"./runner-interface": "./src/lib/runner-interface.js",
|
|
12
|
+
"./run-loop": "./src/lib/run-loop.js"
|
|
13
|
+
},
|
|
9
14
|
"files": [
|
|
10
15
|
"bin/",
|
|
11
16
|
"src/",
|
|
@@ -21,6 +26,7 @@
|
|
|
21
26
|
"preflight:release": "bash scripts/release-preflight.sh",
|
|
22
27
|
"preflight:release:strict": "bash scripts/release-preflight.sh --strict",
|
|
23
28
|
"postflight:release": "bash scripts/release-postflight.sh",
|
|
29
|
+
"postflight:downstream": "bash scripts/release-downstream-truth.sh",
|
|
24
30
|
"build:macos": "bun build bin/agentxchain.js --compile --target=bun-darwin-arm64 --outfile=dist/agentxchain-macos-arm64",
|
|
25
31
|
"build:linux": "bun build bin/agentxchain.js --compile --target=bun-linux-x64 --outfile=dist/agentxchain-linux-x64",
|
|
26
32
|
"publish:npm": "bash scripts/publish-npm.sh"
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Release downstream truth — run after all downstream surfaces are updated.
|
|
3
|
+
# Verifies: GitHub release exists, Homebrew tap SHA and URL match registry tarball.
|
|
4
|
+
# Usage: bash scripts/release-downstream-truth.sh --target-version <semver>
|
|
5
|
+
set -uo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
CLI_DIR="${SCRIPT_DIR}/.."
|
|
9
|
+
REPO_ROOT="${CLI_DIR}/.."
|
|
10
|
+
cd "$CLI_DIR"
|
|
11
|
+
|
|
12
|
+
TARGET_VERSION=""
|
|
13
|
+
RETRY_ATTEMPTS="${RELEASE_DOWNSTREAM_RETRY_ATTEMPTS:-3}"
|
|
14
|
+
RETRY_DELAY_SECONDS="${RELEASE_DOWNSTREAM_RETRY_DELAY_SECONDS:-5}"
|
|
15
|
+
|
|
16
|
+
usage() {
|
|
17
|
+
echo "Usage: bash scripts/release-downstream-truth.sh --target-version <semver>" >&2
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
while [[ $# -gt 0 ]]; do
|
|
21
|
+
case "$1" in
|
|
22
|
+
--target-version)
|
|
23
|
+
if [[ -z "${2:-}" ]]; then
|
|
24
|
+
echo "Error: --target-version requires a semver argument" >&2
|
|
25
|
+
usage
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
if ! [[ "$2" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
29
|
+
echo "Invalid semver: $2" >&2
|
|
30
|
+
usage
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
TARGET_VERSION="$2"
|
|
34
|
+
shift 2
|
|
35
|
+
;;
|
|
36
|
+
*)
|
|
37
|
+
usage
|
|
38
|
+
exit 1
|
|
39
|
+
;;
|
|
40
|
+
esac
|
|
41
|
+
done
|
|
42
|
+
|
|
43
|
+
if [[ -z "$TARGET_VERSION" ]]; then
|
|
44
|
+
echo "Error: --target-version is required" >&2
|
|
45
|
+
usage
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
PACKAGE_NAME="$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json', 'utf8')).name)")"
|
|
50
|
+
HOMEBREW_FORMULA="${REPO_ROOT}/cli/homebrew/agentxchain.rb"
|
|
51
|
+
|
|
52
|
+
PASS=0
|
|
53
|
+
FAIL=0
|
|
54
|
+
|
|
55
|
+
pass() { PASS=$((PASS + 1)); echo " PASS: $1"; }
|
|
56
|
+
fail() { FAIL=$((FAIL + 1)); echo " FAIL: $1"; }
|
|
57
|
+
|
|
58
|
+
echo "AgentXchain v${TARGET_VERSION} Downstream Release Truth"
|
|
59
|
+
echo "====================================="
|
|
60
|
+
echo "Checks downstream surfaces after publish: GitHub release, Homebrew tap."
|
|
61
|
+
echo ""
|
|
62
|
+
|
|
63
|
+
# --- Check 1: GitHub Release ---
|
|
64
|
+
echo "[1/3] GitHub release"
|
|
65
|
+
if ! command -v gh >/dev/null 2>&1; then
|
|
66
|
+
fail "gh CLI not available — cannot verify GitHub release"
|
|
67
|
+
else
|
|
68
|
+
GH_FOUND=false
|
|
69
|
+
for attempt in $(seq 1 "$RETRY_ATTEMPTS"); do
|
|
70
|
+
GH_TAG="$(gh release view "v${TARGET_VERSION}" --json tagName -q '.tagName' 2>/dev/null || true)"
|
|
71
|
+
if [[ "$GH_TAG" == "v${TARGET_VERSION}" ]]; then
|
|
72
|
+
GH_FOUND=true
|
|
73
|
+
break
|
|
74
|
+
fi
|
|
75
|
+
if [[ "$attempt" -lt "$RETRY_ATTEMPTS" ]]; then
|
|
76
|
+
echo " INFO: GitHub release not found (attempt ${attempt}/${RETRY_ATTEMPTS}); retrying in ${RETRY_DELAY_SECONDS}s..."
|
|
77
|
+
sleep "$RETRY_DELAY_SECONDS"
|
|
78
|
+
fi
|
|
79
|
+
done
|
|
80
|
+
if $GH_FOUND; then
|
|
81
|
+
pass "GitHub release v${TARGET_VERSION} exists"
|
|
82
|
+
else
|
|
83
|
+
fail "GitHub release v${TARGET_VERSION} not found after ${RETRY_ATTEMPTS} attempts"
|
|
84
|
+
fi
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# --- Get registry tarball URL and compute SHA ---
|
|
88
|
+
echo "[2/3] Homebrew tap SHA matches registry tarball"
|
|
89
|
+
REGISTRY_TARBALL_URL="$(npm view "${PACKAGE_NAME}@${TARGET_VERSION}" dist.tarball 2>/dev/null || true)"
|
|
90
|
+
if [[ -z "$REGISTRY_TARBALL_URL" ]]; then
|
|
91
|
+
fail "cannot fetch registry tarball URL for ${PACKAGE_NAME}@${TARGET_VERSION}"
|
|
92
|
+
else
|
|
93
|
+
REGISTRY_SHA="$(curl -sL "$REGISTRY_TARBALL_URL" | shasum -a 256 | awk '{print $1}')"
|
|
94
|
+
if [[ -z "$REGISTRY_SHA" ]]; then
|
|
95
|
+
fail "cannot compute SHA256 of registry tarball"
|
|
96
|
+
elif [[ ! -f "$HOMEBREW_FORMULA" ]]; then
|
|
97
|
+
fail "Homebrew formula not found at ${HOMEBREW_FORMULA}"
|
|
98
|
+
else
|
|
99
|
+
FORMULA_SHA="$(grep -E '^\s*sha256\s+"' "$HOMEBREW_FORMULA" | sed 's/.*sha256 *"\([a-f0-9]*\)".*/\1/')"
|
|
100
|
+
if [[ "$REGISTRY_SHA" == "$FORMULA_SHA" ]]; then
|
|
101
|
+
pass "Homebrew formula SHA256 matches registry tarball (${REGISTRY_SHA:0:16}...)"
|
|
102
|
+
else
|
|
103
|
+
fail "Homebrew formula SHA256 mismatch: formula=${FORMULA_SHA:0:16}... registry=${REGISTRY_SHA:0:16}..."
|
|
104
|
+
fi
|
|
105
|
+
fi
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# --- Check 3: Homebrew tap URL matches registry tarball URL ---
|
|
109
|
+
echo "[3/3] Homebrew tap URL matches registry tarball"
|
|
110
|
+
if [[ -z "$REGISTRY_TARBALL_URL" ]]; then
|
|
111
|
+
fail "cannot verify URL — registry tarball URL unavailable"
|
|
112
|
+
elif [[ ! -f "$HOMEBREW_FORMULA" ]]; then
|
|
113
|
+
fail "Homebrew formula not found at ${HOMEBREW_FORMULA}"
|
|
114
|
+
else
|
|
115
|
+
FORMULA_URL="$(grep -E '^\s*url\s+"' "$HOMEBREW_FORMULA" | sed 's/.*url *"\([^"]*\)".*/\1/')"
|
|
116
|
+
if [[ "$FORMULA_URL" == "$REGISTRY_TARBALL_URL" ]]; then
|
|
117
|
+
pass "Homebrew formula URL matches registry tarball"
|
|
118
|
+
else
|
|
119
|
+
fail "Homebrew formula URL mismatch: formula=${FORMULA_URL} registry=${REGISTRY_TARBALL_URL}"
|
|
120
|
+
fi
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
echo ""
|
|
124
|
+
echo "====================================="
|
|
125
|
+
echo "Results: ${PASS} passed, ${FAIL} failed"
|
|
126
|
+
if [ "$FAIL" -gt 0 ]; then
|
|
127
|
+
echo "DOWNSTREAM TRUTH FAILED — at least one downstream surface is inconsistent."
|
|
128
|
+
exit 1
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
echo "DOWNSTREAM TRUTH PASSED — all downstream surfaces are consistent."
|
|
132
|
+
exit 0
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
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 can execute its CLI entrypoint, and runner package exports
|
|
5
|
+
# are importable in a clean consumer project.
|
|
5
6
|
# Usage: bash scripts/release-postflight.sh --target-version <semver> [--tag vX.Y.Z]
|
|
6
7
|
set -uo pipefail
|
|
7
8
|
|
|
@@ -15,7 +16,7 @@ RETRY_ATTEMPTS="${RELEASE_POSTFLIGHT_RETRY_ATTEMPTS:-12}"
|
|
|
15
16
|
RETRY_DELAY_SECONDS="${RELEASE_POSTFLIGHT_RETRY_DELAY_SECONDS:-10}"
|
|
16
17
|
|
|
17
18
|
usage() {
|
|
18
|
-
|
|
19
|
+
echo "Usage: bash scripts/release-postflight.sh --target-version <semver> [--tag vX.Y.Z]" >&2
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
while [[ $# -gt 0 ]]; do
|
|
@@ -76,6 +77,7 @@ TARBALL_URL=""
|
|
|
76
77
|
REGISTRY_CHECKSUM=""
|
|
77
78
|
PACKAGE_NAME="$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json', 'utf8')).name)")"
|
|
78
79
|
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]);")"
|
|
80
|
+
RUNNER_INTERFACE_VERSION_EXPECTED="$(node --input-type=module -e "import('./src/lib/runner-interface.js').then((mod) => { console.log(mod.RUNNER_INTERFACE_VERSION); }).catch((error) => { console.error(error.message); process.exit(1); });")"
|
|
79
81
|
|
|
80
82
|
pass() { PASS=$((PASS + 1)); echo " PASS: $1"; }
|
|
81
83
|
fail() { FAIL=$((FAIL + 1)); echo " FAIL: $1"; }
|
|
@@ -136,6 +138,67 @@ run_install_smoke() {
|
|
|
136
138
|
return "$version_status"
|
|
137
139
|
}
|
|
138
140
|
|
|
141
|
+
run_runner_export_smoke() {
|
|
142
|
+
if [[ -z "$TARBALL_URL" ]]; then
|
|
143
|
+
echo "registry tarball metadata unavailable for runner export smoke" >&2
|
|
144
|
+
return 1
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
local smoke_root
|
|
148
|
+
local consumer_root
|
|
149
|
+
local smoke_npmrc
|
|
150
|
+
local install_status
|
|
151
|
+
local node_status
|
|
152
|
+
|
|
153
|
+
smoke_root="$(mktemp -d "${TMPDIR:-/tmp}/agentxchain-runner-postflight.XXXXXX")"
|
|
154
|
+
consumer_root="${smoke_root}/consumer"
|
|
155
|
+
mkdir -p "$consumer_root"
|
|
156
|
+
|
|
157
|
+
smoke_npmrc="${smoke_root}/.npmrc"
|
|
158
|
+
echo "registry=https://registry.npmjs.org/" > "$smoke_npmrc"
|
|
159
|
+
|
|
160
|
+
cat > "${consumer_root}/package.json" <<'EOF'
|
|
161
|
+
{
|
|
162
|
+
"name": "agentxchain-runner-postflight-smoke",
|
|
163
|
+
"private": true,
|
|
164
|
+
"type": "module"
|
|
165
|
+
}
|
|
166
|
+
EOF
|
|
167
|
+
|
|
168
|
+
(
|
|
169
|
+
cd "$consumer_root" || exit 1
|
|
170
|
+
env -u NODE_AUTH_TOKEN NPM_CONFIG_USERCONFIG="$smoke_npmrc" \
|
|
171
|
+
npm install "$TARBALL_URL" >/dev/null 2>&1
|
|
172
|
+
)
|
|
173
|
+
install_status=$?
|
|
174
|
+
if [[ "$install_status" -ne 0 ]]; then
|
|
175
|
+
rm -rf "$smoke_root"
|
|
176
|
+
return "$install_status"
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
cat > "${consumer_root}/runner-export-smoke.mjs" <<'EOF'
|
|
180
|
+
import { RUNNER_INTERFACE_VERSION, loadContext } from 'agentxchain/runner-interface';
|
|
181
|
+
import { runLoop } from 'agentxchain/run-loop';
|
|
182
|
+
|
|
183
|
+
if (typeof loadContext !== 'function') {
|
|
184
|
+
throw new Error('loadContext export missing');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (typeof runLoop !== 'function') {
|
|
188
|
+
throw new Error('runLoop export missing');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log(RUNNER_INTERFACE_VERSION);
|
|
192
|
+
EOF
|
|
193
|
+
|
|
194
|
+
local runner_output
|
|
195
|
+
runner_output="$(cd "$consumer_root" && node runner-export-smoke.mjs 2>&1)"
|
|
196
|
+
node_status=$?
|
|
197
|
+
printf '%s\n' "$runner_output"
|
|
198
|
+
rm -rf "$smoke_root"
|
|
199
|
+
return "$node_status"
|
|
200
|
+
}
|
|
201
|
+
|
|
139
202
|
run_with_retry() {
|
|
140
203
|
local __output_var="$1"
|
|
141
204
|
local description="$2"
|
|
@@ -189,17 +252,17 @@ run_with_retry() {
|
|
|
189
252
|
|
|
190
253
|
echo "AgentXchain v${TARGET_VERSION} Release Postflight"
|
|
191
254
|
echo "====================================="
|
|
192
|
-
echo "Checks release truth after publish: tag, registry visibility, metadata,
|
|
255
|
+
echo "Checks release truth after publish: tag, registry visibility, metadata, CLI install smoke, and runner export smoke."
|
|
193
256
|
echo ""
|
|
194
257
|
|
|
195
|
-
echo "[1/
|
|
258
|
+
echo "[1/6] Git tag"
|
|
196
259
|
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null 2>&1; then
|
|
197
260
|
pass "Git tag ${TAG} exists locally"
|
|
198
261
|
else
|
|
199
262
|
fail "Git tag ${TAG} is missing locally"
|
|
200
263
|
fi
|
|
201
264
|
|
|
202
|
-
echo "[2/
|
|
265
|
+
echo "[2/6] Registry version"
|
|
203
266
|
if run_with_retry VERSION_OUTPUT "registry version" equals "${TARGET_VERSION}" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" version; then
|
|
204
267
|
PUBLISHED_VERSION="$(trim_last_line "$VERSION_OUTPUT")"
|
|
205
268
|
if [[ "$PUBLISHED_VERSION" == "$TARGET_VERSION" ]]; then
|
|
@@ -212,7 +275,7 @@ else
|
|
|
212
275
|
printf '%s\n' "$VERSION_OUTPUT" | tail -20
|
|
213
276
|
fi
|
|
214
277
|
|
|
215
|
-
echo "[3/
|
|
278
|
+
echo "[3/6] Registry tarball metadata"
|
|
216
279
|
if run_with_retry TARBALL_OUTPUT "registry tarball metadata" nonempty "" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" dist.tarball; then
|
|
217
280
|
TARBALL_URL="$(trim_last_line "$TARBALL_OUTPUT")"
|
|
218
281
|
if [[ -n "$TARBALL_URL" ]]; then
|
|
@@ -225,7 +288,7 @@ else
|
|
|
225
288
|
printf '%s\n' "$TARBALL_OUTPUT" | tail -20
|
|
226
289
|
fi
|
|
227
290
|
|
|
228
|
-
echo "[4/
|
|
291
|
+
echo "[4/6] Registry checksum metadata"
|
|
229
292
|
if run_with_retry INTEGRITY_OUTPUT "registry checksum metadata" nonempty "" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" dist.integrity; then
|
|
230
293
|
REGISTRY_CHECKSUM="$(trim_last_line "$INTEGRITY_OUTPUT")"
|
|
231
294
|
fi
|
|
@@ -240,7 +303,7 @@ else
|
|
|
240
303
|
fail "registry did not return checksum metadata"
|
|
241
304
|
fi
|
|
242
305
|
|
|
243
|
-
echo "[5/
|
|
306
|
+
echo "[5/6] Install smoke"
|
|
244
307
|
if run_with_retry EXEC_OUTPUT "install smoke" nonempty "" run_install_smoke; then
|
|
245
308
|
EXEC_VERSION="$(trim_last_line "$EXEC_OUTPUT")"
|
|
246
309
|
if [[ "$EXEC_VERSION" == "$TARGET_VERSION" ]]; then
|
|
@@ -253,6 +316,19 @@ else
|
|
|
253
316
|
printf '%s\n' "$EXEC_OUTPUT" | tail -20
|
|
254
317
|
fi
|
|
255
318
|
|
|
319
|
+
echo "[6/6] Runner export smoke"
|
|
320
|
+
if run_with_retry RUNNER_EXPORT_OUTPUT "runner export smoke" nonempty "" run_runner_export_smoke; then
|
|
321
|
+
RUNNER_EXPORT_VERSION="$(trim_last_line "$RUNNER_EXPORT_OUTPUT")"
|
|
322
|
+
if [[ "$RUNNER_EXPORT_VERSION" == "$RUNNER_INTERFACE_VERSION_EXPECTED" ]]; then
|
|
323
|
+
pass "published runner exports import with interface ${RUNNER_INTERFACE_VERSION_EXPECTED}"
|
|
324
|
+
else
|
|
325
|
+
fail "published runner exports reported interface '${RUNNER_EXPORT_VERSION}', expected '${RUNNER_INTERFACE_VERSION_EXPECTED}'"
|
|
326
|
+
fi
|
|
327
|
+
else
|
|
328
|
+
fail "published runner exports install smoke failed"
|
|
329
|
+
printf '%s\n' "$RUNNER_EXPORT_OUTPUT" | tail -20
|
|
330
|
+
fi
|
|
331
|
+
|
|
256
332
|
echo ""
|
|
257
333
|
echo "====================================="
|
|
258
334
|
echo "Results: ${PASS} passed, ${FAIL} failed"
|
|
@@ -66,6 +66,16 @@ function readRepoLocalHistory(repoPath) {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
function isAcceptedRepoHistoryEntry(entry) {
|
|
70
|
+
if (!entry || typeof entry !== 'object') {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Real governed history records the turn outcome in `status` and acceptance
|
|
75
|
+
// via `accepted_at`. Older coordinator fixtures used `status: "accepted"`.
|
|
76
|
+
return Boolean(entry.accepted_at) || entry.status === 'accepted';
|
|
77
|
+
}
|
|
78
|
+
|
|
69
79
|
// ── Divergence Detection ────────────────────────────────────────────────────
|
|
70
80
|
|
|
71
81
|
/**
|
|
@@ -166,7 +176,7 @@ export function detectDivergence(workspacePath, state, config) {
|
|
|
166
176
|
// Read repo-local history to determine what happened
|
|
167
177
|
const repoHistory = readRepoLocalHistory(repo.resolved_path);
|
|
168
178
|
const turnAccepted = repoHistory.some(
|
|
169
|
-
e => e?.turn_id === dispatch.repo_turn_id && e
|
|
179
|
+
e => e?.turn_id === dispatch.repo_turn_id && isAcceptedRepoHistoryEntry(e)
|
|
170
180
|
);
|
|
171
181
|
const turnRejected = repoHistory.some(
|
|
172
182
|
e => e?.turn_id === dispatch.repo_turn_id && e?.status === 'rejected'
|
|
@@ -279,7 +289,7 @@ export function resyncFromRepoAuthority(workspacePath, state, config) {
|
|
|
279
289
|
|
|
280
290
|
const repoHistory = readRepoLocalHistory(repo.resolved_path);
|
|
281
291
|
const acceptedEntry = repoHistory.find(
|
|
282
|
-
e => e?.turn_id === dispatch.repo_turn_id && e
|
|
292
|
+
e => e?.turn_id === dispatch.repo_turn_id && isAcceptedRepoHistoryEntry(e)
|
|
283
293
|
);
|
|
284
294
|
|
|
285
295
|
if (acceptedEntry) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
|
-
import { evaluatePhaseExit } from './gate-evaluator.js';
|
|
4
|
+
import { evaluatePhaseExit, evaluateRunCompletion } from './gate-evaluator.js';
|
|
5
5
|
import {
|
|
6
6
|
approvePhaseTransition,
|
|
7
7
|
approveRunCompletion,
|
|
@@ -708,6 +708,51 @@ function executeFixtureOperation(workspace, fixture) {
|
|
|
708
708
|
};
|
|
709
709
|
}
|
|
710
710
|
|
|
711
|
+
case 'evaluate_run_completion': {
|
|
712
|
+
const state = readJson(join(root, '.agentxchain', 'state.json'));
|
|
713
|
+
const acceptedTurn = {
|
|
714
|
+
run_completion_request: fixture.setup.turn_result?.run_completion_request ?? true,
|
|
715
|
+
verification: fixture.setup.turn_result?.verification || { status: 'pass' },
|
|
716
|
+
};
|
|
717
|
+
const result = evaluateRunCompletion({
|
|
718
|
+
state,
|
|
719
|
+
config: fixtureConfig,
|
|
720
|
+
acceptedTurn,
|
|
721
|
+
root,
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
if (result.action === 'awaiting_human_approval') {
|
|
725
|
+
return {
|
|
726
|
+
result: 'success',
|
|
727
|
+
action: result.action,
|
|
728
|
+
new_status: 'paused',
|
|
729
|
+
pending_run_completion: {
|
|
730
|
+
phase: state.phase,
|
|
731
|
+
gate: result.gate_id,
|
|
732
|
+
},
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (result.action === 'complete') {
|
|
737
|
+
return {
|
|
738
|
+
result: 'success',
|
|
739
|
+
action: result.action,
|
|
740
|
+
new_status: 'completed',
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
return {
|
|
745
|
+
result: 'success',
|
|
746
|
+
action: result.action,
|
|
747
|
+
state_unchanged: true,
|
|
748
|
+
reason: result.missing_files.length > 0
|
|
749
|
+
? 'requires_files predicate failed'
|
|
750
|
+
: result.missing_verification
|
|
751
|
+
? 'requires_verification_pass predicate failed'
|
|
752
|
+
: (result.reasons[0] || null),
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
|
|
711
756
|
case 'append_decision': {
|
|
712
757
|
const ledgerPath = join(root, '.agentxchain', 'decision-ledger.jsonl');
|
|
713
758
|
const existingLedger = readJsonl(ledgerPath);
|