feat: add dynamic workflow team orchestration surface

Adds dynamic workflow/team orchestration skills, the content pack, and control-pane work-item/Kanban state DB support. Includes reviewer hardening for state-db CLI validation, optional state DB failure handling, and mergeStateStatus projection.
This commit is contained in:
Affaan Mustafa
2026-06-04 21:45:13 +08:00
committed by GitHub
parent 0f84c0e279
commit bc8e12bb80
19 changed files with 872 additions and 17 deletions

View File

@@ -215,6 +215,7 @@ function renderControlPaneHtml() {
.result,
.connector,
.work-item,
.action {
padding: 12px 14px;
border-bottom: 1px solid rgba(52, 64, 56, 0.7);
@@ -224,10 +225,42 @@ function renderControlPaneHtml() {
.result:last-child,
.connector:last-child,
.work-item:last-child,
.action:last-child {
border-bottom: 0;
}
.kanban {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 8px;
padding: 12px 14px;
border-bottom: 1px solid rgba(52, 64, 56, 0.7);
}
.kanban-lane {
min-width: 0;
padding: 9px;
border: 1px solid rgba(52, 64, 56, 0.8);
border-radius: 6px;
background: #141917;
}
.kanban-lane span {
display: block;
color: var(--muted);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0;
}
.kanban-lane strong {
display: block;
margin-top: 6px;
font-size: 20px;
line-height: 1;
}
.row {
display: flex;
justify-content: space-between;
@@ -310,6 +343,7 @@ function renderControlPaneHtml() {
@media (max-width: 560px) {
main { padding: 12px; }
.metrics { grid-template-columns: 1fr; }
.kanban { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.query { flex-direction: column; }
th:nth-child(4), td:nth-child(4) { display: none; }
}
@@ -338,6 +372,13 @@ function renderControlPaneHtml() {
</div>
<div id="sessions"></div>
</section>
<section>
<div class="section-head">
<h2>Work Items</h2>
<span class="subtle" id="work-item-count"></span>
</div>
<div id="work-items"></div>
</section>
</div>
<div class="stack">
<section>
@@ -413,7 +454,13 @@ function renderControlPaneHtml() {
function statePill(stateName) {
const state = String(stateName || 'unknown');
const klass = state === 'running' ? 'good' : state === 'failed' ? 'bad' : state === 'pending' ? 'warn' : 'blue';
const klass = ['running', 'done'].includes(state)
? 'good'
: ['failed', 'blocked'].includes(state)
? 'bad'
: ['pending', 'ready'].includes(state)
? 'warn'
: 'blue';
return '<span class="pill ' + klass + '">' + escapeHtml(state) + '</span>';
}
@@ -446,6 +493,37 @@ function renderControlPaneHtml() {
'</tbody></table>';
}
function renderWorkItems(workItems) {
const summary = workItems || { totalCount: 0, openCount: 0, blockedCount: 0, doneCount: 0, kanban: {}, items: [] };
const items = Array.isArray(summary.items) ? summary.items : [];
const kanban = summary.kanban || {};
$('#work-item-count').textContent = summary.openCount + ' open / ' + summary.blockedCount + ' blocked';
const lanes = ['ready', 'running', 'blocked', 'done'];
const laneHtml = '<div class="kanban">' + lanes.map(lane =>
'<div class="kanban-lane"><span>' + escapeHtml(lane) + '</span><strong>' + escapeHtml(kanban[lane] || 0) + '</strong></div>'
).join('') + '</div>';
if (!items.length) {
$('#work-items').innerHTML = laneHtml + '<div class="empty">No agent work items found.</div>';
return;
}
$('#work-items').innerHTML = laneHtml + items.slice(0, 8).map(item => {
const branch = item.branch || (item.metadata && item.metadata.branch) || '';
const mergeGate = item.mergeGate || (item.metadata && item.metadata.mergeGate) || '';
const blocker = item.blocker || (item.metadata && item.metadata.blocker) || '';
const owner = item.owner || item.source || 'unassigned';
return '<div class="work-item">' +
'<div class="row"><strong>' + escapeHtml(item.title || item.id) + '</strong>' + statePill(item.kanbanState || item.status) + '</div>' +
'<div class="subtle">' + escapeHtml(owner) + ' - ' + escapeHtml(item.source || 'manual') + (item.priority ? ' - ' + escapeHtml(item.priority) : '') + '</div>' +
(branch ? '<div class="subtle">branch: ' + escapeHtml(branch) + '</div>' : '') +
(mergeGate ? '<div class="subtle">merge gate: ' + escapeHtml(mergeGate) + '</div>' : '') +
(blocker ? '<div class="subtle">blocker: ' + escapeHtml(blocker) + '</div>' : '') +
'</div>';
}).join('');
}
function renderKnowledge(knowledge) {
$('#knowledge-count').textContent = knowledge.entityCount + ' entities';
if (!knowledge.results.length) {
@@ -526,6 +604,7 @@ function renderControlPaneHtml() {
$('#action-status').textContent = snapshot.execution.allowActions ? 'local allowlist' : 'read-only';
renderMetrics(snapshot.summary);
renderSessions(snapshot.sessions);
renderWorkItems(snapshot.workItems);
renderKnowledge(snapshot.knowledge);
renderConnectors(snapshot.connectors);
renderActions(snapshot.actions.map(action => ({