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.
Files changed (74) hide show
  1. package/README.md +136 -136
  2. package/bin/agentxchain.js +186 -5
  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 +14 -6
  13. package/scripts/live-api-proxy-preflight-smoke.sh +531 -0
  14. package/scripts/publish-from-tag.sh +88 -0
  15. package/scripts/release-postflight.sh +231 -0
  16. package/scripts/release-preflight.sh +167 -0
  17. package/src/commands/accept-turn.js +160 -0
  18. package/src/commands/approve-completion.js +80 -0
  19. package/src/commands/approve-transition.js +85 -0
  20. package/src/commands/dashboard.js +70 -0
  21. package/src/commands/init.js +516 -0
  22. package/src/commands/migrate.js +348 -0
  23. package/src/commands/multi.js +549 -0
  24. package/src/commands/plugin.js +157 -0
  25. package/src/commands/reject-turn.js +204 -0
  26. package/src/commands/resume.js +389 -0
  27. package/src/commands/status.js +196 -3
  28. package/src/commands/step.js +947 -0
  29. package/src/commands/template-list.js +33 -0
  30. package/src/commands/template-set.js +279 -0
  31. package/src/commands/validate.js +20 -11
  32. package/src/commands/verify.js +71 -0
  33. package/src/lib/adapters/api-proxy-adapter.js +1076 -0
  34. package/src/lib/adapters/local-cli-adapter.js +337 -0
  35. package/src/lib/adapters/manual-adapter.js +169 -0
  36. package/src/lib/blocked-state.js +94 -0
  37. package/src/lib/config.js +97 -1
  38. package/src/lib/context-compressor.js +121 -0
  39. package/src/lib/context-section-parser.js +220 -0
  40. package/src/lib/coordinator-acceptance.js +428 -0
  41. package/src/lib/coordinator-config.js +461 -0
  42. package/src/lib/coordinator-dispatch.js +276 -0
  43. package/src/lib/coordinator-gates.js +487 -0
  44. package/src/lib/coordinator-hooks.js +239 -0
  45. package/src/lib/coordinator-recovery.js +523 -0
  46. package/src/lib/coordinator-state.js +365 -0
  47. package/src/lib/cross-repo-context.js +247 -0
  48. package/src/lib/dashboard/bridge-server.js +284 -0
  49. package/src/lib/dashboard/file-watcher.js +93 -0
  50. package/src/lib/dashboard/state-reader.js +96 -0
  51. package/src/lib/dispatch-bundle.js +568 -0
  52. package/src/lib/dispatch-manifest.js +252 -0
  53. package/src/lib/gate-evaluator.js +285 -0
  54. package/src/lib/governed-state.js +2139 -0
  55. package/src/lib/governed-templates.js +145 -0
  56. package/src/lib/hook-runner.js +788 -0
  57. package/src/lib/normalized-config.js +539 -0
  58. package/src/lib/plugin-config-schema.js +192 -0
  59. package/src/lib/plugins.js +692 -0
  60. package/src/lib/protocol-conformance.js +291 -0
  61. package/src/lib/reference-conformance-adapter.js +858 -0
  62. package/src/lib/repo-observer.js +597 -0
  63. package/src/lib/repo.js +0 -31
  64. package/src/lib/schema.js +121 -0
  65. package/src/lib/schemas/turn-result.schema.json +205 -0
  66. package/src/lib/token-budget.js +206 -0
  67. package/src/lib/token-counter.js +27 -0
  68. package/src/lib/turn-paths.js +67 -0
  69. package/src/lib/turn-result-validator.js +496 -0
  70. package/src/lib/validation.js +137 -0
  71. package/src/templates/governed/api-service.json +31 -0
  72. package/src/templates/governed/cli-tool.json +30 -0
  73. package/src/templates/governed/generic.json +10 -0
  74. 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
+ }