@yemi33/minions 0.1.2286 → 0.1.2287
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/js/render-inbox.js +32 -3
- package/dashboard/js/render-other.js +1 -1
- package/dashboard/js/render-plans.js +3 -3
- package/dashboard/js/render-prs.js +45 -4
- package/dashboard/js/render-schedules.js +2 -2
- package/dashboard/js/render-skills.js +3 -3
- package/dashboard/js/render-watches.js +2 -2
- package/dashboard/js/render-work-items.js +3 -1
- package/dashboard/js/settings.js +45 -0
- package/dashboard/pages/engine.html +3 -3
- package/dashboard/pages/meetings.html +2 -2
- package/dashboard/pages/pipelines.html +1 -1
- package/dashboard/pages/tools.html +3 -3
- package/dashboard/pages/watches.html +1 -1
- package/dashboard/slim/js/status.js +29 -15
- package/dashboard/styles.css +22 -4
- package/dashboard.js +51 -12
- package/docs/deprecated.json +35 -161
- package/docs/live-checkout-mode.md +54 -0
- package/docs/project-skills.md +0 -1
- package/engine/cleanup.js +51 -2
- package/engine/db/migrations/015-plans-prds.js +0 -0
- package/engine/features.js +14 -0
- package/engine/llm.js +1 -0
- package/engine/playbook.js +4 -4
- package/engine/prd-store.js +264 -0
- package/engine/queries.js +97 -59
- package/engine/runtimes/copilot.js +32 -5
- package/engine/shared.js +24 -8
- package/engine/watch-actions.js +4 -1
- package/engine.js +42 -7
- package/package.json +1 -1
|
@@ -75,6 +75,31 @@ async function fetchNotesFromDisk() {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
// Promote a human-readable title to the inbox card heading. Prefers an
|
|
79
|
+
// explicit `title` field from the API, then a `title:` YAML frontmatter key,
|
|
80
|
+
// then the first markdown heading, then the first non-empty body line. Mirrors
|
|
81
|
+
// the engine's own title derivation (engine/consolidation.js — first `# ` ATX
|
|
82
|
+
// heading). Returns '' when nothing readable is extractable so the caller can
|
|
83
|
+
// fall back to the raw filename.
|
|
84
|
+
function _extractInboxTitle(item) {
|
|
85
|
+
if (item && item.title && String(item.title).trim()) return String(item.title).trim();
|
|
86
|
+
const content = (item && item.content) || '';
|
|
87
|
+
let body = content;
|
|
88
|
+
const fm = content.match(/^\s*---\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n?/);
|
|
89
|
+
if (fm) {
|
|
90
|
+
const titleField = fm[1].match(/^title:[ \t]*(.+)$/m);
|
|
91
|
+
if (titleField && titleField[1].trim()) {
|
|
92
|
+
return titleField[1].trim().replace(/^["']|["']$/g, '').trim();
|
|
93
|
+
}
|
|
94
|
+
body = content.slice(fm[0].length);
|
|
95
|
+
}
|
|
96
|
+
const heading = body.match(/^#{1,6}[ \t]+(.+)$/m);
|
|
97
|
+
if (heading && heading[1].trim()) return heading[1].trim();
|
|
98
|
+
const firstLine = body.split(/\r?\n/).map((l) => l.trim()).find(Boolean);
|
|
99
|
+
if (firstLine) return firstLine.replace(/^#+[ \t]*/, '').trim();
|
|
100
|
+
return '';
|
|
101
|
+
}
|
|
102
|
+
|
|
78
103
|
function _inboxPrev() { if (_inboxPage > 0) { _inboxPage--; renderInbox(inboxData, { preserveScroll: false }); } }
|
|
79
104
|
function _inboxNext() { _inboxPage++; renderInbox(inboxData, { preserveScroll: false }); }
|
|
80
105
|
|
|
@@ -99,15 +124,19 @@ function renderInbox(inbox, opts) {
|
|
|
99
124
|
const inboxStart = _inboxPage * INBOX_PER_PAGE;
|
|
100
125
|
const pageInbox = inbox.slice(inboxStart, inboxStart + INBOX_PER_PAGE);
|
|
101
126
|
|
|
102
|
-
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: inbox name, age, content, pin key)
|
|
127
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escapeHtml() (fields: inbox name, derived title/heading, age, content, pin key)
|
|
103
128
|
list.innerHTML = pageInbox.map((item, i) => {
|
|
104
129
|
const idx = inboxStart + i;
|
|
105
130
|
const pk = inboxPinKey(item.name);
|
|
106
131
|
const pinned = isPinned(pk);
|
|
132
|
+
const title = _extractInboxTitle(item);
|
|
133
|
+
const hasTitle = !!title;
|
|
134
|
+
const heading = hasTitle ? title : item.name;
|
|
107
135
|
return `<div class="inbox-item${pinned ? ' item-pinned' : ''}" data-file="notes/inbox/${escapeHtml(item.name)}">
|
|
108
|
-
<div class="inbox-name" onclick="openModal(${idx})" style="cursor:pointer">
|
|
109
|
-
<span>${escapeHtml(
|
|
136
|
+
<div class="inbox-name${hasTitle ? '' : ' inbox-name-fallback'}" onclick="openModal(${idx})" style="cursor:pointer">
|
|
137
|
+
<span>${escapeHtml(heading)}</span><span>${escapeHtml(item.age || '')}</span>
|
|
110
138
|
</div>
|
|
139
|
+
${hasTitle ? `<div class="inbox-filename">${escapeHtml(item.name)}</div>` : ''}
|
|
111
140
|
<div class="inbox-preview" onclick="openModal(${idx})" style="cursor:pointer">${escapeHtml(item.content.slice(0,200))}</div>
|
|
112
141
|
<div style="display:flex;gap:6px;margin-top:6px;align-items:center">
|
|
113
142
|
${pinButton(pk, pinned, 'inbox')}
|
|
@@ -265,7 +265,7 @@ function renderLlmPerf(metrics) {
|
|
|
265
265
|
return '<tr><td style="font-weight:600">' + escHtml(type) + '</td>' +
|
|
266
266
|
'<td>' + calls + '</td>' +
|
|
267
267
|
'<td style="color:var(--muted)">' + (totalMs ? fmtTotal : '-') + '</td>' +
|
|
268
|
-
'<td style="color:var(--
|
|
268
|
+
'<td style="color:var(--green)">' + (avgMs ? fmtAvg : '-') + '</td>' +
|
|
269
269
|
'<td style="color:var(--muted)">' + cost + '</td></tr>';
|
|
270
270
|
}
|
|
271
271
|
let html = '<table class="pr-table"><thead><tr><th>Call Type</th><th>Calls</th><th>Total Time</th><th>Avg Time</th><th>Cost</th></tr></thead><tbody>';
|
|
@@ -306,7 +306,7 @@ function renderPlans(plans) {
|
|
|
306
306
|
'completed': 'Completed', 'dispatched': 'In Progress', 'converting': 'Converting to PRD',
|
|
307
307
|
'paused': 'Paused', 'awaiting-approval': 'Awaiting Approval', 'approved': 'Approved',
|
|
308
308
|
'rejected': 'Rejected', 'revision-requested': 'Revision Requested',
|
|
309
|
-
'has-failures': 'Has Failures', 'active': 'Active'
|
|
309
|
+
'has-failures': 'Has Failures', 'active': 'Active', 'draft': 'Draft'
|
|
310
310
|
};
|
|
311
311
|
const label = statusLabelsMap[effectiveStatus] || effectiveStatus;
|
|
312
312
|
const needsAction = (effectiveStatus === 'awaiting-approval' || effectiveStatus === 'paused') && !isArchived;
|
|
@@ -366,7 +366,7 @@ function renderPlans(plans) {
|
|
|
366
366
|
'onclick="event.stopPropagation();planDelete(\'' + escapeHtml(p.file) + '\')">Delete</button>' : '';
|
|
367
367
|
|
|
368
368
|
const versionBadge = p.version ? ' <span style="font-size:var(--text-xs);font-weight:700;padding:1px 5px;border-radius:3px;background:rgba(56,139,253,0.15);color:var(--blue);vertical-align:middle">v' + p.version + '</span>' : '';
|
|
369
|
-
const statusColors = { 'completed': 'var(--green)', 'dispatched': 'var(--blue)', 'converting': 'var(--yellow)', 'paused': 'var(--muted)', 'awaiting-approval': 'var(--yellow)', 'approved': 'var(--green)', 'rejected': 'var(--red)', 'has-failures': 'var(--red)', 'revision-requested': 'var(--purple,#a855f7)', 'active': 'var(--muted)' };
|
|
369
|
+
const statusColors = { 'completed': 'var(--green)', 'dispatched': 'var(--blue)', 'converting': 'var(--yellow)', 'paused': 'var(--muted)', 'awaiting-approval': 'var(--yellow)', 'approved': 'var(--green)', 'rejected': 'var(--red)', 'has-failures': 'var(--red)', 'revision-requested': 'var(--purple,#a855f7)', 'active': 'var(--muted)', 'draft': 'var(--muted)' };
|
|
370
370
|
const cardClass = effectiveStatus === 'dispatched' || effectiveStatus === 'converting' ? 'working' : effectiveStatus === 'awaiting-approval' || effectiveStatus === 'paused' ? 'awaiting' : effectiveStatus;
|
|
371
371
|
// P-e8d49105 — cross-repo plans surface every touched project as its
|
|
372
372
|
// own badge. Single-project plans (and old PRDs without _projects)
|
|
@@ -402,7 +402,7 @@ function renderPlans(plans) {
|
|
|
402
402
|
'<div class="plan-card-header">' +
|
|
403
403
|
'<div><div class="plan-card-title">' + escapeHtml(p.summary || p.file) + versionBadge + '</div>' +
|
|
404
404
|
'<div class="plan-card-meta">' +
|
|
405
|
-
'<span style="font-weight:600;color:' + (statusColors[effectiveStatus] || 'var(--muted)') + '">' + label + '</span>' +
|
|
405
|
+
'<span style="font-size:var(--text-xs);font-weight:600;padding:1px 8px;border-radius:10px;background:color-mix(in srgb, ' + (statusColors[effectiveStatus] || 'var(--muted)') + ' 15%, transparent);color:' + (statusColors[effectiveStatus] || 'var(--muted)') + ';border:1px solid color-mix(in srgb, ' + (statusColors[effectiveStatus] || 'var(--muted)') + ' 40%, transparent)">' + escapeHtml(label) + '</span>' +
|
|
406
406
|
projectMeta +
|
|
407
407
|
'<span>' + p.itemCount + ' items</span>' +
|
|
408
408
|
perProjectPills +
|
|
@@ -93,6 +93,44 @@ function _parseCanonicalPrId(id) {
|
|
|
93
93
|
return { host: m[1], slug: m[2], number: parseInt(m[3], 10) };
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
// Polish (W-mqv5ccn7): collapse the canonical PR id (`<host>:<slug>#<number>`)
|
|
97
|
+
// into a short, readable label for the narrow PR column — `gh #481`,
|
|
98
|
+
// `ado #5383607` — instead of the full slug, which ellipsis-clipped mid-org
|
|
99
|
+
// (`ado:of…`, `github…`) and looked like an error. The full canonical id stays
|
|
100
|
+
// on the cell's title attr for hover. Unrecognized ids pass through unchanged.
|
|
101
|
+
function prShortId(id) {
|
|
102
|
+
if (!id || typeof id !== 'string') return id || '—';
|
|
103
|
+
var m = id.match(/^([^:]+):.+#(\d+)$/);
|
|
104
|
+
if (!m) return id;
|
|
105
|
+
var host = m[1].toLowerCase();
|
|
106
|
+
var label = host === 'github' ? 'gh' : host === 'ado' ? 'ado' : host;
|
|
107
|
+
return label + ' #' + m[2];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Polish (W-mqv5ccn7): strip common Markdown syntax so the PR body preview
|
|
111
|
+
// shows plain text instead of raw source (`## Summary`, `**bold**`, backticks,
|
|
112
|
+
// links, …). This is a display-only flatten — not a renderer — so headings,
|
|
113
|
+
// emphasis, code spans, list markers, blockquotes, links and images all reduce
|
|
114
|
+
// to their visible text on a single collapsed line.
|
|
115
|
+
function stripMarkdown(text) {
|
|
116
|
+
if (!text || typeof text !== 'string') return '';
|
|
117
|
+
return text
|
|
118
|
+
.replace(/```[\s\S]*?```/g, ' ') // fenced code blocks
|
|
119
|
+
.replace(/`([^`]+)`/g, '$1') // inline code
|
|
120
|
+
.replace(/!\[[^\]]*\]\([^)]*\)/g, ' ') // images
|
|
121
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1') // links -> link text
|
|
122
|
+
.replace(/^\s{0,3}#{1,6}\s+/gm, '') // ATX headings
|
|
123
|
+
.replace(/^\s{0,3}>\s?/gm, '') // blockquotes
|
|
124
|
+
.replace(/^\s*([-*_]\s*){3,}$/gm, ' ') // horizontal rules
|
|
125
|
+
.replace(/^\s*[-*+]\s+/gm, '') // bullet list markers
|
|
126
|
+
.replace(/^\s*\d+\.\s+/gm, '') // ordered list markers
|
|
127
|
+
.replace(/(\*\*|__)(.*?)\1/g, '$2') // bold
|
|
128
|
+
.replace(/(\*|_)(.*?)\1/g, '$2') // italic
|
|
129
|
+
.replace(/~~(.*?)~~/g, '$1') // strikethrough
|
|
130
|
+
.replace(/\s+/g, ' ') // collapse whitespace/newlines
|
|
131
|
+
.trim();
|
|
132
|
+
}
|
|
133
|
+
|
|
96
134
|
function prRow(pr) {
|
|
97
135
|
// Minions review (agent) state — separate from ADO human review
|
|
98
136
|
const sq = pr.minionsReview || {};
|
|
@@ -150,6 +188,9 @@ function prRow(pr) {
|
|
|
150
188
|
}).join('');
|
|
151
189
|
}
|
|
152
190
|
const titleText = pr.title || 'Untitled';
|
|
191
|
+
// Polish (W-mqv5ccn7): flatten Markdown in the body so the preview shows
|
|
192
|
+
// plain text instead of raw `##` / `**…**` / backtick source.
|
|
193
|
+
const descPreview = stripMarkdown(pr.description || '');
|
|
153
194
|
const agentText = pr.agent || '—';
|
|
154
195
|
const reviewerCell = sq.reviewer && sq.status !== 'waiting'
|
|
155
196
|
? '<span class="pr-agent" title="' + escapeHtml(sq.note || sq.reviewer) + '">' + escapeHtml(sq.reviewer) + '</span>'
|
|
@@ -200,8 +241,8 @@ function prRow(pr) {
|
|
|
200
241
|
// the header-to-cell count assertion in test/unit.test.js continues to
|
|
201
242
|
// balance.
|
|
202
243
|
return '<tr>' +
|
|
203
|
-
'<td><span class="pr-id" title="' + escapeHtml(String(prId)) + '">' + escapeHtml(String(prId)) + '</span></td>' +
|
|
204
|
-
'<td><a class="pr-title" title="' + escapeHtml(titleText) + '" href="' + escapeHtml(safeUrl(url)) + '" target="_blank" rel="noopener">' + escapeHtml(titleText) + '</a>' + followupChip + pausedChips + (
|
|
244
|
+
'<td><span class="pr-id" title="' + escapeHtml(String(prId)) + '">' + escapeHtml(prShortId(String(prId))) + '</span></td>' +
|
|
245
|
+
'<td><a class="pr-title" title="' + escapeHtml(titleText) + '" href="' + escapeHtml(safeUrl(url)) + '" target="_blank" rel="noopener">' + escapeHtml(titleText) + '</a>' + followupChip + pausedChips + (descPreview ? '<div class="pr-desc" title="' + escapeHtml(descPreview) + '">' + escapeHtml(descPreview.length > 120 ? descPreview.slice(0, 120) + '...' : descPreview) + '</div>' : '') + '</td>' +
|
|
205
246
|
'<td><span class="pr-agent" title="' + escapeHtml(agentText) + '">' + escapeHtml(agentText) + '</span></td>' +
|
|
206
247
|
'<td><span class="' + branchClass + '" title="' + escapeHtml(branchError || branchLabel) + '">' + escapeHtml(branchLabel) + '</span>' + pendingReasonHtml + '</td>' +
|
|
207
248
|
'<td><span class="pr-badge ' + reviewClass + '" title="' + escapeHtml(reviewTitle || reviewLabel) + '">' + escapeHtml(reviewLabel) + '</span></td>' +
|
|
@@ -216,12 +257,12 @@ function prRow(pr) {
|
|
|
216
257
|
}
|
|
217
258
|
|
|
218
259
|
// Explicit per-column widths keep the PR table from ballooning when titles or
|
|
219
|
-
// branches are long. Total ≈
|
|
260
|
+
// branches are long. Total ≈1455px → table grows past viewport on narrow
|
|
220
261
|
// windows and the .pr-table-wrap--prs container scrolls horizontally inside
|
|
221
262
|
// the viewport (sticky scrollbar — see styles.css).
|
|
222
263
|
const PRS_COLGROUP =
|
|
223
264
|
'<colgroup>' +
|
|
224
|
-
'<col style="width:
|
|
265
|
+
'<col style="width:110px">' + // PR id (short label: `gh #481` / `ado #5383607`)
|
|
225
266
|
'<col style="width:320px">' + // Title
|
|
226
267
|
'<col style="width:140px">' + // Agent
|
|
227
268
|
'<col style="width:200px">' + // Branch
|
|
@@ -340,12 +340,12 @@ function renderSchedules(schedules, opts) {
|
|
|
340
340
|
? '<span class="pr-badge approved">enabled</span>'
|
|
341
341
|
: '<span class="pr-badge rejected">disabled</span>';
|
|
342
342
|
const lastRun = s._lastRun ? timeAgo(s._lastRun) : 'never';
|
|
343
|
-
const typeBadge = '<span class="
|
|
343
|
+
const typeBadge = '<span class="pr-badge draft">' + escHtml(s.type || 'implement') + '</span>';
|
|
344
344
|
const humanCron = _cronToHuman(s.cron || '');
|
|
345
345
|
html += '<tr data-sched-id="' + escHtml(s.id || '') + '" style="cursor:pointer" onclick="if(shouldIgnoreSelectionClick(event))return;openScheduleDetail(\'' + escHtml(s.id) + '\')">' +
|
|
346
346
|
'<td><span class="pr-id">' + escHtml(s.id || '') + '</span></td>' +
|
|
347
347
|
'<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml(s.title || '') + '">' + escHtml(s.title || '') + '</td>' +
|
|
348
|
-
'<td><span title="' + escHtml(s.cron || '') + '" style="font-size:var(--text-base);color:var(--
|
|
348
|
+
'<td><span title="' + escHtml(s.cron || '') + '" style="font-size:var(--text-base);color:var(--muted)">' + escHtml(humanCron) + '</span></td>' +
|
|
349
349
|
'<td>' + typeBadge + '</td>' +
|
|
350
350
|
'<td><span style="font-size:var(--text-sm);color:var(--muted)">' + escHtml(s.project || '') + '</span></td>' +
|
|
351
351
|
'<td><span class="pr-agent">' + escHtml(s.agent || 'auto') + '</span></td>' +
|
|
@@ -73,7 +73,7 @@ function renderSkills(skills) {
|
|
|
73
73
|
// native locations (~/.claude/skills, ~/.copilot/skills, plugin skills) are
|
|
74
74
|
// only visible to that runtime. The "agent" tab (~/.agents/skills) is the
|
|
75
75
|
// cross-runtime portable bucket and IS visible to every runtime.
|
|
76
|
-
html += '<div style="font-size:var(--text-xs);color:var(--muted);margin-bottom:8px;line-height:1.4">' +
|
|
76
|
+
html += '<div style="font-size:var(--text-xs);font-style:italic;color:var(--muted);margin-bottom:8px;line-height:1.4">' +
|
|
77
77
|
'Skills are reference docs agents read on demand — they are not injected wholesale into prompts. ' +
|
|
78
78
|
'Each tab reflects what the matching runtime would see; runtime-native skills are NOT cross-visible. ' +
|
|
79
79
|
'The agent tab (~/.agents/skills) is the cross-runtime portable bucket — visible to every runtime.' +
|
|
@@ -92,10 +92,10 @@ function renderSkills(skills) {
|
|
|
92
92
|
const meta = _skillMetaOf(r.source);
|
|
93
93
|
const autoTag = r.autoGenerated ? '<span style="font-size:var(--text-xs);background:rgba(63,185,80,0.15);color:var(--green);padding:1px 4px;border-radius:3px;margin-left:4px">auto</span>' : '';
|
|
94
94
|
html += '<div class="inbox-item" onclick="openSkill(' + _jsArg(r.file) + ',' + _jsArg(r.source || 'claude-code') + ',' + _jsArg(r.dir || '') + ')" style="border-left-color:var(--green)">' +
|
|
95
|
-
'<div class="inbox-name"><span style="color:var(--green);font-weight:
|
|
95
|
+
'<div class="inbox-name"><span style="color:var(--green);font-weight:700">' + meta.icon + ' ' + escHtml(r.name) + '</span>' + autoTag +
|
|
96
96
|
'<span style="font-size:var(--text-xs);color:var(--muted);margin-left:auto">' + escHtml(meta.label) + '</span>' +
|
|
97
97
|
'</div>' +
|
|
98
|
-
(r.description ? '<div class="inbox-preview" style="color:var(--text)">' + escHtml(r.description) + '</div>' : '') +
|
|
98
|
+
(r.description ? '<div class="inbox-preview" style="font-size:var(--text-sm);color:var(--text)">' + escHtml(r.description) + '</div>' : '') +
|
|
99
99
|
'</div>';
|
|
100
100
|
}
|
|
101
101
|
html += '</div>';
|
|
@@ -242,9 +242,9 @@ function renderWatches(watchesData, opts) {
|
|
|
242
242
|
|
|
243
243
|
html += '<tr style="cursor:pointer" onclick="if(shouldIgnoreSelectionClick(event))return;openWatchDetail(\'' + escHtml(w.id) + '\')">' +
|
|
244
244
|
'<td><span class="pr-id">' + escHtml(w.id) + '</span></td>' +
|
|
245
|
-
'<td style="max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml(_watchTitleFallback(w)) + '">' + escHtml(_formatWatchTarget(w.target, w.targetType) || _watchTitleFallback(w)) + '</td>' +
|
|
245
|
+
'<td style="max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escHtml(_watchTitleFallback(w)) + '"><span class="pr-id">' + escHtml(_formatWatchTarget(w.target, w.targetType) || _watchTitleFallback(w)) + '</span></td>' +
|
|
246
246
|
'<td><span class="dispatch-type explore">' + escHtml(targetLabel) + '</span></td>' +
|
|
247
|
-
'<td><span style="
|
|
247
|
+
'<td><span class="pr-badge" style="background:rgba(139,148,158,0.15);color:var(--muted);border-color:var(--muted)">' + escHtml(condLabel) + '</span></td>' +
|
|
248
248
|
'<td><span style="font-size:var(--text-sm)">' + actionLabel + '</span></td>' +
|
|
249
249
|
'<td><span style="font-size:var(--text-sm);color:var(--muted)">' + escHtml(_intervalToHuman(w.interval)) + '</span></td>' +
|
|
250
250
|
'<td><span class="pr-agent">' + escHtml(w.owner || 'human') + '</span></td>' +
|
|
@@ -175,7 +175,9 @@ function wiRow(item) {
|
|
|
175
175
|
// the PR column (not the Agent column). The engine prefixes failReason with
|
|
176
176
|
// "Non-retryable failure:" and stamps item._failureClass on non-retryable
|
|
177
177
|
// demotion (engine/dispatch.js writeDispatchResult / force-demote path).
|
|
178
|
-
var isNonRetryableFail = !!item._failureClass
|
|
178
|
+
var isNonRetryableFail = !!item._failureClass
|
|
179
|
+
|| /^Non-retryable failure:/i.test(item.failReason || '')
|
|
180
|
+
|| /^Project "[^"]+" not found\./i.test(item.failReason || '');
|
|
179
181
|
var failSnippet = item.failReason
|
|
180
182
|
? '<span style="display:block;font-size:var(--text-xs);color:var(--red)" title="' + escapeHtml(item.failReason) + '">' + escapeHtml(item.failReason.slice(0, 30)) + '</span>'
|
|
181
183
|
: '';
|
package/dashboard/js/settings.js
CHANGED
|
@@ -289,6 +289,32 @@ async function openSettings() {
|
|
|
289
289
|
'⚠ Live mode: dispatches run directly in this repo\'s checkout. Only one mutating dispatch runs at a time. Dirty working trees block dispatch — commit or stash before running.' +
|
|
290
290
|
'</div>' +
|
|
291
291
|
'</div>';
|
|
292
|
+
// M006 — liveValidation section (per-project hybrid mode config).
|
|
293
|
+
// Controls: type (work item type that stays serialized in live checkout)
|
|
294
|
+
// and autoDispatch (auto-create validation WI after coding WI completes).
|
|
295
|
+
// Section is rendered with reduced opacity + pointer-events:none when
|
|
296
|
+
// checkoutMode is not 'live' — it only applies in hybrid mode.
|
|
297
|
+
var lvType = (p.liveValidation && p.liveValidation.type) ? p.liveValidation.type : '';
|
|
298
|
+
var lvAutoDispatch = !!(p.liveValidation && p.liveValidation.autoDispatch);
|
|
299
|
+
var lvDisabled = (currentWtMode !== 'live');
|
|
300
|
+
var lvSectionStyle = lvDisabled
|
|
301
|
+
? 'opacity:0.45;pointer-events:none;margin-bottom:6px'
|
|
302
|
+
: 'margin-bottom:6px';
|
|
303
|
+
var liveValidationBlock =
|
|
304
|
+
'<div data-live-validation-section="' + escHtml(p.name) + '" data-search="live validation deferred build test auto dispatch worktree" style="' + lvSectionStyle + '">' +
|
|
305
|
+
'<label style="font-size:var(--text-sm);color:var(--muted);display:block;margin-bottom:2px">Live validation (deferred build/test)' +
|
|
306
|
+
(lvDisabled ? ' <span style="font-size:var(--text-xs);opacity:0.7">(requires Live checkout)</span>' : '') +
|
|
307
|
+
'</label>' +
|
|
308
|
+
'<div style="font-size:var(--text-xs);color:var(--muted);margin-bottom:4px;line-height:1.4">' +
|
|
309
|
+
'For checkoutMode: live projects — coding agents run in worktrees; a separate dispatch validates in live checkout.' +
|
|
310
|
+
'</div>' +
|
|
311
|
+
'<input id="set-liveValidationType-' + escHtml(p.name) + '" value="' + escHtml(lvType) + '" placeholder="e.g. build-and-test" style="width:100%;padding:4px 6px;background:var(--surface);border:1px solid var(--border);border-radius:4px;color:var(--text);font-size:var(--text-md);margin-bottom:4px">' +
|
|
312
|
+
'<div style="font-size:var(--text-xs);color:var(--muted);margin-bottom:4px">Validation work item type (e.g. <code>build-and-test</code>, <code>test</code>, <code>verify</code>). Leave blank to disable.</div>' +
|
|
313
|
+
'<div style="display:flex;align-items:center;gap:8px;padding:2px 0">' +
|
|
314
|
+
'<input type="checkbox" id="set-liveValidationAutoDispatch-' + escHtml(p.name) + '"' + (lvAutoDispatch ? ' checked' : '') + ' style="accent-color:var(--blue);width:16px;height:16px;cursor:pointer">' +
|
|
315
|
+
'<label for="set-liveValidationAutoDispatch-' + escHtml(p.name) + '" style="font-size:var(--text-md);color:var(--text);cursor:pointer">Auto-dispatch validation WI after coding WI completes</label>' +
|
|
316
|
+
'</div>' +
|
|
317
|
+
'</div>';
|
|
292
318
|
return '<div data-settings-project="' + escHtml(p.name) + '" data-search="project ' + escHtml(p.name.toLowerCase()) + '" style="border:1px solid var(--border);border-radius:6px;padding:10px 12px;margin-bottom:12px">' +
|
|
293
319
|
'<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">' +
|
|
294
320
|
'<div style="font-size:var(--text-md);font-weight:600">' + escHtml(p.name) + '</div>' +
|
|
@@ -297,6 +323,7 @@ async function openSettings() {
|
|
|
297
323
|
pathRow +
|
|
298
324
|
branchGrid +
|
|
299
325
|
worktreeModeBlock +
|
|
326
|
+
liveValidationBlock +
|
|
300
327
|
driftNote +
|
|
301
328
|
'<div style="display:flex;flex-direction:column;gap:6px;margin-top:8px">' +
|
|
302
329
|
settingsToggle('Discover from PRs', 'set-ws-prs-' + p.name, p.workSources.pullRequests.enabled, 'Discovery gate: scan repo for open PRs and surface them as review tasks. Independent of ADO/GitHub polling — does not affect already-tracked PRs.') +
|
|
@@ -700,6 +727,14 @@ async function openSettings() {
|
|
|
700
727
|
const chip = document.querySelector('[data-checkout-mode-chip="' + (window.CSS && CSS.escape ? CSS.escape(projName) : projName) + '"]');
|
|
701
728
|
if (!chip) return;
|
|
702
729
|
chip.style.display = (sel.value === 'live') ? '' : 'none';
|
|
730
|
+
// M006 — also toggle the liveValidation section opacity/pointer-events
|
|
731
|
+
// reactively: disabled when checkoutMode is not 'live'.
|
|
732
|
+
const lvSection = document.querySelector('[data-live-validation-section="' + (window.CSS && CSS.escape ? CSS.escape(projName) : projName) + '"]');
|
|
733
|
+
if (lvSection) {
|
|
734
|
+
const isLive = (sel.value === 'live');
|
|
735
|
+
lvSection.style.opacity = isLive ? '' : '0.45';
|
|
736
|
+
lvSection.style.pointerEvents = isLive ? '' : 'none';
|
|
737
|
+
}
|
|
703
738
|
});
|
|
704
739
|
});
|
|
705
740
|
}
|
|
@@ -1143,10 +1178,20 @@ async function saveSettings() {
|
|
|
1143
1178
|
// values.
|
|
1144
1179
|
const wtModeInput = document.getElementById('set-checkoutMode-' + p.name);
|
|
1145
1180
|
const wtModeValue = (wtModeInput && wtModeInput.value === 'live') ? 'live' : 'worktree';
|
|
1181
|
+
// M006 — liveValidation: read type text input and autoDispatch checkbox.
|
|
1182
|
+
// Empty type → send null so the server clears the field.
|
|
1183
|
+
const lvTypeInput = document.getElementById('set-liveValidationType-' + p.name);
|
|
1184
|
+
const lvAutoDispatchInput = document.getElementById('set-liveValidationAutoDispatch-' + p.name);
|
|
1185
|
+
const lvTypeValue = lvTypeInput ? lvTypeInput.value.trim() : '';
|
|
1186
|
+
const lvAutoDispatchValue = lvAutoDispatchInput ? !!lvAutoDispatchInput.checked : false;
|
|
1187
|
+
const liveValidationValue = lvTypeValue
|
|
1188
|
+
? { type: lvTypeValue, autoDispatch: lvAutoDispatchValue }
|
|
1189
|
+
: null;
|
|
1146
1190
|
return {
|
|
1147
1191
|
name: p.name,
|
|
1148
1192
|
mainBranch: mainBranchValue || null,
|
|
1149
1193
|
checkoutMode: wtModeValue,
|
|
1194
|
+
liveValidation: liveValidationValue,
|
|
1150
1195
|
workSources: {
|
|
1151
1196
|
pullRequests: { enabled: document.getElementById('set-ws-prs-' + p.name)?.checked ?? true },
|
|
1152
1197
|
workItems: { enabled: document.getElementById('set-ws-wi-' + p.name)?.checked ?? true }
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
</section>
|
|
6
6
|
<!-- __ENGINE_MEMORY_PANEL__ -->
|
|
7
7
|
<section>
|
|
8
|
-
<h2>Engine Log <span
|
|
8
|
+
<h2>Engine Log <span class="qa-section-subtitle">tick-by-tick audit trail of engine operations</span></h2>
|
|
9
9
|
<div class="log-list" id="engine-log">No log entries yet.</div>
|
|
10
10
|
</section>
|
|
11
11
|
<section>
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
</section>
|
|
23
23
|
<section id="keep-processes-section">
|
|
24
24
|
<h2>Keep-Processes <span class="count" id="keep-processes-count">0</span>
|
|
25
|
-
<span
|
|
25
|
+
<span class="qa-section-subtitle">processes left running by agents</span>
|
|
26
26
|
</h2>
|
|
27
27
|
<div id="keep-processes-content"><p class="empty">No agents have left processes running. Set <code>meta.keep_processes: true</code> on a work item to enable.</p></div>
|
|
28
28
|
</section>
|
|
29
29
|
<section id="managed-processes-section">
|
|
30
30
|
<h2>Managed Processes <span class="count" id="managed-processes-count">0</span>
|
|
31
|
-
<span
|
|
31
|
+
<span class="qa-section-subtitle">engine-managed long-running services</span>
|
|
32
32
|
</h2>
|
|
33
33
|
<div id="managed-processes-content"><p class="empty">No managed processes. Agents declare them via <code>agents/<id>/managed-spawn.json</code>.</p></div>
|
|
34
34
|
</section>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<section>
|
|
2
|
-
<h2>
|
|
2
|
+
<h2>Meetings <span class="count" id="meetings-count">0</span>
|
|
3
3
|
<button class="btn-add" style="margin-left:8px" onclick="openCreateMeetingModal()">+ New Meeting</button>
|
|
4
|
-
<span
|
|
4
|
+
<span class="qa-section-subtitle">multi-round agent discussions — investigate, debate, conclude</span>
|
|
5
5
|
</h2>
|
|
6
6
|
<div id="meetings-content"><p class="empty">No meetings yet. Start one to have agents investigate, debate, and conclude on a topic.</p></div>
|
|
7
7
|
</section>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<section>
|
|
2
2
|
<h2>Pipelines <span class="count" id="pipelines-count">0</span>
|
|
3
3
|
<button class="btn-add" style="margin-left:8px" onclick="openCreatePipelineModal()">+ New Pipeline</button>
|
|
4
|
-
<span
|
|
4
|
+
<span class="qa-section-subtitle">multi-stage workflows with dependencies — chain meetings, plans, tasks, merges in any order, on a schedule or manual</span>
|
|
5
5
|
</h2>
|
|
6
6
|
<div id="pipelines-content"><p class="empty">No pipelines yet. Create one to chain stages like audit → meeting → plan → merge.</p></div>
|
|
7
7
|
</section>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<section>
|
|
2
|
-
<h2>Minions Skills <span class="count" id="skills-count">0</span> <span
|
|
2
|
+
<h2>Minions Skills <span class="count" id="skills-count">0</span> <span class="qa-section-subtitle">discovered from runtime native dirs, plugin installs, and configured project repos</span></h2>
|
|
3
3
|
<div id="skills-list"><p class="empty">No skills yet. Agents create these when they discover repeatable workflows.</p></div>
|
|
4
4
|
</section>
|
|
5
5
|
<section>
|
|
6
|
-
<h2>Slash Commands <span class="count" id="commands-count">0</span> <span
|
|
6
|
+
<h2>Slash Commands <span class="count" id="commands-count">0</span> <span class="qa-section-subtitle">discovered from runtime native command dirs, plugin installs, and configured project repos</span></h2>
|
|
7
7
|
<div id="commands-list"><p class="empty">No slash commands discovered.</p></div>
|
|
8
8
|
</section>
|
|
9
9
|
<section>
|
|
@@ -11,6 +11,6 @@
|
|
|
11
11
|
<div id="mcp-list"><p class="empty">No MCP servers synced.</p></div>
|
|
12
12
|
</section>
|
|
13
13
|
<section>
|
|
14
|
-
<h2>Harness Propagation <span
|
|
14
|
+
<h2>Harness Propagation <span class="qa-section-subtitle">what each runtime sees, what's attached via --add-dir, and which assets won't propagate to a fresh worktree</span></h2>
|
|
15
15
|
<div id="harness-diag"><p class="empty">Loading harness diagnostics…</p></div>
|
|
16
16
|
</section>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<section id="watches-section">
|
|
2
2
|
<h2>Watches <span class="count" id="watches-count">0</span>
|
|
3
3
|
<button class="btn-add" style="margin-left:8px" onclick="openCreateWatchModal()">+ New</button>
|
|
4
|
-
<span
|
|
4
|
+
<span class="qa-section-subtitle">persistent watches that monitor PRs, work items, and branches for changes</span>
|
|
5
5
|
</h2>
|
|
6
6
|
<div id="watches-content"><p class="empty">No active watches. Create one to monitor PRs, work items, or branches.</p></div>
|
|
7
7
|
</section>
|
|
@@ -86,21 +86,35 @@
|
|
|
86
86
|
|
|
87
87
|
// ── Engine tile ────────────────────────────────────────────
|
|
88
88
|
var engine = data.engine || {};
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
var
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
89
|
+
// Use lastTickAt (stamped by engine.js#tickInner on every successful tick)
|
|
90
|
+
// to show recency instead of the static started_at. This catches a frozen
|
|
91
|
+
// engine that is still "running" per state but hasn't ticked in minutes.
|
|
92
|
+
//
|
|
93
|
+
// Stale detection mirrors classic dashboard render-dispatch.js#renderEngineStatus:
|
|
94
|
+
// when the server flags heartbeatStale (BOTH heartbeat AND lastTickAt have
|
|
95
|
+
// aged past their thresholds) we override the display state to 'stale' and
|
|
96
|
+
// light the tile red — the engine claims running but may have crashed.
|
|
97
|
+
var rawState = engine.state || 'stopped';
|
|
98
|
+
var lastTickAt = engine.lastTickAt;
|
|
99
|
+
var heartbeatStale = !!engine.heartbeatStale;
|
|
100
|
+
|
|
101
|
+
var engineState = rawState;
|
|
102
|
+
if ((rawState === 'running' || rawState === 'degraded') && heartbeatStale) {
|
|
103
|
+
engineState = 'stale';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
var engineLit = engineState === 'running' ? 'green'
|
|
107
|
+
: engineState === 'paused' ? 'amber'
|
|
108
|
+
: 'red'; // stopped / stale / degraded
|
|
109
|
+
|
|
110
|
+
var engineValue = engineState === 'stopped' ? 'down' : engineState;
|
|
111
|
+
|
|
112
|
+
// Detail: time since the last successful tick. When lastTickAt is absent
|
|
113
|
+
// (engine never ticked or very fresh start) fall back to '—'.
|
|
114
|
+
var engineDetail = lastTickAt ? 'last tick ' + relTime(lastTickAt) : '—';
|
|
115
|
+
if (engineState === 'stale') engineDetail += ' · may be down';
|
|
116
|
+
|
|
117
|
+
updateTile('engine', engineValue, engineDetail, engineLit);
|
|
104
118
|
|
|
105
119
|
// ── Dispatches ─────────────────────────────────────────────
|
|
106
120
|
// The per-minion "working" view moved to the Team cards (renderMembers);
|
package/dashboard/styles.css
CHANGED
|
@@ -274,6 +274,10 @@
|
|
|
274
274
|
.pipeline-card-meta { margin-top: var(--space-2); display: flex; flex-wrap: wrap; gap: var(--space-4); font-size: var(--text-sm); color: var(--muted); }
|
|
275
275
|
.pipeline-card-badges { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: var(--space-4); flex-shrink: 0; }
|
|
276
276
|
.pipeline-empty-runs { border: 1px dashed var(--border); border-radius: var(--radius-md); padding: var(--space-6); color: var(--muted); font-size: var(--text-md); background: rgba(139,148,158,0.04); }
|
|
277
|
+
/* Separate the empty-state line from the muted section subtitle so the two
|
|
278
|
+
* muted lines don't read as one block (W-mqv5dvl8000odd79). Scoped to the
|
|
279
|
+
* pipelines list only; the global .empty stays untouched for other screens. */
|
|
280
|
+
#pipelines-content > .empty { margin-top: 2rem; }
|
|
277
281
|
.prd-items-list { display: flex; flex-direction: column; gap: 3px; max-height: 400px; overflow-y: auto; padding: 0 8px; }
|
|
278
282
|
.prd-item-row { display: flex; align-items: center; gap: 8px; padding: 4px 8px; border-radius: var(--radius-sm); font-size: var(--text-base); background: var(--surface2); border: 1px solid var(--border); border-left: 3px solid var(--border); }
|
|
279
283
|
.prd-item-row.st-done { border-left-color: var(--green); }
|
|
@@ -285,7 +289,7 @@
|
|
|
285
289
|
.prd-item-row.st-paused { border-left-color: var(--muted); opacity: 0.5; }
|
|
286
290
|
.prd-item-id { font-family: Consolas, monospace; color: var(--muted); min-width: 36px; font-size: var(--text-code); }
|
|
287
291
|
.prd-item-name { flex: 1; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
288
|
-
.prd-item-priority { font-size: var(--text-sm); padding: var(--space-1) var(--space-3); border-radius: var(--radius-lg); }
|
|
292
|
+
.prd-item-priority { font-size: var(--text-sm); padding: var(--space-1) var(--space-3); border-radius: var(--radius-lg); text-transform: uppercase; }
|
|
289
293
|
.prd-item-priority.high { background: rgba(248,81,73,0.15); color: var(--red); }
|
|
290
294
|
.prd-item-priority.medium { background: rgba(210,153,34,0.15); color: var(--yellow); }
|
|
291
295
|
.prd-item-priority.low { background: rgba(139,148,158,0.15); color: var(--muted); }
|
|
@@ -301,6 +305,8 @@
|
|
|
301
305
|
.pin-btn { color: var(--muted); border-color: var(--border); }
|
|
302
306
|
.pin-btn.pinned { color: var(--yellow); border-color: var(--yellow); }
|
|
303
307
|
.inbox-name { font-weight: 500; font-size: var(--text-md); color: var(--purple); margin-bottom: var(--space-2); display: flex; justify-content: space-between; }
|
|
308
|
+
.inbox-name-fallback { font-weight: 400; color: var(--muted); }
|
|
309
|
+
.inbox-filename { font-size: var(--text-sm); color: var(--muted); margin-bottom: var(--space-2); word-break: break-all; line-height: 1.4; }
|
|
304
310
|
.inbox-preview { font-size: var(--text-base); color: var(--muted); line-height: 1.5; max-height: 60px; overflow: hidden; }
|
|
305
311
|
|
|
306
312
|
.prd-panel, .pr-panel { border-bottom: 1px solid var(--border); overflow: visible; min-width: 0; }
|
|
@@ -324,8 +330,12 @@
|
|
|
324
330
|
ellipsis overflow, and the horizontal scrollbar is pinned inside the
|
|
325
331
|
viewport via a bounded-height container on the standalone /prs page so
|
|
326
332
|
it stays reachable without scrolling to the bottom of a tall table. */
|
|
327
|
-
.pr-table--prs { table-layout: fixed; width: 100%; min-width:
|
|
333
|
+
.pr-table--prs { table-layout: fixed; width: 100%; min-width: 1455px; }
|
|
328
334
|
.pr-table--prs th, .pr-table--prs td { overflow: hidden; text-overflow: ellipsis; }
|
|
335
|
+
/* W-mqv5ccn7 — the Status pill (e.g. ABANDONED) must never ellipsis-truncate
|
|
336
|
+
its own label; let the Status cell (8th column) render its pill in full
|
|
337
|
+
rather than inheriting the shared cell-level clip above. */
|
|
338
|
+
.pr-table--prs td:nth-child(8) { overflow: visible; }
|
|
329
339
|
.pr-table--prs th:last-child, .pr-table--prs td:last-child { width: auto; min-width: 0; }
|
|
330
340
|
.pr-table--prs .pr-title { display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
331
341
|
.pr-table--prs .pr-agent { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; vertical-align: middle; white-space: nowrap; }
|
|
@@ -1286,8 +1296,16 @@
|
|
|
1286
1296
|
font-size: var(--text-sm); color: var(--muted);
|
|
1287
1297
|
font-weight: 400; text-transform: none; letter-spacing: 0;
|
|
1288
1298
|
}
|
|
1289
|
-
.qa-engine-link { color: var(--
|
|
1290
|
-
.qa-engine-link:hover {
|
|
1299
|
+
.qa-engine-link { color: var(--muted); text-decoration: underline; }
|
|
1300
|
+
.qa-engine-link:hover { color: var(--text); }
|
|
1301
|
+
/* Inline text link rendered from a <button> — strip the button chrome so it
|
|
1302
|
+
* reads as a colored underlined link mid-sentence, not a boxed control. */
|
|
1303
|
+
.qa-inline-link {
|
|
1304
|
+
border: none; background: none; padding: 0;
|
|
1305
|
+
display: inline; font: inherit;
|
|
1306
|
+
color: var(--blue); text-decoration: underline; cursor: pointer;
|
|
1307
|
+
}
|
|
1308
|
+
.qa-inline-link:hover { text-decoration: none; }
|
|
1291
1309
|
|
|
1292
1310
|
/* Targets list — slim row per dedup'd target. */
|
|
1293
1311
|
.qa-targets-list {
|