agentxchain 2.30.0 → 2.32.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/dashboard/app.js +3 -0
- package/dashboard/components/artifacts.js +113 -0
- package/dashboard/index.html +1 -0
- package/package.json +1 -1
- package/src/lib/dashboard/bridge-server.js +7 -0
- package/src/lib/dashboard/workflow-kit-artifacts.js +131 -0
- package/src/lib/export.js +1 -0
- package/src/lib/report.js +57 -0
package/dashboard/app.js
CHANGED
|
@@ -13,6 +13,7 @@ import { render as renderGate } from './components/gate.js';
|
|
|
13
13
|
import { render as renderInitiative } from './components/initiative.js';
|
|
14
14
|
import { render as renderCrossRepo } from './components/cross-repo.js';
|
|
15
15
|
import { render as renderBlockers } from './components/blockers.js';
|
|
16
|
+
import { render as renderArtifacts } from './components/artifacts.js';
|
|
16
17
|
|
|
17
18
|
const VIEWS = {
|
|
18
19
|
timeline: { fetch: ['state', 'history', 'audit', 'annotations'], render: renderTimeline },
|
|
@@ -23,6 +24,7 @@ const VIEWS = {
|
|
|
23
24
|
initiative: { fetch: ['coordinatorState', 'coordinatorBarriers', 'barrierLedger', 'coordinatorBlockers'], render: renderInitiative },
|
|
24
25
|
'cross-repo': { fetch: ['coordinatorState', 'coordinatorHistory'], render: renderCrossRepo },
|
|
25
26
|
blockers: { fetch: ['coordinatorBlockers'], render: renderBlockers },
|
|
27
|
+
artifacts: { fetch: ['workflowKitArtifacts'], render: renderArtifacts },
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
const API_MAP = {
|
|
@@ -37,6 +39,7 @@ const API_MAP = {
|
|
|
37
39
|
barrierLedger: '/api/coordinator/barrier-ledger',
|
|
38
40
|
coordinatorAudit: '/api/coordinator/hooks/audit',
|
|
39
41
|
coordinatorBlockers: '/api/coordinator/blockers',
|
|
42
|
+
workflowKitArtifacts: '/api/workflow-kit-artifacts',
|
|
40
43
|
};
|
|
41
44
|
|
|
42
45
|
const viewState = {
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow-Kit Artifacts view — renders live workflow-kit artifact status.
|
|
3
|
+
*
|
|
4
|
+
* Pure render function: takes data from /api/workflow-kit-artifacts, returns HTML.
|
|
5
|
+
* Ownership resolution and file-existence checks are server-side. This view
|
|
6
|
+
* renders the snapshot without reimplementing any logic.
|
|
7
|
+
*
|
|
8
|
+
* See: WORKFLOW_KIT_DASHBOARD_SPEC.md
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
function esc(str) {
|
|
12
|
+
if (!str) return '';
|
|
13
|
+
return String(str)
|
|
14
|
+
.replace(/&/g, '&')
|
|
15
|
+
.replace(/</g, '<')
|
|
16
|
+
.replace(/>/g, '>')
|
|
17
|
+
.replace(/"/g, '"')
|
|
18
|
+
.replace(/'/g, ''');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function badge(label, color = 'var(--text-dim)') {
|
|
22
|
+
return `<span class="badge" style="color:${color};border-color:${color}">${esc(label)}</span>`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function statusIndicator(exists) {
|
|
26
|
+
if (exists) {
|
|
27
|
+
return `<span style="color:var(--green);font-weight:600" title="File exists">✓ exists</span>`;
|
|
28
|
+
}
|
|
29
|
+
return `<span style="color:var(--red);font-weight:600" title="File missing">✗ missing</span>`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function renderArtifactRow(artifact) {
|
|
33
|
+
const isMissingRequired = !artifact.exists && artifact.required;
|
|
34
|
+
const rowStyle = isMissingRequired
|
|
35
|
+
? ' style="border-left:3px solid var(--red)"'
|
|
36
|
+
: '';
|
|
37
|
+
|
|
38
|
+
return `<tr${rowStyle}>
|
|
39
|
+
<td class="mono">${esc(artifact.path)}</td>
|
|
40
|
+
<td>${artifact.required ? badge('required', 'var(--yellow)') : badge('optional', 'var(--text-dim)')}</td>
|
|
41
|
+
<td>${artifact.semantics ? `<span class="mono">${esc(artifact.semantics)}</span>` : '<span style="color:var(--text-dim)">—</span>'}</td>
|
|
42
|
+
<td>${artifact.owned_by ? esc(artifact.owned_by) : '<span style="color:var(--text-dim)">—</span>'}</td>
|
|
43
|
+
<td>${artifact.owner_resolution ? badge(artifact.owner_resolution, artifact.owner_resolution === 'explicit' ? 'var(--accent)' : 'var(--text-dim)') : '—'}</td>
|
|
44
|
+
<td>${statusIndicator(artifact.exists)}</td>
|
|
45
|
+
</tr>`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function render({ workflowKitArtifacts }) {
|
|
49
|
+
if (!workflowKitArtifacts) {
|
|
50
|
+
return `<div class="placeholder"><h2>Workflow Artifacts</h2><p>No workflow artifact data available. Ensure a governed run is active.</p></div>`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (workflowKitArtifacts.ok === false) {
|
|
54
|
+
const hint = workflowKitArtifacts.code === 'config_missing' || workflowKitArtifacts.code === 'state_missing'
|
|
55
|
+
? ' Run <code>agentxchain init --governed</code> to get started.'
|
|
56
|
+
: '';
|
|
57
|
+
return `<div class="placeholder"><h2>Workflow Artifacts</h2><p>${esc(workflowKitArtifacts.error || 'Failed to load workflow artifact data.')}${hint}</p></div>`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const data = workflowKitArtifacts;
|
|
61
|
+
const artifacts = data.artifacts;
|
|
62
|
+
|
|
63
|
+
// No workflow_kit configured
|
|
64
|
+
if (artifacts === null) {
|
|
65
|
+
return `<div class="placeholder"><h2>Workflow Artifacts</h2><p>No <code>workflow_kit</code> configured in <code>agentxchain.json</code>. Add a <code>workflow_kit</code> section to track phase artifacts.</p></div>`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Phase has no artifacts
|
|
69
|
+
if (!Array.isArray(artifacts) || artifacts.length === 0) {
|
|
70
|
+
return `<div class="placeholder"><h2>Workflow Artifacts</h2><p>Current phase <strong>${esc(data.phase || 'unknown')}</strong> has no workflow-kit artifacts defined.</p></div>`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const missingRequired = artifacts.filter((a) => a.required && !a.exists).length;
|
|
74
|
+
const totalExists = artifacts.filter((a) => a.exists).length;
|
|
75
|
+
|
|
76
|
+
let html = `<div class="artifacts-view">`;
|
|
77
|
+
|
|
78
|
+
// Header
|
|
79
|
+
html += `<div class="run-header"><div class="run-meta">`;
|
|
80
|
+
if (data.phase) {
|
|
81
|
+
html += `<span class="phase-label">Phase: <strong>${esc(data.phase)}</strong></span>`;
|
|
82
|
+
}
|
|
83
|
+
html += `<span class="turn-count">${artifacts.length} artifact${artifacts.length !== 1 ? 's' : ''}</span>`;
|
|
84
|
+
html += `<span class="turn-count">${totalExists}/${artifacts.length} present</span>`;
|
|
85
|
+
if (missingRequired > 0) {
|
|
86
|
+
html += badge(`${missingRequired} missing required`, 'var(--red)');
|
|
87
|
+
}
|
|
88
|
+
html += `</div></div>`;
|
|
89
|
+
|
|
90
|
+
// Table
|
|
91
|
+
html += `<div class="section"><h3>Workflow-Kit Artifacts</h3>
|
|
92
|
+
<table class="data-table">
|
|
93
|
+
<thead>
|
|
94
|
+
<tr>
|
|
95
|
+
<th>Path</th>
|
|
96
|
+
<th>Required</th>
|
|
97
|
+
<th>Semantics</th>
|
|
98
|
+
<th>Owner</th>
|
|
99
|
+
<th>Resolution</th>
|
|
100
|
+
<th>Status</th>
|
|
101
|
+
</tr>
|
|
102
|
+
</thead>
|
|
103
|
+
<tbody>`;
|
|
104
|
+
|
|
105
|
+
for (const artifact of artifacts) {
|
|
106
|
+
html += renderArtifactRow(artifact);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
html += `</tbody></table></div>`;
|
|
110
|
+
|
|
111
|
+
html += `</div>`;
|
|
112
|
+
return html;
|
|
113
|
+
}
|
package/dashboard/index.html
CHANGED
package/package.json
CHANGED
|
@@ -18,6 +18,7 @@ import { readResource } from './state-reader.js';
|
|
|
18
18
|
import { FileWatcher } from './file-watcher.js';
|
|
19
19
|
import { approvePendingDashboardGate } from './actions.js';
|
|
20
20
|
import { readCoordinatorBlockerSnapshot } from './coordinator-blockers.js';
|
|
21
|
+
import { readWorkflowKitArtifacts } from './workflow-kit-artifacts.js';
|
|
21
22
|
|
|
22
23
|
const MIME_TYPES = {
|
|
23
24
|
'.html': 'text/html; charset=utf-8',
|
|
@@ -279,6 +280,12 @@ export function createBridgeServer({ agentxchainDir, dashboardDir, port = 3847 }
|
|
|
279
280
|
return;
|
|
280
281
|
}
|
|
281
282
|
|
|
283
|
+
if (pathname === '/api/workflow-kit-artifacts') {
|
|
284
|
+
const result = readWorkflowKitArtifacts(workspacePath);
|
|
285
|
+
writeJson(res, result.status, result.body);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
282
289
|
// API routes
|
|
283
290
|
if (pathname.startsWith('/api/')) {
|
|
284
291
|
const result = readResource(agentxchainDir, pathname);
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow-kit artifact status — computed dashboard endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Reads agentxchain.json config and .agentxchain/state.json to derive
|
|
5
|
+
* current-phase workflow-kit artifact status for live dashboard observation.
|
|
6
|
+
*
|
|
7
|
+
* Per DEC-WK-REPORT-002: ownership resolution uses explicit owned_by first,
|
|
8
|
+
* then falls back to routing[phase].entry_role.
|
|
9
|
+
*
|
|
10
|
+
* See: WORKFLOW_KIT_DASHBOARD_SPEC.md
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync } from 'fs';
|
|
14
|
+
import { join } from 'path';
|
|
15
|
+
import { loadConfig } from '../config.js';
|
|
16
|
+
import { readJsonFile } from './state-reader.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Read workflow-kit artifact status for the current phase.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} workspacePath — project root (parent of .agentxchain/)
|
|
22
|
+
* @returns {{ ok: boolean, status: number, body: object }}
|
|
23
|
+
*/
|
|
24
|
+
export function readWorkflowKitArtifacts(workspacePath) {
|
|
25
|
+
const configResult = loadConfig(workspacePath);
|
|
26
|
+
if (!configResult) {
|
|
27
|
+
return {
|
|
28
|
+
ok: false,
|
|
29
|
+
status: 404,
|
|
30
|
+
body: {
|
|
31
|
+
ok: false,
|
|
32
|
+
code: 'config_missing',
|
|
33
|
+
error: 'Project config not found. Run `agentxchain init --governed` first.',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const agentxchainDir = join(workspacePath, '.agentxchain');
|
|
39
|
+
const state = readJsonFile(agentxchainDir, 'state.json');
|
|
40
|
+
if (!state) {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
status: 404,
|
|
44
|
+
body: {
|
|
45
|
+
ok: false,
|
|
46
|
+
code: 'state_missing',
|
|
47
|
+
error: 'Run state not found. Run `agentxchain init --governed` first.',
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const config = configResult.config;
|
|
53
|
+
const phase = state.phase || null;
|
|
54
|
+
|
|
55
|
+
if (!config.workflow_kit) {
|
|
56
|
+
return {
|
|
57
|
+
ok: true,
|
|
58
|
+
status: 200,
|
|
59
|
+
body: {
|
|
60
|
+
ok: true,
|
|
61
|
+
phase,
|
|
62
|
+
artifacts: null,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!phase) {
|
|
68
|
+
return {
|
|
69
|
+
ok: true,
|
|
70
|
+
status: 200,
|
|
71
|
+
body: {
|
|
72
|
+
ok: true,
|
|
73
|
+
phase: null,
|
|
74
|
+
artifacts: [],
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const phaseConfig = config.workflow_kit.phases?.[phase];
|
|
80
|
+
if (!phaseConfig) {
|
|
81
|
+
return {
|
|
82
|
+
ok: true,
|
|
83
|
+
status: 200,
|
|
84
|
+
body: {
|
|
85
|
+
ok: true,
|
|
86
|
+
phase,
|
|
87
|
+
artifacts: [],
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const artifacts = Array.isArray(phaseConfig.artifacts) ? phaseConfig.artifacts : [];
|
|
93
|
+
if (artifacts.length === 0) {
|
|
94
|
+
return {
|
|
95
|
+
ok: true,
|
|
96
|
+
status: 200,
|
|
97
|
+
body: {
|
|
98
|
+
ok: true,
|
|
99
|
+
phase,
|
|
100
|
+
artifacts: [],
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const entryRole = config.routing?.[phase]?.entry_role || null;
|
|
106
|
+
|
|
107
|
+
const result = artifacts
|
|
108
|
+
.filter((a) => a && typeof a.path === 'string')
|
|
109
|
+
.map((a) => {
|
|
110
|
+
const hasExplicitOwner = typeof a.owned_by === 'string' && a.owned_by.length > 0;
|
|
111
|
+
return {
|
|
112
|
+
path: a.path,
|
|
113
|
+
required: a.required !== false,
|
|
114
|
+
semantics: a.semantics || null,
|
|
115
|
+
owned_by: hasExplicitOwner ? a.owned_by : entryRole,
|
|
116
|
+
owner_resolution: hasExplicitOwner ? 'explicit' : 'entry_role',
|
|
117
|
+
exists: existsSync(join(workspacePath, a.path)),
|
|
118
|
+
};
|
|
119
|
+
})
|
|
120
|
+
.sort((a, b) => a.path.localeCompare(b.path, 'en'));
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
ok: true,
|
|
124
|
+
status: 200,
|
|
125
|
+
body: {
|
|
126
|
+
ok: true,
|
|
127
|
+
phase,
|
|
128
|
+
artifacts: result,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
package/src/lib/export.js
CHANGED
package/src/lib/report.js
CHANGED
|
@@ -471,6 +471,38 @@ function deriveRepoStatusCounts(repoStatuses) {
|
|
|
471
471
|
return counts;
|
|
472
472
|
}
|
|
473
473
|
|
|
474
|
+
export function extractWorkflowKitArtifacts(artifact) {
|
|
475
|
+
const config = artifact.config;
|
|
476
|
+
if (!config || typeof config !== 'object' || !config.workflow_kit) return null;
|
|
477
|
+
|
|
478
|
+
const phase = artifact.summary?.phase || artifact.state?.phase;
|
|
479
|
+
if (!phase) return null;
|
|
480
|
+
|
|
481
|
+
const phaseConfig = config.workflow_kit.phases?.[phase];
|
|
482
|
+
if (!phaseConfig) return [];
|
|
483
|
+
|
|
484
|
+
const artifacts = Array.isArray(phaseConfig.artifacts) ? phaseConfig.artifacts : [];
|
|
485
|
+
if (artifacts.length === 0) return [];
|
|
486
|
+
|
|
487
|
+
const entryRole = config.routing?.[phase]?.entry_role || null;
|
|
488
|
+
const fileKeys = new Set(Object.keys(artifact.files || {}));
|
|
489
|
+
|
|
490
|
+
return artifacts
|
|
491
|
+
.filter((a) => a && typeof a.path === 'string')
|
|
492
|
+
.map((a) => {
|
|
493
|
+
const hasExplicitOwner = typeof a.owned_by === 'string' && a.owned_by.length > 0;
|
|
494
|
+
return {
|
|
495
|
+
path: a.path,
|
|
496
|
+
required: a.required !== false,
|
|
497
|
+
semantics: a.semantics || null,
|
|
498
|
+
owned_by: hasExplicitOwner ? a.owned_by : entryRole,
|
|
499
|
+
owner_resolution: hasExplicitOwner ? 'explicit' : 'entry_role',
|
|
500
|
+
exists: fileKeys.has(a.path),
|
|
501
|
+
};
|
|
502
|
+
})
|
|
503
|
+
.sort((a, b) => a.path.localeCompare(b.path, 'en'));
|
|
504
|
+
}
|
|
505
|
+
|
|
474
506
|
function buildRunSubject(artifact) {
|
|
475
507
|
const activeTurns = artifact.summary?.active_turn_ids || [];
|
|
476
508
|
const retainedTurns = artifact.summary?.retained_turn_ids || [];
|
|
@@ -518,6 +550,7 @@ function buildRunSubject(artifact) {
|
|
|
518
550
|
gate_summary: gateSummary,
|
|
519
551
|
intake_links: intakeLinks,
|
|
520
552
|
recovery_summary: recoverySummary,
|
|
553
|
+
workflow_kit_artifacts: extractWorkflowKitArtifacts(artifact),
|
|
521
554
|
},
|
|
522
555
|
artifacts: {
|
|
523
556
|
history_entries: artifact.summary?.history_entries || 0,
|
|
@@ -810,6 +843,17 @@ export function formatGovernanceReportText(report) {
|
|
|
810
843
|
lines.push(` Turn retained: ${run.recovery_summary.turn_retained == null ? 'n/a' : yesNo(run.recovery_summary.turn_retained)}`);
|
|
811
844
|
}
|
|
812
845
|
|
|
846
|
+
if (Array.isArray(run.workflow_kit_artifacts) && run.workflow_kit_artifacts.length > 0) {
|
|
847
|
+
lines.push('', `Workflow Artifacts (${run.phase || 'unknown'} phase):`);
|
|
848
|
+
for (const art of run.workflow_kit_artifacts) {
|
|
849
|
+
const req = art.required ? 'required' : 'optional';
|
|
850
|
+
const sem = art.semantics || 'none';
|
|
851
|
+
const owner = art.owned_by ? `${art.owned_by} (${art.owner_resolution})` : 'none';
|
|
852
|
+
const status = art.exists ? 'exists' : 'missing';
|
|
853
|
+
lines.push(` ${art.path} | ${req} | ${sem} | owner: ${owner} | ${status}`);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
813
857
|
return lines.join('\n');
|
|
814
858
|
}
|
|
815
859
|
|
|
@@ -1066,6 +1110,19 @@ export function formatGovernanceReportMarkdown(report) {
|
|
|
1066
1110
|
lines.push(`- Turn retained: \`${run.recovery_summary.turn_retained == null ? 'n/a' : yesNo(run.recovery_summary.turn_retained)}\``);
|
|
1067
1111
|
}
|
|
1068
1112
|
|
|
1113
|
+
if (Array.isArray(run.workflow_kit_artifacts) && run.workflow_kit_artifacts.length > 0) {
|
|
1114
|
+
lines.push('', '## Workflow Artifacts', '');
|
|
1115
|
+
lines.push(`Phase: \`${run.phase || 'unknown'}\``, '');
|
|
1116
|
+
lines.push('| Artifact | Required | Semantics | Owner | Resolution | Status |', '|----------|----------|-----------|-------|------------|--------|');
|
|
1117
|
+
for (const art of run.workflow_kit_artifacts) {
|
|
1118
|
+
const req = art.required ? 'yes' : 'no';
|
|
1119
|
+
const sem = art.semantics ? `\`${art.semantics}\`` : 'none';
|
|
1120
|
+
const owner = art.owned_by ? `\`${art.owned_by}\`` : 'none';
|
|
1121
|
+
const status = art.exists ? 'exists' : '**missing**';
|
|
1122
|
+
lines.push(`| \`${art.path}\` | ${req} | ${sem} | ${owner} | ${art.owner_resolution} | ${status} |`);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1069
1126
|
return lines.join('\n');
|
|
1070
1127
|
}
|
|
1071
1128
|
|