agentxchain 2.31.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
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
|
+
}
|