@yemi33/minions 0.1.2178 → 0.1.2180
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/README.md +7 -5
- package/bin/minions.js +39 -17
- package/dashboard/js/command-parser.js +1 -1
- package/dashboard/js/memory-panel.js +324 -0
- package/dashboard/js/qa.js +2 -2
- package/dashboard/js/refresh.js +19 -1
- package/dashboard/js/render-other.js +143 -2
- package/dashboard/js/render-prs.js +2 -1
- package/dashboard/js/render-schedules.js +1 -1
- package/dashboard/js/render-watches.js +1 -1
- package/dashboard/js/render-work-items.js +18 -1
- package/dashboard/js/settings.js +23 -0
- package/dashboard/pages/engine-memory-panel.html +56 -0
- package/dashboard/pages/engine.html +1 -0
- package/dashboard/pages/tools.html +8 -0
- package/dashboard/slim/js/link-pr.js +5 -5
- package/dashboard/slim/js/modals-tiles.js +44 -3
- package/dashboard/slim/js/projects.js +8 -6
- package/dashboard/slim/styles.css +20 -0
- package/dashboard-build.js +17 -2
- package/dashboard.js +693 -19
- package/docs/branch-derivation.md +13 -1
- package/docs/diagnostics-memory.md +446 -0
- package/docs/harness-propagation.md +273 -0
- package/docs/human-vs-automated.md +1 -1
- package/docs/runtime-adapters.md +5 -0
- package/engine/cli.js +24 -5
- package/engine/diagnostics-memory.js +190 -0
- package/engine/lifecycle.js +111 -1
- package/engine/preflight.js +265 -0
- package/engine/queries.js +331 -19
- package/engine/runtimes/claude.js +36 -0
- package/engine/runtimes/codex.js +19 -0
- package/engine/runtimes/copilot.js +27 -36
- package/engine/shared.js +390 -15
- package/engine/spawn-agent.js +178 -12
- package/engine/watchdog.js +6 -0
- package/engine.js +277 -4
- package/package.json +2 -2
|
@@ -12,7 +12,7 @@ function renderProjects(projects) {
|
|
|
12
12
|
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: project name, path, branch metadata)
|
|
13
13
|
list.innerHTML = visible.map(p =>
|
|
14
14
|
'<span data-project="' + escHtml(p.name) + '" title="' + escHtml(p.path || '') + '" style="display:inline-flex;align-items:center;gap:6px;background:var(--surface2);border:1px solid var(--border);border-radius:4px;padding:3px 10px;color:var(--blue);font-weight:500;cursor:help">' +
|
|
15
|
-
escHtml(p.name) +
|
|
15
|
+
escHtml(p.displayName || p.name) +
|
|
16
16
|
_renderProjectBranch(p) +
|
|
17
17
|
'<span onclick="event.stopPropagation();projectChipRemove(\'' + escHtml(p.name) + '\')" title="Remove project (cancels pending work, archives data dir)" style="color:var(--muted);font-weight:600;cursor:pointer;padding:0 2px;line-height:1" onmouseover="this.style.color=\'var(--red)\'" onmouseout="this.style.color=\'var(--muted)\'">×</span>' +
|
|
18
18
|
'</span>'
|
|
@@ -523,7 +523,148 @@ async function _addSelectedProjects() {
|
|
|
523
523
|
}
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
-
|
|
526
|
+
function _scopeBadge(scope, projectName, pluginName) {
|
|
527
|
+
// PRD-shaped scope label (user|project:<name>|plugin|agent-skill). The
|
|
528
|
+
// commands feed today only emits user-roots (per-runtime), plugin, and
|
|
529
|
+
// project; agent-skill is reserved so when a runtime starts shipping
|
|
530
|
+
// agent-scoped commands they don't need a new badge style.
|
|
531
|
+
let label = scope;
|
|
532
|
+
let color = 'var(--muted)';
|
|
533
|
+
if (scope === 'project' && projectName) { label = 'project:' + projectName; color = 'var(--blue)'; }
|
|
534
|
+
else if (scope === 'plugin') { label = pluginName ? 'plugin:' + pluginName : 'plugin'; color = 'var(--purple)'; }
|
|
535
|
+
else if (scope === 'agent-skill') { color = 'var(--orange)'; }
|
|
536
|
+
else if (scope === 'claude-code' || scope === 'copilot' || scope === 'codex') {
|
|
537
|
+
label = 'user:' + scope;
|
|
538
|
+
}
|
|
539
|
+
return '<span style="font-size:var(--text-xs);color:' + color + ';margin-left:6px;font-weight:500">[' + escHtml(label) + ']</span>';
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function renderCommands(commands) {
|
|
543
|
+
const el = document.getElementById('commands-list');
|
|
544
|
+
const countEl = document.getElementById('commands-count');
|
|
545
|
+
const arr = Array.isArray(commands) ? commands : [];
|
|
546
|
+
if (countEl) countEl.textContent = arr.length;
|
|
547
|
+
if (!el) return;
|
|
548
|
+
if (!arr.length) {
|
|
549
|
+
el.innerHTML = '<p class="empty">No slash commands found. Drop a <code>.md</code> file into <code>~/.claude/commands/</code>, <code>~/.copilot/commands/</code>, a plugin\'s commands dir, or a registered project\'s <code>.claude/commands/</code> — they\'ll appear here automatically.</p>';
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Group by scope bucket so the list reads "user roots first, then plugins, then projects".
|
|
554
|
+
const buckets = new Map();
|
|
555
|
+
for (const c of arr) {
|
|
556
|
+
const key = c.scope === 'project' ? 'project:' + (c.projectName || '?')
|
|
557
|
+
: c.scope === 'plugin' ? 'plugin:' + (c.pluginName || '?')
|
|
558
|
+
: c.scope; // user-roots use their runtime name
|
|
559
|
+
if (!buckets.has(key)) buckets.set(key, []);
|
|
560
|
+
buckets.get(key).push(c);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const sections = [];
|
|
564
|
+
for (const [key, list] of buckets) {
|
|
565
|
+
const header = '<div style="font-size:var(--text-sm);color:var(--muted);text-transform:uppercase;letter-spacing:0.04em;margin:10px 0 4px">' + escHtml(key) + ' <span style="color:var(--muted);text-transform:none;letter-spacing:0">(' + list.length + ')</span></div>';
|
|
566
|
+
const rows = list.map(c =>
|
|
567
|
+
'<div style="font-size:var(--text-base);padding:4px 10px;background:var(--surface2);border:1px solid var(--border);border-radius:6px;color:var(--text);display:inline-flex;align-items:center;gap:6px;margin:2px 4px 2px 0" title="' + escHtml((c.title || '') + (c.rel ? ' • ' + c.rel : '')) + '">' +
|
|
568
|
+
'<code style="color:var(--blue);background:transparent;padding:0">/' + escHtml(c.commandName || '') + '</code>' +
|
|
569
|
+
_scopeBadge(c.scope, c.projectName, c.pluginName) +
|
|
570
|
+
'</div>'
|
|
571
|
+
).join('');
|
|
572
|
+
sections.push(header + '<div style="display:flex;flex-wrap:wrap;gap:4px">' + rows + '</div>');
|
|
573
|
+
}
|
|
574
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: scope key, command name, project name, plugin name, title, rel path)
|
|
575
|
+
el.innerHTML = sections.join('') +
|
|
576
|
+
'<p style="font-size:var(--text-sm);color:var(--muted);margin:10px 0 0">Mirrors the runtime-native command roots that adapters expose via <code>getCommandRoots()</code>. Projects and plugins are aggregated alongside user-scope dirs.</p>';
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function _harnessRowsBlock(label, rows) {
|
|
580
|
+
if (!rows || !rows.length) {
|
|
581
|
+
return '<div style="font-size:var(--text-sm);color:var(--muted);margin:4px 0 0 12px">' + escHtml(label) + ': <em>(adapter does not expose this)</em></div>';
|
|
582
|
+
}
|
|
583
|
+
const body = rows.map(r => {
|
|
584
|
+
const marker = r.exists ? '<span style="color:var(--green)">✓</span>' : '<span style="color:var(--orange)">⚠</span>';
|
|
585
|
+
const missing = r.exists ? '' : ' <span style="color:var(--orange);font-size:var(--text-xs)">(missing on disk)</span>';
|
|
586
|
+
return '<div style="font-family:monospace;font-size:var(--text-sm);margin-left:18px;display:flex;gap:8px;align-items:baseline">' +
|
|
587
|
+
marker + ' <span>' + escHtml(r.path) + '</span> <span style="color:var(--muted)">[' + escHtml(r.scope) + ']</span>' + missing +
|
|
588
|
+
'</div>';
|
|
589
|
+
}).join('');
|
|
590
|
+
return '<div style="margin:4px 0 0 12px"><div style="font-size:var(--text-sm);color:var(--muted);margin-bottom:2px">' + escHtml(label) + ':</div>' + body + '</div>';
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function renderHarnessDiagnostics(diag) {
|
|
594
|
+
const el = document.getElementById('harness-diag');
|
|
595
|
+
if (!el) return;
|
|
596
|
+
if (!diag || typeof diag !== 'object') {
|
|
597
|
+
el.innerHTML = '<p class="empty">Harness diagnostics unavailable.</p>';
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
const parts = [];
|
|
601
|
+
parts.push('<div style="font-size:var(--text-sm);color:var(--muted);margin-bottom:8px">Fleet-default runtime: <code style="color:var(--blue)">' + escHtml(diag.fleetDefaultCli || '?') + '</code></div>');
|
|
602
|
+
|
|
603
|
+
// Per-runtime adapter rows
|
|
604
|
+
for (const r of diag.runtimes || []) {
|
|
605
|
+
parts.push('<details style="margin:6px 0;border:1px solid var(--border);border-radius:6px;padding:8px 10px;background:var(--surface2)"><summary style="font-weight:600;cursor:pointer">Runtime: ' + escHtml(r.name) + '</summary>' +
|
|
606
|
+
_harnessRowsBlock('User asset dirs (--add-dir)', r.userAssetDirs) +
|
|
607
|
+
_harnessRowsBlock('Skill roots (CLI native discovery)', r.skillRoots) +
|
|
608
|
+
_harnessRowsBlock('Skill write targets', r.skillWriteTargets) +
|
|
609
|
+
_harnessRowsBlock('Slash command roots', r.commandRoots) +
|
|
610
|
+
_harnessRowsBlock('MCP config files', r.mcpConfigPaths) +
|
|
611
|
+
'</details>');
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// --add-dir snapshot for fleet default
|
|
615
|
+
const snap = diag.addDirSnapshot || [];
|
|
616
|
+
parts.push('<details open style="margin:6px 0;border:1px solid var(--border);border-radius:6px;padding:8px 10px;background:var(--surface2)"><summary style="font-weight:600;cursor:pointer">--add-dir snapshot for ' + escHtml(diag.fleetDefaultCli || 'fleet default') + ' <span style="color:var(--muted);font-weight:400">(' + snap.length + ' dir' + (snap.length === 1 ? '' : 's') + ')</span></summary>' +
|
|
617
|
+
(snap.length === 0
|
|
618
|
+
? '<div style="font-size:var(--text-sm);color:var(--muted);margin:6px 0 0 12px"><em>(no dirs attached — engine/spawn-agent.js unavailable or adapter has no asset dirs)</em></div>'
|
|
619
|
+
: snap.map(d =>
|
|
620
|
+
'<div style="font-family:monospace;font-size:var(--text-sm);margin-left:18px;display:flex;gap:8px;align-items:baseline"><span style="color:var(--green)">✓</span> <span>' + escHtml(d.path) + '</span> <span style="color:var(--muted)">[' + escHtml(d.scope) + ']</span></div>'
|
|
621
|
+
).join('')
|
|
622
|
+
) +
|
|
623
|
+
'</details>');
|
|
624
|
+
|
|
625
|
+
// Suppressed assets
|
|
626
|
+
const suppressed = diag.suppressed || [];
|
|
627
|
+
if (suppressed.length > 0) {
|
|
628
|
+
parts.push('<details open style="margin:6px 0;border:1px solid var(--border);border-radius:6px;padding:8px 10px;background:var(--surface2)"><summary style="font-weight:600;cursor:pointer">Suppressed assets <span style="color:var(--muted);font-weight:400">(' + suppressed.length + ')</span></summary>' +
|
|
629
|
+
suppressed.map(s =>
|
|
630
|
+
'<div style="margin:6px 0 0 12px"><code style="color:var(--orange)">' + escHtml(s.flag) + '</code> <span style="color:var(--muted);font-size:var(--text-sm)">(' + escHtml(String(s.value)) + ')</span>' +
|
|
631
|
+
'<div style="font-size:var(--text-sm);color:var(--muted);margin-left:18px">' + escHtml(s.effect || '') + '</div></div>'
|
|
632
|
+
).join('') +
|
|
633
|
+
'</details>');
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Project-local-on-main footgun
|
|
637
|
+
const footgun = (diag.projectLocalOnMain || []).filter(e => e && e.uncommittedAssets && e.uncommittedAssets.length > 0);
|
|
638
|
+
if (footgun.length > 0) {
|
|
639
|
+
parts.push('<details open style="margin:6px 0;border:1px solid var(--orange);border-radius:6px;padding:8px 10px;background:var(--surface2)"><summary style="font-weight:600;cursor:pointer;color:var(--orange)">⚠ Project-local assets on main checkout (won\'t propagate to worktree)</summary>' +
|
|
640
|
+
footgun.map(entry =>
|
|
641
|
+
'<div style="margin:8px 0 0 12px">' +
|
|
642
|
+
'<div style="font-weight:600">' + escHtml(entry.project) + ' <span style="font-family:monospace;color:var(--muted);font-weight:400">' + escHtml(entry.localPath) + '</span></div>' +
|
|
643
|
+
'<div style="font-size:var(--text-sm);color:var(--muted);margin:4px 0 6px">' + escHtml(entry.footgunWarning || '') + '</div>' +
|
|
644
|
+
entry.uncommittedAssets.map(a =>
|
|
645
|
+
'<div style="font-family:monospace;font-size:var(--text-sm);margin-left:12px;display:flex;gap:8px;align-items:baseline"><span style="color:var(--orange)">⚠</span> <span>' + escHtml(a.file) + '</span> <span style="color:var(--muted)">[' + escHtml(a.kind) + ']</span></div>'
|
|
646
|
+
).join('') +
|
|
647
|
+
'</div>'
|
|
648
|
+
).join('') +
|
|
649
|
+
'</details>');
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Missing dirs summary (aggregate)
|
|
653
|
+
const missing = diag.missingDirs || [];
|
|
654
|
+
if (missing.length > 0) {
|
|
655
|
+
parts.push('<details style="margin:6px 0;border:1px solid var(--border);border-radius:6px;padding:8px 10px;background:var(--surface2)"><summary style="font-weight:600;cursor:pointer">Missing on-disk dirs <span style="color:var(--muted);font-weight:400">(' + missing.length + ')</span></summary>' +
|
|
656
|
+
'<div style="font-size:var(--text-sm);color:var(--muted);margin:4px 0 6px 12px">Adapters declare these paths but they do not exist on this host. Warnings, not failures — create them if the runtime should look there.</div>' +
|
|
657
|
+
missing.map(m =>
|
|
658
|
+
'<div style="font-family:monospace;font-size:var(--text-sm);margin-left:18px"><span style="color:var(--orange)">⚠</span> ' + escHtml(m.path) + ' <span style="color:var(--muted)">[' + escHtml(m.scope) + ' · ' + escHtml(m.kind) + ' · ' + escHtml(m.runtime) + ']</span></div>'
|
|
659
|
+
).join('') +
|
|
660
|
+
'</details>');
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// eslint-disable-next-line no-unsanitized/property -- reason: structural HTML is a string literal; all user data wrapped in escHtml() (fields: runtime name, path, scope, flag, effect, project name, localPath, file, kind)
|
|
664
|
+
el.innerHTML = parts.join('');
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
window.MinionsOther = { renderProjects, optimisticallyAddProject, projectChipRemove, renderMcpServers, renderCommands, renderHarnessDiagnostics, renderMetrics, renderLlmPerf, renderTokenUsage, _aggregateEngineUsageForTokenTile, openScanProjectsModal };
|
|
527
668
|
|
|
528
669
|
// ─── keep_processes panel (W-mp68q6ke0010de68) ─────────────────────────────
|
|
529
670
|
// Polls /api/keep-processes every refresh (engine page only) and renders a
|
|
@@ -320,7 +320,8 @@ function openAddPrModal() {
|
|
|
320
320
|
const projects = (typeof cmdProjects !== 'undefined' ? cmdProjects : []) || [];
|
|
321
321
|
const projOpts = projects.map(p => {
|
|
322
322
|
const name = typeof p === 'object' ? p.name : p;
|
|
323
|
-
|
|
323
|
+
const label = (typeof p === 'object' && (p.displayName || p.name)) || name;
|
|
324
|
+
return '<option value="' + escapeHtml(name) + '">' + escapeHtml(label) + '</option>';
|
|
324
325
|
}).join('');
|
|
325
326
|
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);font-size:var(--text-md);font-family:inherit';
|
|
326
327
|
|
|
@@ -422,7 +422,7 @@ function _scheduleFormHtml(sched, isEdit) {
|
|
|
422
422
|
const priorities = ['high', 'medium', 'low'];
|
|
423
423
|
const typeOpts = types.map(t => '<option value="' + t + '"' + ((sched.type || 'implement') === t ? ' selected' : '') + '>' + t + '</option>').join('');
|
|
424
424
|
const priOpts = priorities.map(p => '<option value="' + p + '"' + ((sched.priority || 'medium') === p ? ' selected' : '') + '>' + p + '</option>').join('');
|
|
425
|
-
const projOpts = '<option value="">Any</option>' + (cmdProjects || []).map(p => '<option value="' + escHtml(p.name) + '"' + (sched.project === p.name ? ' selected' : '') + '>' + escHtml(p.name) + '</option>').join('');
|
|
425
|
+
const projOpts = '<option value="">Any</option>' + (cmdProjects || []).map(p => '<option value="' + escHtml(p.name) + '"' + (sched.project === p.name ? ' selected' : '') + '>' + escHtml(p.displayName || p.name) + '</option>').join('');
|
|
426
426
|
const agentOpts = '<option value="">Auto</option>' + (cmdAgents || []).map(a => '<option value="' + escHtml(a.id) + '"' + (sched.agent === a.id ? ' selected' : '') + '>' + escHtml(a.name) + '</option>').join('');
|
|
427
427
|
|
|
428
428
|
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);font-size:var(--text-md);font-family:inherit';
|
|
@@ -486,7 +486,7 @@ function _watchFormHtml() {
|
|
|
486
486
|
}).join('');
|
|
487
487
|
|
|
488
488
|
var agentOpts = '<option value="">human</option>' + (cmdAgents || []).map(function(a) { return '<option value="' + escHtml(a.id) + '">' + escHtml(a.name) + '</option>'; }).join('');
|
|
489
|
-
var projOpts = '<option value="">Any</option>' + (cmdProjects || []).map(function(p) { return '<option value="' + escHtml(p.name) + '">' + escHtml(p.name) + '</option>'; }).join('');
|
|
489
|
+
var projOpts = '<option value="">Any</option>' + (cmdProjects || []).map(function(p) { return '<option value="' + escHtml(p.name) + '">' + escHtml(p.displayName || p.name) + '</option>'; }).join('');
|
|
490
490
|
|
|
491
491
|
return '<div style="display:flex;flex-direction:column;gap:12px;font-family:inherit">' +
|
|
492
492
|
'<div id="watch-form-error" style="display:none;color:var(--red);font-size:var(--text-md);padding:6px 10px;background:rgba(255,50,50,0.1);border-radius:var(--radius-sm)"></div>' +
|
|
@@ -372,6 +372,10 @@ async function editWorkItem(id, source) {
|
|
|
372
372
|
'<label style="color:var(--text);font-size:var(--text-md)">Depends On (work-item ids, comma- or newline-separated)' +
|
|
373
373
|
'<textarea id="wi-edit-depends-on" rows="2" placeholder="W-foo, W-bar" style="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);font-size:var(--text-md);font-family:inherit;resize:vertical">' + escapeHtml((Array.isArray(item.depends_on) ? item.depends_on : []).join(', ')) + '</textarea>' +
|
|
374
374
|
'</label>' +
|
|
375
|
+
'<label style="color:var(--text);font-size:var(--text-md)">Working directory (subpath, optional)' +
|
|
376
|
+
'<input id="wi-edit-workdir" placeholder="packages/foo" value="' + escapeHtml((item.meta && item.meta.workdir) || '') + '" style="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);font-size:var(--text-md);font-family:inherit">' +
|
|
377
|
+
'<small style="display:block;margin-top:4px;color:var(--muted);font-size:var(--text-sm)">Relative POSIX subpath under the project root (or worktree, for code-mutating types). Leave empty to dispatch at the project root. Examples: <code>packages/foo</code>, <code>apps/dashboard</code>. Absolute paths and <code>..</code> segments are rejected.</small>' +
|
|
378
|
+
'</label>' +
|
|
375
379
|
'<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:8px">' +
|
|
376
380
|
'<button onclick="closeModal()" class="pr-pager-btn" style="padding:6px 16px;font-size:var(--text-md)">Cancel</button>' +
|
|
377
381
|
'<button onclick="submitWorkItemEdit(\'' + escapeHtml(id) + '\',\'' + escapeHtml(source || '') + '\',event)" class="btn-primary-lg">Save</button>' +
|
|
@@ -396,13 +400,14 @@ async function submitWorkItemEdit(id, source, e) {
|
|
|
396
400
|
const acceptanceCriteria = acRaw.split('\n').filter(function(l) { return l.trim(); });
|
|
397
401
|
const dependsRaw = document.getElementById('wi-edit-depends-on')?.value || '';
|
|
398
402
|
const depends_on = dependsRaw.split(/[\n,]/).map(function(s) { return s.trim(); }).filter(Boolean);
|
|
403
|
+
const workdirRaw = (document.getElementById('wi-edit-workdir')?.value || '').trim();
|
|
399
404
|
if (!title) { if (btn) { btn.disabled = false; btn.textContent = 'Save'; } alert('Title is required'); return; }
|
|
400
405
|
try { closeModal(); } catch { /* may not be open */ }
|
|
401
406
|
showToast('cmd-toast', 'Work item updated', true);
|
|
402
407
|
try {
|
|
403
408
|
const res = await fetch('/api/work-items/update', {
|
|
404
409
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
405
|
-
body: JSON.stringify({ id, source: source || undefined, title, description, type, priority, agent, references, acceptanceCriteria, depends_on })
|
|
410
|
+
body: JSON.stringify({ id, source: source || undefined, title, description, type, priority, agent, references, acceptanceCriteria, depends_on, workdir: workdirRaw || null })
|
|
406
411
|
});
|
|
407
412
|
if (res.ok) { refresh(); } else { const d = await res.json().catch(() => ({})); alert('Update failed: ' + (d.error || 'unknown')); editWorkItem(id, source); }
|
|
408
413
|
} catch (e) { alert('Update error: ' + e.message); editWorkItem(id, source); }
|
|
@@ -592,6 +597,9 @@ function openCreateWorkItemModal() {
|
|
|
592
597
|
'<label style="color:var(--text);font-size:var(--text-md)">Acceptance Criteria <textarea id="wi-new-ac" rows="2" style="' + inputStyle + ';resize:vertical" placeholder="One criterion per line (optional)"></textarea></label>' +
|
|
593
598
|
'<label style="color:var(--text);font-size:var(--text-md)">Depends On <textarea id="wi-new-depends-on" rows="2" style="' + inputStyle + ';resize:vertical" placeholder="W-foo, W-bar — comma- or newline-separated work-item ids (optional)"></textarea></label>' +
|
|
594
599
|
'<label style="color:var(--text);font-size:var(--text-md)">References <textarea id="wi-new-refs" rows="2" style="' + inputStyle + ';resize:vertical" placeholder="url | title | type — one per line (optional)"></textarea></label>' +
|
|
600
|
+
'<label style="color:var(--text);font-size:var(--text-md)">Working directory (subpath, optional) <input id="wi-new-workdir" style="' + inputStyle + '" placeholder="packages/foo — leave empty for project root">' +
|
|
601
|
+
'<small style="display:block;margin-top:4px;color:var(--muted);font-size:var(--text-sm)">Relative subpath under the project root. Use for monorepo subpackage dispatches so the agent\'s cwd lands inside <code>packages/foo</code> instead of the project root.</small>' +
|
|
602
|
+
'</label>' +
|
|
595
603
|
'<label id="wi-new-skippr-row" style="color:var(--text);font-size:var(--text-md);display:flex;gap:8px;align-items:center;cursor:pointer"><input type="checkbox" id="wi-new-skippr"> Skip PR creation (push branch only)</label>' +
|
|
596
604
|
'<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:4px">' +
|
|
597
605
|
'<button onclick="closeModal()" class="pr-pager-btn">Cancel</button>' +
|
|
@@ -629,6 +637,7 @@ async function _submitCreateWorkItem(e) {
|
|
|
629
637
|
const parts = l.split('|').map(s => s.trim());
|
|
630
638
|
return { url: parts[0] || '', title: parts[1] || parts[0] || '', type: parts[2] || 'link' };
|
|
631
639
|
}).filter(r => r.url);
|
|
640
|
+
const workdir = (document.getElementById('wi-new-workdir')?.value || '').trim();
|
|
632
641
|
|
|
633
642
|
try {
|
|
634
643
|
const body = { title, description: desc, type, priority };
|
|
@@ -637,6 +646,7 @@ async function _submitCreateWorkItem(e) {
|
|
|
637
646
|
if (acceptanceCriteria.length) body.acceptanceCriteria = acceptanceCriteria;
|
|
638
647
|
if (references.length && references[0].url) body.references = references;
|
|
639
648
|
if (depends_on.length) body.depends_on = depends_on;
|
|
649
|
+
if (workdir) body.meta = Object.assign({}, body.meta, { workdir: workdir });
|
|
640
650
|
const skipPr = document.getElementById('wi-new-skippr')?.checked || false;
|
|
641
651
|
if (skipPr) body.skipPr = true;
|
|
642
652
|
|
|
@@ -707,6 +717,13 @@ function _wiRenderDetail(item) {
|
|
|
707
717
|
html += field('Description', '<div id="wi-detail-desc" style="font-size:var(--text-md);max-height:320px;overflow-y:auto;padding:8px 10px;background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius-sm)">' + _descHtml + '</div>');
|
|
708
718
|
html += field('Agent', escapeHtml(item.dispatched_to || item.agent || 'Auto'));
|
|
709
719
|
html += field('Source', escapeHtml(item._source || 'central'));
|
|
720
|
+
// P-714ef144 — surface meta.workdir in the detail modal so operators can
|
|
721
|
+
// confirm the agent's actual cwd. Renders nothing when unset (which is
|
|
722
|
+
// the back-compat default = project root). Code-tagged so the path is
|
|
723
|
+
// visually clearly distinct from prose fields.
|
|
724
|
+
if (item.meta && typeof item.meta.workdir === 'string' && item.meta.workdir) {
|
|
725
|
+
html += field('Working directory', '<code style="font-size:var(--text-sm);background:var(--surface2);padding:2px 6px;border-radius:var(--radius-sm)">' + escapeHtml(item.meta.workdir) + '</code>');
|
|
726
|
+
}
|
|
710
727
|
if (item.created) html += field('Created', escapeHtml(formatLocalDateTime(item.created)));
|
|
711
728
|
if (item.dispatched_at) html += field('Dispatched', escapeHtml(formatLocalDateTime(item.dispatched_at)) + ' to ' + escapeHtml(item.dispatched_to || '?'));
|
|
712
729
|
if (item.completedAt) html += field('Completed', escapeHtml(formatLocalDateTime(item.completedAt)));
|
package/dashboard/js/settings.js
CHANGED
|
@@ -422,6 +422,22 @@ async function openSettings() {
|
|
|
422
422
|
'Permission bypass is runtime-owned: Claude agents use <code>--dangerously-skip-permissions</code>; Copilot agents use <code>--autopilot --allow-all --no-ask-user</code>. There is no dashboard permission-mode setting.' +
|
|
423
423
|
'</div>';
|
|
424
424
|
|
|
425
|
+
// Harness propagation knobs (PL-seamless-harness-invocation, P-2bd84e91).
|
|
426
|
+
// All three flags govern what the spawned agent inherits from the operator's
|
|
427
|
+
// local CLI harness (user-scope skills/commands/MCPs, project-local-on-main
|
|
428
|
+
// assets, Claude workspace .mcp.json pre-approval). See docs/harness-propagation.md.
|
|
429
|
+
const paneHarness =
|
|
430
|
+
'<h3>Harness Propagation</h3>' +
|
|
431
|
+
'<div class="settings-pane-sub">Controls what the spawned agent sees from your local CLI harness (skills, commands, MCPs). Defaults match the docs/harness-propagation.md contract — flip these only if you understand the tradeoff. The Tools tab\'s Harness diagnostics view shows the live propagated surface.</div>' +
|
|
432
|
+
'<div class="settings-stack" style="margin-bottom:12px">' +
|
|
433
|
+
settingsToggle('Propagate project-local harness on main', 'set-harnessPropagateProjectLocal', e.harnessPropagateProjectLocal !== false,
|
|
434
|
+
'P-08b62d49: surface uncommitted <repo>/.claude/skills, <repo>/.copilot/commands, etc. into the worktree dispatch via extra --add-dir entries. Default ON closes the worktree-uncommitted footgun. Turn OFF to fall back to the legacy "only committed assets are visible" behavior.') +
|
|
435
|
+
settingsToggle('Claude: pre-approve workspace .mcp.json', 'set-claudePreApproveWorkspaceMcps', e.claudePreApproveWorkspaceMcps !== false,
|
|
436
|
+
'P-7d31a06b: pre-warms ~/.claude.json projects.<worktree>.enabledMcpjsonServers on every Claude spawn so the first invocation in a brand-new worktree skips the "trust this server?" prompt — which would otherwise be invisible behind --dangerously-skip-permissions and silently drop workspace MCPs. Claude-only (no-op on Copilot/Codex). Best-effort: failures never block dispatch.') +
|
|
437
|
+
settingsToggle('Hermetic harness (drop user-scope assets)', 'set-hermeticHarness', !!e.hermeticHarness,
|
|
438
|
+
'⚠ P-49e1c8b7 fleet-wide opt-out: when ON, --add-dir is exactly [minionsDir] (user-scope skill/command/MCP roots dropped), project-local propagation skipped, and Claude workspace .mcp.json pre-approval skipped. Use when you want a known-empty harness surface (reproducible CI runs, debugging "works in my CLI" bugs). Per-agent override available via agent.hermeticHarness. Default OFF.') +
|
|
439
|
+
'</div>';
|
|
440
|
+
|
|
425
441
|
const paneBudget =
|
|
426
442
|
'<h3>Budget</h3>' +
|
|
427
443
|
'<div class="settings-pane-sub">Fleet-wide spend ceiling. Per-agent monthly caps live in the Agents table on the Runtime & Models tab.</div>' +
|
|
@@ -522,6 +538,7 @@ async function openSettings() {
|
|
|
522
538
|
{ id: 'worktree', label: 'Worker Pool & Worktrees', html: paneWorktree },
|
|
523
539
|
{ id: 'copilot', label: 'Copilot Tuning', html: paneCopilot },
|
|
524
540
|
{ id: 'claude', label: 'Claude Tuning', html: paneClaude },
|
|
541
|
+
{ id: 'harness', label: 'Harness Propagation', html: paneHarness },
|
|
525
542
|
{ id: 'budget', label: 'Budget', html: paneBudget },
|
|
526
543
|
{ id: 'maxturns', label: 'Max Turns', html: paneMaxTurns },
|
|
527
544
|
{ id: 'features', label: 'Feature Flags', html: paneFeatures },
|
|
@@ -1025,6 +1042,12 @@ async function saveSettings() {
|
|
|
1025
1042
|
copilotSuppressAgentsMd: !!document.getElementById('set-copilotSuppressAgentsMd')?.checked,
|
|
1026
1043
|
copilotStreamMode: document.getElementById('set-copilotStreamMode')?.value || 'on',
|
|
1027
1044
|
copilotReasoningSummaries: !!document.getElementById('set-copilotReasoningSummaries')?.checked,
|
|
1045
|
+
// Harness propagation knobs (P-2bd84e91). Booleans round-trip through
|
|
1046
|
+
// handleSettingsUpdate's auto boolean-fields loop because all three live
|
|
1047
|
+
// in ENGINE_DEFAULTS as booleans.
|
|
1048
|
+
harnessPropagateProjectLocal: !!document.getElementById('set-harnessPropagateProjectLocal')?.checked,
|
|
1049
|
+
claudePreApproveWorkspaceMcps: !!document.getElementById('set-claudePreApproveWorkspaceMcps')?.checked,
|
|
1050
|
+
hermeticHarness: !!document.getElementById('set-hermeticHarness')?.checked,
|
|
1028
1051
|
maxBudgetUsd: (document.getElementById('set-maxBudgetUsd')?.value ?? '').trim(),
|
|
1029
1052
|
disableModelDiscovery: !!document.getElementById('set-disableModelDiscovery')?.checked,
|
|
1030
1053
|
qaDualWriteJson: !!document.getElementById('set-qaDualWriteJson')?.checked,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
dashboard/pages/engine-memory-panel.html — Memory panel fragment (P-d4e5f6a7).
|
|
3
|
+
|
|
4
|
+
Side-by-side cards for the engine and dashboard processes. Live values are
|
|
5
|
+
refreshed every 10 s by dashboard/js/memory-panel.js (GET /api/diagnostics/memory);
|
|
6
|
+
the inline SVG sparkline is refreshed every 60 s
|
|
7
|
+
(GET /api/diagnostics/memory/history?process=...). The yellow
|
|
8
|
+
"engine sample stale" badge is unhidden by the JS module when
|
|
9
|
+
engineStale === true.
|
|
10
|
+
|
|
11
|
+
dashboard-build.js substitutes this fragment into dashboard/pages/engine.html
|
|
12
|
+
at assembly time (see pageSubFragments).
|
|
13
|
+
-->
|
|
14
|
+
<section id="memory-panel-section">
|
|
15
|
+
<h2>Memory <span style="font-size:var(--text-sm);color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">RSS / heap / event-loop / GC for engine + dashboard processes</span></h2>
|
|
16
|
+
<div id="memory-panel-content" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:12px">
|
|
17
|
+
<div id="memory-card-engine" class="memory-card" style="border:1px solid var(--border);border-radius:4px;padding:10px;background:var(--surface2)">
|
|
18
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px">
|
|
19
|
+
<div style="font-weight:600;font-size:var(--text-md);font-family:monospace">engine</div>
|
|
20
|
+
<span id="memory-engine-stale-badge" style="display:none;background:var(--yellow);color:#000;padding:2px 8px;border-radius:3px;font-size:var(--text-sm);font-weight:600">engine sample stale</span>
|
|
21
|
+
</div>
|
|
22
|
+
<dl style="display:grid;grid-template-columns:max-content 1fr;gap:3px 12px;margin:0 0 8px 0;font-size:var(--text-base)">
|
|
23
|
+
<dt style="color:var(--muted);margin:0">RSS</dt><dd id="memory-engine-rss" style="margin:0;font-family:monospace">—</dd>
|
|
24
|
+
<dt style="color:var(--muted);margin:0">Heap used / total</dt><dd id="memory-engine-heap" style="margin:0;font-family:monospace">—</dd>
|
|
25
|
+
<dt style="color:var(--muted);margin:0">External</dt><dd id="memory-engine-external" style="margin:0;font-family:monospace">—</dd>
|
|
26
|
+
<dt style="color:var(--muted);margin:0">Event-loop lag p50 / p99</dt><dd id="memory-engine-lag" style="margin:0;font-family:monospace">—</dd>
|
|
27
|
+
<dt style="color:var(--muted);margin:0">Last GC pause</dt><dd id="memory-engine-gc" style="margin:0;font-family:monospace">—</dd>
|
|
28
|
+
<dt style="color:var(--muted);margin:0">Uptime</dt><dd id="memory-engine-uptime" style="margin:0;font-family:monospace">—</dd>
|
|
29
|
+
</dl>
|
|
30
|
+
<div style="font-size:var(--text-sm);color:var(--muted);margin-bottom:2px"><span style="color:var(--blue,#4ea1ff)">●</span> RSS <span style="color:var(--green,#4caf50)">●</span> heapUsed <span style="opacity:0.7">(last hour)</span></div>
|
|
31
|
+
<div id="memory-engine-sparkline" style="height:60px;width:100%;color:var(--fg)"></div>
|
|
32
|
+
</div>
|
|
33
|
+
<div id="memory-card-dashboard" class="memory-card" style="border:1px solid var(--border);border-radius:4px;padding:10px;background:var(--surface2)">
|
|
34
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px">
|
|
35
|
+
<div style="font-weight:600;font-size:var(--text-md);font-family:monospace">dashboard</div>
|
|
36
|
+
</div>
|
|
37
|
+
<dl style="display:grid;grid-template-columns:max-content 1fr;gap:3px 12px;margin:0 0 8px 0;font-size:var(--text-base)">
|
|
38
|
+
<dt style="color:var(--muted);margin:0">RSS</dt><dd id="memory-dashboard-rss" style="margin:0;font-family:monospace">—</dd>
|
|
39
|
+
<dt style="color:var(--muted);margin:0">Heap used / total</dt><dd id="memory-dashboard-heap" style="margin:0;font-family:monospace">—</dd>
|
|
40
|
+
<dt style="color:var(--muted);margin:0">External</dt><dd id="memory-dashboard-external" style="margin:0;font-family:monospace">—</dd>
|
|
41
|
+
<dt style="color:var(--muted);margin:0">Event-loop lag p50 / p99</dt><dd id="memory-dashboard-lag" style="margin:0;font-family:monospace">—</dd>
|
|
42
|
+
<dt style="color:var(--muted);margin:0">Last GC pause</dt><dd id="memory-dashboard-gc" style="margin:0;font-family:monospace">—</dd>
|
|
43
|
+
<dt style="color:var(--muted);margin:0">Uptime</dt><dd id="memory-dashboard-uptime" style="margin:0;font-family:monospace">—</dd>
|
|
44
|
+
</dl>
|
|
45
|
+
<div style="font-size:var(--text-sm);color:var(--muted);margin-bottom:2px"><span style="color:var(--blue,#4ea1ff)">●</span> RSS <span style="color:var(--green,#4caf50)">●</span> heapUsed <span style="opacity:0.7">(last hour)</span></div>
|
|
46
|
+
<div id="memory-dashboard-sparkline" style="height:60px;width:100%;color:var(--fg)"></div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<div id="memory-heap-snapshot-panel" style="margin-top:12px;padding:10px;border:1px solid var(--border);border-radius:4px;background:var(--surface2)">
|
|
50
|
+
<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">
|
|
51
|
+
<button type="button" id="memory-heap-snapshot-btn" class="btn" style="cursor:pointer">Capture heap snapshot</button>
|
|
52
|
+
<span style="font-size:var(--text-sm);color:var(--muted)">Stalls engine + dashboard for several seconds, writes a 50–200 MB <code>.heapsnapshot</code> per process under <code>engine/diagnostics/</code> (load in Chrome DevTools → Memory → Load). Rate-limited to 1 / 60 s; only the 5 most-recent per process are kept.</span>
|
|
53
|
+
</div>
|
|
54
|
+
<div id="memory-heap-snapshot-result" style="margin-top:8px;font-family:monospace;font-size:var(--text-sm);color:var(--muted);white-space:pre-wrap"></div>
|
|
55
|
+
</div>
|
|
56
|
+
</section>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<section>
|
|
4
4
|
<div id="engine-quick-stats" style="display:flex;gap:16px;margin-bottom:12px;font-size:var(--text-base);color:var(--muted)"></div>
|
|
5
5
|
</section>
|
|
6
|
+
<!-- __ENGINE_MEMORY_PANEL__ -->
|
|
6
7
|
<section>
|
|
7
8
|
<h2>Engine Log <span style="font-size:var(--text-sm);color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">tick-by-tick audit trail of engine operations</span></h2>
|
|
8
9
|
<div class="log-list" id="engine-log">No log entries yet.</div>
|
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
<h2>Minions Skills <span class="count" id="skills-count">0</span> <span style="font-size:var(--text-sm);color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">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
|
+
<section>
|
|
6
|
+
<h2>Slash Commands <span class="count" id="commands-count">0</span> <span style="font-size:var(--text-sm);color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">discovered from runtime native command dirs, plugin installs, and configured project repos</span></h2>
|
|
7
|
+
<div id="commands-list"><p class="empty">No slash commands discovered.</p></div>
|
|
8
|
+
</section>
|
|
5
9
|
<section>
|
|
6
10
|
<h2>MCP Servers <span class="count" id="mcp-count">0</span></h2>
|
|
7
11
|
<div id="mcp-list"><p class="empty">No MCP servers synced.</p></div>
|
|
8
12
|
</section>
|
|
13
|
+
<section>
|
|
14
|
+
<h2>Harness Propagation <span style="font-size:var(--text-sm);color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">what each runtime sees, what's attached via --add-dir, and which assets won't propagate to a fresh worktree</span></h2>
|
|
15
|
+
<div id="harness-diag"><p class="empty">Loading harness diagnostics…</p></div>
|
|
16
|
+
</section>
|
|
@@ -6,18 +6,18 @@
|
|
|
6
6
|
var modal = document.getElementById('slim-linkpr-modal');
|
|
7
7
|
if (!modal) return;
|
|
8
8
|
var projects = (lastStatusData && Array.isArray(lastStatusData.projects) ? lastStatusData.projects : [])
|
|
9
|
-
.
|
|
10
|
-
.
|
|
9
|
+
.filter(function(p) { return p && p.name; })
|
|
10
|
+
.map(function(p) { return { name: String(p.name), label: String(p.displayName || p.name) }; });
|
|
11
11
|
var sel = document.getElementById('slim-linkpr-project');
|
|
12
12
|
sel.textContent = '';
|
|
13
13
|
var auto = document.createElement('option');
|
|
14
14
|
auto.value = '';
|
|
15
15
|
auto.textContent = 'Auto-detect from URL (central if no unique match)';
|
|
16
16
|
sel.appendChild(auto);
|
|
17
|
-
projects.forEach(function(
|
|
17
|
+
projects.forEach(function(proj) {
|
|
18
18
|
var o = document.createElement('option');
|
|
19
|
-
o.value = name;
|
|
20
|
-
o.textContent =
|
|
19
|
+
o.value = proj.name;
|
|
20
|
+
o.textContent = proj.label;
|
|
21
21
|
sel.appendChild(o);
|
|
22
22
|
});
|
|
23
23
|
var warn = document.getElementById('slim-linkpr-noproj');
|
|
@@ -159,7 +159,7 @@
|
|
|
159
159
|
}
|
|
160
160
|
var prs = Array.isArray(data.pullRequests) ? data.pullRequests : [];
|
|
161
161
|
if (!prs.length) { tileEmpty(body, 'No tracked pull requests.'); return; }
|
|
162
|
-
|
|
162
|
+
function prCard(p) {
|
|
163
163
|
var num = p.prNumber || p.number || p.id;
|
|
164
164
|
var build = p.buildStatus || '';
|
|
165
165
|
var review = p.reviewStatus || '';
|
|
@@ -168,7 +168,7 @@
|
|
|
168
168
|
: ((p.status === 'active' || p.status === 'linked') ? 'blue' : ''));
|
|
169
169
|
var statusText = document.createElement('span');
|
|
170
170
|
statusText.textContent = p.status || 'unknown';
|
|
171
|
-
|
|
171
|
+
return tileCard({
|
|
172
172
|
title: (num ? '#' + num + ' ' : '') + (p.title || '(untitled PR)'),
|
|
173
173
|
// Project slug first (under title), then build/review row below — the
|
|
174
174
|
// project is the strongest disambiguator across PRs and benefits from
|
|
@@ -178,7 +178,48 @@
|
|
|
178
178
|
metaNodes: [statusText, statusSpan('build', build, BUILD_STATUS), statusSpan('review', review, REVIEW_STATUS)],
|
|
179
179
|
chip: { text: p.status || '—', cls: cls },
|
|
180
180
|
href: p.url || null,
|
|
181
|
-
})
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
// Partition once into three fixed buckets (status normalized to lowercase),
|
|
184
|
+
// preserving each PR's relative order within its bucket. Mirrors shared
|
|
185
|
+
// PR_STATUS (active/merged/abandoned/closed/linked); anything that does not
|
|
186
|
+
// match the merged/abandoned buckets defaults to Active so a PR with an
|
|
187
|
+
// unexpected status never silently disappears.
|
|
188
|
+
var buckets = { active: [], merged: [], abandoned: [] };
|
|
189
|
+
prs.forEach(function(p) {
|
|
190
|
+
var s = String(p.status || '').toLowerCase();
|
|
191
|
+
if (s === 'abandoned') buckets.abandoned.push(p);
|
|
192
|
+
else if (s === 'merged' || s === 'completed' || s === 'closed') buckets.merged.push(p);
|
|
193
|
+
else buckets.active.push(p);
|
|
194
|
+
});
|
|
195
|
+
// Fixed section order: Active (open) → Merged / Completed → Abandoned.
|
|
196
|
+
// Empty sections are omitted entirely.
|
|
197
|
+
var sections = [
|
|
198
|
+
{ label: 'Active', prs: buckets.active, open: true },
|
|
199
|
+
{ label: 'Merged / Completed', prs: buckets.merged, open: false },
|
|
200
|
+
{ label: 'Abandoned', prs: buckets.abandoned, open: false },
|
|
201
|
+
];
|
|
202
|
+
sections.forEach(function(section) {
|
|
203
|
+
if (!section.prs.length) return;
|
|
204
|
+
var details = document.createElement('details');
|
|
205
|
+
details.className = 'tile-section';
|
|
206
|
+
if (section.open) details.open = true;
|
|
207
|
+
var summary = document.createElement('summary');
|
|
208
|
+
summary.className = 'tile-section-summary';
|
|
209
|
+
var label = document.createElement('span');
|
|
210
|
+
label.className = 'tile-section-label';
|
|
211
|
+
label.textContent = section.label;
|
|
212
|
+
var count = document.createElement('span');
|
|
213
|
+
count.className = 'tile-section-count';
|
|
214
|
+
count.textContent = '(' + section.prs.length + ')';
|
|
215
|
+
summary.appendChild(label);
|
|
216
|
+
summary.appendChild(count);
|
|
217
|
+
details.appendChild(summary);
|
|
218
|
+
var sectionBody = document.createElement('div');
|
|
219
|
+
sectionBody.className = 'tile-section-body';
|
|
220
|
+
section.prs.forEach(function(p) { sectionBody.appendChild(prCard(p)); });
|
|
221
|
+
details.appendChild(sectionBody);
|
|
222
|
+
body.appendChild(details);
|
|
182
223
|
});
|
|
183
224
|
}
|
|
184
225
|
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
// chosen, so the indicator never auto-defaults and the user can always clear
|
|
32
32
|
// their choice back to it. Returns the select so the caller can attach a
|
|
33
33
|
// change listener.
|
|
34
|
-
function makeContextSelect(projects, selected) {
|
|
34
|
+
function makeContextSelect(projects, selected, displayByName) {
|
|
35
35
|
var sel = document.createElement('select');
|
|
36
36
|
sel.id = 'chat-context-select';
|
|
37
37
|
sel.setAttribute('aria-label', 'Working in project');
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
var p = projects[i];
|
|
45
45
|
var opt = document.createElement('option');
|
|
46
46
|
opt.value = p;
|
|
47
|
-
opt.textContent = p;
|
|
47
|
+
opt.textContent = (displayByName && displayByName[p]) || p;
|
|
48
48
|
if (p === selected) opt.selected = true;
|
|
49
49
|
sel.appendChild(opt);
|
|
50
50
|
}
|
|
@@ -59,9 +59,11 @@
|
|
|
59
59
|
var res = await fetch('/api/status');
|
|
60
60
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
61
61
|
var data = await res.json();
|
|
62
|
-
var
|
|
63
|
-
.
|
|
64
|
-
|
|
62
|
+
var projectList = (data && Array.isArray(data.projects) ? data.projects : [])
|
|
63
|
+
.filter(function(p) { return p && p.name; });
|
|
64
|
+
var projects = projectList.map(function(p) { return String(p.name); });
|
|
65
|
+
var displayByName = {};
|
|
66
|
+
projectList.forEach(function(p) { displayByName[String(p.name)] = String(p.displayName || p.name); });
|
|
65
67
|
if (projects.length === 0) {
|
|
66
68
|
stripEl.style.display = '';
|
|
67
69
|
controlsEl.replaceChildren(makeContextEmptyNode(), makeContextAddBtn());
|
|
@@ -79,7 +81,7 @@
|
|
|
79
81
|
// (italicized via CSS). We deliberately do NOT fall back to projects[0]
|
|
80
82
|
// here (W-mqayzsj3) so CC turns and project-scoped actions don't
|
|
81
83
|
// silently target the wrong project.
|
|
82
|
-
var sel = makeContextSelect(projects, currentProject);
|
|
84
|
+
var sel = makeContextSelect(projects, currentProject, displayByName);
|
|
83
85
|
stripEl.style.display = '';
|
|
84
86
|
controlsEl.replaceChildren(sel, makeContextAddBtn());
|
|
85
87
|
sel.addEventListener('change', function(ev) {
|
|
@@ -681,6 +681,26 @@
|
|
|
681
681
|
.tile-chip.blue { background: rgba(88, 166, 255, 0.12); color: var(--blue); border-color: var(--blue); }
|
|
682
682
|
.tile-empty { color: var(--muted); font-style: italic; font-size: var(--text-md); }
|
|
683
683
|
|
|
684
|
+
/* Collapsible PR sections (Active / Merged-Completed / Abandoned). Uses
|
|
685
|
+
native <details>/<summary> — no JS wiring needed for the toggle. */
|
|
686
|
+
.tile-section { margin-bottom: 10px; }
|
|
687
|
+
.tile-section:last-child { margin-bottom: 0; }
|
|
688
|
+
.tile-section-summary {
|
|
689
|
+
display: flex; align-items: center; gap: 6px;
|
|
690
|
+
cursor: pointer; user-select: none;
|
|
691
|
+
padding: 4px 2px; margin-bottom: 6px;
|
|
692
|
+
list-style: none;
|
|
693
|
+
font-size: var(--text-base); color: var(--muted);
|
|
694
|
+
}
|
|
695
|
+
.tile-section-summary::-webkit-details-marker { display: none; }
|
|
696
|
+
.tile-section-summary::before {
|
|
697
|
+
content: "▸"; color: var(--muted); font-size: var(--text-sm);
|
|
698
|
+
transition: transform 0.15s ease;
|
|
699
|
+
}
|
|
700
|
+
.tile-section[open] > .tile-section-summary::before { content: "▾"; }
|
|
701
|
+
.tile-section-label { font-weight: 700; letter-spacing: 0.3px; text-transform: uppercase; color: var(--text); }
|
|
702
|
+
.tile-section-count { color: var(--muted); }
|
|
703
|
+
|
|
684
704
|
/* Pinned-context list rows (slim-pinned-modal). */
|
|
685
705
|
.pinned-row {
|
|
686
706
|
border: 1px solid var(--border);
|
package/dashboard-build.js
CHANGED
|
@@ -21,9 +21,24 @@ function buildDashboardHtml() {
|
|
|
21
21
|
const css = safeRead(path.join(dashDir, 'styles.css'));
|
|
22
22
|
|
|
23
23
|
const pages = ['home', 'work', 'prs', 'plans', 'inbox', 'tools', 'schedule', 'watches', 'pipelines', 'meetings', 'qa', 'engine'];
|
|
24
|
+
// Sub-fragments substituted into a parent page at assembly time via
|
|
25
|
+
// <!-- __MARKER__ --> tokens. Lets large panels live in their own
|
|
26
|
+
// fragment file without inflating the parent page. P-d4e5f6a7 introduced
|
|
27
|
+
// engine-memory-panel.html as the first such sub-fragment; add more here
|
|
28
|
+
// by mapping marker -> fragment basename.
|
|
29
|
+
const pageSubFragments = {
|
|
30
|
+
engine: { '<!-- __ENGINE_MEMORY_PANEL__ -->': 'engine-memory-panel' },
|
|
31
|
+
};
|
|
24
32
|
let pageHtml = '';
|
|
25
33
|
for (const p of pages) {
|
|
26
|
-
|
|
34
|
+
let content = safeRead(path.join(dashDir, 'pages', p + '.html'));
|
|
35
|
+
const subs = pageSubFragments[p];
|
|
36
|
+
if (subs) {
|
|
37
|
+
for (const [marker, basename] of Object.entries(subs)) {
|
|
38
|
+
const fragment = safeRead(path.join(dashDir, 'pages', basename + '.html'));
|
|
39
|
+
content = content.replace(marker, () => fragment);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
27
42
|
const activeClass = p === 'home' ? ' active' : '';
|
|
28
43
|
pageHtml += ` <div class="page${activeClass}" id="page-${p}">\n${content}\n </div>\n\n`;
|
|
29
44
|
}
|
|
@@ -32,7 +47,7 @@ function buildDashboardHtml() {
|
|
|
32
47
|
'utils', 'state', 'features-client', 'render-utils', 'detail-panel', 'live-stream',
|
|
33
48
|
'render-agents', 'render-dispatch', 'render-work-items', 'render-prd',
|
|
34
49
|
'render-prs', 'render-plans', 'render-inbox', 'render-kb', 'render-skills',
|
|
35
|
-
'render-other', 'render-managed', 'render-schedules', 'render-watches', 'render-pipelines', 'render-meetings', 'render-pinned',
|
|
50
|
+
'render-other', 'render-managed', 'memory-panel', 'render-schedules', 'render-watches', 'render-pipelines', 'render-meetings', 'render-pinned',
|
|
36
51
|
'command-parser', 'command-input', 'command-center', 'command-history',
|
|
37
52
|
'confirm-dialog', 'modal', 'modal-qa', 'settings', 'qa', 'fre', 'refresh'
|
|
38
53
|
];
|