@yemi33/minions 0.1.2454 → 0.1.2455
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/install-internal-minions.js +136 -6
- package/bin/minions.js +27 -13
- package/dashboard/js/agent-identity.js +121 -0
- package/dashboard/js/modal.js +4 -0
- package/dashboard/js/refresh.js +38 -3
- package/dashboard/js/render-agents.js +14 -2
- package/dashboard/js/render-dispatch.js +2 -2
- package/dashboard/js/render-other.js +147 -13
- package/dashboard/js/render-prd.js +6 -5
- package/dashboard/js/render-prs.js +1 -1
- package/dashboard/js/render-work-items.js +436 -21
- package/dashboard/js/utils.js +40 -0
- package/dashboard/pages/tools.html +1 -0
- package/dashboard/pages/work.html +13 -0
- package/dashboard/styles.css +73 -0
- package/dashboard-build.js +2 -2
- package/dashboard.js +93 -7
- package/docs/README.md +1 -0
- package/docs/copilot-cli-schema.md +1 -0
- package/docs/engine-restart.md +4 -2
- package/docs/internal-install.md +44 -5
- package/docs/named-agents.md +48 -0
- package/docs/runtime-adapters.md +39 -0
- package/docs/temporary-agents.md +172 -0
- package/engine/ado/comment.js +261 -4
- package/engine/agents/llm.js +26 -0
- package/engine/agents/playbook.js +2 -1
- package/engine/core/queries.js +27 -2
- package/engine/core/shared.js +148 -1
- package/engine/operations/cli.js +102 -1
- package/engine/orchestration/lifecycle.js +7 -0
- package/engine/orchestration/routing.js +4 -1
- package/engine/providers/gh-comment.js +159 -0
- package/engine/recovery/stop-stack.js +16 -2
- package/engine/runtimes/claude.js +3 -0
- package/engine/runtimes/codex.js +4 -0
- package/engine/runtimes/copilot.js +23 -2
- package/engine.js +2 -2
- package/package.json +1 -1
- package/playbooks/fix.md +23 -2
- package/playbooks/shared-rules.md +24 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<section id="work-items-section" style="overflow:visible">
|
|
2
2
|
<h2>Work Items <span class="count" id="wi-count">0</span>
|
|
3
3
|
<button class="btn-add" style="margin-left:8px" onclick="openCreateWorkItemModal()">+ New</button>
|
|
4
|
+
<button id="wi-bulk-toggle" type="button" class="pr-pager-btn" style="font-size:var(--text-sm);padding:2px 8px;margin-left:4px" onclick="wiToggleBulkMode()" aria-pressed="false" aria-controls="wi-bulk-bar" title="Enter bulk-selection mode">Bulk select</button>
|
|
4
5
|
<button id="work-archive-toggle" type="button" class="pr-pager-btn" style="font-size:var(--text-sm);padding:2px 8px;margin-left:4px" onclick="toggleWorkItemArchive()" aria-expanded="false" aria-controls="work-items-archive">See Archive</button>
|
|
5
6
|
<span style="font-size:var(--text-sm);color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">tasks dispatched to agents — auto-created from PRDs or added manually</span>
|
|
6
7
|
</h2>
|
|
@@ -44,6 +45,18 @@
|
|
|
44
45
|
<button class="pr-filter-reset" type="button" onclick="resetWiFilters()">Reset filters</button>
|
|
45
46
|
<span class="pr-filter-summary" id="wi-filter-summary" aria-live="polite"></span>
|
|
46
47
|
</div>
|
|
48
|
+
<div id="wi-bulk-bar" class="wi-bulk-bar" role="toolbar" aria-label="Bulk work-item actions" hidden>
|
|
49
|
+
<label class="wi-bulk-selectall"><input type="checkbox" id="wi-bulk-selectall-cb" onchange="wiToggleSelectAll(this)" aria-label="Select all filtered work items"> Select all</label>
|
|
50
|
+
<span id="wi-bulk-count" aria-live="polite">0 selected</span>
|
|
51
|
+
<span class="wi-bulk-actions">
|
|
52
|
+
<button type="button" class="pr-pager-btn" data-bulk-action="cancel" onclick="wiBulkAction('cancel')" disabled>Cancel</button>
|
|
53
|
+
<button type="button" class="pr-pager-btn" data-bulk-action="archive" onclick="wiBulkAction('archive')" disabled>Archive</button>
|
|
54
|
+
<button type="button" class="pr-pager-btn" data-bulk-action="retry" onclick="wiBulkAction('retry')" disabled>Retry</button>
|
|
55
|
+
<button type="button" class="btn-destructive" data-bulk-action="delete" onclick="wiBulkAction('delete')" disabled>Delete</button>
|
|
56
|
+
</span>
|
|
57
|
+
<button type="button" class="pr-filter-reset" onclick="wiClearSelection()">Clear</button>
|
|
58
|
+
<button type="button" class="pr-filter-reset" onclick="wiExitBulkMode()">Exit bulk mode</button>
|
|
59
|
+
</div>
|
|
47
60
|
<div id="work-items-content"><p class="empty">No work items yet.</p></div>
|
|
48
61
|
<div id="work-items-archive" style="display:none;margin-top:12px"></div>
|
|
49
62
|
</section>
|
package/dashboard/styles.css
CHANGED
|
@@ -126,6 +126,10 @@
|
|
|
126
126
|
.agent-name { font-weight: 600; font-size: var(--text-xl); }
|
|
127
127
|
.agent-role { font-size: var(--text-base); color: var(--muted); margin-bottom: var(--space-4); }
|
|
128
128
|
.status-badge { font-size: var(--text-sm); font-weight: 600; padding: var(--space-1) var(--space-4); border-radius: var(--radius-xl); text-transform: uppercase; letter-spacing: 0.5px; }
|
|
129
|
+
/* Temporary-agent marker — additive display identity next to a friendly call
|
|
130
|
+
sign. Neutral, low-emphasis pill so the temp status stays unmistakable
|
|
131
|
+
without competing with the agent name (W-msd8zdzb00i550a8). */
|
|
132
|
+
.agent-temp-badge { display: inline-block; font-size: var(--text-sm); font-weight: 600; padding: 0 var(--space-2); margin-left: var(--space-2); border-radius: var(--radius-sm); text-transform: uppercase; letter-spacing: 0.4px; vertical-align: middle; color: var(--muted); background: var(--surface); border: 1px solid var(--border); }
|
|
129
133
|
.status-badge.idle { background: var(--surface); color: var(--muted); border: 1px solid var(--border); }
|
|
130
134
|
.status-badge.working { background: rgba(210,153,34,0.15); color: var(--yellow); border: 1px solid var(--yellow); animation: pulse 1.5s infinite; }
|
|
131
135
|
.status-badge.done { background: rgba(63,185,80,0.15); color: var(--green); border: 1px solid var(--green); }
|
|
@@ -2008,3 +2012,72 @@
|
|
|
2008
2012
|
height (no magic 64px, no vh). */
|
|
2009
2013
|
html.embed-settings .modal-body.settings-body { flex: 1 1 auto; min-height: 0; }
|
|
2010
2014
|
html.embed-settings .settings-layout { height: 100%; }
|
|
2015
|
+
|
|
2016
|
+
/* ── Work-item overflow menu + bulk-operations mode (W-mscjvm33007r79ef) ──
|
|
2017
|
+
* The overflow (⋯) menu is rendered via document.createElement in
|
|
2018
|
+
* wiOpenOverflowMenu (no innerHTML), appended to <body> and positioned under
|
|
2019
|
+
* the clicked button — mirroring .checkout-mode-menu above. The bulk bar and
|
|
2020
|
+
* selection affordances only appear when bulk mode is explicitly enabled. */
|
|
2021
|
+
.wi-overflow-btn { line-height: 1; }
|
|
2022
|
+
.wi-overflow-menu {
|
|
2023
|
+
position: absolute;
|
|
2024
|
+
z-index: 1000;
|
|
2025
|
+
min-width: 180px;
|
|
2026
|
+
background: var(--surface2);
|
|
2027
|
+
border: 1px solid var(--border);
|
|
2028
|
+
border-radius: var(--radius-md, 6px);
|
|
2029
|
+
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
|
|
2030
|
+
padding: 4px;
|
|
2031
|
+
display: flex;
|
|
2032
|
+
flex-direction: column;
|
|
2033
|
+
}
|
|
2034
|
+
.wi-overflow-item {
|
|
2035
|
+
display: block;
|
|
2036
|
+
width: 100%;
|
|
2037
|
+
text-align: left;
|
|
2038
|
+
padding: 6px 10px;
|
|
2039
|
+
border: none;
|
|
2040
|
+
background: none;
|
|
2041
|
+
border-radius: 4px;
|
|
2042
|
+
color: var(--text);
|
|
2043
|
+
font-size: var(--text-sm);
|
|
2044
|
+
font-family: inherit;
|
|
2045
|
+
cursor: pointer;
|
|
2046
|
+
}
|
|
2047
|
+
.wi-overflow-item:hover,
|
|
2048
|
+
.wi-overflow-item:focus {
|
|
2049
|
+
background: var(--surface);
|
|
2050
|
+
outline: none;
|
|
2051
|
+
}
|
|
2052
|
+
.wi-overflow-item.wi-overflow-item-destructive { color: var(--red); }
|
|
2053
|
+
|
|
2054
|
+
.wi-bulk-bar {
|
|
2055
|
+
display: flex;
|
|
2056
|
+
align-items: center;
|
|
2057
|
+
gap: var(--space-4, 8px);
|
|
2058
|
+
flex-wrap: wrap;
|
|
2059
|
+
margin: 8px 0;
|
|
2060
|
+
padding: 8px 10px;
|
|
2061
|
+
background: var(--surface2);
|
|
2062
|
+
border: 1px solid var(--border);
|
|
2063
|
+
border-radius: var(--radius-sm, 4px);
|
|
2064
|
+
font-size: var(--text-sm);
|
|
2065
|
+
}
|
|
2066
|
+
.wi-bulk-bar[hidden] { display: none; }
|
|
2067
|
+
.wi-bulk-selectall {
|
|
2068
|
+
display: inline-flex;
|
|
2069
|
+
align-items: center;
|
|
2070
|
+
gap: 6px;
|
|
2071
|
+
cursor: pointer;
|
|
2072
|
+
color: var(--text);
|
|
2073
|
+
}
|
|
2074
|
+
#wi-bulk-count { color: var(--muted); min-width: 80px; }
|
|
2075
|
+
.wi-bulk-actions { display: inline-flex; gap: 6px; flex-wrap: wrap; }
|
|
2076
|
+
.wi-bulk-actions button:disabled { opacity: 0.5; cursor: default; }
|
|
2077
|
+
#wi-bulk-toggle.active {
|
|
2078
|
+
background: var(--blue);
|
|
2079
|
+
color: #fff;
|
|
2080
|
+
border-color: var(--blue);
|
|
2081
|
+
}
|
|
2082
|
+
.wi-bulk-cell { width: 28px; text-align: center; }
|
|
2083
|
+
tr.wi-row-selected > td { background: rgba(80,140,255,0.12); }
|
package/dashboard-build.js
CHANGED
|
@@ -38,7 +38,7 @@ const DASHBOARD_PAGE_SUB_FRAGMENTS = {
|
|
|
38
38
|
// Every dashboard/js/*.js file belongs here unless it is lazy-loaded through a
|
|
39
39
|
// dedicated route (only memory-search.js today, served at /assets/memory-search.js).
|
|
40
40
|
const DASHBOARD_JS_FILES = [
|
|
41
|
-
'utils', 'state', 'features-client', 'render-utils', 'charter-editor', 'detail-panel', 'live-stream',
|
|
41
|
+
'utils', 'agent-identity', 'state', 'features-client', 'render-utils', 'charter-editor', 'detail-panel', 'live-stream',
|
|
42
42
|
'render-agents', 'render-dispatch', 'render-work-items', 'render-prd',
|
|
43
43
|
'render-prs', 'render-plans', 'render-inbox', 'render-kb', 'render-skills',
|
|
44
44
|
'render-other', 'render-managed', 'memory-panel', 'render-schedules', 'render-watches', 'render-pipelines', 'render-meetings', 'render-pinned',
|
|
@@ -151,7 +151,7 @@ const SLIM_JS_ORDER = [
|
|
|
151
151
|
// charter-editor MUST precede detail-panel (see the "charter-editor registered
|
|
152
152
|
// before detail-panel" test).
|
|
153
153
|
const SLIM_CLASSIC_PANEL_JS = [
|
|
154
|
-
'utils', 'render-utils', 'charter-editor', 'live-stream', 'render-agents', 'detail-panel',
|
|
154
|
+
'utils', 'agent-identity', 'render-utils', 'charter-editor', 'live-stream', 'render-agents', 'detail-panel',
|
|
155
155
|
];
|
|
156
156
|
|
|
157
157
|
// Cache for the assembled slim source fragments (layout/css/body/js). Keyed on
|
package/dashboard.js
CHANGED
|
@@ -2538,6 +2538,9 @@ let _toolsInventoryCacheTs = 0;
|
|
|
2538
2538
|
let _toolsInventoryRefreshPromise = null;
|
|
2539
2539
|
let _toolsInventoryChild = null;
|
|
2540
2540
|
let _toolsInventoryScanner = _scanToolsInventoryInChild;
|
|
2541
|
+
// Most recent failed refresh ({ message, ts }); cleared on the next success.
|
|
2542
|
+
// Surfaced as a response header so a stale-serve still exposes refresh failure.
|
|
2543
|
+
let _toolsInventoryLastError = null;
|
|
2541
2544
|
|
|
2542
2545
|
function _validateToolsInventoryPayload(payload) {
|
|
2543
2546
|
if (!payload || typeof payload !== 'object'
|
|
@@ -2600,11 +2603,12 @@ function _terminateToolsInventoryChild() {
|
|
|
2600
2603
|
try { shared.terminateProcess('dashboard.tools-inventory-shutdown', child); } catch { /* child may already be gone */ }
|
|
2601
2604
|
}
|
|
2602
2605
|
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2606
|
+
// Start (or join) the single-flight child-process scan. Concurrent callers
|
|
2607
|
+
// share one in-flight promise so a burst of /api/tools polls never spawns
|
|
2608
|
+
// duplicate children. On success the cache + payload-derived ETag are swapped
|
|
2609
|
+
// in and the last-error is cleared; on failure the error is recorded (so a
|
|
2610
|
+
// stale-serve can still surface it) and re-thrown to the caller that awaited.
|
|
2611
|
+
function _startToolsInventoryScan() {
|
|
2608
2612
|
if (_toolsInventoryRefreshPromise) return _toolsInventoryRefreshPromise;
|
|
2609
2613
|
|
|
2610
2614
|
_toolsInventoryRefreshPromise = Promise.resolve()
|
|
@@ -2616,19 +2620,68 @@ function _refreshToolsInventory() {
|
|
|
2616
2620
|
_toolsInventoryCacheJson = json;
|
|
2617
2621
|
_toolsInventoryCacheEtag = `"tools-${crypto.createHash('sha256').update(json).digest('hex')}"`;
|
|
2618
2622
|
_toolsInventoryCacheTs = Date.now();
|
|
2623
|
+
_toolsInventoryLastError = null;
|
|
2619
2624
|
return next;
|
|
2620
2625
|
})
|
|
2626
|
+
.catch(err => {
|
|
2627
|
+
_toolsInventoryLastError = { message: String((err && err.message) || err), ts: Date.now() };
|
|
2628
|
+
throw err;
|
|
2629
|
+
})
|
|
2621
2630
|
.finally(() => {
|
|
2622
2631
|
_toolsInventoryRefreshPromise = null;
|
|
2623
2632
|
});
|
|
2624
2633
|
return _toolsInventoryRefreshPromise;
|
|
2625
2634
|
}
|
|
2626
2635
|
|
|
2636
|
+
function _toolsInventoryAgeMs() {
|
|
2637
|
+
return _toolsInventoryCacheTs ? Date.now() - _toolsInventoryCacheTs : Infinity;
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
// Resolve the tools inventory. Default (no opts) preserves the original
|
|
2641
|
+
// block-on-expiry contract used by the CC warm path. `{ allowStale: true }`
|
|
2642
|
+
// opts into stale-while-revalidate: once a cache exists it is always returned
|
|
2643
|
+
// immediately while a background single-flight refresh runs, so the request
|
|
2644
|
+
// never blocks on the multi-second child scan again. Only a truly cold cache
|
|
2645
|
+
// (no inventory yet) blocks so callers never see missing data. A persistently
|
|
2646
|
+
// failing background refresh stays visible via _toolsInventoryLastError (and
|
|
2647
|
+
// the X-Tools-Inventory-Error header) rather than re-introducing a blocking
|
|
2648
|
+
// rescan — a rescan that is itself likely to fail would just restore the very
|
|
2649
|
+
// ~8-10s hang this path removed.
|
|
2650
|
+
function _refreshToolsInventory(opts) {
|
|
2651
|
+
const allowStale = !!(opts && opts.allowStale);
|
|
2652
|
+
const age = _toolsInventoryAgeMs();
|
|
2653
|
+
|
|
2654
|
+
// Fresh — serve without a scan.
|
|
2655
|
+
if (_toolsInventoryCache && age < TOOLS_INVENTORY_CACHE_TTL_MS) {
|
|
2656
|
+
return Promise.resolve(_toolsInventoryCache);
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
// Stale-while-revalidate: any cache, however old, is served now while a
|
|
2660
|
+
// single-flight background refresh runs (errors swallowed here — the
|
|
2661
|
+
// recorded last-error still surfaces via the response header).
|
|
2662
|
+
if (allowStale && _toolsInventoryCache) {
|
|
2663
|
+
const bg = _startToolsInventoryScan();
|
|
2664
|
+
if (bg && typeof bg.catch === 'function') bg.catch(() => { /* surfaced via header */ });
|
|
2665
|
+
return Promise.resolve(_toolsInventoryCache);
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
// Cold cache (nothing to serve) — block on a fresh scan.
|
|
2669
|
+
return _startToolsInventoryScan();
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2627
2672
|
async function handleToolsInventory(req, res) {
|
|
2628
2673
|
try {
|
|
2629
|
-
await _refreshToolsInventory();
|
|
2674
|
+
await _refreshToolsInventory({ allowStale: true });
|
|
2675
|
+
const age = _toolsInventoryAgeMs();
|
|
2676
|
+
const refreshing = _toolsInventoryRefreshPromise != null;
|
|
2630
2677
|
res.setHeader('ETag', _toolsInventoryCacheEtag);
|
|
2631
2678
|
res.setHeader('Cache-Control', 'private, max-age=0, must-revalidate');
|
|
2679
|
+
// Freshness/error signals so the page can represent stale/refresh/error
|
|
2680
|
+
// state without changing the inventory payload shape.
|
|
2681
|
+
res.setHeader('X-Tools-Inventory-Age-Ms', String(Number.isFinite(age) ? age : 0));
|
|
2682
|
+
if (age >= TOOLS_INVENTORY_CACHE_TTL_MS) res.setHeader('X-Tools-Inventory-Stale', '1');
|
|
2683
|
+
if (refreshing) res.setHeader('X-Tools-Inventory-Refreshing', '1');
|
|
2684
|
+
if (_toolsInventoryLastError) res.setHeader('X-Tools-Inventory-Error', '1');
|
|
2632
2685
|
if (_ifNoneMatchHasEtag(req?.headers?.['if-none-match'], _toolsInventoryCacheEtag)) {
|
|
2633
2686
|
res.statusCode = 304;
|
|
2634
2687
|
res.end();
|
|
@@ -2653,9 +2706,26 @@ function _resetToolsInventoryCacheForTesting() {
|
|
|
2653
2706
|
_toolsInventoryCacheEtag = null;
|
|
2654
2707
|
_toolsInventoryCacheTs = 0;
|
|
2655
2708
|
_toolsInventoryRefreshPromise = null;
|
|
2709
|
+
_toolsInventoryLastError = null;
|
|
2656
2710
|
_toolsInventoryScanner = _scanToolsInventoryInChild;
|
|
2657
2711
|
}
|
|
2658
2712
|
|
|
2713
|
+
// Age the cache just past the fresh-TTL so the next request exercises the
|
|
2714
|
+
// stale-while-revalidate path (serve cached now, refresh in the background).
|
|
2715
|
+
function _expireToolsInventoryCacheForTesting() {
|
|
2716
|
+
if (_toolsInventoryCacheTs) {
|
|
2717
|
+
_toolsInventoryCacheTs = Date.now() - TOOLS_INVENTORY_CACHE_TTL_MS - 1;
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
// Age the cache back by an arbitrary amount so a test can drive it far past any
|
|
2722
|
+
// prior staleness window and assert the serve-cached-now contract still holds.
|
|
2723
|
+
function _ageToolsInventoryCacheForTesting(ageMs) {
|
|
2724
|
+
if (_toolsInventoryCacheTs) {
|
|
2725
|
+
_toolsInventoryCacheTs = Date.now() - ageMs;
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2659
2729
|
function parsePinnedEntries(content) {
|
|
2660
2730
|
if (!content) return [];
|
|
2661
2731
|
const entries = [];
|
|
@@ -17170,7 +17240,7 @@ module.exports = {
|
|
|
17170
17240
|
_resetStatusCacheForTesting,
|
|
17171
17241
|
_ifNoneMatchHasEtag,
|
|
17172
17242
|
_scanStatusMtimes, _refreshStatusMtimes, _resetStatusMtimeCacheForTesting, // exported for testing
|
|
17173
|
-
handleToolsInventory, _refreshToolsInventory, _setToolsInventoryScannerForTesting, _resetToolsInventoryCacheForTesting, // exported for testing
|
|
17243
|
+
handleToolsInventory, _refreshToolsInventory, _setToolsInventoryScannerForTesting, _resetToolsInventoryCacheForTesting, _expireToolsInventoryCacheForTesting, _ageToolsInventoryCacheForTesting, // exported for testing
|
|
17174
17244
|
collectCcProjectSkills, _mapProjectSkillsForCc, // exported for testing
|
|
17175
17245
|
_ensureConfiguredProjectStateFiles: ensureConfiguredProjectStateFiles, _resetInitializedProjectStatesForTesting, // exported for testing
|
|
17176
17246
|
_countWorktrees, _refreshWorktreeCount, _scanWorktreeCount, _resetWorktreeCountCacheForTesting, // exported for testing
|
|
@@ -17326,6 +17396,22 @@ if (require.main === module) {
|
|
|
17326
17396
|
Promise.resolve(queries.getKnowledgeBaseEntries())
|
|
17327
17397
|
.catch(err => console.warn(`[dashboard] KB cache warm failed: ${err && err.message}`));
|
|
17328
17398
|
|
|
17399
|
+
// W-msdbk1rg — warm the tools inventory (skills/commands/MCP) shortly after
|
|
17400
|
+
// boot so the first Skills & MCP page open renders instantly instead of
|
|
17401
|
+
// blocking ~8-10s on the cold child scan (the nested project-embedded skill
|
|
17402
|
+
// walk over large monorepos dominates). Deferred + unref'd so it never
|
|
17403
|
+
// competes with boot or the first /api/status snapshot, and single-flighted
|
|
17404
|
+
// inside _refreshToolsInventory so it can't duplicate an in-flight scan.
|
|
17405
|
+
const _toolsWarmTimer = setTimeout(() => {
|
|
17406
|
+
try {
|
|
17407
|
+
const pending = _refreshToolsInventory();
|
|
17408
|
+
if (pending && typeof pending.catch === 'function') {
|
|
17409
|
+
pending.catch(err => console.warn(`[dashboard] tools inventory warm failed: ${err && err.message}`));
|
|
17410
|
+
}
|
|
17411
|
+
} catch (err) { console.warn(`[dashboard] tools inventory warm failed: ${err && err.message}`); }
|
|
17412
|
+
}, 3000);
|
|
17413
|
+
if (typeof _toolsWarmTimer.unref === 'function') _toolsWarmTimer.unref();
|
|
17414
|
+
|
|
17329
17415
|
// Auto-open the browser. `minions restart` and the upgrade path set
|
|
17330
17416
|
// MINIONS_NO_AUTO_OPEN=1 because the CLI orchestrates the open itself
|
|
17331
17417
|
// after observing whether an existing tab reconnected; the primitive
|
package/docs/README.md
CHANGED
|
@@ -78,6 +78,7 @@ Architecture, design proposals, and lifecycle references for people working on t
|
|
|
78
78
|
- [slim-ux/concepts.md](slim-ux/concepts.md) — Slim-UX design notes: simplified surface concepts driving the project picker, inline project link, and decoupled folder picker.
|
|
79
79
|
- [slim-ux/architecture-suggestions.md](slim-ux/architecture-suggestions.md) — Slim-UX follow-up architecture suggestions paired with `concepts.md`.
|
|
80
80
|
- [team-memory.md](team-memory.md) — End-to-end hybrid memory system: file-backed inputs and consolidation, SQL/FTS5 records, retrieval and fallback, prompt bounds, episodic capture, the review-learning lifecycle (capture/recall/promotion/contradiction/diagnostics) and its rollout history + acceptance measurement, security, APIs, and operations.
|
|
81
|
+
- [temporary-agents.md](temporary-agents.md) — Ephemeral `temp-<uid>` fallback agents: the opt-in `engine.allowTempAgents` + per-tick concurrency gates, in-memory naming/state, why they carry no charter/persona and no personal memory (but do get pinned/team/project memory), fleet-default runtime/model/tool/manifest resolution, routing/retry behavior, lifecycle/cleanup across restart, and isolation implications.
|
|
81
82
|
- [timeouts-and-liveness.md](timeouts-and-liveness.md) — What kills (or doesn't kill) a live tracked agent: the wall-clock vs steering kill invariants, spawn-phase watchdog gates, steering safety nets, and stale-orphan detection ladder.
|
|
82
83
|
- [visual-evidence-ci.md](visual-evidence-ci.md) — GitHub Actions before/after dashboard capture: path and label triggers, deterministic base/head fixtures, artifact/comment lifecycle, trust boundary, and local reproduction.
|
|
83
84
|
- [watches.md](watches.md) — Persistent monitoring jobs: target-type registry, conditions, follow-up actions, and the `watch-plugins/` extension folder.
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
| `capabilities.systemPromptFile` | **`false`** | No `--system-prompt-file` flag exists. Inject system prompt via a `<system>` block prepended to stdin. |
|
|
23
23
|
| `capabilities.effortLevels` | **`true`** | `--effort` accepts `low|medium|high|xhigh` (no `max`). Adapter must map `'max' → 'xhigh'`. |
|
|
24
24
|
| `capabilities.costTracking` | **`false`** | `result.usage` contains `premiumRequests` (count, not USD), no token counts, no cost. |
|
|
25
|
+
| `capabilities.billableUnit` | **`'premiumRequests'`** | Native billable unit. The adapter emits `usage.billable = { unit: 'premiumRequests', value, reported }` and the dashboard renders premium requests in the same position a USD runtime shows dollar cost. When the CLI does not report the field, `value` is `null` and `reported` is `false` (recorded as unavailable, never a fake `0`). See [runtime-adapters.md → Billable-unit usage accounting](runtime-adapters.md#billable-unit-usage-accounting). |
|
|
25
26
|
| `capabilities.modelShorthands` | **`false`** | The Copilot CLI requires full model IDs (`claude-sonnet-4.5`, `gpt-5.4`). Minions may accept internal aliases (`haiku`, `sonnet`, `opus`), but the adapter translates them to Copilot model IDs before invoking the CLI. |
|
|
26
27
|
| `capabilities.budgetCap` | **`false`** | No `--max-budget-usd` flag. |
|
|
27
28
|
| `capabilities.bareMode` | **`false`** | No `--bare` equivalent. |
|
package/docs/engine-restart.md
CHANGED
|
@@ -134,8 +134,10 @@ Stopping is deliberately two commands, because "ask the engine to shut down" and
|
|
|
134
134
|
| `minions stop --all` | Ordered whole-stack teardown: stop-intent → supervisor → dashboard → graceful engine drain → identity-verified reap → late-respawn sweep → verification. | The daemons being verified down. It reports the `engine/state.db-shm` state but does not wait for it. |
|
|
135
135
|
| `minions stop --all --wait` | The same teardown, and then waits for `engine/state.db-shm` to be released. | The database handles being released, or the budget expiring. |
|
|
136
136
|
|
|
137
|
-
`--timeout <ms>` sets the budget for the whole sequence (default 60000,
|
|
138
|
-
the internal installer's quiescence window
|
|
137
|
+
`--timeout <ms>` sets the budget for the whole sequence (default 60000, the
|
|
138
|
+
floor of the internal installer's quiescence window — that window is derived from
|
|
139
|
+
the target runtime's own `engine.shutdownTimeout` and only ever widens from
|
|
140
|
+
here, so a stop is never shorter than the wait that follows it).
|
|
139
141
|
|
|
140
142
|
Bare `minions stop` is unchanged and will stay that way: recovery paths, operator
|
|
141
143
|
scripts, and the engine's own callers depend on "request shutdown and return".
|
package/docs/internal-install.md
CHANGED
|
@@ -164,16 +164,55 @@ by `test/unit/install-internal-minions.test.js`.
|
|
|
164
164
|
until the database handles are actually released. Release requires **both**
|
|
165
165
|
signals: the processes are dead *and* `engine/state.db-shm` is absent or
|
|
166
166
|
empty — a live shared-memory index means a connection still holds the WAL. If
|
|
167
|
-
the handles are not released within the
|
|
168
|
-
closed**, before any uninstall or package
|
|
169
|
-
was taken, the exact argv it issued,
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
the handles are not released within the derived budget (see *How long the gate
|
|
168
|
+
waits* below) the run **fails closed**, before any uninstall or package
|
|
169
|
+
mutation, and prints which stop path was taken, the exact argv it issued, the
|
|
170
|
+
budget that expired and where it came from, and the exact PIDs still holding
|
|
171
|
+
the database. Whether services were actually stopped is recorded so a rollback
|
|
172
|
+
can restart them. Skipped on a machine with no existing runtime.
|
|
172
173
|
|
|
173
174
|
When the run does fail closed here, end the holders it names yourself — the
|
|
174
175
|
refusal prints each surviving process by name and PID, and the dashboard is
|
|
175
176
|
the usual one — then re-run the installer.
|
|
176
177
|
|
|
178
|
+
### How long the gate waits
|
|
179
|
+
|
|
180
|
+
The wait budget is **derived from the runtime being stopped**, not fixed. It is
|
|
181
|
+
the pinned root's own `engine.shutdownTimeout` — the budget the engine gives
|
|
182
|
+
itself to drain pooled leases before exiting — plus the same grace
|
|
183
|
+
`minions restart` applies when it derives a teardown deadline the same way
|
|
184
|
+
(`engine/recovery/stop-stack.js#DRAIN_GRACE_MS`, 5 s).
|
|
185
|
+
|
|
186
|
+
| Input | Budget |
|
|
187
|
+
|-------|--------|
|
|
188
|
+
| `engine.shutdownTimeout` in the pinned root's `config.json` | that value + 5 s grace |
|
|
189
|
+
| config missing, unreadable, malformed, or nonsensical (negative, zero, non-numeric, `NaN`) | `ENGINE_DEFAULTS.shutdownTimeout` (300 s) + 5 s grace |
|
|
190
|
+
| derived budget below the floor | **floor 60 s** |
|
|
191
|
+
| derived budget above the ceiling | **ceiling 900 s (15 min)** |
|
|
192
|
+
|
|
193
|
+
A fixed window could not be right: the *runtime* decides how long a teardown
|
|
194
|
+
legitimately takes. The previous fixed 60 s was shorter than the **default**
|
|
195
|
+
drain budget of five minutes, so a busy stack was declared stuck while it was
|
|
196
|
+
still shutting down exactly as designed.
|
|
197
|
+
|
|
198
|
+
Bounded on both sides because the input is operator-supplied data this gate
|
|
199
|
+
does not own. The **floor** is `stop-stack.js#DEFAULT_STOP_TIMEOUT_MS`, so the
|
|
200
|
+
change can only ever widen the window — a runtime configured with a very short
|
|
201
|
+
drain never makes the gate stricter than it was — and the wait after a stop is
|
|
202
|
+
never shorter than the stop's own budget. The **ceiling** means a corrupt or
|
|
203
|
+
hostile `config.json` cannot hang an unattended migration indefinitely; at ~3x
|
|
204
|
+
the default runtime's budget it clamps nothing a real stack needs. A clamp is
|
|
205
|
+
named in the refusal output, so "it gave up too early" stays an answerable
|
|
206
|
+
question.
|
|
207
|
+
|
|
208
|
+
The budget is always read from the **pinned** root — the one `MINIONS_HOME` is
|
|
209
|
+
pinned to and the one whose services are being stopped — never from the
|
|
210
|
+
installing process's own root. The installer is deliberately standalone (Node
|
|
211
|
+
built-ins only, so it runs on a machine with no Minions to import from), so the
|
|
212
|
+
engine default and the grace are *mirrored* constants; a drift test in
|
|
213
|
+
`test/unit/install-internal-minions.test.js` binds both to their engine-side
|
|
214
|
+
originals so the copies cannot silently diverge.
|
|
215
|
+
|
|
177
216
|
### Which stop verb is issued
|
|
178
217
|
|
|
179
218
|
The installer prefers `minions stop --all --wait`, and falls back to the bare
|
package/docs/named-agents.md
CHANGED
|
@@ -29,6 +29,48 @@ The `name`, `emoji`, `role`, and `expertise` fields are **human-facing metadata*
|
|
|
29
29
|
label the agent in the dashboard and document intent. They do **not** drive dispatch (see
|
|
30
30
|
§3) and the `expertise` array is not executable (see §4).
|
|
31
31
|
|
|
32
|
+
### 1.1 Temporary agents and friendly call signs
|
|
33
|
+
|
|
34
|
+
When every permanent agent is busy and `engine.allowTempAgents` is set, the engine
|
|
35
|
+
spawns an **ephemeral agent** with a machine id of the form `temp-<uid>` (see
|
|
36
|
+
[`engine/orchestration/routing.js`](../engine/orchestration/routing.js)). That canonical
|
|
37
|
+
`temp-…` id is the **only** machine identity — it is unchanged for routing, SQL keys,
|
|
38
|
+
filesystem paths, live-output logs, process ownership, APIs, per-agent memory rules
|
|
39
|
+
(§8: `temp-*` ids are skipped for personal memory), and PR/reconciliation records.
|
|
40
|
+
|
|
41
|
+
For display only, each temp agent also gets a stable, memorable **call sign** such as
|
|
42
|
+
`Cosmic Comet`, rendered next to a visible `Temp` badge (`CallSign` + badge). This is
|
|
43
|
+
an *additive display identity*, never a rename of the machine id.
|
|
44
|
+
|
|
45
|
+
**Deterministic-from-id invariant (no persisted state).** The call sign is derived
|
|
46
|
+
purely from the immutable canonical id by
|
|
47
|
+
[`shared.tempAgentCallSign(id)`](../engine/core/shared.js): a single FNV-1a 32-bit hash of
|
|
48
|
+
the lower-cased id selects an adjective from its low bits (`h % ADJ`) and a name from its
|
|
49
|
+
high bits (`⌊h / ADJ⌋ % NAMES`), each drawn from a curated, roster-disjoint vocabulary.
|
|
50
|
+
Because the derivation depends only on the id and nothing else:
|
|
51
|
+
|
|
52
|
+
- **Restart- and history-stable** — the same id always yields the same call sign across
|
|
53
|
+
engine/dashboard restarts and for long-exited agents in archived/history views, so no
|
|
54
|
+
SQL migration or JSON runtime state is needed (and none is added). An already-assigned
|
|
55
|
+
name is preserved by construction — it cannot drift when the active set changes.
|
|
56
|
+
- **Collision behavior is deterministic** — the ~24×24 adjective/name space plus the
|
|
57
|
+
single-hash bit-slice keeps concurrently-visible temp agents well-spread; the curated
|
|
58
|
+
`name` list is disjoint from the shipped roster (Ripley/Dallas/Lambert/Rebecca/Ralph)
|
|
59
|
+
so a call sign never equals a configured display name, and the always-visible `Temp`
|
|
60
|
+
badge disambiguates even a rare full collision.
|
|
61
|
+
- **Case-insensitive** — legacy mixed-case `Temp-*` ids derive the same call sign as
|
|
62
|
+
their canonical lower-case form.
|
|
63
|
+
|
|
64
|
+
The client mirrors this vocabulary + algorithm in
|
|
65
|
+
[`dashboard/js/agent-identity.js`](../dashboard/js/agent-identity.js) (`MinionsAgentIdentity`),
|
|
66
|
+
the single formatter every user-facing renderer calls, so a legacy record missing the
|
|
67
|
+
server-stamped `agentName` still renders the identical stable call sign. Parity between
|
|
68
|
+
the two implementations is asserted by
|
|
69
|
+
[`test/unit/temp-agent-identity.test.js`](../test/unit/temp-agent-identity.test.js).
|
|
70
|
+
API payloads expose structured fields (`id`, friendly `name`/`callSign`, `isTemp`) rather
|
|
71
|
+
than replacing ids with formatted strings, and the canonical `temp-…` id stays discoverable
|
|
72
|
+
in detail views/tooltips for debugging.
|
|
73
|
+
|
|
32
74
|
## 2. How an agent is configured
|
|
33
75
|
|
|
34
76
|
The full per-agent config shape supported under `config.agents.<id>`:
|
|
@@ -123,6 +165,12 @@ context; `_any_` routes to any available idle agent (lowest error rate first).
|
|
|
123
165
|
`implement:large` is selected for items with `estimated_complexity: "large"`. If both
|
|
124
166
|
preferred and fallback are busy, the engine falls back to any idle agent.
|
|
125
167
|
|
|
168
|
+
**Overflow → temp agents.** If no configured agent is idle and the operator has enabled
|
|
169
|
+
`engine.allowTempAgents` (default off), the engine may spawn an ephemeral, personaless
|
|
170
|
+
`temp-<uid>` agent as a last-resort fallback. Temp agents inherit fleet-default runtime/model/
|
|
171
|
+
tools and shared/project memory but carry no charter and no personal memory. See
|
|
172
|
+
[`temporary-agents.md`](temporary-agents.md).
|
|
173
|
+
|
|
126
174
|
**Dispatch rules** (from `routing.md`):
|
|
127
175
|
|
|
128
176
|
- **Eager by default** — the engine spawns every agent that can start work, not one at a time.
|
package/docs/runtime-adapters.md
CHANGED
|
@@ -108,6 +108,44 @@ marker and visible sign-off, and lifecycle persists the same value as
|
|
|
108
108
|
`minionsReview.model`. Model catalogs are never used to guess an implicit
|
|
109
109
|
runtime default.
|
|
110
110
|
|
|
111
|
+
### Billable-unit usage accounting
|
|
112
|
+
|
|
113
|
+
Some runtimes bill a native, non-USD unit — the GitHub Copilot CLI reports
|
|
114
|
+
**premium requests** per session/turn. `parseOutput().usage` therefore carries a
|
|
115
|
+
generic typed descriptor alongside the token/cost fields so the engine accounts
|
|
116
|
+
for credits without ever branching on a runtime name:
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
usage.billable = { unit: 'premiumRequests', value: <number|null>, reported: <bool> }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- **Authoritative, never derived.** `value` is the count the CLI reported; the
|
|
123
|
+
engine never estimates credits from tokens, model, elapsed time, or pricing.
|
|
124
|
+
- **Unavailable ≠ 0.** When the installed CLI did not expose the field for a run,
|
|
125
|
+
the adapter emits `value: null, reported: false` (and Copilot leaves
|
|
126
|
+
`premiumRequests` / `costUsd` `null`, not `0`). Surfaces render unreported as
|
|
127
|
+
`N/A`, distinct from a genuine reported `0`.
|
|
128
|
+
- **Already scoped — no delta diffing.** Copilot `parseOutput` reassigns
|
|
129
|
+
`usage` on each terminal `result` event, so the value is the finalized count
|
|
130
|
+
for THIS invocation / pooled turn, not a running session total. Downstream code
|
|
131
|
+
therefore **sums** per-invocation values; it does not diff cumulative counters.
|
|
132
|
+
This per-invocation scoping is what makes streamed, resumed, retried, and
|
|
133
|
+
pooled dispatches safe from double-counting.
|
|
134
|
+
|
|
135
|
+
`shared.accumulateBillableUnits(target, usage.billable)` folds a descriptor into
|
|
136
|
+
`target.billableUnits[unit] = { total, reported, unavailable }`, used by
|
|
137
|
+
`engine/orchestration/lifecycle.js#updateMetrics` (per-agent / `_engine` /
|
|
138
|
+
`_daily`) and `engine/agents/llm.js` (Command Center / doc-chat). USD is
|
|
139
|
+
deliberately **excluded** from this path — dollar cost already flows through the
|
|
140
|
+
richer `costUsd` + token accounting, so routing it here too would double-count.
|
|
141
|
+
The map persists inside the existing metrics JSON blob (no migration; older rows
|
|
142
|
+
simply lack the field), and `engine/core/queries.js#getMetrics` resolves
|
|
143
|
+
`m.billableUnit` from the runtime capability so the dashboard keys off typed
|
|
144
|
+
metadata, never the runtime name. A future adapter can surface its own unit by
|
|
145
|
+
setting `capabilities.billableUnit` and emitting `usage.billable` — no call-site
|
|
146
|
+
changes required.
|
|
147
|
+
|
|
148
|
+
|
|
111
149
|
## Capability Flags
|
|
112
150
|
|
|
113
151
|
Engine code branches on consumed flags, never on runtime names. A capability
|
|
@@ -122,6 +160,7 @@ to act on that behavioral split.
|
|
|
122
160
|
| `systemPromptFile` | ✓ | ✗ | ✗ | sysprompt via `--system-prompt-file` (else inlined into stdin by `buildPrompt`). |
|
|
123
161
|
| `effortLevels` | ✓ | ✓ | ✓ | Runtime reasoning-effort controls. Codex maps through `--config model_reasoning_effort=...`; Copilot maps `'max'` to `'xhigh'`; Claude leaves `'max'` alone. |
|
|
124
162
|
| `costTracking` | ✓ | ✗ | ✗ | USD + token counts in the result event. Copilot only emits `premiumRequests`; Codex accounting is not assumed. |
|
|
163
|
+
| `billableUnit` | `usd` | `premiumRequests` | `usd` | Typed native billable unit for the display layer. The dashboard renders THIS unit in the same position a USD runtime shows dollar cost, switching labels/units/tooltips off the metadata — never a runtime-name branch. Adapters with no native credit unit default to `'usd'`. |
|
|
125
164
|
| `modelShorthands` | ✓ | ✗ | ✗ | Bare `sonnet`/`opus`/`haiku` accepted by Claude only. |
|
|
126
165
|
| `modelDiscovery` | ✗ | ✓ | ✓ | `listModels()` returns a real catalog when supported (Copilot API, Codex `debug models --bundled`). |
|
|
127
166
|
| `strictModelCatalog` | — | — | ✗ | An explicit false lets the runtime validate IDs missing from its cached catalog; Codex uses this because bundled catalogs can lag staged/custom models. |
|