@yemi33/minions 0.1.1926 → 0.1.1928
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dashboard/js/render-other.js +48 -14
- package/engine/lifecycle.js +7 -1
- package/engine.js +15 -0
- package/package.json +1 -1
|
@@ -166,9 +166,15 @@ function renderLlmPerf(metrics) {
|
|
|
166
166
|
el.innerHTML = '<p class="empty">No LLM performance data yet.</p>';
|
|
167
167
|
return;
|
|
168
168
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
169
|
+
// Split entries: per-LLM-call rows vs per-agent-task agent-dispatch row.
|
|
170
|
+
// `calls` semantics differ between the two — agent-dispatch counts dispatched
|
|
171
|
+
// work items (with runtimeMs = wall-clock of the agent process), the others
|
|
172
|
+
// count LLM API round-trips (with totalDurationMs = sum of per-call latencies).
|
|
173
|
+
const filtered = Object.entries(engine).filter(([k]) => !k.startsWith('test'));
|
|
174
|
+
const perCall = filtered.filter(([k]) => k !== 'agent-dispatch')
|
|
175
|
+
.sort((a, b) => (b[1].calls || 0) - (a[1].calls || 0));
|
|
176
|
+
const perTask = filtered.filter(([k]) => k === 'agent-dispatch');
|
|
177
|
+
function renderRow(type, m) {
|
|
172
178
|
const calls = m.calls || 0;
|
|
173
179
|
const totalMs = m.totalDurationMs || 0;
|
|
174
180
|
const timedCalls = m.timedCalls || 0;
|
|
@@ -176,16 +182,48 @@ function renderLlmPerf(metrics) {
|
|
|
176
182
|
const fmtTotal = totalMs < 60000 ? Math.round(totalMs / 1000) + 's' : Math.round(totalMs / 60000) + 'm';
|
|
177
183
|
const fmtAvg = avgMs < 1000 ? Math.round(avgMs) + 'ms' : avgMs < 60000 ? Math.round(avgMs / 1000) + 's' : Math.round(avgMs / 60000) + 'm';
|
|
178
184
|
const cost = m.costUsd ? '$' + m.costUsd.toFixed(2) : '-';
|
|
179
|
-
|
|
185
|
+
return '<tr><td style="font-weight:600">' + escHtml(type) + '</td>' +
|
|
180
186
|
'<td>' + calls + '</td>' +
|
|
181
187
|
'<td style="color:var(--muted)">' + (totalMs ? fmtTotal : '-') + '</td>' +
|
|
182
188
|
'<td style="color:var(--blue)">' + (avgMs ? fmtAvg : '-') + '</td>' +
|
|
183
189
|
'<td style="color:var(--muted)">' + cost + '</td></tr>';
|
|
184
190
|
}
|
|
191
|
+
let html = '<table class="pr-table"><thead><tr><th>Call Type</th><th>Calls</th><th>Total Time</th><th>Avg Time</th><th>Cost</th></tr></thead><tbody>';
|
|
192
|
+
for (const [type, m] of perCall) {
|
|
193
|
+
html += renderRow(type, m);
|
|
194
|
+
}
|
|
195
|
+
if (perTask.length > 0) {
|
|
196
|
+
// Visual divider + caption row to distinguish per-call (above) from
|
|
197
|
+
// per-task (below) — column units differ.
|
|
198
|
+
html += '<tr><td colspan="5" style="border-top:2px solid var(--border);padding-top:6px;font-size:10px;color:var(--muted);font-style:italic">— per agent task (calls = dispatched work items, time = wall-clock of agent process) —</td></tr>';
|
|
199
|
+
for (const [type, m] of perTask) {
|
|
200
|
+
html += renderRow(type + ' (per task)', m);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
185
203
|
html += '</tbody></table>';
|
|
186
204
|
el.innerHTML = html;
|
|
187
205
|
}
|
|
188
206
|
|
|
207
|
+
// Aggregate _engine entries for the Token Usage summary tiles.
|
|
208
|
+
// Excludes:
|
|
209
|
+
// - 'agent-dispatch' (already counted in per-agent metrics[agentId] totals;
|
|
210
|
+
// summing both would double-count agent spend in the tile)
|
|
211
|
+
// - 'test-*' / '_test*' (matching engine/llm.js trackEngineUsage filter so
|
|
212
|
+
// stale test categories don't inflate engineCalls)
|
|
213
|
+
function _aggregateEngineUsageForTokenTile(engine) {
|
|
214
|
+
let cost = 0, input = 0, output = 0, cache = 0, calls = 0;
|
|
215
|
+
for (const [k, e] of Object.entries(engine || {})) {
|
|
216
|
+
if (k === 'agent-dispatch') continue;
|
|
217
|
+
if (k.startsWith('test-') || k.startsWith('_test')) continue;
|
|
218
|
+
cost += e.costUsd || 0;
|
|
219
|
+
input += e.inputTokens || 0;
|
|
220
|
+
output += e.outputTokens || 0;
|
|
221
|
+
cache += e.cacheRead || 0;
|
|
222
|
+
calls += e.calls || 0;
|
|
223
|
+
}
|
|
224
|
+
return { cost, input, output, cache, calls };
|
|
225
|
+
}
|
|
226
|
+
|
|
189
227
|
function renderTokenUsage(metrics) {
|
|
190
228
|
const el = document.getElementById('token-usage-content');
|
|
191
229
|
const agents = Object.entries(metrics).filter(([k]) => !k.startsWith('_'));
|
|
@@ -201,15 +239,11 @@ function renderTokenUsage(metrics) {
|
|
|
201
239
|
agentCache += m.totalCacheRead || 0;
|
|
202
240
|
}
|
|
203
241
|
|
|
204
|
-
// Aggregate engine totals
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
engineOutput += e.outputTokens || 0;
|
|
210
|
-
engineCache += e.cacheRead || 0;
|
|
211
|
-
engineCalls += e.calls || 0;
|
|
212
|
-
}
|
|
242
|
+
// Aggregate engine totals (excludes agent-dispatch + test-* — see helper)
|
|
243
|
+
const engAgg = _aggregateEngineUsageForTokenTile(engine);
|
|
244
|
+
const engineCost = engAgg.cost, engineInput = engAgg.input,
|
|
245
|
+
engineOutput = engAgg.output, engineCache = engAgg.cache,
|
|
246
|
+
engineCalls = engAgg.calls; // eslint-disable-line no-unused-vars
|
|
213
247
|
|
|
214
248
|
const totalCost = agentCost + engineCost;
|
|
215
249
|
const totalInput = agentInput + engineInput;
|
|
@@ -430,4 +464,4 @@ async function _addSelectedProjects() {
|
|
|
430
464
|
}
|
|
431
465
|
}
|
|
432
466
|
|
|
433
|
-
window.MinionsOther = { renderProjects, optimisticallyAddProject, projectChipRemove, renderMcpServers, renderMetrics, renderLlmPerf, renderTokenUsage, openScanProjectsModal };
|
|
467
|
+
window.MinionsOther = { renderProjects, optimisticallyAddProject, projectChipRemove, renderMcpServers, renderMetrics, renderLlmPerf, renderTokenUsage, _aggregateEngineUsageForTokenTile, openScanProjectsModal };
|
package/engine/lifecycle.js
CHANGED
|
@@ -2550,8 +2550,14 @@ function updateMetrics(agentId, dispatchItem, result, taskUsage, prsCreatedCount
|
|
|
2550
2550
|
metrics._engine['agent-dispatch'] = { calls: 0, costUsd: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheCreation: 0, totalDurationMs: 0 };
|
|
2551
2551
|
}
|
|
2552
2552
|
const eng = metrics._engine['agent-dispatch'];
|
|
2553
|
-
|
|
2553
|
+
// Gate calls on runtimeMs > 0 so pre-spawn skips (deps unmet, cooldown,
|
|
2554
|
+
// classified-fail) don't inflate the dispatch-tile call count. `calls` and
|
|
2555
|
+
// `timedCalls` advance together for agent-dispatch; both fields are kept
|
|
2556
|
+
// for schema parity with other _engine entries (CC/doc-chat/consolidation/
|
|
2557
|
+
// kb-sweep) where calls = LLM round-trips and timedCalls subset = ones with
|
|
2558
|
+
// a measured durationMs.
|
|
2554
2559
|
if (runtimeMs > 0) {
|
|
2560
|
+
eng.calls++;
|
|
2555
2561
|
eng.totalDurationMs = (eng.totalDurationMs || 0) + runtimeMs;
|
|
2556
2562
|
eng.timedCalls = (eng.timedCalls || 0) + 1;
|
|
2557
2563
|
}
|
package/engine.js
CHANGED
|
@@ -823,7 +823,9 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
823
823
|
shared.assertWorktreeOutsideProject(worktreePath, rootDir);
|
|
824
824
|
|
|
825
825
|
// If branch is already checked out in an existing worktree, reuse it
|
|
826
|
+
_phaseT.findExistingStart = Date.now();
|
|
826
827
|
const existingWt = await findExistingWorktree(rootDir, branchName);
|
|
828
|
+
_phaseT.findExistingEnd = Date.now();
|
|
827
829
|
if (existingWt) {
|
|
828
830
|
// Same guard for reuse — a previously-created bad worktree must not
|
|
829
831
|
// be silently reused either; the cleanup sweep flags these so the
|
|
@@ -834,13 +836,16 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
834
836
|
// Probe origin first — locally-created branches that were never pushed
|
|
835
837
|
// (orphan/timeout retry before first push) would otherwise emit a
|
|
836
838
|
// "couldn't find remote ref" warn pair on every reuse.
|
|
839
|
+
_phaseT.reuseSyncStart = Date.now();
|
|
837
840
|
await syncReusedWorktree(rootDir, existingWt, branchName, _gitOpts);
|
|
841
|
+
_phaseT.reuseSyncEnd = Date.now();
|
|
838
842
|
} else if (READ_ONLY_ROOT_TASK_TYPES.has(type) && !isPipelineBranchName(branchName)) {
|
|
839
843
|
// Read-only tasks — no worktree needed, run in rootDir
|
|
840
844
|
log('info', `${type}: read-only task, no worktree needed — running in rootDir`);
|
|
841
845
|
branchName = null;
|
|
842
846
|
worktreePath = null;
|
|
843
847
|
} else {
|
|
848
|
+
_phaseT.createWorktreeStart = Date.now();
|
|
844
849
|
try {
|
|
845
850
|
if (!fs.existsSync(worktreePath)) {
|
|
846
851
|
const isSharedBranch = meta?.branchStrategy === 'shared-branch' || meta?.useExistingBranch;
|
|
@@ -965,6 +970,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
965
970
|
return null;
|
|
966
971
|
}
|
|
967
972
|
}
|
|
973
|
+
_phaseT.createWorktreeEnd = Date.now();
|
|
968
974
|
}
|
|
969
975
|
|
|
970
976
|
// Shared-branch preflight (#2439): refuse to dispatch into a dirty shared
|
|
@@ -974,7 +980,9 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
974
980
|
// fires AFTER spawn — converting orchestration hygiene into fake
|
|
975
981
|
// implementation failures and cascading dependent items.
|
|
976
982
|
if (worktreePath && fs.existsSync(worktreePath) && meta?.branchStrategy === 'shared-branch' && branchName) {
|
|
983
|
+
_phaseT.cleanCheckStart = Date.now();
|
|
977
984
|
const cleanResult = await assertCleanSharedWorktree(rootDir, worktreePath, branchName, id, _gitOpts);
|
|
985
|
+
_phaseT.cleanCheckEnd = Date.now();
|
|
978
986
|
if (!cleanResult.clean) {
|
|
979
987
|
const previewFiles = (cleanResult.dirtyFiles || []).slice(0, 5).join(', ');
|
|
980
988
|
const reasonMsg = `DIRTY_WORKTREE: shared branch ${branchName} worktree at ${worktreePath} is dirty (${cleanResult.reason}); ${cleanResult.dirtyFiles?.length || 0} file(s)${previewFiles ? ': ' + previewFiles : ''}`;
|
|
@@ -1281,6 +1289,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1281
1289
|
// Inject dirty file list when worktree has uncommitted changes (e.g., max_turns retry)
|
|
1282
1290
|
// This signals to the respawned agent that prior work exists in the worktree (#960)
|
|
1283
1291
|
if (worktreePath && fs.existsSync(worktreePath)) {
|
|
1292
|
+
_phaseT.dirtyProbeStart = Date.now();
|
|
1284
1293
|
try {
|
|
1285
1294
|
const dirtyResult = await execAsync('git status --porcelain', { ..._gitOpts, cwd: worktreePath, timeout: 10000 });
|
|
1286
1295
|
const dirtyOutput = (dirtyResult.stdout || '').trim();
|
|
@@ -1298,6 +1307,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1298
1307
|
log('info', `Injected ${dirtyFiles.length} dirty files into prompt for ${id}`);
|
|
1299
1308
|
}
|
|
1300
1309
|
} catch (e) { log('warn', `git status --porcelain for dirty files: ${e.message}`); }
|
|
1310
|
+
_phaseT.dirtyProbeEnd = Date.now();
|
|
1301
1311
|
}
|
|
1302
1312
|
|
|
1303
1313
|
// Safety check: warn if a write-capable task is running in the main repo without a worktree
|
|
@@ -1504,6 +1514,11 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1504
1514
|
const timings = {
|
|
1505
1515
|
prompt: _diff('start', 'afterPrompt'),
|
|
1506
1516
|
worktree_total: _diff('afterPrompt', 'afterWorktree'),
|
|
1517
|
+
wt_find_existing: _diff('findExistingStart', 'findExistingEnd'),
|
|
1518
|
+
wt_reuse_sync: _diff('reuseSyncStart', 'reuseSyncEnd'),
|
|
1519
|
+
wt_create: _diff('createWorktreeStart', 'createWorktreeEnd'),
|
|
1520
|
+
wt_clean_check: _diff('cleanCheckStart', 'cleanCheckEnd'),
|
|
1521
|
+
wt_dirty_probe: _diff('dirtyProbeStart', 'dirtyProbeEnd'),
|
|
1507
1522
|
dep_fetch: _diff('depFetchStart', 'depFetchEnd'),
|
|
1508
1523
|
dep_preflight: _diff('depPreflightStart', 'depPreflightEnd'),
|
|
1509
1524
|
dep_merge: _diff('depMergeStart', 'depMergeEnd'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1928",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|