@yemi33/minions 0.1.2144 → 0.1.2146
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/bin/minions.js +85 -0
- package/bin/minions.js.rej +16 -0
- package/dashboard/js/refresh.js +14 -0
- package/dashboard/js/render-pinned.js +119 -3
- package/dashboard/js/utils.js +20 -0
- package/dashboard/layout.html +1 -0
- package/dashboard/slim/body.html +113 -1
- package/dashboard/slim/body.html.rej +11 -0
- package/dashboard/slim/js/command-send.js.rej +12 -0
- package/dashboard/slim/js/helpers.js +9 -0
- package/dashboard/slim/js/history.js +153 -88
- package/dashboard/slim/js/history.js.rej +26 -0
- package/dashboard/slim/js/modals-tiles.js +8 -2
- package/dashboard/slim/js/pinned.js +182 -0
- package/dashboard/slim/js/settings.js +126 -6
- package/dashboard/slim/js/status.js +9 -6
- package/dashboard/slim/layout.html +1 -0
- package/dashboard/slim/styles.css +77 -2
- package/dashboard/slim/styles.css.rej +124 -0
- package/dashboard/styles.css +19 -0
- package/dashboard-build.js +9 -2
- package/dashboard.js +44 -1
- package/docs/README.md.rej +9 -0
- package/docs/auto-discovery.md +2 -2
- package/docs/constellation-style-telemetry.md +161 -0
- package/docs/engine-restart.md +1 -1
- package/docs/kb-sweep.md +2 -2
- package/docs/managed-spawn.md +1 -1
- package/docs/watches.md +11 -11
- package/engine/cleanup.js +9 -0
- package/engine/cli.js +57 -12
- package/engine/features.js +11 -0
- package/engine/shared.js +290 -37
- package/engine/watchdog.js +458 -0
- package/engine/worktree-gc.js +9 -1
- package/engine.js +57 -8
- package/package.json +1 -1
package/bin/minions.js
CHANGED
|
@@ -1338,6 +1338,91 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
|
1338
1338
|
ensureInstalled();
|
|
1339
1339
|
const { doctor } = require(path.join(MINIONS_HOME, 'engine', 'preflight'));
|
|
1340
1340
|
doctor(MINIONS_HOME).then(ok => process.exit(ok ? 0 : 1));
|
|
1341
|
+
} else if (cmd === 'watchdog') {
|
|
1342
|
+
// External recovery scheduled by the OS. Survives cluster-kill scenarios
|
|
1343
|
+
// the in-process supervisor can't (Windows job-object teardown when the
|
|
1344
|
+
// parent terminal closes, Linux OOM-killer, cgroup limits, pkill, reboot).
|
|
1345
|
+
ensureInstalled();
|
|
1346
|
+
const watchdog = require(path.join(MINIONS_HOME, 'engine', 'watchdog'));
|
|
1347
|
+
const sub = rest[0];
|
|
1348
|
+
const minionsBin = __filename;
|
|
1349
|
+
if (sub === 'tick') {
|
|
1350
|
+
const shared = require(path.join(MINIONS_HOME, 'engine', 'shared'));
|
|
1351
|
+
watchdog.tick({
|
|
1352
|
+
minionsHome: MINIONS_HOME,
|
|
1353
|
+
minionsBin,
|
|
1354
|
+
dashPort: resolveDashboardPort([]).port,
|
|
1355
|
+
readEnginePid,
|
|
1356
|
+
isPortListening,
|
|
1357
|
+
isStopIntentSet: shared.isStopIntentSet || (() => false),
|
|
1358
|
+
}).then(result => {
|
|
1359
|
+
// ALWAYS exit 0 — the scheduler must never see a failure for the
|
|
1360
|
+
// recovery we're trying to make boring. The `result` is logged by tick
|
|
1361
|
+
// to engine/watchdog-stdio.log; print it for interactive invocations.
|
|
1362
|
+
if (process.stdout.isTTY) {
|
|
1363
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1364
|
+
}
|
|
1365
|
+
process.exit(0);
|
|
1366
|
+
}).catch(err => {
|
|
1367
|
+
// Defense in depth: tick promises never to throw, but if it does,
|
|
1368
|
+
// swallow the error and exit 0 so the scheduler keeps ticking.
|
|
1369
|
+
try { console.error(`watchdog tick: ${err && err.message || err}`); } catch {}
|
|
1370
|
+
process.exit(0);
|
|
1371
|
+
});
|
|
1372
|
+
} else if (sub === 'install') {
|
|
1373
|
+
const intervalArg = rest.find(a => a.startsWith('--interval='));
|
|
1374
|
+
const intervalMin = intervalArg ? parseInt(intervalArg.split('=')[1], 10) : undefined;
|
|
1375
|
+
try {
|
|
1376
|
+
const r = watchdog.install({
|
|
1377
|
+
minionsBin,
|
|
1378
|
+
minionsHome: MINIONS_HOME,
|
|
1379
|
+
intervalMin,
|
|
1380
|
+
});
|
|
1381
|
+
console.log(`\n Watchdog installed via ${r.scheduler} (every ${r.intervalMin} min).`);
|
|
1382
|
+
if (r.taskName) console.log(` Task: ${r.taskName}`);
|
|
1383
|
+
if (r.launcherPath) console.log(` Launcher: ${r.launcherPath}`);
|
|
1384
|
+
if (r.plistPath) console.log(` Plist: ${r.plistPath}`);
|
|
1385
|
+
if (r.servicePath) console.log(` Service: ${r.servicePath}`);
|
|
1386
|
+
if (r.timerPath) console.log(` Timer: ${r.timerPath}`);
|
|
1387
|
+
if (r.lingerHint) console.log(` Note: ${r.lingerHint}`);
|
|
1388
|
+
console.log(`\n MINIONS_HOME=${MINIONS_HOME} (baked into scheduler entry — re-run install if it moves)`);
|
|
1389
|
+
console.log(` Logs: ${path.join(MINIONS_HOME, 'engine', 'watchdog-stdio.log')}\n`);
|
|
1390
|
+
process.exit(0);
|
|
1391
|
+
} catch (err) {
|
|
1392
|
+
console.error(`\n ERROR: ${err && err.message || err}\n`);
|
|
1393
|
+
process.exit(1);
|
|
1394
|
+
}
|
|
1395
|
+
} else if (sub === 'uninstall') {
|
|
1396
|
+
try {
|
|
1397
|
+
const r = watchdog.uninstall({ minionsHome: MINIONS_HOME });
|
|
1398
|
+
console.log(`\n Watchdog uninstalled from ${r.scheduler}.${r.removed ? '' : ' (already absent — no-op)'}\n`);
|
|
1399
|
+
process.exit(0);
|
|
1400
|
+
} catch (err) {
|
|
1401
|
+
console.error(`\n ERROR: ${err && err.message || err}\n`);
|
|
1402
|
+
process.exit(1);
|
|
1403
|
+
}
|
|
1404
|
+
} else if (sub === 'status' || !sub) {
|
|
1405
|
+
try {
|
|
1406
|
+
const r = watchdog.status();
|
|
1407
|
+
console.log(`\n Watchdog (${r.scheduler}): ${r.installed ? 'installed' : 'NOT installed'}`);
|
|
1408
|
+
if (r.taskName) console.log(` Task: ${r.taskName}`);
|
|
1409
|
+
if (r.plistPath) console.log(` Plist: ${r.plistPath}${r.loaded ? ' (loaded)' : ''}`);
|
|
1410
|
+
if (r.timerPath) console.log(` Timer: ${r.timerPath}${r.active ? ' (active)' : ''}`);
|
|
1411
|
+
if (r.details) {
|
|
1412
|
+
console.log('\n Scheduler details:');
|
|
1413
|
+
console.log(r.details.split('\n').map(l => ` ${l}`).join('\n'));
|
|
1414
|
+
}
|
|
1415
|
+
console.log('');
|
|
1416
|
+
process.exit(r.installed ? 0 : 1);
|
|
1417
|
+
} catch (err) {
|
|
1418
|
+
console.error(`\n ERROR: ${err && err.message || err}\n`);
|
|
1419
|
+
process.exit(1);
|
|
1420
|
+
}
|
|
1421
|
+
} else {
|
|
1422
|
+
console.error(`\n Unknown watchdog subcommand: ${sub}`);
|
|
1423
|
+
console.error(` Usage: minions watchdog (install|uninstall|status|tick)\n`);
|
|
1424
|
+
process.exit(1);
|
|
1425
|
+
}
|
|
1341
1426
|
} else if (cmd === 'dash' || cmd === 'dashboard') {
|
|
1342
1427
|
ensureInstalled();
|
|
1343
1428
|
// If dashboard is already running, just open the browser. The runtime
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
diff a/bin/minions.js b/bin/minions.js (rejected hunks)
|
|
2
|
+
@@ -852,6 +852,14 @@ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
3
|
+
|
|
4
|
+
Dashboard:
|
|
5
|
+
minions dash Start web dashboard (default :7331)
|
|
6
|
+
+
|
|
7
|
+
+ Watchdog (out-of-process recovery):
|
|
8
|
+
+ minions watchdog install [--interval=5]
|
|
9
|
+
+ Register OS scheduler task to probe + heal every N minutes
|
|
10
|
+
+ (Windows Task Scheduler / macOS launchd / Linux systemd --user)
|
|
11
|
+
+ minions watchdog uninstall Remove the scheduled task (idempotent)
|
|
12
|
+
+ minions watchdog status Show registration + last-run details from the OS scheduler
|
|
13
|
+
+ minions watchdog tick One-shot probe + recovery (used by the scheduler; safe to run by hand)
|
|
14
|
+
${fs.existsSync(path.join(PKG_ROOT, '.git')) ? `
|
|
15
|
+
Dev mode (this checkout, contributors only):
|
|
16
|
+
minions --dev <cmd> Run against this checkout instead of ~/.minions/
|
package/dashboard/js/refresh.js
CHANGED
|
@@ -1293,6 +1293,20 @@ async function refresh(opts) {
|
|
|
1293
1293
|
|
|
1294
1294
|
refresh();
|
|
1295
1295
|
_syncPinsFromServer(); // Load server-side pins on startup
|
|
1296
|
+
// Gate the "Try new Slim UX" promo button behind the slim-ux-promo feature
|
|
1297
|
+
// flag (off by default). The button ships hidden in layout.html; reveal it
|
|
1298
|
+
// only when the flag is on. The classic HTML (and its window.MINIONS_FEATURES
|
|
1299
|
+
// bootstrap) is built once at dashboard startup, so a runtime flag toggle
|
|
1300
|
+
// wouldn't be reflected there — read the LIVE flag state from /api/features
|
|
1301
|
+
// instead so flipping the flag takes effect on reload with no restart.
|
|
1302
|
+
(function _gateSlimUxPromoButton() {
|
|
1303
|
+
fetch('/api/features').then(function(r) { return r.ok ? r.json() : null; }).then(function(data) {
|
|
1304
|
+
if (!data || !Array.isArray(data.features)) return;
|
|
1305
|
+
var promo = data.features.find(function(f) { return f.id === 'slim-ux-promo'; });
|
|
1306
|
+
var btn = document.getElementById('try-slim-ux-btn');
|
|
1307
|
+
if (btn && promo && promo.enabled) btn.style.display = '';
|
|
1308
|
+
}).catch(function() { /* offline / error — leave the button hidden */ });
|
|
1309
|
+
})();
|
|
1296
1310
|
// W-mpmwxkrw000872ec — reconcile the font-size preference from the server
|
|
1297
1311
|
// once on cold load. The inline bootstrap in layout.html has already applied
|
|
1298
1312
|
// localStorage's value (if any) to avoid flash; this fetch promotes the
|
|
@@ -102,8 +102,15 @@ function openPinnedView(idx) {
|
|
|
102
102
|
const entry = (window._pinnedEntries || [])[idx];
|
|
103
103
|
if (!entry) return;
|
|
104
104
|
document.getElementById('modal-title').textContent = entry.title;
|
|
105
|
-
//
|
|
106
|
-
|
|
105
|
+
// Content and Edit button share one top-aligned flex row: rendered text fills
|
|
106
|
+
// the left (flex:1, starting at the top), the Edit button sits top-right — so
|
|
107
|
+
// both read as anchored to the top of the dialog body.
|
|
108
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes all user-controlled fields; the wrapper + Edit button are string literals with a numeric idx
|
|
109
|
+
document.getElementById('modal-body').innerHTML =
|
|
110
|
+
'<div style="display:flex;align-items:flex-start;gap:12px">' +
|
|
111
|
+
'<div style="flex:1;min-width:0">' + renderMd(entry.content) + '</div>' +
|
|
112
|
+
'<button class="pr-pager-btn" style="flex:none" onclick="openPinnedEdit(' + idx + ')">Edit</button>' +
|
|
113
|
+
'</div>';
|
|
107
114
|
document.getElementById('modal-body').style.fontFamily = "'Segoe UI', system-ui, sans-serif";
|
|
108
115
|
document.getElementById('modal-body').style.whiteSpace = 'normal';
|
|
109
116
|
document.getElementById('modal').classList.add('open');
|
|
@@ -112,4 +119,113 @@ function openPinnedView(idx) {
|
|
|
112
119
|
showModalQa();
|
|
113
120
|
}
|
|
114
121
|
|
|
115
|
-
|
|
122
|
+
// Edit an existing pinned note. Opened from the "Edit" button in the view
|
|
123
|
+
// modal. The form is DOM-built (not innerHTML) so pre-filled user content
|
|
124
|
+
// can't inject markup, and the document-Q&A panel is hidden while editing.
|
|
125
|
+
function openPinnedEdit(idx) {
|
|
126
|
+
const entry = (window._pinnedEntries || [])[idx];
|
|
127
|
+
if (!entry) return;
|
|
128
|
+
const originalTitle = entry.title;
|
|
129
|
+
document.getElementById('modal-title').textContent = 'Edit Pinned Note';
|
|
130
|
+
const body = document.getElementById('modal-body');
|
|
131
|
+
// The view modal sets a serif font + normal whitespace for rendered markdown;
|
|
132
|
+
// reset so the form inputs use the default UI font.
|
|
133
|
+
body.style.fontFamily = '';
|
|
134
|
+
body.style.whiteSpace = '';
|
|
135
|
+
// Hide the doc-Q&A panel the view modal opened — irrelevant while editing.
|
|
136
|
+
const qa = document.getElementById('modal-qa'); if (qa) qa.style.display = 'none';
|
|
137
|
+
|
|
138
|
+
const inputStyle = 'display:block;width:100%;margin-top:4px;padding:6px 8px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text)';
|
|
139
|
+
function field(labelText) {
|
|
140
|
+
const label = document.createElement('label');
|
|
141
|
+
label.style.cssText = 'color:var(--text);font-size:var(--text-md)';
|
|
142
|
+
label.appendChild(document.createTextNode(labelText));
|
|
143
|
+
return label;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const wrap = document.createElement('div');
|
|
147
|
+
wrap.style.cssText = 'display:flex;flex-direction:column;gap:12px';
|
|
148
|
+
|
|
149
|
+
const titleLabel = field('Title');
|
|
150
|
+
const titleInput = document.createElement('input');
|
|
151
|
+
titleInput.id = 'pin-edit-title';
|
|
152
|
+
titleInput.style.cssText = inputStyle;
|
|
153
|
+
titleInput.value = entry.title || '';
|
|
154
|
+
titleLabel.appendChild(titleInput);
|
|
155
|
+
|
|
156
|
+
const contentLabel = field('Content');
|
|
157
|
+
const contentInput = document.createElement('textarea');
|
|
158
|
+
contentInput.id = 'pin-edit-content';
|
|
159
|
+
contentInput.rows = 6;
|
|
160
|
+
contentInput.style.cssText = inputStyle + ';resize:vertical;font-family:inherit';
|
|
161
|
+
contentInput.value = entry.content || '';
|
|
162
|
+
contentLabel.appendChild(contentInput);
|
|
163
|
+
|
|
164
|
+
const levelLabel = field('Level');
|
|
165
|
+
const levelSelect = document.createElement('select');
|
|
166
|
+
levelSelect.id = 'pin-edit-level';
|
|
167
|
+
levelSelect.style.cssText = inputStyle;
|
|
168
|
+
[['info', 'Info'], ['warning', 'Warning'], ['critical', 'Critical']].forEach(function(o) {
|
|
169
|
+
const opt = document.createElement('option');
|
|
170
|
+
opt.value = o[0];
|
|
171
|
+
opt.textContent = o[1];
|
|
172
|
+
if ((entry.level || 'info') === o[0]) opt.selected = true;
|
|
173
|
+
levelSelect.appendChild(opt);
|
|
174
|
+
});
|
|
175
|
+
levelLabel.appendChild(levelSelect);
|
|
176
|
+
|
|
177
|
+
const actions = document.createElement('div');
|
|
178
|
+
actions.style.cssText = 'display:flex;justify-content:flex-end;gap:8px';
|
|
179
|
+
const cancelBtn = document.createElement('button');
|
|
180
|
+
cancelBtn.className = 'pr-pager-btn';
|
|
181
|
+
cancelBtn.textContent = 'Cancel';
|
|
182
|
+
cancelBtn.onclick = function() { openPinnedView(idx); };
|
|
183
|
+
const saveBtn = document.createElement('button');
|
|
184
|
+
saveBtn.style.cssText = 'padding:6px 16px;background:var(--blue);color:#fff;border:none;border-radius:var(--radius-sm);cursor:pointer';
|
|
185
|
+
saveBtn.textContent = 'Save';
|
|
186
|
+
saveBtn.onclick = function(ev) { submitPinnedEdit(originalTitle, ev); };
|
|
187
|
+
actions.appendChild(cancelBtn);
|
|
188
|
+
actions.appendChild(saveBtn);
|
|
189
|
+
|
|
190
|
+
wrap.appendChild(titleLabel);
|
|
191
|
+
wrap.appendChild(contentLabel);
|
|
192
|
+
wrap.appendChild(levelLabel);
|
|
193
|
+
wrap.appendChild(actions);
|
|
194
|
+
body.replaceChildren(wrap);
|
|
195
|
+
document.getElementById('modal').classList.add('open');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function submitPinnedEdit(originalTitle, e) {
|
|
199
|
+
const btn = (e || window.event)?.target; if (btn) { btn.disabled = true; btn.textContent = 'Saving...'; }
|
|
200
|
+
const title = (document.getElementById('pin-edit-title').value || '').trim();
|
|
201
|
+
const content = document.getElementById('pin-edit-content').value;
|
|
202
|
+
const level = document.getElementById('pin-edit-level').value;
|
|
203
|
+
if (!title || !content) { if (btn) { btn.disabled = false; btn.textContent = 'Save'; } alert('Title and content required'); return; }
|
|
204
|
+
try { closeModal(); } catch { /* may not be open */ }
|
|
205
|
+
|
|
206
|
+
// Optimistic update: swap the edited entry in window._pinnedEntries (keyed on
|
|
207
|
+
// the original title) and re-render immediately. Snapshot to revert on failure.
|
|
208
|
+
const prevEntries = Array.isArray(window._pinnedEntries) ? window._pinnedEntries.slice() : [];
|
|
209
|
+
const nextEntries = prevEntries.map(en => en.title === originalTitle ? { title, content, level: level || 'info' } : en);
|
|
210
|
+
window._pinnedEntries = nextEntries;
|
|
211
|
+
try { renderPinned(nextEntries); } catch { /* DOM may be missing — non-fatal */ }
|
|
212
|
+
showToast('cmd-toast', 'Note updated', true);
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const res = await fetch('/api/pinned/update', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ originalTitle, title, content, level }) });
|
|
216
|
+
if (res.ok) {
|
|
217
|
+
refresh();
|
|
218
|
+
} else {
|
|
219
|
+
window._pinnedEntries = prevEntries;
|
|
220
|
+
try { renderPinned(prevEntries); } catch { /* ignore */ }
|
|
221
|
+
const d = await res.json().catch(() => ({}));
|
|
222
|
+
showToast('cmd-toast', 'Update failed: ' + (d.error || 'unknown'), false);
|
|
223
|
+
}
|
|
224
|
+
} catch (err) {
|
|
225
|
+
window._pinnedEntries = prevEntries;
|
|
226
|
+
try { renderPinned(prevEntries); } catch { /* ignore */ }
|
|
227
|
+
showToast('cmd-toast', 'Error: ' + err.message, false);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
window.MinionsPinned = { renderPinned, openPinNoteModal, submitPinnedNote, removePinnedNote, openPinnedView, openPinnedEdit, submitPinnedEdit };
|
package/dashboard/js/utils.js
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
// Signal the engine to tick immediately (pick up new work without waiting 60s)
|
|
4
4
|
function wakeEngine() { fetch('/api/engine/wakeup', { method: 'POST' }).catch(() => {}); }
|
|
5
5
|
|
|
6
|
+
// "Try new Slim UX" — flips the slim-ux feature flag ON then reloads so the
|
|
7
|
+
// root route serves the slim cockpit (the takeover is checked at request time,
|
|
8
|
+
// so no engine restart is needed). The first-visit welcome popup is gated on
|
|
9
|
+
// the browser side in dashboard/slim/js/settings.js, not here.
|
|
10
|
+
async function trySlimUx(btn) {
|
|
11
|
+
if (btn) { btn.disabled = true; btn.textContent = 'Switching…'; }
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch('/api/features/toggle', {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: { 'Content-Type': 'application/json' },
|
|
16
|
+
body: JSON.stringify({ id: 'slim-ux', enabled: true }),
|
|
17
|
+
});
|
|
18
|
+
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
19
|
+
location.reload();
|
|
20
|
+
} catch (e) {
|
|
21
|
+
if (btn) { btn.disabled = false; btn.textContent = '✨ Try new Slim UX'; }
|
|
22
|
+
alert('Could not switch to Slim UX: ' + (e && e.message ? e.message : 'unknown error'));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
6
26
|
// Optimistic delete suppression
|
|
7
27
|
const _deletedIds = new Map();
|
|
8
28
|
function markDeleted(key) { _deletedIds.set(key, Date.now() + 10000); }
|
package/dashboard/layout.html
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
<header>
|
|
26
26
|
<h1>Minions Mission Control</h1>
|
|
27
27
|
<div style="display:flex;align-items:center;gap:8px;">
|
|
28
|
+
<button id="try-slim-ux-btn" class="try-slim-ux-btn" onclick="trySlimUx(this)" title="Switch to the experimental Slim UX — a focused, single-screen cockpit" style="display:none">✨ Try new Slim UX</button>
|
|
28
29
|
<span class="engine-badge stopped" id="engine-badge">STOPPED</span>
|
|
29
30
|
<span id="version-banner" style="font-size:var(--text-xs);color:var(--muted)"></span>
|
|
30
31
|
<div class="timestamp" id="ts">—</div>
|
package/dashboard/slim/body.html
CHANGED
|
@@ -43,10 +43,15 @@
|
|
|
43
43
|
</div>
|
|
44
44
|
<div class="actions-buttons">
|
|
45
45
|
<button id="slim-linkpr-btn" class="link-pr-btn" type="button">
|
|
46
|
-
<span class="link-pr-icon"
|
|
46
|
+
<span class="link-pr-icon act-ic-link"></span>
|
|
47
47
|
<span class="link-pr-label">Link PR</span>
|
|
48
48
|
<span class="link-pr-sub">track a pull request</span>
|
|
49
49
|
</button>
|
|
50
|
+
<button id="slim-pin-btn" class="link-pr-btn" type="button">
|
|
51
|
+
<span class="link-pr-icon act-ic-pin"></span>
|
|
52
|
+
<span class="link-pr-label">Pin Content</span>
|
|
53
|
+
<span class="link-pr-sub">context for all agents</span>
|
|
54
|
+
</button>
|
|
50
55
|
</div>
|
|
51
56
|
</div>
|
|
52
57
|
|
|
@@ -96,6 +101,12 @@
|
|
|
96
101
|
<div class="cockpit-value dim">0</div>
|
|
97
102
|
<div class="cockpit-detail">no watches set</div>
|
|
98
103
|
</div>
|
|
104
|
+
<div class="cockpit-tile" data-tile="pinned">
|
|
105
|
+
<button id="slim-tile-pin-chip" class="linkpr-chip on-tile" type="button" title="Pin content">+ Pin</button>
|
|
106
|
+
<div class="cockpit-label"><span class="cockpit-dot"></span> Pinned context</div>
|
|
107
|
+
<div class="cockpit-value dim">0</div>
|
|
108
|
+
<div class="cockpit-detail">nothing pinned</div>
|
|
109
|
+
</div>
|
|
99
110
|
</div>
|
|
100
111
|
</div>
|
|
101
112
|
</div>
|
|
@@ -117,6 +128,107 @@
|
|
|
117
128
|
</div>
|
|
118
129
|
</div>
|
|
119
130
|
|
|
131
|
+
<!-- First-visit welcome popup. Shown exactly once per browser — gated on the
|
|
132
|
+
localStorage key 'minions:slim-ux-welcomed', set on dismiss. Wired in
|
|
133
|
+
dashboard/slim/js/settings.js (maybeShowSlimWelcome / dismissSlimWelcome).
|
|
134
|
+
Switching the slim-ux flag off and on again will NOT re-show it, matching
|
|
135
|
+
"only the first time users see the Slim UX". -->
|
|
136
|
+
<div class="modal-bg" id="slim-welcome-modal">
|
|
137
|
+
<div class="modal slim-welcome">
|
|
138
|
+
<div class="modal-header">
|
|
139
|
+
<h3>✨ Welcome to the new Slim UX</h3>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="modal-body">
|
|
142
|
+
<p>A focused, single-screen cockpit for your Minions team. Here’s what’s new:</p>
|
|
143
|
+
<ul class="welcome-list">
|
|
144
|
+
<li><strong>Command Center front and center</strong> — chat with your team from the main panel, with multi-tab conversations that carry over from the classic dashboard.</li>
|
|
145
|
+
<li><strong>Live status tiles</strong> — engine, dispatches, queue, PRs and watches at a glance. Click any tile for details.</li>
|
|
146
|
+
<li><strong>Your team</strong> — every agent as a card with live status.</li>
|
|
147
|
+
<li><strong>Recent activity</strong> — a running feed of completions and pull requests.</li>
|
|
148
|
+
</ul>
|
|
149
|
+
<p class="welcome-note">Your data and engine are unchanged — this is just a new view. Switch back any time with <strong>← Classic dashboard</strong> in the top bar.</p>
|
|
150
|
+
<p class="welcome-note">Please add your feedback using the <strong>Report Bug</strong> button.</p>
|
|
151
|
+
</div>
|
|
152
|
+
<div class="modal-footer">
|
|
153
|
+
<button id="slim-welcome-back" class="btn-secondary" type="button">Back to classic</button>
|
|
154
|
+
<button id="slim-welcome-dismiss" class="btn-primary" type="button">Got it — let’s go</button>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<!-- Report Bug dialog — files a GitHub issue on the Minions repo via
|
|
160
|
+
POST /api/issues/create (the same endpoint the classic dashboard uses).
|
|
161
|
+
Wired in dashboard/slim/js/settings.js. -->
|
|
162
|
+
<div class="modal-bg" id="slim-bug-modal">
|
|
163
|
+
<div class="modal">
|
|
164
|
+
<div class="modal-header">
|
|
165
|
+
<h3>🐛 Report a bug</h3>
|
|
166
|
+
<button id="slim-bug-close" class="icon-btn" title="Close">×</button>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="modal-body">
|
|
169
|
+
<p>File a bug on the Minions repo. Include steps to reproduce and expected vs actual behavior.</p>
|
|
170
|
+
<label class="linkpr-label">Title
|
|
171
|
+
<input id="slim-bug-title" class="linkpr-input" type="text" placeholder="Short description of the bug">
|
|
172
|
+
</label>
|
|
173
|
+
<label class="linkpr-label">Description
|
|
174
|
+
<textarea id="slim-bug-desc" class="linkpr-input" rows="6" placeholder="Steps to reproduce, expected vs actual behavior…"></textarea>
|
|
175
|
+
</label>
|
|
176
|
+
<div id="slim-bug-msg" style="margin-top:8px;font-size:var(--text-base);min-height:16px"></div>
|
|
177
|
+
</div>
|
|
178
|
+
<div class="modal-footer">
|
|
179
|
+
<button id="slim-bug-cancel" class="btn-secondary" type="button">Cancel</button>
|
|
180
|
+
<button id="slim-bug-submit" class="btn-primary" type="button">File Bug</button>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<!-- Pinned context list — opened from the "Pinned context" status tile. Lists
|
|
186
|
+
all pinned notes with view/edit/unpin; "+ Pin content" opens the editor.
|
|
187
|
+
Backed by /api/pinned (GET via /api/status), /api/pinned/update, /remove.
|
|
188
|
+
Wired in dashboard/slim/js/pinned.js. -->
|
|
189
|
+
<div class="modal-bg" id="slim-pinned-modal">
|
|
190
|
+
<div class="modal">
|
|
191
|
+
<div class="modal-header">
|
|
192
|
+
<h3>Pinned context</h3>
|
|
193
|
+
<button id="slim-pinned-add" class="linkpr-chip" type="button" style="margin-left:auto;margin-right:8px">+ Pin content</button>
|
|
194
|
+
<button id="slim-pinned-close" class="icon-btn" title="Close">×</button>
|
|
195
|
+
</div>
|
|
196
|
+
<div class="modal-body" id="slim-pinned-body"></div>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
|
|
200
|
+
<!-- Pin editor — create (Pin Content action / "+ Pin") or edit an existing
|
|
201
|
+
note. Submits to POST /api/pinned (create) or /api/pinned/update (edit). -->
|
|
202
|
+
<div class="modal-bg" id="slim-pin-edit-modal">
|
|
203
|
+
<div class="modal">
|
|
204
|
+
<div class="modal-header">
|
|
205
|
+
<h3 id="slim-pin-edit-heading">Pin content</h3>
|
|
206
|
+
<button id="slim-pin-edit-close" class="icon-btn" title="Close">×</button>
|
|
207
|
+
</div>
|
|
208
|
+
<div class="modal-body">
|
|
209
|
+
<p>Context all agents see, prepended to every prompt as “read first”.</p>
|
|
210
|
+
<label class="linkpr-label">Title
|
|
211
|
+
<input id="slim-pin-title" class="linkpr-input" type="text" placeholder="e.g. API freeze until Friday">
|
|
212
|
+
</label>
|
|
213
|
+
<label class="linkpr-label">Content
|
|
214
|
+
<textarea id="slim-pin-content" class="linkpr-input" rows="6" placeholder="What should every agent know?…"></textarea>
|
|
215
|
+
</label>
|
|
216
|
+
<label class="linkpr-label">Level
|
|
217
|
+
<select id="slim-pin-level" class="linkpr-input">
|
|
218
|
+
<option value="info">Info</option>
|
|
219
|
+
<option value="warning">Warning</option>
|
|
220
|
+
<option value="critical">Critical</option>
|
|
221
|
+
</select>
|
|
222
|
+
</label>
|
|
223
|
+
<div id="slim-pin-msg" style="margin-top:8px;font-size:var(--text-base);min-height:16px"></div>
|
|
224
|
+
</div>
|
|
225
|
+
<div class="modal-footer">
|
|
226
|
+
<button id="slim-pin-cancel" class="btn-secondary" type="button">Cancel</button>
|
|
227
|
+
<button id="slim-pin-submit" class="btn-primary" type="button">Pin</button>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
|
|
120
232
|
<!-- Settings dialog (preserved from r2): lists experimental flags so the
|
|
121
233
|
user can flip 'slim-ux' off and revert to the original dashboard. -->
|
|
122
234
|
<div class="modal-bg" id="slim-settings-modal">
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
diff a/dashboard/slim/body.html b/dashboard/slim/body.html (rejected hunks)
|
|
2
|
+
@@ -8,7 +8,8 @@
|
|
3
|
+
silently break (handler attaches before crash, button still renders,
|
|
4
|
+
click fires but no global handler). addEventListener attaches inside
|
|
5
|
+
the same scope and is observable in DevTools when wiring fails. -->
|
|
6
|
+
- <button id="slim-new-chat-btn" class="icon-btn" title="New chat (opens a new tab)">✎</button>
|
|
7
|
+
+ <button id="slim-back-classic-btn" class="topbar-back-btn" title="Return to the classic dashboard">← Classic dashboard</button>
|
|
8
|
+
+ <button id="slim-report-bug-btn" class="topbar-back-btn" title="Report a bug in Minions">Report Bug</button>
|
|
9
|
+
<button id="slim-settings-btn" class="icon-btn" title="Settings">⚙</button>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
diff a/dashboard/slim/js/command-send.js b/dashboard/slim/js/command-send.js (rejected hunks)
|
|
2
|
+
@@ -195,8 +195,8 @@
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
// ── Wiring ──────────────────────────────────────────────────────
|
|
6
|
+
- var newChatBtn = document.getElementById('slim-new-chat-btn');
|
|
7
|
+
- if (newChatBtn) newChatBtn.addEventListener('click', function() { newTab(); });
|
|
8
|
+
+ // (The header "new chat" button was replaced by "Report Bug"; new tabs are
|
|
9
|
+
+ // created via the "+" affordance in the chat tab bar — see renderTabBar.)
|
|
10
|
+
sendBtn.addEventListener('click', sendMessage);
|
|
11
|
+
stopBtn.addEventListener('click', abortActive);
|
|
12
|
+
inputEl.addEventListener('keydown', function(ev) {
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
|
|
2
2
|
// ── Helpers ────────────────────────────────────────────────────
|
|
3
|
+
// Read a feature flag from window.MINIONS_FEATURES (injected into the slim
|
|
4
|
+
// HTML head before this IIFE). Mirrors MinionsFeatures.isOn from the classic
|
|
5
|
+
// dashboard without pulling features-client.js into the slim bundle.
|
|
6
|
+
function slimFeatureOn(id) {
|
|
7
|
+
var f = window.MINIONS_FEATURES || { flags: {}, defaults: {} };
|
|
8
|
+
if (Object.prototype.hasOwnProperty.call(f.flags || {}, id)) return f.flags[id] === true;
|
|
9
|
+
return !!(f.defaults && f.defaults[id] === true);
|
|
10
|
+
}
|
|
11
|
+
|
|
3
12
|
function escHtml(s) {
|
|
4
13
|
return String(s == null ? '' : s)
|
|
5
14
|
.replace(/&/g, '&')
|