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.
- package/README.md +123 -154
- package/bin/agentxchain.js +240 -8
- package/dashboard/app.js +305 -0
- package/dashboard/components/blocked.js +145 -0
- package/dashboard/components/cross-repo.js +126 -0
- package/dashboard/components/gate.js +311 -0
- package/dashboard/components/hooks.js +177 -0
- package/dashboard/components/initiative.js +147 -0
- package/dashboard/components/ledger.js +165 -0
- package/dashboard/components/timeline.js +222 -0
- package/dashboard/index.html +352 -0
- package/package.json +16 -7
- package/scripts/agentxchain-autonudge.applescript +32 -5
- package/scripts/live-api-proxy-preflight-smoke.sh +531 -0
- package/scripts/publish-from-tag.sh +88 -0
- package/scripts/release-postflight.sh +231 -0
- package/scripts/release-preflight.sh +167 -0
- package/scripts/run-autonudge.sh +1 -1
- package/src/adapters/claude-code.js +7 -14
- package/src/adapters/cursor-local.js +17 -16
- package/src/commands/accept-turn.js +160 -0
- package/src/commands/approve-completion.js +80 -0
- package/src/commands/approve-transition.js +85 -0
- package/src/commands/branch.js +2 -2
- package/src/commands/claim.js +84 -9
- package/src/commands/config.js +16 -0
- package/src/commands/dashboard.js +70 -0
- package/src/commands/doctor.js +9 -1
- package/src/commands/init.js +540 -5
- package/src/commands/migrate.js +348 -0
- package/src/commands/multi.js +549 -0
- package/src/commands/plugin.js +157 -0
- package/src/commands/reject-turn.js +204 -0
- package/src/commands/resume.js +389 -0
- package/src/commands/status.js +196 -3
- package/src/commands/step.js +947 -0
- package/src/commands/stop.js +65 -33
- package/src/commands/template-list.js +33 -0
- package/src/commands/template-set.js +279 -0
- package/src/commands/update.js +24 -3
- package/src/commands/validate.js +20 -11
- package/src/commands/verify.js +71 -0
- package/src/commands/watch.js +112 -25
- package/src/lib/adapters/api-proxy-adapter.js +1076 -0
- package/src/lib/adapters/local-cli-adapter.js +337 -0
- package/src/lib/adapters/manual-adapter.js +169 -0
- package/src/lib/blocked-state.js +94 -0
- package/src/lib/config.js +143 -12
- package/src/lib/context-compressor.js +121 -0
- package/src/lib/context-section-parser.js +220 -0
- package/src/lib/coordinator-acceptance.js +428 -0
- package/src/lib/coordinator-config.js +461 -0
- package/src/lib/coordinator-dispatch.js +276 -0
- package/src/lib/coordinator-gates.js +487 -0
- package/src/lib/coordinator-hooks.js +239 -0
- package/src/lib/coordinator-recovery.js +523 -0
- package/src/lib/coordinator-state.js +365 -0
- package/src/lib/cross-repo-context.js +247 -0
- package/src/lib/dashboard/bridge-server.js +284 -0
- package/src/lib/dashboard/file-watcher.js +93 -0
- package/src/lib/dashboard/state-reader.js +96 -0
- package/src/lib/dispatch-bundle.js +568 -0
- package/src/lib/dispatch-manifest.js +252 -0
- package/src/lib/filter-agents.js +12 -0
- package/src/lib/gate-evaluator.js +285 -0
- package/src/lib/generate-vscode.js +158 -68
- package/src/lib/governed-state.js +2139 -0
- package/src/lib/governed-templates.js +145 -0
- package/src/lib/hook-runner.js +788 -0
- package/src/lib/next-owner.js +61 -6
- package/src/lib/normalized-config.js +539 -0
- package/src/lib/notify.js +14 -12
- package/src/lib/plugin-config-schema.js +192 -0
- package/src/lib/plugins.js +692 -0
- package/src/lib/prompt-core.js +108 -0
- package/src/lib/protocol-conformance.js +291 -0
- package/src/lib/reference-conformance-adapter.js +717 -0
- package/src/lib/repo-observer.js +597 -0
- package/src/lib/repo.js +0 -31
- package/src/lib/safe-write.js +44 -0
- package/src/lib/schema.js +189 -0
- package/src/lib/schemas/turn-result.schema.json +205 -0
- package/src/lib/seed-prompt-polling.js +15 -73
- package/src/lib/seed-prompt.js +17 -63
- package/src/lib/token-budget.js +206 -0
- package/src/lib/token-counter.js +27 -0
- package/src/lib/turn-paths.js +67 -0
- package/src/lib/turn-result-validator.js +496 -0
- package/src/lib/validation.js +167 -19
- package/src/lib/verify-command.js +72 -0
- package/src/templates/governed/api-service.json +31 -0
- package/src/templates/governed/cli-tool.json +30 -0
- package/src/templates/governed/generic.json +10 -0
- package/src/templates/governed/web-app.json +30 -0
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat <<'EOF'
|
|
6
|
+
Usage: bash cli/scripts/live-api-proxy-preflight-smoke.sh [--mode happy|overflow|both] [--keep-temp]
|
|
7
|
+
|
|
8
|
+
Runs a live smoke harness for api_proxy preflight tokenization using a temp copy
|
|
9
|
+
of examples/governed-todo-app.
|
|
10
|
+
EOF
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
MODE="both"
|
|
14
|
+
KEEP_TEMP=0
|
|
15
|
+
|
|
16
|
+
while [[ $# -gt 0 ]]; do
|
|
17
|
+
case "$1" in
|
|
18
|
+
--mode)
|
|
19
|
+
MODE="${2:-}"
|
|
20
|
+
shift 2
|
|
21
|
+
;;
|
|
22
|
+
--keep-temp)
|
|
23
|
+
KEEP_TEMP=1
|
|
24
|
+
shift
|
|
25
|
+
;;
|
|
26
|
+
-h|--help)
|
|
27
|
+
usage
|
|
28
|
+
exit 0
|
|
29
|
+
;;
|
|
30
|
+
*)
|
|
31
|
+
echo "Unknown argument: $1" >&2
|
|
32
|
+
usage >&2
|
|
33
|
+
exit 1
|
|
34
|
+
;;
|
|
35
|
+
esac
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
case "$MODE" in
|
|
39
|
+
happy|overflow|both) ;;
|
|
40
|
+
*)
|
|
41
|
+
echo "Invalid --mode: $MODE" >&2
|
|
42
|
+
usage >&2
|
|
43
|
+
exit 1
|
|
44
|
+
;;
|
|
45
|
+
esac
|
|
46
|
+
|
|
47
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
48
|
+
CLI_DIR="${SCRIPT_DIR}/.."
|
|
49
|
+
REPO_ROOT="$(cd "${CLI_DIR}/.." && pwd)"
|
|
50
|
+
EXAMPLE_DIR="${REPO_ROOT}/examples/governed-todo-app"
|
|
51
|
+
|
|
52
|
+
load_repo_env() {
|
|
53
|
+
local env_file="${REPO_ROOT}/.env"
|
|
54
|
+
if [[ ! -f "$env_file" ]]; then
|
|
55
|
+
return 0
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
59
|
+
line="${line#"${line%%[![:space:]]*}"}"
|
|
60
|
+
[[ -z "$line" || "${line:0:1}" == "#" ]] && continue
|
|
61
|
+
[[ "$line" != *=* ]] && continue
|
|
62
|
+
|
|
63
|
+
local key="${line%%=*}"
|
|
64
|
+
local value="${line#*=}"
|
|
65
|
+
key="${key%"${key##*[![:space:]]}"}"
|
|
66
|
+
value="${value#"${value%%[![:space:]]*}"}"
|
|
67
|
+
value="${value%"${value##*[![:space:]]}"}"
|
|
68
|
+
|
|
69
|
+
if [[ "${value:0:1}" == "\"" && "${value: -1}" == "\"" ]]; then
|
|
70
|
+
value="${value:1:${#value}-2}"
|
|
71
|
+
elif [[ "${value:0:1}" == "'" && "${value: -1}" == "'" ]]; then
|
|
72
|
+
value="${value:1:${#value}-2}"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
if [[ -z "${!key:-}" ]]; then
|
|
76
|
+
export "$key=$value"
|
|
77
|
+
fi
|
|
78
|
+
done < "$env_file"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
require_cmd() {
|
|
82
|
+
if ! command -v "$1" >/dev/null 2>&1; then
|
|
83
|
+
echo "Required command not found: $1" >&2
|
|
84
|
+
exit 1
|
|
85
|
+
fi
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
make_workspace() {
|
|
89
|
+
local scenario="$1"
|
|
90
|
+
local workspace
|
|
91
|
+
workspace="$(mktemp -d "${TMPDIR:-/tmp}/agentxchain-preflight-${scenario}-XXXXXX")"
|
|
92
|
+
cp -R "${EXAMPLE_DIR}/." "$workspace"
|
|
93
|
+
echo "$workspace"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
patch_runtime_config() {
|
|
97
|
+
local workspace="$1"
|
|
98
|
+
local scenario="$2"
|
|
99
|
+
|
|
100
|
+
node --input-type=commonjs - "$workspace" "$scenario" <<'NODE'
|
|
101
|
+
const fs = require('fs');
|
|
102
|
+
const path = require('path');
|
|
103
|
+
|
|
104
|
+
const workspace = process.argv[2];
|
|
105
|
+
const scenario = process.argv[3];
|
|
106
|
+
const configPath = path.join(workspace, 'agentxchain.json');
|
|
107
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
108
|
+
const runtime = config.runtimes['api-qa'];
|
|
109
|
+
|
|
110
|
+
if (!runtime || runtime.type !== 'api_proxy') {
|
|
111
|
+
throw new Error('api-qa runtime missing or not api_proxy');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
runtime.preflight_tokenization = {
|
|
115
|
+
enabled: true,
|
|
116
|
+
tokenizer: 'provider_local',
|
|
117
|
+
safety_margin_tokens: scenario === 'happy' ? 1024 : 64,
|
|
118
|
+
};
|
|
119
|
+
runtime.context_window_tokens = scenario === 'happy' ? 200000 : 512;
|
|
120
|
+
runtime.max_output_tokens = scenario === 'happy' ? 2048 : 128;
|
|
121
|
+
if (scenario === 'happy') {
|
|
122
|
+
runtime.retry_policy = {
|
|
123
|
+
enabled: true,
|
|
124
|
+
max_attempts: 3,
|
|
125
|
+
base_delay_ms: 1000,
|
|
126
|
+
max_delay_ms: 1000,
|
|
127
|
+
backoff_multiplier: 1,
|
|
128
|
+
jitter: 'none',
|
|
129
|
+
retry_on: ['turn_result_extraction_failure'],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
134
|
+
NODE
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
bootstrap_to_qa() {
|
|
138
|
+
local workspace="$1"
|
|
139
|
+
|
|
140
|
+
node --input-type=commonjs - "$workspace" "$CLI_DIR" <<'NODE'
|
|
141
|
+
const fs = require('fs');
|
|
142
|
+
const path = require('path');
|
|
143
|
+
const { execSync } = require('child_process');
|
|
144
|
+
const { pathToFileURL } = require('url');
|
|
145
|
+
|
|
146
|
+
async function main() {
|
|
147
|
+
const workspace = process.argv[2];
|
|
148
|
+
const cliDir = process.argv[3];
|
|
149
|
+
|
|
150
|
+
const normalized = await import(pathToFileURL(path.join(cliDir, 'src/lib/normalized-config.js')).href);
|
|
151
|
+
const governed = await import(pathToFileURL(path.join(cliDir, 'src/lib/governed-state.js')).href);
|
|
152
|
+
|
|
153
|
+
const rawConfig = JSON.parse(fs.readFileSync(path.join(workspace, 'agentxchain.json'), 'utf8'));
|
|
154
|
+
const loaded = normalized.loadNormalizedConfig(rawConfig);
|
|
155
|
+
if (!loaded.ok) {
|
|
156
|
+
throw new Error(`Config normalization failed: ${loaded.errors.join('; ')}`);
|
|
157
|
+
}
|
|
158
|
+
const config = loaded.normalized;
|
|
159
|
+
|
|
160
|
+
const git = (args) => {
|
|
161
|
+
execSync(args, {
|
|
162
|
+
cwd: workspace,
|
|
163
|
+
stdio: 'ignore',
|
|
164
|
+
env: {
|
|
165
|
+
...process.env,
|
|
166
|
+
GIT_AUTHOR_NAME: 'AgentXchain Smoke',
|
|
167
|
+
GIT_AUTHOR_EMAIL: 'smoke@agentxchain.local',
|
|
168
|
+
GIT_COMMITTER_NAME: 'AgentXchain Smoke',
|
|
169
|
+
GIT_COMMITTER_EMAIL: 'smoke@agentxchain.local',
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const readState = () => JSON.parse(fs.readFileSync(path.join(workspace, '.agentxchain/state.json'), 'utf8'));
|
|
175
|
+
const stageResult = (state, overrides = {}) => {
|
|
176
|
+
const base = {
|
|
177
|
+
schema_version: '1.0',
|
|
178
|
+
run_id: state.run_id,
|
|
179
|
+
turn_id: state.current_turn.turn_id,
|
|
180
|
+
role: state.current_turn.assigned_role,
|
|
181
|
+
runtime_id: state.current_turn.runtime_id,
|
|
182
|
+
status: 'completed',
|
|
183
|
+
summary: `Smoke bootstrap turn completed by ${state.current_turn.assigned_role}.`,
|
|
184
|
+
decisions: [
|
|
185
|
+
{
|
|
186
|
+
id: 'DEC-001',
|
|
187
|
+
category: 'process',
|
|
188
|
+
statement: 'Bootstrap progressed to the next governed phase.',
|
|
189
|
+
rationale: 'Live preflight smoke only needs a stable QA entry point.',
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
objections: [
|
|
193
|
+
{
|
|
194
|
+
id: 'OBJ-001',
|
|
195
|
+
severity: 'low',
|
|
196
|
+
statement: 'Bootstrap result is synthetic and exists only to reach QA.',
|
|
197
|
+
status: 'raised',
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
files_changed: [],
|
|
201
|
+
artifacts_created: [],
|
|
202
|
+
verification: {
|
|
203
|
+
status: 'pass',
|
|
204
|
+
commands: ['echo smoke-bootstrap'],
|
|
205
|
+
evidence_summary: 'Synthetic bootstrap succeeded.',
|
|
206
|
+
machine_evidence: [{ command: 'echo smoke-bootstrap', exit_code: 0 }],
|
|
207
|
+
},
|
|
208
|
+
artifact: { type: 'review', ref: null },
|
|
209
|
+
proposed_next_role: 'human',
|
|
210
|
+
phase_transition_request: null,
|
|
211
|
+
run_completion_request: false,
|
|
212
|
+
needs_human_reason: null,
|
|
213
|
+
cost: { input_tokens: 0, output_tokens: 0, usd: 0 },
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
fs.writeFileSync(
|
|
217
|
+
path.join(workspace, '.agentxchain/staging/turn-result.json'),
|
|
218
|
+
JSON.stringify({ ...base, ...overrides }, null, 2) + '\n'
|
|
219
|
+
);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
git('git init');
|
|
223
|
+
git('git add -A');
|
|
224
|
+
git('git commit --allow-empty -m "initial scaffold"');
|
|
225
|
+
|
|
226
|
+
let result = governed.initializeGovernedRun(workspace, config);
|
|
227
|
+
if (!result.ok) throw new Error(result.error);
|
|
228
|
+
|
|
229
|
+
result = governed.assignGovernedTurn(workspace, config, 'pm');
|
|
230
|
+
if (!result.ok) throw new Error(result.error);
|
|
231
|
+
|
|
232
|
+
fs.writeFileSync(path.join(workspace, '.planning/PM_SIGNOFF.md'), '# PM Signoff\nApproved: YES\n');
|
|
233
|
+
fs.writeFileSync(
|
|
234
|
+
path.join(workspace, '.planning/ROADMAP.md'),
|
|
235
|
+
'# Roadmap\n\n## Scope\n\nBootstrap workspace for live api_proxy preflight smoke.\n'
|
|
236
|
+
);
|
|
237
|
+
git('git add -A');
|
|
238
|
+
git('git commit --allow-empty -m "pm artifacts"');
|
|
239
|
+
|
|
240
|
+
stageResult(readState(), {
|
|
241
|
+
summary: 'PM bootstrap completed.',
|
|
242
|
+
artifacts_created: ['.planning/PM_SIGNOFF.md', '.planning/ROADMAP.md'],
|
|
243
|
+
artifact: { type: 'review', path: '.planning/PM_SIGNOFF.md' },
|
|
244
|
+
proposed_next_role: 'human',
|
|
245
|
+
phase_transition_request: 'implementation',
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
result = governed.acceptGovernedTurn(workspace, config);
|
|
249
|
+
if (!result.ok) throw new Error(result.error);
|
|
250
|
+
|
|
251
|
+
result = governed.approvePhaseTransition(workspace);
|
|
252
|
+
if (!result.ok) throw new Error(result.error);
|
|
253
|
+
|
|
254
|
+
git('git add -A');
|
|
255
|
+
git('git commit --allow-empty -m "orchestrator: accept pm turn"');
|
|
256
|
+
|
|
257
|
+
result = governed.assignGovernedTurn(workspace, config, 'dev');
|
|
258
|
+
if (!result.ok) throw new Error(result.error);
|
|
259
|
+
|
|
260
|
+
fs.writeFileSync(path.join(workspace, 'index.js'), 'console.log("smoke");\n');
|
|
261
|
+
fs.writeFileSync(
|
|
262
|
+
path.join(workspace, 'package.json'),
|
|
263
|
+
JSON.stringify({
|
|
264
|
+
name: 'governed-todo-app-smoke',
|
|
265
|
+
version: '0.0.0-smoke',
|
|
266
|
+
private: true,
|
|
267
|
+
type: 'module',
|
|
268
|
+
scripts: {
|
|
269
|
+
test: 'node -e "console.log(\'ok\')"',
|
|
270
|
+
},
|
|
271
|
+
}, null, 2) + '\n'
|
|
272
|
+
);
|
|
273
|
+
git('git add -A');
|
|
274
|
+
git('git commit --allow-empty -m "dev bootstrap"');
|
|
275
|
+
|
|
276
|
+
stageResult(readState(), {
|
|
277
|
+
summary: 'Dev bootstrap completed.',
|
|
278
|
+
files_changed: ['index.js', 'package.json'],
|
|
279
|
+
artifact: { type: 'commit', ref: 'smoke-bootstrap' },
|
|
280
|
+
proposed_next_role: 'qa',
|
|
281
|
+
phase_transition_request: 'qa',
|
|
282
|
+
verification: {
|
|
283
|
+
status: 'pass',
|
|
284
|
+
commands: ['node index.js'],
|
|
285
|
+
evidence_summary: 'Bootstrap implementation is runnable.',
|
|
286
|
+
machine_evidence: [{ command: 'node index.js', exit_code: 0 }],
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
result = governed.acceptGovernedTurn(workspace, config);
|
|
291
|
+
if (!result.ok) throw new Error(result.error);
|
|
292
|
+
|
|
293
|
+
git('git add -A');
|
|
294
|
+
git('git commit --allow-empty -m "orchestrator: accept dev turn"');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
main().catch((err) => {
|
|
298
|
+
console.error(err.message || err);
|
|
299
|
+
process.exit(1);
|
|
300
|
+
});
|
|
301
|
+
NODE
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
run_scenario() {
|
|
305
|
+
local scenario="$1"
|
|
306
|
+
local workspace="$2"
|
|
307
|
+
|
|
308
|
+
node --input-type=commonjs - "$workspace" "$CLI_DIR" "$scenario" <<'NODE'
|
|
309
|
+
const fs = require('fs');
|
|
310
|
+
const path = require('path');
|
|
311
|
+
const { pathToFileURL } = require('url');
|
|
312
|
+
|
|
313
|
+
async function main() {
|
|
314
|
+
const workspace = process.argv[2];
|
|
315
|
+
const cliDir = process.argv[3];
|
|
316
|
+
const scenario = process.argv[4];
|
|
317
|
+
|
|
318
|
+
const normalized = await import(pathToFileURL(path.join(cliDir, 'src/lib/normalized-config.js')).href);
|
|
319
|
+
const governed = await import(pathToFileURL(path.join(cliDir, 'src/lib/governed-state.js')).href);
|
|
320
|
+
const dispatchBundle = await import(pathToFileURL(path.join(cliDir, 'src/lib/dispatch-bundle.js')).href);
|
|
321
|
+
const apiProxy = await import(pathToFileURL(path.join(cliDir, 'src/lib/adapters/api-proxy-adapter.js')).href);
|
|
322
|
+
const validator = await import(pathToFileURL(path.join(cliDir, 'src/lib/turn-result-validator.js')).href);
|
|
323
|
+
|
|
324
|
+
const rawConfig = JSON.parse(fs.readFileSync(path.join(workspace, 'agentxchain.json'), 'utf8'));
|
|
325
|
+
const loaded = normalized.loadNormalizedConfig(rawConfig);
|
|
326
|
+
if (!loaded.ok) {
|
|
327
|
+
throw new Error(`Config normalization failed: ${loaded.errors.join('; ')}`);
|
|
328
|
+
}
|
|
329
|
+
const config = loaded.normalized;
|
|
330
|
+
let promptOverride = null;
|
|
331
|
+
|
|
332
|
+
let assign = governed.assignGovernedTurn(workspace, config, 'qa');
|
|
333
|
+
if (!assign.ok) {
|
|
334
|
+
throw new Error(assign.error);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (scenario === 'happy') {
|
|
338
|
+
// Use a literal PROMPT.md override for the live happy path so the smoke
|
|
339
|
+
// harness exercises adapter/preflight behavior instead of general QA
|
|
340
|
+
// prompt-following variance.
|
|
341
|
+
const runId = assign.state.run_id;
|
|
342
|
+
const turnId = assign.state.current_turn.turn_id;
|
|
343
|
+
promptOverride = `# Live QA Smoke Prompt Override
|
|
344
|
+
|
|
345
|
+
Return ONLY the JSON object below EXACTLY as written.
|
|
346
|
+
Do not add any text before or after it.
|
|
347
|
+
Do not wrap it in markdown fences.
|
|
348
|
+
Do not change field names, values, array element types, or nulls.
|
|
349
|
+
In particular, \`artifacts_created\` must remain an empty array of strings.
|
|
350
|
+
|
|
351
|
+
{
|
|
352
|
+
"schema_version": "1.0",
|
|
353
|
+
"run_id": "${runId}",
|
|
354
|
+
"turn_id": "${turnId}",
|
|
355
|
+
"role": "qa",
|
|
356
|
+
"runtime_id": "api-qa",
|
|
357
|
+
"status": "completed",
|
|
358
|
+
"summary": "QA smoke response returned exact governed JSON for live preflight validation.",
|
|
359
|
+
"decisions": [
|
|
360
|
+
{
|
|
361
|
+
"id": "DEC-001",
|
|
362
|
+
"category": "quality",
|
|
363
|
+
"statement": "Happy-path smoke dispatch succeeded.",
|
|
364
|
+
"rationale": "This harness validates provider-backed preflight send plus governed JSON extraction."
|
|
365
|
+
}
|
|
366
|
+
],
|
|
367
|
+
"objections": [
|
|
368
|
+
{
|
|
369
|
+
"id": "OBJ-001",
|
|
370
|
+
"severity": "low",
|
|
371
|
+
"statement": "This is a synthetic smoke artifact, not a substantive QA verdict.",
|
|
372
|
+
"status": "raised"
|
|
373
|
+
}
|
|
374
|
+
],
|
|
375
|
+
"files_changed": [],
|
|
376
|
+
"artifacts_created": [],
|
|
377
|
+
"verification": {
|
|
378
|
+
"status": "skipped",
|
|
379
|
+
"commands": [],
|
|
380
|
+
"evidence_summary": "Smoke harness response only; no verification commands were run.",
|
|
381
|
+
"machine_evidence": []
|
|
382
|
+
},
|
|
383
|
+
"artifact": {
|
|
384
|
+
"type": "review",
|
|
385
|
+
"ref": null
|
|
386
|
+
},
|
|
387
|
+
"proposed_next_role": "human",
|
|
388
|
+
"phase_transition_request": null,
|
|
389
|
+
"run_completion_request": null,
|
|
390
|
+
"needs_human_reason": null,
|
|
391
|
+
"cost": {
|
|
392
|
+
"input_tokens": 0,
|
|
393
|
+
"output_tokens": 0,
|
|
394
|
+
"usd": 0
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
`;
|
|
398
|
+
} else if (scenario === 'overflow') {
|
|
399
|
+
const filler = '\n\n## Overflow Filler\n' + 'This line intentionally inflates the QA prompt.\n'.repeat(800);
|
|
400
|
+
fs.appendFileSync(path.join(workspace, '.agentxchain/prompts/qa.md'), filler);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const bundle = dispatchBundle.writeDispatchBundle(workspace, assign.state, config);
|
|
404
|
+
if (!bundle.ok) {
|
|
405
|
+
throw new Error(bundle.error);
|
|
406
|
+
}
|
|
407
|
+
if (promptOverride) {
|
|
408
|
+
fs.writeFileSync(
|
|
409
|
+
path.join(workspace, '.agentxchain/dispatch/current/PROMPT.md'),
|
|
410
|
+
promptOverride
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const dispatch = await apiProxy.dispatchApiProxy(workspace, assign.state, config);
|
|
415
|
+
const tokenBudgetPath = path.join(workspace, '.agentxchain/dispatch/current/TOKEN_BUDGET.json');
|
|
416
|
+
const effectiveContextPath = path.join(workspace, '.agentxchain/dispatch/current/CONTEXT.effective.md');
|
|
417
|
+
const providerResponsePath = path.join(workspace, '.agentxchain/staging/provider-response.json');
|
|
418
|
+
const stagedResultPath = path.join(workspace, '.agentxchain/staging/turn-result.json');
|
|
419
|
+
|
|
420
|
+
if (!fs.existsSync(tokenBudgetPath)) {
|
|
421
|
+
throw new Error('TOKEN_BUDGET.json missing');
|
|
422
|
+
}
|
|
423
|
+
if (!fs.existsSync(effectiveContextPath)) {
|
|
424
|
+
throw new Error('CONTEXT.effective.md missing');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const report = JSON.parse(fs.readFileSync(tokenBudgetPath, 'utf8'));
|
|
428
|
+
const sentToProvider = report.sent_to_provider === true;
|
|
429
|
+
const providerResponseExists = fs.existsSync(providerResponsePath);
|
|
430
|
+
const stagedResultExists = fs.existsSync(stagedResultPath);
|
|
431
|
+
|
|
432
|
+
if (scenario === 'happy') {
|
|
433
|
+
if (!dispatch.ok) {
|
|
434
|
+
throw new Error(`Happy-path dispatch failed: ${dispatch.error}`);
|
|
435
|
+
}
|
|
436
|
+
if (!sentToProvider) {
|
|
437
|
+
throw new Error('Happy-path report indicates sent_to_provider = false');
|
|
438
|
+
}
|
|
439
|
+
if (!providerResponseExists) {
|
|
440
|
+
throw new Error('provider-response.json missing for happy path');
|
|
441
|
+
}
|
|
442
|
+
if (!stagedResultExists) {
|
|
443
|
+
throw new Error('turn-result.json missing for happy path');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const validation = validator.validateStagedTurnResult(workspace, assign.state, config);
|
|
447
|
+
console.log(JSON.stringify({
|
|
448
|
+
mode: scenario,
|
|
449
|
+
workspace,
|
|
450
|
+
dispatch_ok: true,
|
|
451
|
+
validation_ok: validation.ok === true,
|
|
452
|
+
error_class: null,
|
|
453
|
+
sent_to_provider: true,
|
|
454
|
+
token_budget_path: tokenBudgetPath,
|
|
455
|
+
effective_context_path: effectiveContextPath,
|
|
456
|
+
provider_response_path: providerResponsePath,
|
|
457
|
+
staged_result_path: stagedResultPath,
|
|
458
|
+
}, null, 2));
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (dispatch.ok) {
|
|
463
|
+
throw new Error('Overflow-path dispatch unexpectedly succeeded');
|
|
464
|
+
}
|
|
465
|
+
if (dispatch.classified?.error_class !== 'context_overflow') {
|
|
466
|
+
throw new Error(`Expected context_overflow, got ${dispatch.classified?.error_class || 'unknown'}`);
|
|
467
|
+
}
|
|
468
|
+
if (sentToProvider) {
|
|
469
|
+
throw new Error('Overflow-path report indicates sent_to_provider = true');
|
|
470
|
+
}
|
|
471
|
+
if (providerResponseExists) {
|
|
472
|
+
throw new Error('provider-response.json should not exist for overflow path');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
console.log(JSON.stringify({
|
|
476
|
+
mode: scenario,
|
|
477
|
+
workspace,
|
|
478
|
+
dispatch_ok: false,
|
|
479
|
+
error_class: dispatch.classified?.error_class || null,
|
|
480
|
+
sent_to_provider: false,
|
|
481
|
+
token_budget_path: tokenBudgetPath,
|
|
482
|
+
effective_context_path: effectiveContextPath,
|
|
483
|
+
provider_response_path: providerResponsePath,
|
|
484
|
+
staged_result_path: stagedResultPath,
|
|
485
|
+
}, null, 2));
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
main().catch((err) => {
|
|
489
|
+
console.error(err.message || err);
|
|
490
|
+
process.exit(1);
|
|
491
|
+
});
|
|
492
|
+
NODE
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
cleanup_workspace() {
|
|
496
|
+
local workspace="$1"
|
|
497
|
+
if [[ "$KEEP_TEMP" -eq 1 ]]; then
|
|
498
|
+
return 0
|
|
499
|
+
fi
|
|
500
|
+
rm -rf "$workspace"
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
require_cmd node
|
|
504
|
+
require_cmd git
|
|
505
|
+
load_repo_env
|
|
506
|
+
|
|
507
|
+
if [[ "$MODE" != "overflow" && -z "${ANTHROPIC_API_KEY:-}" ]]; then
|
|
508
|
+
echo "ANTHROPIC_API_KEY is not set. Load it in the shell or repo-root .env before running this harness." >&2
|
|
509
|
+
exit 1
|
|
510
|
+
fi
|
|
511
|
+
|
|
512
|
+
echo "Live api_proxy preflight smoke"
|
|
513
|
+
echo "Mode: $MODE"
|
|
514
|
+
|
|
515
|
+
if [[ "$MODE" == "happy" || "$MODE" == "both" ]]; then
|
|
516
|
+
HAPPY_WORKSPACE="$(make_workspace happy)"
|
|
517
|
+
echo "Happy workspace: $HAPPY_WORKSPACE"
|
|
518
|
+
patch_runtime_config "$HAPPY_WORKSPACE" happy
|
|
519
|
+
bootstrap_to_qa "$HAPPY_WORKSPACE"
|
|
520
|
+
run_scenario happy "$HAPPY_WORKSPACE"
|
|
521
|
+
cleanup_workspace "$HAPPY_WORKSPACE"
|
|
522
|
+
fi
|
|
523
|
+
|
|
524
|
+
if [[ "$MODE" == "overflow" || "$MODE" == "both" ]]; then
|
|
525
|
+
OVERFLOW_WORKSPACE="$(make_workspace overflow)"
|
|
526
|
+
echo "Overflow workspace: $OVERFLOW_WORKSPACE"
|
|
527
|
+
patch_runtime_config "$OVERFLOW_WORKSPACE" overflow
|
|
528
|
+
bootstrap_to_qa "$OVERFLOW_WORKSPACE"
|
|
529
|
+
run_scenario overflow "$OVERFLOW_WORKSPACE"
|
|
530
|
+
cleanup_workspace "$OVERFLOW_WORKSPACE"
|
|
531
|
+
fi
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
5
|
+
CLI_DIR="${SCRIPT_DIR}/.."
|
|
6
|
+
cd "$CLI_DIR"
|
|
7
|
+
|
|
8
|
+
TMP_NPMRC=""
|
|
9
|
+
|
|
10
|
+
cleanup() {
|
|
11
|
+
if [[ -n "$TMP_NPMRC" && -f "$TMP_NPMRC" ]]; then
|
|
12
|
+
rm -f "$TMP_NPMRC"
|
|
13
|
+
fi
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
trap cleanup EXIT
|
|
17
|
+
|
|
18
|
+
usage() {
|
|
19
|
+
echo "Usage: bash scripts/publish-from-tag.sh <vX.Y.Z>" >&2
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
TAG="${1:-}"
|
|
23
|
+
if [[ -z "$TAG" ]]; then
|
|
24
|
+
echo "Error: release tag is required" >&2
|
|
25
|
+
usage
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
if ! [[ "$TAG" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
|
|
30
|
+
echo "Error: release tag must match v<semver>; got '${TAG}'" >&2
|
|
31
|
+
usage
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
RELEASE_VERSION="${BASH_REMATCH[1]}"
|
|
36
|
+
PACKAGE_NAME="$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json', 'utf8')).name)")"
|
|
37
|
+
PACKAGE_VERSION="$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json', 'utf8')).version)")"
|
|
38
|
+
|
|
39
|
+
if [[ "$PACKAGE_VERSION" != "$RELEASE_VERSION" ]]; then
|
|
40
|
+
echo "Error: package.json version is ${PACKAGE_VERSION}, expected ${RELEASE_VERSION} from ${TAG}" >&2
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
RETRY_ATTEMPTS="${NPM_VIEW_RETRY_ATTEMPTS:-12}"
|
|
45
|
+
RETRY_DELAY_SECONDS="${NPM_VIEW_RETRY_DELAY_SECONDS:-5}"
|
|
46
|
+
|
|
47
|
+
if ! [[ "$RETRY_ATTEMPTS" =~ ^[0-9]+$ ]] || [[ "$RETRY_ATTEMPTS" -lt 1 ]]; then
|
|
48
|
+
echo "Error: NPM_VIEW_RETRY_ATTEMPTS must be a positive integer" >&2
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
if ! [[ "$RETRY_DELAY_SECONDS" =~ ^[0-9]+$ ]]; then
|
|
53
|
+
echo "Error: NPM_VIEW_RETRY_DELAY_SECONDS must be a non-negative integer" >&2
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
echo "Publishing ${PACKAGE_NAME}@${RELEASE_VERSION} from ${TAG}"
|
|
58
|
+
echo "Running strict release preflight..."
|
|
59
|
+
bash scripts/release-preflight.sh --strict --target-version "${RELEASE_VERSION}"
|
|
60
|
+
|
|
61
|
+
echo "Running npm publish..."
|
|
62
|
+
if [[ -n "${NPM_TOKEN:-}" ]]; then
|
|
63
|
+
echo "Publish auth mode: token"
|
|
64
|
+
TMP_NPMRC="$(mktemp "${TMPDIR:-/tmp}/agentxchain-npmrc.XXXXXX")"
|
|
65
|
+
chmod 600 "$TMP_NPMRC"
|
|
66
|
+
printf '%s\n' "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > "$TMP_NPMRC"
|
|
67
|
+
NPM_CONFIG_USERCONFIG="$TMP_NPMRC" npm publish --access public
|
|
68
|
+
else
|
|
69
|
+
echo "Publish auth mode: trusted publishing (OIDC)"
|
|
70
|
+
npm publish --access public
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
echo "Verifying registry visibility..."
|
|
74
|
+
for ((attempt = 1; attempt <= RETRY_ATTEMPTS; attempt++)); do
|
|
75
|
+
PUBLISHED_VERSION="$(npm view "${PACKAGE_NAME}@${RELEASE_VERSION}" version 2>/dev/null || true)"
|
|
76
|
+
if [[ "$PUBLISHED_VERSION" == "$RELEASE_VERSION" ]]; then
|
|
77
|
+
echo "Verified ${PACKAGE_NAME}@${RELEASE_VERSION} on npm (attempt ${attempt}/${RETRY_ATTEMPTS})"
|
|
78
|
+
exit 0
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
if [[ "$attempt" -lt "$RETRY_ATTEMPTS" ]]; then
|
|
82
|
+
echo "Registry not updated yet (attempt ${attempt}/${RETRY_ATTEMPTS}); retrying in ${RETRY_DELAY_SECONDS}s..."
|
|
83
|
+
sleep "$RETRY_DELAY_SECONDS"
|
|
84
|
+
fi
|
|
85
|
+
done
|
|
86
|
+
|
|
87
|
+
echo "Error: npm registry did not serve ${PACKAGE_NAME}@${RELEASE_VERSION} after ${RETRY_ATTEMPTS} attempts" >&2
|
|
88
|
+
exit 1
|