agentxchain 2.103.0 → 2.105.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 (66) hide show
  1. package/README.md +13 -7
  2. package/bin/agentxchain.js +16 -8
  3. package/dashboard/app.js +111 -7
  4. package/dashboard/components/blocked.js +95 -11
  5. package/dashboard/components/blockers.js +85 -86
  6. package/dashboard/components/coordinator-timeouts.js +13 -0
  7. package/dashboard/components/cross-repo.js +17 -12
  8. package/dashboard/components/gate.js +31 -11
  9. package/dashboard/components/initiative.js +173 -78
  10. package/dashboard/components/ledger.js +28 -0
  11. package/dashboard/components/live-status.js +39 -0
  12. package/dashboard/components/run-history.js +76 -1
  13. package/dashboard/components/timeline.js +5 -1
  14. package/dashboard/index.html +21 -0
  15. package/dashboard/live-observer.js +91 -0
  16. package/package.json +1 -1
  17. package/scripts/release-bump.sh +26 -3
  18. package/scripts/release-preflight.sh +82 -38
  19. package/src/commands/accept-turn.js +3 -3
  20. package/src/commands/decisions.js +98 -29
  21. package/src/commands/diff.js +27 -4
  22. package/src/commands/doctor.js +48 -16
  23. package/src/commands/generate.js +126 -1
  24. package/src/commands/history.js +21 -3
  25. package/src/commands/init.js +15 -97
  26. package/src/commands/multi.js +223 -54
  27. package/src/commands/phase.js +11 -13
  28. package/src/commands/reject-turn.js +1 -1
  29. package/src/commands/restart.js +28 -11
  30. package/src/commands/resume.js +6 -6
  31. package/src/commands/role.js +51 -14
  32. package/src/commands/run.js +5 -11
  33. package/src/commands/status.js +145 -13
  34. package/src/commands/step.js +36 -29
  35. package/src/lib/admission-control.js +14 -12
  36. package/src/lib/blocked-state.js +150 -0
  37. package/src/lib/conflict-actions.js +17 -0
  38. package/src/lib/context-section-parser.js +2 -0
  39. package/src/lib/continuity-status.js +1 -1
  40. package/src/lib/coordinator-blocker-presentation.js +127 -0
  41. package/src/lib/coordinator-event-narrative.js +43 -0
  42. package/src/lib/coordinator-gate-approval.js +98 -0
  43. package/src/lib/coordinator-gate-evaluation-presentation.js +57 -0
  44. package/src/lib/coordinator-next-actions.js +128 -0
  45. package/src/lib/coordinator-pending-gate-presentation.js +79 -0
  46. package/src/lib/coordinator-presentation-detail.js +11 -0
  47. package/src/lib/coordinator-repo-snapshots.js +53 -0
  48. package/src/lib/coordinator-repo-status-presentation.js +134 -0
  49. package/src/lib/dashboard/actions.js +105 -29
  50. package/src/lib/dashboard/bridge-server.js +7 -0
  51. package/src/lib/dashboard/coordinator-blockers.js +17 -0
  52. package/src/lib/dashboard/coordinator-repo-status.js +50 -0
  53. package/src/lib/dashboard/coordinator-timeout-status.js +34 -11
  54. package/src/lib/dashboard/state-reader.js +36 -1
  55. package/src/lib/dispatch-bundle.js +23 -0
  56. package/src/lib/export-diff.js +70 -38
  57. package/src/lib/export-verifier.js +3 -0
  58. package/src/lib/history-diff-summary.js +249 -0
  59. package/src/lib/manual-qa-fallback.js +18 -0
  60. package/src/lib/normalized-config.js +27 -22
  61. package/src/lib/planning-artifacts.js +131 -0
  62. package/src/lib/recent-event-summary.js +132 -0
  63. package/src/lib/repo-decisions.js +69 -28
  64. package/src/lib/report.js +353 -145
  65. package/src/lib/run-diff.js +4 -0
  66. package/src/lib/runtime-capabilities.js +222 -0
package/README.md CHANGED
@@ -17,7 +17,7 @@ Legacy IDE-window coordination is still shipped as a compatibility mode for team
17
17
  - [Templates](https://agentxchain.dev/docs/templates/)
18
18
  - [Export schema reference](https://agentxchain.dev/docs/export-schema/)
19
19
  - [Adapter reference](https://agentxchain.dev/docs/adapters/)
20
- - [Protocol spec (v6)](https://agentxchain.dev/docs/protocol/)
20
+ - [Protocol v7](https://agentxchain.dev/docs/protocol/)
21
21
  - [Protocol reference](https://agentxchain.dev/docs/protocol-reference/)
22
22
  - [Build your own runner](https://agentxchain.dev/docs/build-your-own-runner/)
23
23
  - [Why governed multi-agent delivery matters](https://agentxchain.dev/why/)
@@ -173,16 +173,17 @@ agentxchain step
173
173
  | `verify turn` | Replay a staged turn's declared machine-evidence commands to confirm reproducibility before acceptance |
174
174
  | `replay turn` | Replay an accepted turn's machine-evidence commands from history for audit and drift detection |
175
175
  | `verify protocol` | Run the shipped protocol conformance suite against a target implementation |
176
- | `dashboard` | Open the local governance dashboard in your browser for repo-local runs or multi-repo coordinator initiatives, including pending gate approvals |
176
+ | `dashboard` | Open the live local governance dashboard in your browser for the current repo/workspace or multi-repo coordinator initiative, including pending gate approvals |
177
177
  | `run [--auto-approve] [--max-turns N] [--dry-run]` | Drive a governed run from start to completion — dispatches turns, handles gates, manages rejection/retry |
178
178
 
179
179
  ### Governed proof and inspection
180
180
 
181
181
  | Command | What it does |
182
182
  |---|---|
183
- | `audit [--format json]` | Live governance audit report with cost summary, decision history, and artifact inventory |
183
+ | `audit [--format json]` | Live governance audit report for the current repo/workspace with cost summary, decision history, and artifact inventory |
184
184
  | `diff <left> <right>` | Compare two governed runs side by side (phase, decisions, artifacts, timing) |
185
- | `report` | Generate a governance report for the current run |
185
+ | `report` | Generate a governance report from a verified export artifact (`--input <path>` or stdin) |
186
+ | `replay export <export-file>` | Open an existing verified export artifact in the read-only dashboard for offline post-mortem inspection |
186
187
  | `events [--type <type>] [--limit N]` | Inspect the lifecycle event stream (turns, phases, gates, governance events) |
187
188
  | `history [--limit N] [--role <role>]` | Query accepted-turn history from append-only JSONL |
188
189
  | `role list\|show` | List all configured roles or inspect a single role's charter, runtime, and phase assignment |
@@ -192,6 +193,8 @@ agentxchain step
192
193
  | `doctor [--json]` | Governed project health check: config, roles, runtimes, state, schedules, plugins, workflow-kit, connector handoff |
193
194
  | `connector check [--json]` | Live health probes for all configured connectors (api_proxy, remote_agent, MCP stdio/streamable_http) |
194
195
 
196
+ Partial coordinator artifacts are first-class here too: `audit` and `report` keep repo rows plus `repo_ok_count` / `repo_error_count` export-health totals when a child export fails, and they do not fabricate child drill-down for the failed repo.
197
+
195
198
  ### Governed automation, plugins, and continuity
196
199
 
197
200
  | Command | What it does |
@@ -202,10 +205,12 @@ agentxchain step
202
205
  | `schedule list\|run-due\|daemon\|status` | Run repo-local lights-out scheduling: inspect schedules, execute due runs, poll in a local daemon loop, or check daemon heartbeat |
203
206
  | `plugin install\|list\|remove` | Install, inspect, or remove governed hook plugins under `.agentxchain/plugins/` |
204
207
  | `plugin list-available` | List bundled built-in plugins installable by short name |
205
- | `export [--output <path>]` | Export run state for cross-machine continuity |
208
+ | `export [--output <path>]` | Export the portable raw governed/coordinator artifact for continuity or offline review |
206
209
  | `restore --input <path>` | Restore run state from a prior export on a same-repo, same-commit checkout |
207
210
  | `restart` | Rebuild lost session context from `.agentxchain/session.json` |
208
211
 
212
+ `dashboard` and `audit` read the live current repo/workspace. `report --input` and `replay export` read an existing verified export artifact instead: `report` renders a derived summary, while `replay export` opens the read-only dashboard. Partial coordinator artifacts remain first-class here too: `report` and `replay export` keep repo rows plus `repo_ok_count` / `repo_error_count` export-health totals when a child export fails, and they do not fabricate child drill-down for the failed repo.
213
+
209
214
  ### Shared utilities
210
215
 
211
216
  | Command | What it does |
@@ -224,7 +229,7 @@ agentxchain step
224
229
  | `supervise` | Run `watch` plus optional macOS auto-nudge |
225
230
  | `claim` / `release` | Human override of legacy lock ownership |
226
231
  | `rebind` | Rebuild Cursor bindings |
227
- | `generate` | Regenerate VS Code agent files |
232
+ | `generate` | Regenerate VS Code agent files; use `generate planning` to restore scaffold-owned governed planning docs |
228
233
  | `branch` | Manage Cursor branch override for launches |
229
234
  | `doctor` | Check local environment and setup |
230
235
  | `stop` | Stop watch daemon and local sessions |
@@ -273,6 +278,7 @@ The first-party governed workflow kit includes `.planning/SYSTEM_SPEC.md` alongs
273
278
  - `local_cli`: implemented
274
279
  - `mcp`: implemented for stdio and streamable HTTP tool-contract dispatch
275
280
  - `api_proxy`: implemented for synchronous `review_only` and `proposed` write-authority turns; stages a provider-backed result during `step`
281
+ - `remote_agent`: implemented for governed HTTP request/response dispatch against long-running remote agents
276
282
 
277
283
  ## Legacy IDE Mode
278
284
 
@@ -309,7 +315,7 @@ Requires:
309
315
  - [Quickstart](https://agentxchain.dev/docs/quickstart/)
310
316
  - [CLI reference](https://agentxchain.dev/docs/cli/)
311
317
  - [Adapter reference](https://agentxchain.dev/docs/adapters/)
312
- - [Protocol spec (v6)](https://agentxchain.dev/docs/protocol/)
318
+ - [Protocol v7](https://agentxchain.dev/docs/protocol/)
313
319
  - [GitHub](https://github.com/shivamtiwari93/agentXchain.dev)
314
320
  - [Legacy Protocol v3 spec](https://github.com/shivamtiwari93/agentXchain.dev/blob/main/PROTOCOL-v3.md)
315
321
 
@@ -55,7 +55,7 @@ import { configCommand } from '../src/commands/config.js';
55
55
  import { updateCommand } from '../src/commands/update.js';
56
56
  import { watchCommand } from '../src/commands/watch.js';
57
57
  import { claimCommand, releaseCommand } from '../src/commands/claim.js';
58
- import { generateCommand } from '../src/commands/generate.js';
58
+ import { generateCommand, generatePlanningCommand } from '../src/commands/generate.js';
59
59
  import { doctorCommand } from '../src/commands/doctor.js';
60
60
  import { superviseCommand } from '../src/commands/supervise.js';
61
61
  import { validateCommand } from '../src/commands/validate.js';
@@ -153,14 +153,14 @@ program
153
153
 
154
154
  program
155
155
  .command('export')
156
- .description('Export the governed run audit surface as a single artifact')
156
+ .description('Write a portable governed/coordinator export artifact from the current repo/workspace')
157
157
  .option('--format <format>', 'Export format (json)', 'json')
158
158
  .option('--output <path>', 'Write the export artifact to a file instead of stdout')
159
159
  .action(exportCommand);
160
160
 
161
161
  program
162
162
  .command('audit')
163
- .description('Render a governance audit directly from the current governed project or coordinator workspace')
163
+ .description('Render a governance audit from the live current repo/workspace')
164
164
  .option('--format <format>', 'Output format: text, json, markdown, or html', 'text')
165
165
  .action(auditCommand);
166
166
 
@@ -178,7 +178,7 @@ program
178
178
 
179
179
  program
180
180
  .command('report')
181
- .description('Render a human-readable governance summary from an export artifact')
181
+ .description('Render a governance summary from an existing verified export artifact')
182
182
  .option('--input <path>', 'Export artifact path, or "-" for stdin', '-')
183
183
  .option('--format <format>', 'Output format: text, json, markdown, or html', 'text')
184
184
  .action(reportCommand);
@@ -223,11 +223,19 @@ program
223
223
  .option('--unset', 'Remove override and follow the active git branch automatically')
224
224
  .action(branchCommand);
225
225
 
226
- program
226
+ const generateCmd = program
227
227
  .command('generate')
228
- .description('Regenerate VS Code agent files (.agent.md, hooks) from agentxchain.json')
228
+ .description('Regenerate VS Code agent files, or governed planning artifacts via subcommands')
229
229
  .action(generateCommand);
230
230
 
231
+ generateCmd
232
+ .command('planning')
233
+ .description('Generate or restore scaffold-owned governed planning artifacts')
234
+ .option('--dry-run', 'Show which planning artifacts would be written without changing files')
235
+ .option('--force', 'Overwrite existing scaffold-owned planning artifacts')
236
+ .option('-j, --json', 'Output as JSON')
237
+ .action(generatePlanningCommand);
238
+
231
239
  program
232
240
  .command('watch')
233
241
  .description('Watch lock.json and coordinate agent turns (the referee)')
@@ -441,7 +449,7 @@ replayCmd
441
449
 
442
450
  replayCmd
443
451
  .command('export <export-file>')
444
- .description('Browse a completed export in the dashboard for offline post-mortem analysis')
452
+ .description('Open an existing export artifact in the read-only dashboard')
445
453
  .option('-j, --json', 'Output session info as JSON')
446
454
  .option('--port <port>', 'Dashboard port', '3847')
447
455
  .option('--no-open', 'Do not auto-open browser')
@@ -522,7 +530,7 @@ program
522
530
 
523
531
  program
524
532
  .command('dashboard')
525
- .description('Open the read-only governance dashboard in your browser')
533
+ .description('Open the live governance dashboard for the current repo/workspace')
526
534
  .option('--port <port>', 'Server port', '3847')
527
535
  .option('--daemon', 'Run the dashboard in background mode')
528
536
  .option('--no-open', 'Do not auto-open the browser')
package/dashboard/app.js CHANGED
@@ -18,15 +18,20 @@ import { render as renderArtifacts } from './components/artifacts.js';
18
18
  import { render as renderRunHistory } from './components/run-history.js';
19
19
  import { render as renderTimeouts } from './components/timeouts.js';
20
20
  import { render as renderCoordinatorTimeouts } from './components/coordinator-timeouts.js';
21
+ import {
22
+ buildLiveMeta,
23
+ createLiveEventFromMessage,
24
+ shouldRefreshViewForLiveMessage,
25
+ } from './live-observer.js';
21
26
 
22
27
  const VIEWS = {
23
28
  timeline: { fetch: ['state', 'continuity', 'history', 'audit', 'annotations', 'connectors', 'coordinatorAudit', 'coordinatorAnnotations'], render: renderTimeline },
24
29
  delegations: { fetch: ['state', 'history'], render: renderDelegations },
25
- ledger: { fetch: ['state', 'ledger', 'coordinatorState', 'coordinatorLedger'], render: renderLedger },
30
+ ledger: { fetch: ['state', 'ledger', 'coordinatorState', 'coordinatorLedger', 'repoDecisionsSummary'], render: renderLedger },
26
31
  hooks: { fetch: ['audit', 'annotations', 'coordinatorAudit', 'coordinatorAnnotations'], render: renderHooks },
27
- blocked: { fetch: ['state', 'audit', 'coordinatorState', 'coordinatorAudit'], render: renderBlocked },
32
+ blocked: { fetch: ['state', 'audit', 'coordinatorState', 'coordinatorAudit', 'coordinatorBlockers', 'coordinatorRepoStatusRows'], render: renderBlocked },
28
33
  gate: { fetch: ['state', 'history', 'coordinatorState', 'coordinatorHistory', 'coordinatorBarriers'], render: renderGate },
29
- initiative: { fetch: ['coordinatorState', 'coordinatorBarriers', 'barrierLedger', 'coordinatorBlockers'], render: renderInitiative },
34
+ initiative: { fetch: ['coordinatorState', 'coordinatorBarriers', 'barrierLedger', 'coordinatorBlockers', 'coordinatorRepoStatusRows'], render: renderInitiative },
30
35
  'cross-repo': { fetch: ['coordinatorState', 'coordinatorHistory'], render: renderCrossRepo },
31
36
  blockers: { fetch: ['coordinatorBlockers'], render: renderBlockers },
32
37
  artifacts: { fetch: ['workflowKitArtifacts'], render: renderArtifacts },
@@ -40,6 +45,7 @@ const API_MAP = {
40
45
  continuity: '/api/continuity',
41
46
  history: '/api/history',
42
47
  ledger: '/api/ledger',
48
+ repoDecisionsSummary: '/api/repo-decisions-summary',
43
49
  coordinatorLedger: '/api/coordinator/ledger',
44
50
  audit: '/api/hooks/audit',
45
51
  annotations: '/api/hooks/annotations',
@@ -50,6 +56,7 @@ const API_MAP = {
50
56
  coordinatorAudit: '/api/coordinator/hooks/audit',
51
57
  coordinatorAnnotations: '/api/coordinator/hooks/annotations',
52
58
  coordinatorBlockers: '/api/coordinator/blockers',
59
+ coordinatorRepoStatusRows: '/api/coordinator/repo-status',
53
60
  workflowKitArtifacts: '/api/workflow-kit-artifacts',
54
61
  connectors: '/api/connectors',
55
62
  runHistory: '/api/run-history',
@@ -76,6 +83,12 @@ let activeViewName = null;
76
83
  let activeViewData = null;
77
84
  let dashboardSession = null;
78
85
  let actionInFlight = false;
86
+ const liveObserverState = {
87
+ connected: false,
88
+ lastRefreshAt: null,
89
+ lastRunEvent: null,
90
+ lastCoordinatorEvent: null,
91
+ };
79
92
 
80
93
  function escapeHtml(str) {
81
94
  if (str == null) return '';
@@ -132,19 +145,40 @@ async function pickInitialView() {
132
145
  }
133
146
 
134
147
  function buildRenderData(viewName, data) {
148
+ const liveMeta = viewName === 'timeline'
149
+ ? buildLiveMeta({
150
+ connected: liveObserverState.connected,
151
+ lastRefreshAt: liveObserverState.lastRefreshAt,
152
+ lastEvent: liveObserverState.lastRunEvent,
153
+ scope: 'run',
154
+ })
155
+ : viewName === 'cross-repo'
156
+ ? buildLiveMeta({
157
+ connected: liveObserverState.connected,
158
+ lastRefreshAt: liveObserverState.lastRefreshAt,
159
+ lastEvent: liveObserverState.lastCoordinatorEvent,
160
+ scope: 'coordinator',
161
+ })
162
+ : null;
163
+
135
164
  if (viewName === 'ledger') {
136
165
  return {
137
166
  ...data,
138
167
  filter: viewState.ledger,
168
+ liveMeta,
139
169
  };
140
170
  }
141
171
  if (viewName === 'hooks') {
142
172
  return {
143
173
  ...data,
144
174
  filter: viewState.hooks,
175
+ liveMeta,
145
176
  };
146
177
  }
147
- return data;
178
+ return {
179
+ ...data,
180
+ liveMeta,
181
+ };
148
182
  }
149
183
 
150
184
  function renderView(viewName, data) {
@@ -172,6 +206,51 @@ function setActionBanner(message, tone = 'info') {
172
206
  banner.className = `action-banner visible ${tone === 'error' ? 'error' : tone === 'success' ? 'success' : ''}`.trim();
173
207
  }
174
208
 
209
+ function formatActionErrorMessage(payload, status) {
210
+ if (!payload || typeof payload !== 'object') {
211
+ return `Dashboard action failed with HTTP ${status}.`;
212
+ }
213
+
214
+ const parts = [];
215
+ if (typeof payload.error === 'string' && payload.error.trim()) {
216
+ parts.push(payload.error.trim());
217
+ } else {
218
+ parts.push(`Dashboard action failed with HTTP ${status}.`);
219
+ }
220
+
221
+ const detail = payload.recovery_summary?.detail;
222
+ if (typeof detail === 'string' && detail.trim()) {
223
+ parts.push(detail.trim());
224
+ }
225
+
226
+ const nextAction = payload.next_actions?.[0]?.command || payload.next_action || null;
227
+ if (typeof nextAction === 'string' && nextAction.trim()) {
228
+ parts.push(`Next: ${nextAction.trim()}`);
229
+ }
230
+
231
+ return parts.join(' ');
232
+ }
233
+
234
+ function formatActionSuccessMessage(payload) {
235
+ if (!payload || typeof payload !== 'object') {
236
+ return 'Gate approved.';
237
+ }
238
+
239
+ const parts = [];
240
+ if (typeof payload.message === 'string' && payload.message.trim()) {
241
+ parts.push(payload.message.trim());
242
+ } else {
243
+ parts.push('Gate approved.');
244
+ }
245
+
246
+ const nextAction = payload.next_actions?.[0]?.command || payload.next_action || null;
247
+ if (typeof nextAction === 'string' && nextAction.trim()) {
248
+ parts.push(`Next: ${nextAction.trim()}`);
249
+ }
250
+
251
+ return parts.join(' ');
252
+ }
253
+
175
254
  async function loadView(viewName, { refresh = true } = {}) {
176
255
  const view = VIEWS[viewName];
177
256
  if (!view) {
@@ -183,11 +262,17 @@ async function loadView(viewName, { refresh = true } = {}) {
183
262
  activeViewName = viewName;
184
263
  if (shouldRefetch) {
185
264
  activeViewData = await fetchData(view.fetch);
265
+ liveObserverState.lastRefreshAt = new Date().toISOString();
186
266
  }
187
267
 
188
268
  renderView(viewName, activeViewData);
189
269
  }
190
270
 
271
+ function rerenderActiveView() {
272
+ if (!activeViewName) return;
273
+ renderView(activeViewName, activeViewData);
274
+ }
275
+
191
276
  // ── WebSocket connection ──────────────────────────────────────────────────
192
277
 
193
278
  let ws = null;
@@ -204,13 +289,30 @@ function connect() {
204
289
  statusDot.classList.remove('disconnected');
205
290
  statusLabel.textContent = 'Connected';
206
291
  reconnectDelay = 1000;
292
+ liveObserverState.connected = true;
207
293
  loadView(currentView());
208
294
  };
209
295
 
210
296
  ws.onmessage = (event) => {
211
297
  try {
212
298
  const msg = JSON.parse(event.data);
213
- if (msg.type === 'invalidate') {
299
+ if (msg.type === 'event') {
300
+ liveObserverState.lastRunEvent = createLiveEventFromMessage(msg);
301
+ rerenderActiveView();
302
+ return;
303
+ }
304
+
305
+ if (msg.type === 'coordinator_event') {
306
+ liveObserverState.lastCoordinatorEvent = createLiveEventFromMessage(msg);
307
+ if (shouldRefreshViewForLiveMessage(currentView(), msg.type)) {
308
+ loadView(currentView());
309
+ } else {
310
+ rerenderActiveView();
311
+ }
312
+ return;
313
+ }
314
+
315
+ if (shouldRefreshViewForLiveMessage(currentView(), msg.type)) {
214
316
  loadView(currentView());
215
317
  }
216
318
  } catch {}
@@ -219,6 +321,8 @@ function connect() {
219
321
  ws.onclose = () => {
220
322
  statusDot.classList.add('disconnected');
221
323
  statusLabel.textContent = 'Disconnected';
324
+ liveObserverState.connected = false;
325
+ rerenderActiveView();
222
326
  setTimeout(() => {
223
327
  reconnectDelay = Math.min(reconnectDelay * 2, 30000);
224
328
  connect();
@@ -344,11 +448,11 @@ document.addEventListener('click', async (event) => {
344
448
  }));
345
449
 
346
450
  if (!res.ok || payload.ok === false) {
347
- setActionBanner(payload.error || `Dashboard action failed with HTTP ${res.status}.`, 'error');
451
+ setActionBanner(formatActionErrorMessage(payload, res.status), 'error');
348
452
  return;
349
453
  }
350
454
 
351
- setActionBanner(payload.message || 'Gate approved.', 'success');
455
+ setActionBanner(formatActionSuccessMessage(payload), 'success');
352
456
  await loadView(currentView());
353
457
  } catch (error) {
354
458
  setActionBanner(error?.message || 'Dashboard action failed.', 'error');
@@ -1,3 +1,6 @@
1
+ import { getCoordinatorPendingGateDetails } from '../../src/lib/coordinator-pending-gate-presentation.js';
2
+ import { buildCoordinatorRepoStatusRows } from '../../src/lib/coordinator-repo-status-presentation.js';
3
+
1
4
  /**
2
5
  * Blocked State view — renders current blocked state with recovery info.
3
6
  *
@@ -23,6 +26,18 @@ function getHookName(entry) {
23
26
  return entry?.hook_name || entry?.hook || entry?.name || '';
24
27
  }
25
28
 
29
+ function renderDetailRows(details) {
30
+ if (!Array.isArray(details) || details.length === 0) {
31
+ return '';
32
+ }
33
+
34
+ let html = '';
35
+ for (const detail of details) {
36
+ html += `<dt>${esc(detail.label)}</dt><dd${detail.mono ? ' class="mono"' : ''}>${esc(detail.value)}</dd>`;
37
+ }
38
+ return html;
39
+ }
40
+
26
41
  function selectRelevantAuditEntries(state, audit) {
27
42
  if (!Array.isArray(audit) || audit.length === 0) {
28
43
  return [];
@@ -55,7 +70,36 @@ function selectRelevantAuditEntries(state, audit) {
55
70
  return audit.slice(-3);
56
71
  }
57
72
 
58
- export function render({ state, audit = [], coordinatorState = null, coordinatorAudit = [] }) {
73
+ function getCoordinatorRepoRows(coordinatorState, coordinatorRepoStatusRows) {
74
+ if (Array.isArray(coordinatorRepoStatusRows) && coordinatorRepoStatusRows.length > 0) {
75
+ return coordinatorRepoStatusRows;
76
+ }
77
+
78
+ return buildCoordinatorRepoStatusRows({
79
+ config: null,
80
+ coordinatorRepoRuns: coordinatorState?.repo_runs || {},
81
+ });
82
+ }
83
+
84
+ function formatCoordinatorRepoCardMeta(row) {
85
+ const parts = [];
86
+ if (row?.run_id) {
87
+ parts.push(`run ${row.run_id}`);
88
+ }
89
+ for (const detail of Array.isArray(row?.details) ? row.details : []) {
90
+ parts.push(`${detail.label}: ${detail.value}`);
91
+ }
92
+ return parts.join(' | ') || '-';
93
+ }
94
+
95
+ export function render({
96
+ state,
97
+ audit = [],
98
+ coordinatorState = null,
99
+ coordinatorAudit = [],
100
+ coordinatorBlockers = null,
101
+ coordinatorRepoStatusRows = null,
102
+ }) {
59
103
  const activeState = state?.status === 'blocked' ? state : coordinatorState;
60
104
  const activeAudit = activeState === state ? audit : coordinatorAudit;
61
105
  const isCoordinator = activeState === coordinatorState;
@@ -75,6 +119,26 @@ export function render({ state, audit = [], coordinatorState = null, coordinator
75
119
  const typedReason = recovery.typed_reason || null;
76
120
  const turnRetained = typeof recovery.turn_retained === 'boolean' ? recovery.turn_retained : null;
77
121
  const blockedAt = blocked.blocked_at || null;
122
+ const runtimeGuidance = Array.isArray(activeState.runtime_guidance) ? activeState.runtime_guidance : [];
123
+ const coordinatorNextActions = coordinatorBlockers?.ok === false
124
+ ? []
125
+ : Array.isArray(coordinatorBlockers?.next_actions)
126
+ ? coordinatorBlockers.next_actions
127
+ : [];
128
+ const nextActions = isCoordinator
129
+ ? coordinatorNextActions
130
+ : Array.isArray(activeState.next_actions)
131
+ ? activeState.next_actions
132
+ : [];
133
+ const coordinatorPendingGateDetails = isCoordinator
134
+ ? getCoordinatorPendingGateDetails({
135
+ pendingGate: activeState.pending_gate,
136
+ active: coordinatorBlockers?.active,
137
+ })
138
+ : [];
139
+ const coordinatorRepoRows = isCoordinator
140
+ ? getCoordinatorRepoRows(coordinatorState, coordinatorRepoStatusRows)
141
+ : [];
78
142
  const relevantAudit = selectRelevantAuditEntries(activeState, activeAudit);
79
143
 
80
144
  let html = `<div class="blocked-view">
@@ -104,22 +168,42 @@ export function render({ state, audit = [], coordinatorState = null, coordinator
104
168
  </div>`;
105
169
  }
106
170
 
107
- if (isCoordinator && activeState.pending_gate) {
171
+ if (runtimeGuidance.length > 0) {
172
+ html += `<div class="section"><h3>Runtime Guidance</h3><div class="annotation-list">`;
173
+ for (const entry of runtimeGuidance) {
174
+ html += `<div class="annotation-card">
175
+ <span class="mono">${esc(entry.code || '-')}</span>
176
+ <span class="mono">${esc(entry.command || '-')}</span>
177
+ <span>${esc(entry.reason || '-')}</span>
178
+ </div>`;
179
+ }
180
+ html += `</div></div>`;
181
+ }
182
+
183
+ if (nextActions.length > 0) {
184
+ html += `<div class="section"><h3>Next Actions</h3><div class="annotation-list">`;
185
+ for (const action of nextActions) {
186
+ html += `<div class="annotation-card">
187
+ <span class="mono">${esc(action.command || '-')}</span>
188
+ <span>${esc(action.reason || '-')}</span>
189
+ </div>`;
190
+ }
191
+ html += `</div></div>`;
192
+ }
193
+
194
+ if (isCoordinator && coordinatorPendingGateDetails.length > 0) {
108
195
  html += `<div class="section"><h3>Pending Gate</h3>
109
- <dl class="detail-list">
110
- <dt>Gate</dt><dd class="mono">${esc(activeState.pending_gate.gate || '-')}</dd>
111
- <dt>Type</dt><dd>${esc(activeState.pending_gate.gate_type || '-')}</dd>
112
- </dl>
113
- <pre class="recovery-command mono" data-copy="agentxchain multi approve-gate">agentxchain multi approve-gate</pre>
196
+ <dl class="detail-list">${renderDetailRows(coordinatorPendingGateDetails)}</dl>
114
197
  </div>`;
115
198
  }
116
199
 
117
- if (isCoordinator && activeState.repo_runs && Object.keys(activeState.repo_runs).length > 0) {
200
+ if (isCoordinator && coordinatorRepoRows.length > 0) {
118
201
  html += `<div class="section"><h3>Repo Status</h3><div class="annotation-list">`;
119
- for (const [repoId, repoRun] of Object.entries(activeState.repo_runs)) {
202
+ for (const row of coordinatorRepoRows) {
120
203
  html += `<div class="annotation-card">
121
- <span class="mono">${esc(repoId)}</span>
122
- <span>${esc(`${repoRun.status || 'unknown'}${repoRun.phase ? ` [${repoRun.phase}]` : ''}`)}</span>
204
+ <span class="mono">${esc(row.repo_id || '-')}</span>
205
+ <span>${esc(`${row.status || 'unknown'}${row.phase ? ` [${row.phase}]` : ''}`)}</span>
206
+ <span>${esc(formatCoordinatorRepoCardMeta(row))}</span>
123
207
  </div>`;
124
208
  }
125
209
  html += `</div></div>`;