agentxchain 0.8.7 → 2.1.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.
Files changed (94) hide show
  1. package/README.md +123 -154
  2. package/bin/agentxchain.js +240 -8
  3. package/dashboard/app.js +305 -0
  4. package/dashboard/components/blocked.js +145 -0
  5. package/dashboard/components/cross-repo.js +126 -0
  6. package/dashboard/components/gate.js +311 -0
  7. package/dashboard/components/hooks.js +177 -0
  8. package/dashboard/components/initiative.js +147 -0
  9. package/dashboard/components/ledger.js +165 -0
  10. package/dashboard/components/timeline.js +222 -0
  11. package/dashboard/index.html +352 -0
  12. package/package.json +16 -7
  13. package/scripts/agentxchain-autonudge.applescript +32 -5
  14. package/scripts/live-api-proxy-preflight-smoke.sh +531 -0
  15. package/scripts/publish-from-tag.sh +88 -0
  16. package/scripts/release-postflight.sh +231 -0
  17. package/scripts/release-preflight.sh +167 -0
  18. package/scripts/run-autonudge.sh +1 -1
  19. package/src/adapters/claude-code.js +7 -14
  20. package/src/adapters/cursor-local.js +17 -16
  21. package/src/commands/accept-turn.js +160 -0
  22. package/src/commands/approve-completion.js +80 -0
  23. package/src/commands/approve-transition.js +85 -0
  24. package/src/commands/branch.js +2 -2
  25. package/src/commands/claim.js +84 -9
  26. package/src/commands/config.js +16 -0
  27. package/src/commands/dashboard.js +70 -0
  28. package/src/commands/doctor.js +9 -1
  29. package/src/commands/init.js +540 -5
  30. package/src/commands/migrate.js +348 -0
  31. package/src/commands/multi.js +549 -0
  32. package/src/commands/plugin.js +157 -0
  33. package/src/commands/reject-turn.js +204 -0
  34. package/src/commands/resume.js +389 -0
  35. package/src/commands/status.js +196 -3
  36. package/src/commands/step.js +947 -0
  37. package/src/commands/stop.js +65 -33
  38. package/src/commands/template-list.js +33 -0
  39. package/src/commands/template-set.js +279 -0
  40. package/src/commands/update.js +24 -3
  41. package/src/commands/validate.js +20 -11
  42. package/src/commands/verify.js +71 -0
  43. package/src/commands/watch.js +112 -25
  44. package/src/lib/adapters/api-proxy-adapter.js +1076 -0
  45. package/src/lib/adapters/local-cli-adapter.js +337 -0
  46. package/src/lib/adapters/manual-adapter.js +169 -0
  47. package/src/lib/blocked-state.js +94 -0
  48. package/src/lib/config.js +143 -12
  49. package/src/lib/context-compressor.js +121 -0
  50. package/src/lib/context-section-parser.js +220 -0
  51. package/src/lib/coordinator-acceptance.js +428 -0
  52. package/src/lib/coordinator-config.js +461 -0
  53. package/src/lib/coordinator-dispatch.js +276 -0
  54. package/src/lib/coordinator-gates.js +487 -0
  55. package/src/lib/coordinator-hooks.js +239 -0
  56. package/src/lib/coordinator-recovery.js +523 -0
  57. package/src/lib/coordinator-state.js +365 -0
  58. package/src/lib/cross-repo-context.js +247 -0
  59. package/src/lib/dashboard/bridge-server.js +284 -0
  60. package/src/lib/dashboard/file-watcher.js +93 -0
  61. package/src/lib/dashboard/state-reader.js +96 -0
  62. package/src/lib/dispatch-bundle.js +568 -0
  63. package/src/lib/dispatch-manifest.js +252 -0
  64. package/src/lib/filter-agents.js +12 -0
  65. package/src/lib/gate-evaluator.js +285 -0
  66. package/src/lib/generate-vscode.js +158 -68
  67. package/src/lib/governed-state.js +2139 -0
  68. package/src/lib/governed-templates.js +145 -0
  69. package/src/lib/hook-runner.js +788 -0
  70. package/src/lib/next-owner.js +61 -6
  71. package/src/lib/normalized-config.js +539 -0
  72. package/src/lib/notify.js +14 -12
  73. package/src/lib/plugin-config-schema.js +192 -0
  74. package/src/lib/plugins.js +692 -0
  75. package/src/lib/prompt-core.js +108 -0
  76. package/src/lib/protocol-conformance.js +291 -0
  77. package/src/lib/reference-conformance-adapter.js +717 -0
  78. package/src/lib/repo-observer.js +597 -0
  79. package/src/lib/repo.js +0 -31
  80. package/src/lib/safe-write.js +44 -0
  81. package/src/lib/schema.js +189 -0
  82. package/src/lib/schemas/turn-result.schema.json +205 -0
  83. package/src/lib/seed-prompt-polling.js +15 -73
  84. package/src/lib/seed-prompt.js +17 -63
  85. package/src/lib/token-budget.js +206 -0
  86. package/src/lib/token-counter.js +27 -0
  87. package/src/lib/turn-paths.js +67 -0
  88. package/src/lib/turn-result-validator.js +496 -0
  89. package/src/lib/validation.js +167 -19
  90. package/src/lib/verify-command.js +72 -0
  91. package/src/templates/governed/api-service.json +31 -0
  92. package/src/templates/governed/cli-tool.json +30 -0
  93. package/src/templates/governed/generic.json +10 -0
  94. package/src/templates/governed/web-app.json +30 -0
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env bash
2
+ # Release postflight — run this after publish succeeds.
3
+ # Verifies: release tag exists, npm registry serves the version, metadata is present,
4
+ # and the published package can execute its CLI entrypoint.
5
+ # Usage: bash scripts/release-postflight.sh --target-version <semver> [--tag vX.Y.Z]
6
+ set -uo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+ CLI_DIR="${SCRIPT_DIR}/.."
10
+ cd "$CLI_DIR"
11
+
12
+ TARGET_VERSION=""
13
+ TAG=""
14
+ RETRY_ATTEMPTS="${RELEASE_POSTFLIGHT_RETRY_ATTEMPTS:-6}"
15
+ RETRY_DELAY_SECONDS="${RELEASE_POSTFLIGHT_RETRY_DELAY_SECONDS:-10}"
16
+
17
+ usage() {
18
+ echo "Usage: bash scripts/release-postflight.sh --target-version <semver> [--tag vX.Y.Z]" >&2
19
+ }
20
+
21
+ while [[ $# -gt 0 ]]; do
22
+ case "$1" in
23
+ --target-version)
24
+ if [[ -z "${2:-}" ]]; then
25
+ echo "Error: --target-version requires a semver argument" >&2
26
+ usage
27
+ exit 1
28
+ fi
29
+ if ! [[ "$2" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
30
+ echo "Invalid semver: $2" >&2
31
+ usage
32
+ exit 1
33
+ fi
34
+ TARGET_VERSION="$2"
35
+ shift 2
36
+ ;;
37
+ --tag)
38
+ if [[ -z "${2:-}" ]]; then
39
+ echo "Error: --tag requires a git tag argument" >&2
40
+ usage
41
+ exit 1
42
+ fi
43
+ TAG="$2"
44
+ shift 2
45
+ ;;
46
+ *)
47
+ usage
48
+ exit 1
49
+ ;;
50
+ esac
51
+ done
52
+
53
+ if [[ -z "$TARGET_VERSION" ]]; then
54
+ echo "Error: --target-version is required" >&2
55
+ usage
56
+ exit 1
57
+ fi
58
+
59
+ if [[ -z "$TAG" ]]; then
60
+ TAG="v${TARGET_VERSION}"
61
+ fi
62
+
63
+ if ! [[ "$RETRY_ATTEMPTS" =~ ^[0-9]+$ ]] || [[ "$RETRY_ATTEMPTS" -lt 1 ]]; then
64
+ echo "Error: RELEASE_POSTFLIGHT_RETRY_ATTEMPTS must be a positive integer" >&2
65
+ exit 1
66
+ fi
67
+
68
+ if ! [[ "$RETRY_DELAY_SECONDS" =~ ^[0-9]+$ ]]; then
69
+ echo "Error: RELEASE_POSTFLIGHT_RETRY_DELAY_SECONDS must be a non-negative integer" >&2
70
+ exit 1
71
+ fi
72
+
73
+ PASS=0
74
+ FAIL=0
75
+ TARBALL_URL=""
76
+ REGISTRY_CHECKSUM=""
77
+ PACKAGE_NAME="$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json', 'utf8')).name)")"
78
+
79
+ pass() { PASS=$((PASS + 1)); echo " PASS: $1"; }
80
+ fail() { FAIL=$((FAIL + 1)); echo " FAIL: $1"; }
81
+
82
+ run_and_capture() {
83
+ local __var_name="$1"
84
+ shift
85
+
86
+ local captured_output
87
+ local status
88
+ captured_output="$("$@" 2>&1)"
89
+ status=$?
90
+
91
+ printf -v "$__var_name" '%s' "$captured_output"
92
+ return "$status"
93
+ }
94
+
95
+ trim_last_line() {
96
+ printf '%s\n' "$1" | awk 'NF { line=$0 } END { gsub(/^[[:space:]]+|[[:space:]]+$/, "", line); print line }'
97
+ }
98
+
99
+ run_with_retry() {
100
+ local __output_var="$1"
101
+ local description="$2"
102
+ local success_mode="$3"
103
+ local expected_value="$4"
104
+ shift 4
105
+
106
+ local output=""
107
+ local status=0
108
+ local value=""
109
+ local attempt=1
110
+
111
+ while [[ "$attempt" -le "$RETRY_ATTEMPTS" ]]; do
112
+ if run_and_capture output "$@"; then
113
+ status=0
114
+ else
115
+ status=$?
116
+ fi
117
+
118
+ value="$(trim_last_line "$output")"
119
+
120
+ case "$success_mode" in
121
+ equals)
122
+ if [[ "$status" -eq 0 && "$value" == "$expected_value" ]]; then
123
+ printf -v "$__output_var" '%s' "$output"
124
+ return 0
125
+ fi
126
+ ;;
127
+ nonempty)
128
+ if [[ "$status" -eq 0 && -n "$value" ]]; then
129
+ printf -v "$__output_var" '%s' "$output"
130
+ return 0
131
+ fi
132
+ ;;
133
+ *)
134
+ echo "Error: unsupported retry success mode '${success_mode}'" >&2
135
+ exit 1
136
+ ;;
137
+ esac
138
+
139
+ if [[ "$attempt" -lt "$RETRY_ATTEMPTS" ]]; then
140
+ echo " INFO: ${description} not ready (attempt ${attempt}/${RETRY_ATTEMPTS}); retrying in ${RETRY_DELAY_SECONDS}s..."
141
+ sleep "$RETRY_DELAY_SECONDS"
142
+ fi
143
+ attempt=$((attempt + 1))
144
+ done
145
+
146
+ printf -v "$__output_var" '%s' "$output"
147
+ return 1
148
+ }
149
+
150
+ echo "AgentXchain v${TARGET_VERSION} Release Postflight"
151
+ echo "====================================="
152
+ echo "Checks release truth after publish: tag, registry visibility, metadata, and install smoke."
153
+ echo ""
154
+
155
+ echo "[1/5] Git tag"
156
+ if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null 2>&1; then
157
+ pass "Git tag ${TAG} exists locally"
158
+ else
159
+ fail "Git tag ${TAG} is missing locally"
160
+ fi
161
+
162
+ echo "[2/5] Registry version"
163
+ if run_with_retry VERSION_OUTPUT "registry version" equals "${TARGET_VERSION}" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" version; then
164
+ PUBLISHED_VERSION="$(trim_last_line "$VERSION_OUTPUT")"
165
+ if [[ "$PUBLISHED_VERSION" == "$TARGET_VERSION" ]]; then
166
+ pass "npm registry serves ${PACKAGE_NAME}@${TARGET_VERSION}"
167
+ else
168
+ fail "npm registry returned '${PUBLISHED_VERSION}', expected '${TARGET_VERSION}'"
169
+ fi
170
+ else
171
+ fail "npm registry does not serve ${PACKAGE_NAME}@${TARGET_VERSION}"
172
+ printf '%s\n' "$VERSION_OUTPUT" | tail -20
173
+ fi
174
+
175
+ echo "[3/5] Registry tarball metadata"
176
+ if run_with_retry TARBALL_OUTPUT "registry tarball metadata" nonempty "" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" dist.tarball; then
177
+ TARBALL_URL="$(trim_last_line "$TARBALL_OUTPUT")"
178
+ if [[ -n "$TARBALL_URL" ]]; then
179
+ pass "registry exposes dist.tarball metadata"
180
+ else
181
+ fail "registry returned empty dist.tarball metadata"
182
+ fi
183
+ else
184
+ fail "registry did not return dist.tarball metadata"
185
+ printf '%s\n' "$TARBALL_OUTPUT" | tail -20
186
+ fi
187
+
188
+ echo "[4/5] Registry checksum metadata"
189
+ if run_with_retry INTEGRITY_OUTPUT "registry checksum metadata" nonempty "" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" dist.integrity; then
190
+ REGISTRY_CHECKSUM="$(trim_last_line "$INTEGRITY_OUTPUT")"
191
+ fi
192
+ if [[ -z "$REGISTRY_CHECKSUM" ]]; then
193
+ if run_with_retry SHASUM_OUTPUT "registry shasum metadata" nonempty "" npm view "${PACKAGE_NAME}@${TARGET_VERSION}" dist.shasum; then
194
+ REGISTRY_CHECKSUM="$(trim_last_line "$SHASUM_OUTPUT")"
195
+ fi
196
+ fi
197
+ if [[ -n "$REGISTRY_CHECKSUM" ]]; then
198
+ pass "registry exposes checksum metadata"
199
+ else
200
+ fail "registry did not return checksum metadata"
201
+ fi
202
+
203
+ echo "[5/5] Install smoke"
204
+ if run_with_retry EXEC_OUTPUT "install smoke" nonempty "" npm exec --yes --package "${PACKAGE_NAME}@${TARGET_VERSION}" -- agentxchain --version; then
205
+ EXEC_VERSION="$(trim_last_line "$EXEC_OUTPUT")"
206
+ if [[ "$EXEC_VERSION" == "$TARGET_VERSION" ]]; then
207
+ pass "published CLI executes and reports ${TARGET_VERSION}"
208
+ else
209
+ fail "published CLI reported '${EXEC_VERSION}', expected '${TARGET_VERSION}'"
210
+ fi
211
+ else
212
+ fail "published CLI install smoke failed"
213
+ printf '%s\n' "$EXEC_OUTPUT" | tail -20
214
+ fi
215
+
216
+ echo ""
217
+ echo "====================================="
218
+ echo "Results: ${PASS} passed, ${FAIL} failed"
219
+ if [[ -n "$TARBALL_URL" ]]; then
220
+ echo "Tarball: ${TARBALL_URL}"
221
+ fi
222
+ if [[ -n "$REGISTRY_CHECKSUM" ]]; then
223
+ echo "Checksum: ${REGISTRY_CHECKSUM}"
224
+ fi
225
+ if [ "$FAIL" -gt 0 ]; then
226
+ echo "POSTFLIGHT FAILED — do not mark the release complete."
227
+ exit 1
228
+ fi
229
+
230
+ echo "POSTFLIGHT PASSED — registry truth matches the release tag."
231
+ exit 0
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env bash
2
+ # Release preflight — run this before cutting a release.
3
+ # Verifies: clean tree, deps, tests, CHANGELOG entry, pack dry-run.
4
+ # Usage: bash scripts/release-preflight.sh [--strict] [--target-version <semver>]
5
+ set -uo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ CLI_DIR="${SCRIPT_DIR}/.."
9
+ cd "$CLI_DIR"
10
+
11
+ STRICT_MODE=0
12
+ TARGET_VERSION="2.0.0"
13
+
14
+ usage() {
15
+ echo "Usage: bash scripts/release-preflight.sh [--strict] [--target-version <semver>]" >&2
16
+ }
17
+
18
+ while [[ $# -gt 0 ]]; do
19
+ case "$1" in
20
+ --strict)
21
+ STRICT_MODE=1
22
+ shift
23
+ ;;
24
+ --target-version)
25
+ if [[ -z "${2:-}" ]]; then
26
+ echo "Error: --target-version requires a semver argument" >&2
27
+ usage
28
+ exit 1
29
+ fi
30
+ if ! [[ "$2" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
31
+ echo "Invalid semver: $2" >&2
32
+ usage
33
+ exit 1
34
+ fi
35
+ TARGET_VERSION="$2"
36
+ shift 2
37
+ ;;
38
+ *)
39
+ usage
40
+ exit 1
41
+ ;;
42
+ esac
43
+ done
44
+
45
+ PASS=0
46
+ FAIL=0
47
+ WARN=0
48
+
49
+ pass() { PASS=$((PASS + 1)); echo " PASS: $1"; }
50
+ fail() { FAIL=$((FAIL + 1)); echo " FAIL: $1"; }
51
+ warn() { WARN=$((WARN + 1)); echo " WARN: $1"; }
52
+
53
+ run_and_capture() {
54
+ local __var_name="$1"
55
+ shift
56
+
57
+ local output
58
+ local status
59
+ output="$("$@" 2>&1)"
60
+ status=$?
61
+
62
+ printf -v "$__var_name" '%s' "$output"
63
+ return "$status"
64
+ }
65
+
66
+ echo "AgentXchain v${TARGET_VERSION} Release Preflight"
67
+ echo "====================================="
68
+ if [[ "$TARGET_VERSION" == "1.0.0" ]]; then
69
+ echo "Local checks only. Human-gated release items remain in .planning/V1_RELEASE_CHECKLIST.md."
70
+ else
71
+ echo "Local checks only. Human-gated release items remain in .planning/V1_RELEASE_CHECKLIST.md (v1.0) or .planning/V1_1_RELEASE_CHECKLIST.md (v1.1+)."
72
+ fi
73
+ if [[ "$STRICT_MODE" -eq 1 ]]; then
74
+ echo "Mode: STRICT (dirty tree and non-${TARGET_VERSION} package version are hard failures)"
75
+ else
76
+ echo "Mode: DEFAULT (dirty tree and pre-bump package version are warnings)"
77
+ fi
78
+ echo ""
79
+
80
+ # 1. Clean working tree
81
+ echo "[1/6] Git status"
82
+ if git diff --quiet HEAD 2>/dev/null && [ -z "$(git ls-files --others --exclude-standard 2>/dev/null)" ]; then
83
+ pass "Working tree is clean"
84
+ else
85
+ if [[ "$STRICT_MODE" -eq 1 ]]; then
86
+ fail "Working tree is not clean"
87
+ else
88
+ warn "Uncommitted or untracked files present"
89
+ fi
90
+ fi
91
+
92
+ # 2. Dependencies
93
+ echo "[2/6] Dependencies"
94
+ if run_and_capture NPM_CI_OUTPUT npm ci --ignore-scripts; then
95
+ pass "npm ci succeeded"
96
+ else
97
+ fail "npm ci failed"
98
+ printf '%s\n' "$NPM_CI_OUTPUT" | tail -20
99
+ fi
100
+
101
+ # 3. Tests
102
+ echo "[3/6] Test suite"
103
+ if run_and_capture TEST_OUTPUT npm test; then
104
+ TEST_STATUS=0
105
+ else
106
+ TEST_STATUS=$?
107
+ fi
108
+ TEST_PASS="$(printf '%s\n' "$TEST_OUTPUT" | awk '/^# pass / { print $3 }')"
109
+ TEST_FAIL="$(printf '%s\n' "$TEST_OUTPUT" | awk '/^# fail / { print $3 }')"
110
+ if [ "$TEST_STATUS" -eq 0 ] && [ "${TEST_FAIL:-0}" = "0" ]; then
111
+ pass "${TEST_PASS} tests passed, 0 failures"
112
+ else
113
+ fail "npm test failed"
114
+ printf '%s\n' "$TEST_OUTPUT" | tail -20
115
+ fi
116
+
117
+ # 4. CHANGELOG has target version
118
+ echo "[4/6] CHANGELOG"
119
+ if grep -Fxq "## ${TARGET_VERSION}" CHANGELOG.md 2>/dev/null; then
120
+ pass "CHANGELOG.md contains ${TARGET_VERSION} entry"
121
+ else
122
+ fail "CHANGELOG.md missing ${TARGET_VERSION} entry"
123
+ fi
124
+
125
+ # 5. Package version
126
+ echo "[5/6] Package version"
127
+ PKG_VERSION=$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json','utf8')).version)")
128
+ echo " Current version: ${PKG_VERSION}"
129
+ if [ "$PKG_VERSION" = "${TARGET_VERSION}" ]; then
130
+ pass "package.json is at ${TARGET_VERSION}"
131
+ else
132
+ if [[ "$STRICT_MODE" -eq 1 ]]; then
133
+ fail "package.json is at ${PKG_VERSION}, expected ${TARGET_VERSION}"
134
+ else
135
+ warn "package.json is at ${PKG_VERSION}, not yet bumped to ${TARGET_VERSION}"
136
+ fi
137
+ fi
138
+
139
+ # 6. Pack dry-run
140
+ echo "[6/6] npm pack --dry-run"
141
+ if run_and_capture PACK_OUTPUT npm pack --dry-run; then
142
+ pass "npm pack --dry-run succeeded"
143
+ PACK_SIZE_LINE="$(printf '%s\n' "$PACK_OUTPUT" | awk '/total files:/ { print; found=1 } END { if (!found) exit 1 }')"
144
+ if [ -n "${PACK_SIZE_LINE:-}" ]; then
145
+ echo " ${PACK_SIZE_LINE}"
146
+ else
147
+ printf '%s\n' "$PACK_OUTPUT" | tail -5
148
+ fi
149
+ else
150
+ fail "npm pack --dry-run failed"
151
+ printf '%s\n' "$PACK_OUTPUT" | tail -20
152
+ fi
153
+
154
+ # Summary
155
+ echo ""
156
+ echo "====================================="
157
+ echo "Results: ${PASS} passed, ${FAIL} failed, ${WARN} warnings"
158
+ if [ "$FAIL" -gt 0 ]; then
159
+ echo "PREFLIGHT FAILED — fix failures before release."
160
+ exit 1
161
+ elif [ "$WARN" -gt 0 ]; then
162
+ echo "PREFLIGHT PASSED WITH WARNINGS — resolve warnings before release day."
163
+ exit 0
164
+ else
165
+ echo "PREFLIGHT PASSED — ready for release."
166
+ exit 0
167
+ fi
@@ -104,7 +104,7 @@ echo "Mode: $( [[ "${AUTO_SEND}" == "true" ]] && echo "auto-send" || echo "
104
104
  echo "Interval: ${INTERVAL_SECONDS}s"
105
105
  echo ""
106
106
  echo "Requirements:"
107
- echo "- Keep 'agentxchain watch' running in another terminal."
107
+ echo "- Watch must be running (e.g. 'agentxchain supervise --autonudge' starts it in the same supervisor, or run 'agentxchain watch' in another terminal)."
108
108
  echo "- Grant Accessibility permission to Terminal and Cursor."
109
109
  echo ""
110
110
 
@@ -1,8 +1,9 @@
1
1
  import { spawn } from 'child_process';
2
2
  import chalk from 'chalk';
3
3
  import { generateSeedPrompt } from '../lib/seed-prompt.js';
4
- import { writeFileSync } from 'fs';
5
4
  import { join } from 'path';
5
+ import { safeWriteJson } from '../lib/safe-write.js';
6
+ import { filterAgents } from '../lib/filter-agents.js';
6
7
 
7
8
  export async function launchClaudeCodeAgents(config, root, opts) {
8
9
  const agents = filterAgents(config, opts.agent);
@@ -28,22 +29,14 @@ export async function launchClaudeCodeAgents(config, root, opts) {
28
29
  }
29
30
 
30
31
  if (launched.length > 0) {
31
- const sessionFile = JSON.stringify({ launched, started_at: new Date().toISOString(), ide: 'claude-code' }, null, 2);
32
- writeFileSync(join(root, '.agentxchain-session.json'), sessionFile + '\n');
32
+ safeWriteJson(join(root, '.agentxchain-session.json'), {
33
+ launched,
34
+ started_at: new Date().toISOString(),
35
+ ide: 'claude-code'
36
+ });
33
37
  console.log('');
34
38
  console.log(chalk.dim(` Session saved to .agentxchain-session.json`));
35
39
  }
36
40
 
37
41
  return launched;
38
42
  }
39
-
40
- function filterAgents(config, specificId) {
41
- if (specificId) {
42
- if (!config.agents[specificId]) {
43
- console.log(chalk.red(` Agent "${specificId}" not found in agentxchain.json`));
44
- process.exit(1);
45
- }
46
- return { [specificId]: config.agents[specificId] };
47
- }
48
- return config.agents;
49
- }
@@ -4,6 +4,7 @@ import { join } from 'path';
4
4
  import chalk from 'chalk';
5
5
  import inquirer from 'inquirer';
6
6
  import { generatePollingPrompt } from '../lib/seed-prompt-polling.js';
7
+ import { filterAgents } from '../lib/filter-agents.js';
7
8
 
8
9
  export async function launchCursorLocal(config, root, opts) {
9
10
  const agents = filterAgents(config, opts.agent);
@@ -50,13 +51,22 @@ export async function launchCursorLocal(config, root, opts) {
50
51
  console.log(chalk.cyan(` ─── Agent ${i + 1}/${total}: ${chalk.bold(id)} — ${agent.name} ───`));
51
52
  console.log('');
52
53
 
53
- copyToClipboard(prompt);
54
- console.log(chalk.green(` ✓ Prompt copied to clipboard.`));
54
+ const copied = copyToClipboard(prompt);
55
+ if (copied) {
56
+ console.log(chalk.green(' ✓ Prompt copied to clipboard.'));
57
+ } else {
58
+ console.log(chalk.yellow(' ! Clipboard copy failed. Use the saved prompt file manually.'));
59
+ }
55
60
  console.log(chalk.dim(` Saved to: .agentxchain-prompts/${id}.prompt.md`));
56
61
 
57
62
  // Open a separate Cursor window using the symlinked path
58
- openCursorWindow(agentWorkspace);
59
- console.log(chalk.dim(` Cursor window opened for ${id}.`));
63
+ const opened = openCursorWindow(agentWorkspace);
64
+ if (opened) {
65
+ console.log(chalk.dim(` Cursor window opened for ${id}.`));
66
+ } else {
67
+ console.log(chalk.yellow(` Could not open Cursor window automatically for ${id}.`));
68
+ console.log(chalk.dim(` Open manually: cursor --new-window "${agentWorkspace}"`));
69
+ }
60
70
 
61
71
  console.log('');
62
72
  console.log(` ${chalk.bold('In the new Cursor window:')}`);
@@ -108,17 +118,6 @@ export async function launchCursorLocal(config, root, opts) {
108
118
  console.log('');
109
119
  }
110
120
 
111
- function filterAgents(config, specificId) {
112
- if (specificId) {
113
- if (!config.agents[specificId]) {
114
- console.log(chalk.red(` Agent "${specificId}" not found in agentxchain.json`));
115
- process.exit(1);
116
- }
117
- return { [specificId]: config.agents[specificId] };
118
- }
119
- return config.agents;
120
- }
121
-
122
121
  function copyToClipboard(text) {
123
122
  try {
124
123
  if (process.platform === 'darwin') {
@@ -137,10 +136,12 @@ function openCursorWindow(folderPath) {
137
136
  try {
138
137
  if (process.platform === 'darwin') {
139
138
  execSync(`open -na "Cursor" --args "${folderPath}"`, { stdio: 'ignore' });
140
- return;
139
+ return true;
141
140
  }
142
141
  execSync(`cursor --new-window "${folderPath}"`, { stdio: 'ignore' });
142
+ return true;
143
143
  } catch {}
144
+ return false;
144
145
  }
145
146
 
146
147
  function isPmLike(agentId, agentDef) {
@@ -0,0 +1,160 @@
1
+ import chalk from 'chalk';
2
+ import { loadProjectContext } from '../lib/config.js';
3
+ import { acceptGovernedTurn } from '../lib/governed-state.js';
4
+ import { deriveRecoveryDescriptor } from '../lib/blocked-state.js';
5
+
6
+ export async function acceptTurnCommand(opts = {}) {
7
+ const context = loadProjectContext();
8
+ if (!context) {
9
+ console.log(chalk.red('No agentxchain.json found. Run `agentxchain init` first.'));
10
+ process.exit(1);
11
+ }
12
+
13
+ const { root, config } = context;
14
+
15
+ if (config.protocol_mode !== 'governed') {
16
+ console.log(chalk.red('The accept-turn command is only available for governed projects.'));
17
+ console.log(chalk.dim('Legacy projects use: agentxchain release'));
18
+ process.exit(1);
19
+ }
20
+
21
+ const result = acceptGovernedTurn(root, config, {
22
+ turnId: opts.turn,
23
+ resolutionMode: opts.resolution || 'standard',
24
+ });
25
+ if (!result.ok) {
26
+ if (result.error_code?.startsWith('hook_') || result.error_code === 'hook_blocked') {
27
+ const recovery = deriveRecoveryDescriptor(result.state);
28
+ const activeTurn = result.state?.current_turn;
29
+ const hookName = result.hookResults?.blocker?.hook_name
30
+ || result.hookResults?.results?.find((entry) => entry.hook_name)?.hook_name
31
+ || '(unknown)';
32
+
33
+ console.log('');
34
+ console.log(chalk.yellow(` ${result.accepted ? 'Turn Accepted, Hook Failure Detected' : 'Turn Acceptance Blocked By Hook'}`));
35
+ console.log(chalk.dim(' ' + '─'.repeat(44)));
36
+ console.log('');
37
+ console.log(` ${chalk.dim('Turn:')} ${result.accepted?.turn_id || activeTurn?.turn_id || opts.turn || '(unknown)'}`);
38
+ console.log(` ${chalk.dim('Role:')} ${result.accepted?.role || activeTurn?.assigned_role || '(unknown)'}`);
39
+ if (result.accepted?.status) {
40
+ console.log(` ${chalk.dim('Status:')} ${result.accepted.status}`);
41
+ }
42
+ console.log(` ${chalk.dim('Hook:')} ${hookName}`);
43
+ console.log(` ${chalk.dim('Error:')} ${result.error}`);
44
+ if (recovery) {
45
+ console.log(` ${chalk.dim('Reason:')} ${recovery.typed_reason}`);
46
+ console.log(` ${chalk.dim('Owner:')} ${recovery.owner}`);
47
+ console.log(` ${chalk.dim('Action:')} ${recovery.recovery_action}`);
48
+ if (recovery.detail) {
49
+ console.log(` ${chalk.dim('Detail:')} ${recovery.detail}`);
50
+ }
51
+ }
52
+ console.log('');
53
+ process.exit(1);
54
+ }
55
+
56
+ if (result.error_code === 'conflict' && result.conflict) {
57
+ console.log('');
58
+ console.log(chalk.yellow(' Acceptance Conflict Detected'));
59
+ console.log(chalk.dim(' ' + '─'.repeat(44)));
60
+ console.log('');
61
+ console.log(` ${chalk.dim('Turn:')} ${result.conflict.conflicting_turn.turn_id}`);
62
+ console.log(` ${chalk.dim('Role:')} ${result.conflict.conflicting_turn.role}`);
63
+ console.log(` ${chalk.dim('Files:')} ${result.conflict.conflicting_files.join(', ') || '(none)'}`);
64
+ console.log(` ${chalk.dim('Overlap:')} ${(result.conflict.overlap_ratio * 100).toFixed(0)}%`);
65
+ console.log(` ${chalk.dim('Suggest:')} ${result.conflict.suggested_resolution}`);
66
+ if (result.state?.status === 'blocked') {
67
+ const recovery = deriveRecoveryDescriptor(result.state);
68
+ if (recovery) {
69
+ console.log(` ${chalk.dim('Action:')} ${recovery.recovery_action}`);
70
+ }
71
+ }
72
+ console.log('');
73
+ process.exit(1);
74
+ }
75
+
76
+ if (result.error_code === 'lock_timeout') {
77
+ console.log('');
78
+ console.log(chalk.yellow(' Acceptance Lock Held'));
79
+ console.log(chalk.dim(' ' + '─'.repeat(44)));
80
+ console.log('');
81
+ console.log(` ${chalk.dim('Reason:')} ${result.error}`);
82
+ console.log(` ${chalk.dim('Action:')} Wait for the other acceptance to complete, then retry.`);
83
+ console.log(` ${chalk.dim('Note:')} Stale locks are auto-reclaimed after 30 seconds.`);
84
+ console.log('');
85
+ process.exit(1);
86
+ }
87
+
88
+ if (result.error_code === 'protocol_error') {
89
+ console.log(chalk.red(result.error || 'Protocol error.'));
90
+ process.exit(1);
91
+ }
92
+
93
+ if (!result.validation) {
94
+ console.log(chalk.red(result.error || 'Failed to accept turn.'));
95
+ process.exit(1);
96
+ }
97
+
98
+ const errorClass = result.validation?.error_class || 'unknown';
99
+ const stage = result.validation?.stage || 'unknown';
100
+
101
+ console.log('');
102
+ console.log(chalk.red(` Validation failed at stage ${stage}`));
103
+ console.log(chalk.dim(' ' + '─'.repeat(44)));
104
+ console.log('');
105
+ console.log(` ${chalk.dim('Reason:')} ${errorClass}`);
106
+ console.log(` ${chalk.dim('Owner:')} human`);
107
+ console.log(` ${chalk.dim('Action:')} Fix staged result and rerun agentxchain accept-turn, or reject with agentxchain reject-turn --reason "..."`);
108
+ console.log(` ${chalk.dim('Turn:')} retained`);
109
+ if (result.validation?.errors?.length) {
110
+ for (const err of result.validation.errors) {
111
+ console.log(` ${chalk.dim('Detail:')} ${err}`);
112
+ }
113
+ }
114
+ console.log('');
115
+ console.log(chalk.dim('Inspect the staged result with: agentxchain validate --mode turn'));
116
+ process.exit(1);
117
+ }
118
+
119
+ const accepted = result.accepted;
120
+ const turnId = accepted?.turn_id || result.state?.last_completed_turn_id || '(unknown)';
121
+
122
+ console.log('');
123
+ console.log(chalk.green(' Turn Accepted'));
124
+ console.log(chalk.dim(' ' + '─'.repeat(44)));
125
+ console.log('');
126
+ console.log(` ${chalk.dim('Turn:')} ${turnId}`);
127
+ console.log(` ${chalk.dim('Role:')} ${accepted?.role || '(unknown)'}`);
128
+ console.log(` ${chalk.dim('Status:')} ${accepted?.status || 'completed'}`);
129
+ console.log(` ${chalk.dim('Summary:')} ${accepted?.summary || '(none)'}`);
130
+ if (accepted?.proposed_next_role) {
131
+ console.log(` ${chalk.dim('Proposed:')} ${accepted.proposed_next_role}`);
132
+ }
133
+ if (result.state?.accepted_integration_ref) {
134
+ console.log(` ${chalk.dim('Accepted:')} ${result.state.accepted_integration_ref}`);
135
+ }
136
+ if (accepted?.cost?.usd != null) {
137
+ console.log(` ${chalk.dim('Cost:')} $${formatUsd(accepted.cost.usd)}`);
138
+ }
139
+ console.log('');
140
+
141
+ const recovery = deriveRecoveryDescriptor(result.state);
142
+ if (recovery) {
143
+ console.log(` ${chalk.dim('Reason:')} ${recovery.typed_reason}`);
144
+ console.log(` ${chalk.dim('Owner:')} ${recovery.owner}`);
145
+ console.log(` ${chalk.dim('Action:')} ${recovery.recovery_action}`);
146
+ console.log(` ${chalk.dim('Turn:')} ${recovery.turn_retained ? 'retained' : 'cleared'}`);
147
+ if (recovery.detail) {
148
+ console.log(` ${chalk.dim('Detail:')} ${recovery.detail}`);
149
+ }
150
+ } else if (accepted?.proposed_next_role && accepted.proposed_next_role !== 'human') {
151
+ console.log(chalk.dim(` Next: agentxchain resume --role ${accepted.proposed_next_role}`));
152
+ } else {
153
+ console.log(chalk.dim(' Next: review state, then run agentxchain resume when ready.'));
154
+ }
155
+ console.log('');
156
+ }
157
+
158
+ function formatUsd(value) {
159
+ return typeof value === 'number' && !Number.isNaN(value) ? value.toFixed(2) : '0.00';
160
+ }