@yemi33/minions 0.1.2100 → 0.1.2102
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/slim/body.html +251 -0
- package/dashboard/slim/js/chat.js +329 -0
- package/dashboard/slim/js/command-send.js +190 -0
- package/dashboard/slim/js/helpers.js +26 -0
- package/dashboard/slim/js/history.js +442 -0
- package/dashboard/slim/js/link-pr.js +83 -0
- package/dashboard/slim/js/members.js +138 -0
- package/dashboard/slim/js/modals-tiles.js +161 -0
- package/dashboard/slim/js/projects.js +209 -0
- package/dashboard/slim/js/settings.js +130 -0
- package/dashboard/slim/js/status.js +122 -0
- package/dashboard/slim/layout.html +21 -0
- package/dashboard/slim/styles.css +1128 -0
- package/dashboard-build.js +33 -1
- package/dashboard.js +11 -10
- package/docs/pr-review-fix-loop.md +2 -2
- package/engine/ado.js +21 -0
- package/engine/github.js +21 -0
- package/engine/lifecycle.js +35 -0
- package/engine/shared.js +9 -11
- package/package.json +1 -1
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// ── Settings dialog ────────────────────────────────────────────
|
|
2
|
+
async function openSlimSettings() {
|
|
3
|
+
var modal = document.getElementById('slim-settings-modal');
|
|
4
|
+
var body = document.getElementById('slim-settings-body');
|
|
5
|
+
body.innerHTML = '<p>Loading flags…</p>';
|
|
6
|
+
modal.classList.add('open');
|
|
7
|
+
try {
|
|
8
|
+
var res = await fetch('/api/features');
|
|
9
|
+
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
10
|
+
var data = await res.json();
|
|
11
|
+
var flags = (data && data.features) || [];
|
|
12
|
+
|
|
13
|
+
// Build the body as a document fragment so we avoid .innerHTML for
|
|
14
|
+
// the dynamic parts (SEC-03 ratchet — see DYNAMIC_INNERHTML_BASELINE
|
|
15
|
+
// in test/unit.test.js). Static intro paragraph uses innerHTML
|
|
16
|
+
// because it carries a <code> tag and is a pure string literal
|
|
17
|
+
// (exempted by _isStaticInnerHtmlRhs).
|
|
18
|
+
var intro = document.createElement('p');
|
|
19
|
+
intro.innerHTML = 'Toggle <code>slim-ux</code> off to return to the original dashboard. Advanced settings (agents, projects, runtime) live there for now.';
|
|
20
|
+
var frag = document.createDocumentFragment();
|
|
21
|
+
frag.appendChild(intro);
|
|
22
|
+
|
|
23
|
+
if (flags.length === 0) {
|
|
24
|
+
var none = document.createElement('p');
|
|
25
|
+
none.innerHTML = 'No experimental flags registered. Add entries to <code>engine/features.js</code>.';
|
|
26
|
+
frag.appendChild(none);
|
|
27
|
+
} else {
|
|
28
|
+
for (var i = 0; i < flags.length; i++) {
|
|
29
|
+
var f = flags[i];
|
|
30
|
+
var row = document.createElement('div');
|
|
31
|
+
row.className = 'flag-row';
|
|
32
|
+
var info = document.createElement('div');
|
|
33
|
+
var name = document.createElement('div');
|
|
34
|
+
name.className = 'flag-name';
|
|
35
|
+
name.textContent = f.id;
|
|
36
|
+
var desc = document.createElement('div');
|
|
37
|
+
desc.className = 'flag-desc';
|
|
38
|
+
desc.textContent = f.description || '';
|
|
39
|
+
info.appendChild(name);
|
|
40
|
+
info.appendChild(desc);
|
|
41
|
+
var lbl = document.createElement('label');
|
|
42
|
+
lbl.className = 'flag-toggle';
|
|
43
|
+
var input = document.createElement('input');
|
|
44
|
+
input.type = 'checkbox';
|
|
45
|
+
input.setAttribute('data-flag', f.id);
|
|
46
|
+
if (f.enabled) input.checked = true;
|
|
47
|
+
var stateSpan = document.createElement('span');
|
|
48
|
+
stateSpan.textContent = f.enabled ? 'On' : 'Off';
|
|
49
|
+
lbl.appendChild(input);
|
|
50
|
+
lbl.appendChild(stateSpan);
|
|
51
|
+
row.appendChild(info);
|
|
52
|
+
row.appendChild(lbl);
|
|
53
|
+
frag.appendChild(row);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// TEMP: deep-link to the full dashboard's settings page. The slim
|
|
58
|
+
// intentionally doesn't reimplement that whole UI — Carlos asked us
|
|
59
|
+
// to keep settings sprawl off the main slim view.
|
|
60
|
+
var link = document.createElement('a');
|
|
61
|
+
link.className = 'settings-link';
|
|
62
|
+
link.href = '/?fullDashboard=1';
|
|
63
|
+
link.title = 'Open the full dashboard’s settings page';
|
|
64
|
+
link.textContent = 'Advanced settings → full dashboard';
|
|
65
|
+
frag.appendChild(link);
|
|
66
|
+
|
|
67
|
+
body.replaceChildren(frag);
|
|
68
|
+
var inputs = body.querySelectorAll('input[type=checkbox][data-flag]');
|
|
69
|
+
for (var j = 0; j < inputs.length; j++) {
|
|
70
|
+
inputs[j].addEventListener('change', onFlagToggle);
|
|
71
|
+
}
|
|
72
|
+
} catch (e) {
|
|
73
|
+
var err = document.createElement('p');
|
|
74
|
+
err.style.color = 'var(--red)';
|
|
75
|
+
err.textContent = 'Failed to load flags: ' + (e && e.message ? e.message : 'unknown error');
|
|
76
|
+
body.replaceChildren(err);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function closeSlimSettings() {
|
|
81
|
+
document.getElementById('slim-settings-modal').classList.remove('open');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
(function bindSettingsUi() {
|
|
85
|
+
var btn = document.getElementById('slim-settings-btn');
|
|
86
|
+
var closeBtn = document.getElementById('slim-settings-close');
|
|
87
|
+
var backdrop = document.getElementById('slim-settings-modal');
|
|
88
|
+
if (btn) btn.addEventListener('click', openSlimSettings);
|
|
89
|
+
if (closeBtn) closeBtn.addEventListener('click', closeSlimSettings);
|
|
90
|
+
if (backdrop) {
|
|
91
|
+
backdrop.addEventListener('click', function(ev) {
|
|
92
|
+
if (ev.target === backdrop) closeSlimSettings();
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
document.addEventListener('keydown', function(ev) {
|
|
96
|
+
if (ev.key !== 'Escape') return;
|
|
97
|
+
if (backdrop && backdrop.classList.contains('open')) closeSlimSettings();
|
|
98
|
+
});
|
|
99
|
+
})();
|
|
100
|
+
|
|
101
|
+
async function onFlagToggle(ev) {
|
|
102
|
+
var input = ev.currentTarget;
|
|
103
|
+
var id = input.getAttribute('data-flag');
|
|
104
|
+
var enabled = input.checked;
|
|
105
|
+
var label = input.parentElement.querySelector('span');
|
|
106
|
+
if (label) label.textContent = enabled ? 'On' : 'Off';
|
|
107
|
+
try {
|
|
108
|
+
var res = await fetch('/api/features/toggle', {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: { 'Content-Type': 'application/json' },
|
|
111
|
+
body: JSON.stringify({ id: id, enabled: enabled }),
|
|
112
|
+
});
|
|
113
|
+
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
114
|
+
if (id === 'slim-ux' && !enabled) {
|
|
115
|
+
location.reload();
|
|
116
|
+
}
|
|
117
|
+
} catch (e) {
|
|
118
|
+
input.checked = !enabled;
|
|
119
|
+
if (label) label.textContent = !enabled ? 'On' : 'Off';
|
|
120
|
+
alert('Toggle failed: ' + e.message);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ── Action prompt modal (TEMP: routes through chat) ──────────────
|
|
125
|
+
// Each of the 4 action buttons opens this modal with prompt text
|
|
126
|
+
// tailored to the concept. On submit we drop the user's text into the
|
|
127
|
+
// chat input prefixed with intent text and submit it through the
|
|
128
|
+
// existing CC stream pipeline. Direct /api/work-items style wiring is a
|
|
129
|
+
// round-2 task — we want CC's natural-language handling for round 1
|
|
130
|
+
// because it's already working end to end.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// ── Status cockpit + history feed ──────────────────────────────
|
|
2
|
+
// Polls /api/status every 5s and updates the right column. The full
|
|
3
|
+
// dashboard exposes a richer SSE /api/status-stream; we use plain
|
|
4
|
+
// polling for round 1 to keep the slim self-contained. Round-2 should
|
|
5
|
+
// switch to SSE so the "minions working" tile reacts in real time.
|
|
6
|
+
|
|
7
|
+
var STATUS_POLL_MS = 5000;
|
|
8
|
+
var pollTimer = null;
|
|
9
|
+
var refreshTimer = null;
|
|
10
|
+
|
|
11
|
+
function scheduleStatusRefresh(delay) {
|
|
12
|
+
if (refreshTimer) clearTimeout(refreshTimer);
|
|
13
|
+
refreshTimer = setTimeout(function() {
|
|
14
|
+
refreshTimer = null;
|
|
15
|
+
pollStatusOnce();
|
|
16
|
+
}, delay || 0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Apply lit-state and value/detail text to a tile.
|
|
20
|
+
function updateTile(key, value, detail, lit) {
|
|
21
|
+
var tile = document.querySelector('.cockpit-tile[data-tile="' + key + '"]');
|
|
22
|
+
if (!tile) return;
|
|
23
|
+
var valEl = tile.querySelector('.cockpit-value');
|
|
24
|
+
var detEl = tile.querySelector('.cockpit-detail');
|
|
25
|
+
if (valEl) {
|
|
26
|
+
valEl.textContent = (value === null || value === undefined) ? '—' : String(value);
|
|
27
|
+
valEl.classList.toggle('dim', !lit);
|
|
28
|
+
}
|
|
29
|
+
if (detEl) detEl.textContent = detail || '';
|
|
30
|
+
tile.classList.remove('lit-blue', 'lit-green', 'lit-amber', 'lit-red');
|
|
31
|
+
if (lit) tile.classList.add('lit-' + lit);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function describeDispatch(d) {
|
|
35
|
+
var who = d.agent || d.dispatched_to || 'someone';
|
|
36
|
+
var what = d.title || d.workItemTitle || d.id || '(untitled)';
|
|
37
|
+
return who + ' — ' + what;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Latest /api/status snapshot — read by the cockpit-tile detail modals.
|
|
41
|
+
var lastStatusData = null;
|
|
42
|
+
|
|
43
|
+
function applyStatus(data) {
|
|
44
|
+
if (!data) return;
|
|
45
|
+
lastStatusData = data;
|
|
46
|
+
// ── Engine tile ────────────────────────────────────────────
|
|
47
|
+
var engine = data.engine || {};
|
|
48
|
+
// /api/status exposes `state` ('running'|'paused'|'stopping'|'stopped')
|
|
49
|
+
// and `started_at`. Trust `state` only — codeVersion/started_at are static
|
|
50
|
+
// fields that persist when the engine is paused/stopped, so they'd mislabel
|
|
51
|
+
// a down engine as running. Absent state (shouldn't happen) reads as down.
|
|
52
|
+
var engineState = engine.state || '';
|
|
53
|
+
var startedAt = engine.started_at;
|
|
54
|
+
var engineRunning = !!engineState && engineState !== 'stopped';
|
|
55
|
+
if (!engineState) engineState = 'stopped';
|
|
56
|
+
var engineLit = engineState === 'running' ? 'green' : (engineState === 'paused' ? 'amber' : 'red');
|
|
57
|
+
updateTile(
|
|
58
|
+
'engine',
|
|
59
|
+
engineRunning ? engineState : 'down',
|
|
60
|
+
startedAt ? 'up since ' + relTime(startedAt) : '—',
|
|
61
|
+
engineLit
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// ── Dispatches ─────────────────────────────────────────────
|
|
65
|
+
// The per-minion "working" view moved to the Team cards (renderMembers);
|
|
66
|
+
// this section only drives the system tiles now.
|
|
67
|
+
var dispatch = data.dispatch || {};
|
|
68
|
+
var active = Array.isArray(dispatch.active) ? dispatch.active : [];
|
|
69
|
+
var pending = Array.isArray(dispatch.pending) ? dispatch.pending : [];
|
|
70
|
+
var completed = Array.isArray(dispatch.completed) ? dispatch.completed : [];
|
|
71
|
+
updateTile(
|
|
72
|
+
'dispatches',
|
|
73
|
+
active.length,
|
|
74
|
+
active.length ? 'in flight' : 'none in flight',
|
|
75
|
+
active.length ? 'blue' : null
|
|
76
|
+
);
|
|
77
|
+
updateTile(
|
|
78
|
+
'queued',
|
|
79
|
+
pending.length,
|
|
80
|
+
pending.length ? 'waiting to spawn' : 'queue empty',
|
|
81
|
+
pending.length ? 'amber' : null
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// ── PR tile (status snapshot from cached pull-requests) ─────
|
|
85
|
+
var prs = Array.isArray(data.pullRequests) ? data.pullRequests : [];
|
|
86
|
+
var activePrs = prs.filter(function(p) {
|
|
87
|
+
return p && (p.status === 'active' || p.status === 'linked');
|
|
88
|
+
});
|
|
89
|
+
var failingPrs = activePrs.filter(function(p) { return p.buildStatus === 'failing'; });
|
|
90
|
+
var prDetail = activePrs.length === 0
|
|
91
|
+
? 'no open PRs'
|
|
92
|
+
: (failingPrs.length ? failingPrs.length + ' failing build' + (failingPrs.length === 1 ? '' : 's')
|
|
93
|
+
: 'all healthy');
|
|
94
|
+
updateTile(
|
|
95
|
+
'prs',
|
|
96
|
+
activePrs.length,
|
|
97
|
+
prDetail,
|
|
98
|
+
activePrs.length === 0 ? null : (failingPrs.length ? 'red' : 'blue')
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// ── Watches tile ───────────────────────────────────────────
|
|
102
|
+
var watches = Array.isArray(data.watches) ? data.watches : [];
|
|
103
|
+
var activeWatches = watches.filter(function(w) { return w && w.status === 'active'; });
|
|
104
|
+
updateTile(
|
|
105
|
+
'watches',
|
|
106
|
+
activeWatches.length,
|
|
107
|
+
activeWatches.length ? 'monitoring' : 'no watches set',
|
|
108
|
+
activeWatches.length ? 'blue' : null
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// ── Team member cards ──────────────────────────────────────
|
|
112
|
+
renderMembers(Array.isArray(data.agents) ? data.agents : []);
|
|
113
|
+
|
|
114
|
+
// ── Stamps + history ───────────────────────────────────────
|
|
115
|
+
var stamp = document.getElementById('cockpit-stamp');
|
|
116
|
+
if (stamp) stamp.textContent = 'updated ' + relTime(data.timestamp || Date.now());
|
|
117
|
+
|
|
118
|
+
renderHistoryFeed({ active: active, completed: completed, prs: activePrs });
|
|
119
|
+
var hStamp = document.getElementById('history-stamp');
|
|
120
|
+
if (hStamp) hStamp.textContent = relTime(data.timestamp || Date.now());
|
|
121
|
+
}
|
|
122
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>Minions — Slim UX{{title_suffix}}</title>
|
|
6
|
+
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>{{favicon_emoji}}</text></svg>">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
<style>
|
|
9
|
+
/* __CSS__ */
|
|
10
|
+
</style>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<!-- __BODY__ -->
|
|
14
|
+
<script>
|
|
15
|
+
(function() {
|
|
16
|
+
'use strict';
|
|
17
|
+
/* __JS__ */
|
|
18
|
+
})();
|
|
19
|
+
</script>
|
|
20
|
+
</body>
|
|
21
|
+
</html>
|