@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.
@@ -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&hellip;</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>