agentxchain 0.8.8 → 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 +126 -142
- package/bin/agentxchain.js +186 -5
- 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 +14 -6
- 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/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/dashboard.js +70 -0
- package/src/commands/init.js +516 -0
- 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/template-list.js +33 -0
- package/src/commands/template-set.js +279 -0
- package/src/commands/validate.js +20 -11
- package/src/commands/verify.js +71 -0
- 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 +97 -1
- 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/gate-evaluator.js +285 -0
- 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/normalized-config.js +539 -0
- package/src/lib/plugin-config-schema.js +192 -0
- package/src/lib/plugins.js +692 -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/schema.js +121 -0
- package/src/lib/schemas/turn-result.schema.json +205 -0
- 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 +137 -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,291 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const DEFAULT_FIXTURE_ROOT = resolve(__dirname, '..', '..', '..', '.agentxchain-conformance', 'fixtures');
|
|
8
|
+
const VALID_RESPONSE_STATUSES = new Set(['pass', 'fail', 'error']);
|
|
9
|
+
const VALID_TIERS = new Set([1, 2, 3]);
|
|
10
|
+
|
|
11
|
+
function readJsonFile(filePath) {
|
|
12
|
+
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function listJsonFiles(root) {
|
|
16
|
+
const files = [];
|
|
17
|
+
|
|
18
|
+
function walk(dir) {
|
|
19
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
20
|
+
const fullPath = join(dir, entry.name);
|
|
21
|
+
if (entry.isDirectory()) {
|
|
22
|
+
walk(fullPath);
|
|
23
|
+
} else if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
24
|
+
files.push(fullPath);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
walk(root);
|
|
30
|
+
return files.sort();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function validateFixtureShape(fixture, filePath) {
|
|
34
|
+
const errors = [];
|
|
35
|
+
if (!fixture || typeof fixture !== 'object' || Array.isArray(fixture)) {
|
|
36
|
+
return [`${filePath}: fixture must be a JSON object`];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const required = ['fixture_id', 'tier', 'surface', 'description', 'type', 'setup', 'input', 'expected'];
|
|
40
|
+
for (const field of required) {
|
|
41
|
+
if (!(field in fixture)) {
|
|
42
|
+
errors.push(`${filePath}: missing required field "${field}"`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!Number.isInteger(fixture.tier) || !VALID_TIERS.has(fixture.tier)) {
|
|
47
|
+
errors.push(`${filePath}: tier must be 1, 2, or 3`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!fixture.input || typeof fixture.input !== 'object' || Array.isArray(fixture.input)) {
|
|
51
|
+
errors.push(`${filePath}: input must be an object`);
|
|
52
|
+
} else if (typeof fixture.input.operation !== 'string' || !fixture.input.operation.trim()) {
|
|
53
|
+
errors.push(`${filePath}: input.operation must be a non-empty string`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return errors;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function loadCapabilities(targetRoot) {
|
|
60
|
+
const capabilitiesPath = join(targetRoot, '.agentxchain-conformance', 'capabilities.json');
|
|
61
|
+
if (!existsSync(capabilitiesPath)) {
|
|
62
|
+
throw new Error(`Missing capabilities file at ${capabilitiesPath}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const capabilities = readJsonFile(capabilitiesPath);
|
|
66
|
+
const errors = [];
|
|
67
|
+
|
|
68
|
+
if (typeof capabilities.implementation !== 'string' || !capabilities.implementation.trim()) {
|
|
69
|
+
errors.push('capabilities.implementation must be a non-empty string');
|
|
70
|
+
}
|
|
71
|
+
if (!Array.isArray(capabilities.tiers) || capabilities.tiers.length === 0) {
|
|
72
|
+
errors.push('capabilities.tiers must be a non-empty array');
|
|
73
|
+
} else {
|
|
74
|
+
for (const tier of capabilities.tiers) {
|
|
75
|
+
if (!VALID_TIERS.has(tier)) {
|
|
76
|
+
errors.push(`capabilities.tiers contains invalid tier "${tier}"`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!capabilities.adapter || typeof capabilities.adapter !== 'object' || Array.isArray(capabilities.adapter)) {
|
|
81
|
+
errors.push('capabilities.adapter must be an object');
|
|
82
|
+
} else {
|
|
83
|
+
if (capabilities.adapter.protocol !== 'stdio-fixture-v1') {
|
|
84
|
+
errors.push('capabilities.adapter.protocol must be "stdio-fixture-v1"');
|
|
85
|
+
}
|
|
86
|
+
if (!Array.isArray(capabilities.adapter.command) || capabilities.adapter.command.length === 0) {
|
|
87
|
+
errors.push('capabilities.adapter.command must be a non-empty array');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (errors.length > 0) {
|
|
92
|
+
throw new Error(`Invalid capabilities.json: ${errors.join('; ')}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return capabilities;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function selectFixtureFiles(fixtureRoot, requestedTier, surface) {
|
|
99
|
+
if (!existsSync(fixtureRoot)) {
|
|
100
|
+
throw new Error(`Fixture root not found at ${fixtureRoot}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return listJsonFiles(fixtureRoot)
|
|
104
|
+
.filter((filePath) => {
|
|
105
|
+
const fixture = readJsonFile(filePath);
|
|
106
|
+
const shapeErrors = validateFixtureShape(fixture, filePath);
|
|
107
|
+
if (shapeErrors.length > 0) {
|
|
108
|
+
throw new Error(shapeErrors.join('; '));
|
|
109
|
+
}
|
|
110
|
+
return fixture.tier <= requestedTier && (!surface || fixture.surface === surface);
|
|
111
|
+
})
|
|
112
|
+
.map((filePath) => ({ filePath, fixture: readJsonFile(filePath) }));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function createTierSummary(status = 'skipped', note = null) {
|
|
116
|
+
return {
|
|
117
|
+
status,
|
|
118
|
+
fixtures_run: 0,
|
|
119
|
+
fixtures_passed: 0,
|
|
120
|
+
fixtures_failed: 0,
|
|
121
|
+
fixtures_errored: 0,
|
|
122
|
+
surfaces: {},
|
|
123
|
+
failures: [],
|
|
124
|
+
errors: [],
|
|
125
|
+
...(note ? { note } : {}),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function ensureSurfaceSummary(tierSummary, surface) {
|
|
130
|
+
if (!tierSummary.surfaces[surface]) {
|
|
131
|
+
tierSummary.surfaces[surface] = { passed: 0, failed: 0, errored: 0 };
|
|
132
|
+
}
|
|
133
|
+
return tierSummary.surfaces[surface];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function executeFixture(targetRoot, adapterCommand, fixture) {
|
|
137
|
+
const [executable, ...args] = adapterCommand;
|
|
138
|
+
const result = spawnSync(executable, args, {
|
|
139
|
+
cwd: targetRoot,
|
|
140
|
+
encoding: 'utf8',
|
|
141
|
+
input: `${JSON.stringify(fixture)}\n`,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (result.error) {
|
|
145
|
+
return {
|
|
146
|
+
status: 'error',
|
|
147
|
+
message: `Failed to execute adapter: ${result.error.message}`,
|
|
148
|
+
actual: null,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (![0, 1, 2].includes(result.status ?? -1)) {
|
|
153
|
+
return {
|
|
154
|
+
status: 'error',
|
|
155
|
+
message: `Adapter exited with unsupported status ${result.status}`,
|
|
156
|
+
actual: {
|
|
157
|
+
exit_code: result.status,
|
|
158
|
+
stderr: result.stderr?.trim() || '',
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let parsed;
|
|
164
|
+
try {
|
|
165
|
+
parsed = JSON.parse((result.stdout || '').trim() || '{}');
|
|
166
|
+
} catch (error) {
|
|
167
|
+
return {
|
|
168
|
+
status: 'error',
|
|
169
|
+
message: `Malformed adapter response: ${error.message}`,
|
|
170
|
+
actual: {
|
|
171
|
+
stdout: result.stdout || '',
|
|
172
|
+
stderr: result.stderr || '',
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!VALID_RESPONSE_STATUSES.has(parsed.status)) {
|
|
178
|
+
return {
|
|
179
|
+
status: 'error',
|
|
180
|
+
message: 'Adapter response missing valid "status"',
|
|
181
|
+
actual: parsed,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const expectedExitCode = parsed.status === 'pass' ? 0 : parsed.status === 'fail' ? 1 : 2;
|
|
186
|
+
if (result.status !== expectedExitCode) {
|
|
187
|
+
return {
|
|
188
|
+
status: 'error',
|
|
189
|
+
message: `Adapter exit code ${result.status} does not match response status "${parsed.status}"`,
|
|
190
|
+
actual: parsed,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return parsed;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function getDefaultFixtureRoot() {
|
|
198
|
+
return DEFAULT_FIXTURE_ROOT;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function verifyProtocolConformance({
|
|
202
|
+
targetRoot,
|
|
203
|
+
requestedTier = 1,
|
|
204
|
+
surface = null,
|
|
205
|
+
fixtureRoot = DEFAULT_FIXTURE_ROOT,
|
|
206
|
+
}) {
|
|
207
|
+
if (!Number.isInteger(requestedTier) || !VALID_TIERS.has(requestedTier)) {
|
|
208
|
+
throw new Error(`Tier must be 1, 2, or 3. Received "${requestedTier}"`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const resolvedTargetRoot = resolve(targetRoot);
|
|
212
|
+
const capabilities = loadCapabilities(resolvedTargetRoot);
|
|
213
|
+
const fixtureEntries = selectFixtureFiles(fixtureRoot, requestedTier, surface);
|
|
214
|
+
const claimedTiers = new Set(capabilities.tiers);
|
|
215
|
+
const report = {
|
|
216
|
+
implementation: capabilities.implementation,
|
|
217
|
+
protocol_version: capabilities.protocol_version || null,
|
|
218
|
+
tier_requested: requestedTier,
|
|
219
|
+
timestamp: new Date().toISOString(),
|
|
220
|
+
target_root: resolvedTargetRoot,
|
|
221
|
+
fixture_root: fixtureRoot,
|
|
222
|
+
results: {},
|
|
223
|
+
overall: 'pass',
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
for (let tier = 1; tier <= requestedTier; tier += 1) {
|
|
227
|
+
if (claimedTiers.has(tier)) {
|
|
228
|
+
report.results[`tier_${tier}`] = createTierSummary('pass');
|
|
229
|
+
} else {
|
|
230
|
+
report.results[`tier_${tier}`] = createTierSummary('skipped', `Target does not claim Tier ${tier}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
for (const { fixture } of fixtureEntries) {
|
|
235
|
+
const tierKey = `tier_${fixture.tier}`;
|
|
236
|
+
const tierSummary = report.results[tierKey];
|
|
237
|
+
|
|
238
|
+
if (!tierSummary || tierSummary.status === 'skipped') {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const surfaceSummary = ensureSurfaceSummary(tierSummary, fixture.surface);
|
|
243
|
+
const adapterResult = executeFixture(resolvedTargetRoot, capabilities.adapter.command, fixture);
|
|
244
|
+
|
|
245
|
+
tierSummary.fixtures_run += 1;
|
|
246
|
+
|
|
247
|
+
if (adapterResult.status === 'pass') {
|
|
248
|
+
tierSummary.fixtures_passed += 1;
|
|
249
|
+
surfaceSummary.passed += 1;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (adapterResult.status === 'fail') {
|
|
254
|
+
tierSummary.fixtures_failed += 1;
|
|
255
|
+
surfaceSummary.failed += 1;
|
|
256
|
+
tierSummary.status = 'fail';
|
|
257
|
+
tierSummary.failures.push({
|
|
258
|
+
fixture_id: fixture.fixture_id,
|
|
259
|
+
surface: fixture.surface,
|
|
260
|
+
message: adapterResult.message || 'Fixture failed',
|
|
261
|
+
actual: adapterResult.actual ?? null,
|
|
262
|
+
});
|
|
263
|
+
report.overall = 'fail';
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
tierSummary.fixtures_errored += 1;
|
|
268
|
+
surfaceSummary.errored += 1;
|
|
269
|
+
tierSummary.status = 'error';
|
|
270
|
+
tierSummary.errors.push({
|
|
271
|
+
fixture_id: fixture.fixture_id,
|
|
272
|
+
surface: fixture.surface,
|
|
273
|
+
message: adapterResult.message || 'Fixture errored',
|
|
274
|
+
actual: adapterResult.actual ?? null,
|
|
275
|
+
});
|
|
276
|
+
report.overall = 'error';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (report.overall === 'pass') {
|
|
280
|
+
const hasHardError = Object.values(report.results).some((tier) => tier.status === 'error');
|
|
281
|
+
const hasFailure = Object.values(report.results).some((tier) => tier.status === 'fail');
|
|
282
|
+
if (hasHardError) {
|
|
283
|
+
report.overall = 'error';
|
|
284
|
+
} else if (hasFailure) {
|
|
285
|
+
report.overall = 'fail';
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const exitCode = report.overall === 'pass' ? 0 : report.overall === 'fail' ? 1 : 2;
|
|
290
|
+
return { report, exitCode };
|
|
291
|
+
}
|