agentxchain 0.8.8 → 2.2.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 +136 -136
- 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 +858 -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,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coordinator-scoped hook execution.
|
|
3
|
+
*
|
|
4
|
+
* Four phases, distinct from repo-local hook phases:
|
|
5
|
+
* - before_assignment (blocking): can prevent dispatch
|
|
6
|
+
* - after_acceptance (advisory): notification after projection
|
|
7
|
+
* - before_gate (blocking): can prevent phase/completion approval
|
|
8
|
+
* - on_escalation (advisory): fires when coordinator enters blocked
|
|
9
|
+
*
|
|
10
|
+
* Design rules:
|
|
11
|
+
* - Coordinator hooks NEVER write to repo-local state, history, or bundles
|
|
12
|
+
* - Coordinator hooks are defined in agentxchain-multi.json under "hooks"
|
|
13
|
+
* - Reuses the repo-local hook-runner for process execution
|
|
14
|
+
* - Hook payloads include coordinator context (super_run_id, workstreams, barriers)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
18
|
+
import { join, relative } from 'node:path';
|
|
19
|
+
import { runHooks } from './hook-runner.js';
|
|
20
|
+
import { readBarriers } from './coordinator-state.js';
|
|
21
|
+
|
|
22
|
+
// ── Phase Definitions ───────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const COORDINATOR_HOOK_PHASES = [
|
|
25
|
+
'before_assignment',
|
|
26
|
+
'after_acceptance',
|
|
27
|
+
'before_gate',
|
|
28
|
+
'on_escalation',
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const BLOCKING_PHASES = new Set(['before_assignment', 'before_gate']);
|
|
32
|
+
const REPO_LOCAL_PROTECTED_FILES = [
|
|
33
|
+
'.agentxchain/state.json',
|
|
34
|
+
'.agentxchain/history.jsonl',
|
|
35
|
+
'.agentxchain/decision-ledger.jsonl',
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
function summarizePendingBarriers(barriers) {
|
|
39
|
+
return Object.entries(barriers)
|
|
40
|
+
.filter(([, barrier]) => barrier?.status === 'pending' || barrier?.status === 'partially_satisfied')
|
|
41
|
+
.map(([barrierId, barrier]) => ({
|
|
42
|
+
barrier_id: barrierId,
|
|
43
|
+
workstream_id: barrier.workstream_id ?? null,
|
|
44
|
+
type: barrier.type ?? null,
|
|
45
|
+
status: barrier.status ?? null,
|
|
46
|
+
required_repos: Array.isArray(barrier.required_repos) ? [...barrier.required_repos] : [],
|
|
47
|
+
satisfied_repos: Array.isArray(barrier.satisfied_repos) ? [...barrier.satisfied_repos] : [],
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function walkFiles(rootPath) {
|
|
52
|
+
if (!existsSync(rootPath)) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const entries = readdirSync(rootPath, { withFileTypes: true });
|
|
57
|
+
const files = [];
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
const absPath = join(rootPath, entry.name);
|
|
60
|
+
if (entry.isDirectory()) {
|
|
61
|
+
files.push(...walkFiles(absPath));
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (entry.isFile()) {
|
|
65
|
+
files.push(absPath);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return files;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function collectRepoProtectedPaths(workspacePath, config) {
|
|
72
|
+
const protectedPaths = [];
|
|
73
|
+
|
|
74
|
+
for (const repo of Object.values(config.repos || {})) {
|
|
75
|
+
if (!repo?.resolved_path) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const relPath of REPO_LOCAL_PROTECTED_FILES) {
|
|
80
|
+
protectedPaths.push(relative(workspacePath, join(repo.resolved_path, relPath)));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const dispatchRoot = join(repo.resolved_path, '.agentxchain', 'dispatch');
|
|
84
|
+
for (const filePath of walkFiles(dispatchRoot)) {
|
|
85
|
+
protectedPaths.push(relative(workspacePath, filePath));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return protectedPaths;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── Core API ────────────────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Fire coordinator-scoped hooks for a given phase.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} workspacePath - coordinator workspace root
|
|
98
|
+
* @param {object} config - normalized coordinator config
|
|
99
|
+
* @param {string} phase - one of the four coordinator hook phases
|
|
100
|
+
* @param {object} payload - phase-specific payload data
|
|
101
|
+
* @param {object} [options] - additional options
|
|
102
|
+
* @param {string} [options.super_run_id] - current super run ID
|
|
103
|
+
* @returns {{ ok: boolean, blocked: boolean, verdicts: object[] }}
|
|
104
|
+
*/
|
|
105
|
+
export function fireCoordinatorHook(workspacePath, config, phase, payload, options = {}) {
|
|
106
|
+
if (!COORDINATOR_HOOK_PHASES.includes(phase)) {
|
|
107
|
+
return { ok: false, blocked: false, verdicts: [], error: `Invalid coordinator hook phase: "${phase}"` };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const hooksConfig = config.hooks;
|
|
111
|
+
if (!hooksConfig || !hooksConfig[phase] || !Array.isArray(hooksConfig[phase]) || hooksConfig[phase].length === 0) {
|
|
112
|
+
return { ok: true, blocked: false, verdicts: [] };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Build coordinator-scoped payload
|
|
116
|
+
const barriers = readBarriers(workspacePath);
|
|
117
|
+
const coordinatorPayload = {
|
|
118
|
+
...payload,
|
|
119
|
+
super_run_id: options.super_run_id || null,
|
|
120
|
+
pending_barriers: summarizePendingBarriers(barriers),
|
|
121
|
+
pending_gate: payload.pending_gate ?? null,
|
|
122
|
+
coordinator_workspace: workspacePath,
|
|
123
|
+
barriers: Object.fromEntries(
|
|
124
|
+
Object.entries(barriers).map(([id, b]) => [id, { status: b.status, type: b.type }])
|
|
125
|
+
),
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Coordinator hooks protect coordinator state files, not repo-local ones
|
|
129
|
+
const protectedPaths = [
|
|
130
|
+
'.agentxchain/multirepo/state.json',
|
|
131
|
+
'.agentxchain/multirepo/history.jsonl',
|
|
132
|
+
'.agentxchain/multirepo/barriers.json',
|
|
133
|
+
'.agentxchain/multirepo/barrier-ledger.jsonl',
|
|
134
|
+
...collectRepoProtectedPaths(workspacePath, config),
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
const auditDir = join(workspacePath, '.agentxchain', 'multirepo');
|
|
138
|
+
|
|
139
|
+
const result = runHooks(workspacePath, hooksConfig, phase, coordinatorPayload, {
|
|
140
|
+
run_id: options.super_run_id || '',
|
|
141
|
+
protectedPaths,
|
|
142
|
+
auditDir,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const verdicts = (result.results || []).map(r => ({
|
|
146
|
+
hook_name: r.hook_name,
|
|
147
|
+
verdict: r.verdict,
|
|
148
|
+
message: r.message,
|
|
149
|
+
orchestrator_action: r.orchestrator_action,
|
|
150
|
+
}));
|
|
151
|
+
|
|
152
|
+
// For blocking phases, check if any hook blocked
|
|
153
|
+
const blocked = BLOCKING_PHASES.has(phase) && result.blocked === true;
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
ok: result.ok !== false,
|
|
157
|
+
blocked,
|
|
158
|
+
verdicts,
|
|
159
|
+
tamper: result.tamper || null,
|
|
160
|
+
error: result.tamper?.message || result.blocker?.message || null,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Build the payload for a before_assignment hook.
|
|
166
|
+
*/
|
|
167
|
+
export function buildAssignmentPayload(assignment, state) {
|
|
168
|
+
return {
|
|
169
|
+
phase: 'before_assignment',
|
|
170
|
+
workstream_id: assignment.workstream_id,
|
|
171
|
+
repo_id: assignment.repo_id,
|
|
172
|
+
repo_run_id: state.repo_runs?.[assignment.repo_id]?.run_id ?? null,
|
|
173
|
+
role: assignment.role,
|
|
174
|
+
coordinator_status: state.status,
|
|
175
|
+
coordinator_phase: state.phase,
|
|
176
|
+
pending_gate: state.pending_gate || null,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Build the payload for an after_acceptance hook.
|
|
182
|
+
*/
|
|
183
|
+
export function buildAcceptancePayload(projectionResult, repoId, workstreamId, state) {
|
|
184
|
+
return {
|
|
185
|
+
phase: 'after_acceptance',
|
|
186
|
+
workstream_id: workstreamId,
|
|
187
|
+
repo_id: repoId,
|
|
188
|
+
repo_run_id: state.repo_runs?.[repoId]?.run_id ?? null,
|
|
189
|
+
projection_ref: projectionResult.projection_ref,
|
|
190
|
+
repo_turn_id: projectionResult.repo_turn_id ?? null,
|
|
191
|
+
summary: projectionResult.summary ?? '',
|
|
192
|
+
files_changed: projectionResult.files_changed || [],
|
|
193
|
+
decisions: projectionResult.decisions || [],
|
|
194
|
+
verification: projectionResult.verification ?? null,
|
|
195
|
+
barrier_effects: projectionResult.barrier_effects || [],
|
|
196
|
+
context_invalidations: projectionResult.context_invalidations || [],
|
|
197
|
+
coordinator_status: state.status,
|
|
198
|
+
coordinator_phase: state.phase,
|
|
199
|
+
pending_gate: state.pending_gate || null,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Build the payload for a before_gate hook.
|
|
205
|
+
*/
|
|
206
|
+
export function buildGatePayload(pendingGate, state) {
|
|
207
|
+
return {
|
|
208
|
+
phase: 'before_gate',
|
|
209
|
+
workstream_id: pendingGate.workstream_id || null,
|
|
210
|
+
repo_id: null,
|
|
211
|
+
repo_run_id: null,
|
|
212
|
+
gate_type: pendingGate.gate_type,
|
|
213
|
+
gate: pendingGate.gate,
|
|
214
|
+
from_phase: pendingGate.from || null,
|
|
215
|
+
to_phase: pendingGate.to || null,
|
|
216
|
+
required_repos: pendingGate.required_repos || [],
|
|
217
|
+
human_barriers: pendingGate.human_barriers || [],
|
|
218
|
+
coordinator_status: state.status,
|
|
219
|
+
coordinator_phase: state.phase,
|
|
220
|
+
pending_gate: pendingGate,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Build the payload for an on_escalation hook.
|
|
226
|
+
*/
|
|
227
|
+
export function buildEscalationPayload(blockedReason, state) {
|
|
228
|
+
return {
|
|
229
|
+
phase: 'on_escalation',
|
|
230
|
+
workstream_id: null,
|
|
231
|
+
repo_id: null,
|
|
232
|
+
repo_run_id: null,
|
|
233
|
+
blocked_reason: blockedReason,
|
|
234
|
+
coordinator_status: state.status,
|
|
235
|
+
coordinator_phase: state.phase,
|
|
236
|
+
pending_gate: state.pending_gate || null,
|
|
237
|
+
repo_runs: state.repo_runs || {},
|
|
238
|
+
};
|
|
239
|
+
}
|