@yemi33/minions 0.1.1925 → 0.1.1926

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/engine.js CHANGED
@@ -735,6 +735,11 @@ async function spawnAgent(dispatchItem, config) {
735
735
  const claudeConfig = config.claude || {};
736
736
  const engineConfig = config.engine || {};
737
737
  const startedAt = ts();
738
+ // Phase timing — raw timestamps, emitted as one structured `[spawn-timing]`
739
+ // log line after spawn succeeds. Use Date.now() not ts() so deltas are plain
740
+ // milliseconds. Keys are stamped at phase boundaries below.
741
+ const _phaseT = { start: Date.now() };
742
+ let _depCountForLog = 0;
738
743
 
739
744
  updateAgentStatus(id, AGENT_STATUS.SPAWNING, `Preparing ${type} task for ${agentId}`);
740
745
 
@@ -802,6 +807,7 @@ async function spawnAgent(dispatchItem, config) {
802
807
  const sysPromptPath = path.join(tmpDir, `sysprompt-${safeId}.md`);
803
808
  safeWrite(sysPromptPath, systemPrompt);
804
809
  const _cleanupPromptFiles = () => { safeUnlink(promptPath); safeUnlink(sysPromptPath); };
810
+ _phaseT.afterPrompt = Date.now();
805
811
 
806
812
  if (branchName) {
807
813
  updateAgentStatus(id, AGENT_STATUS.WORKTREE_SETUP, `Setting up worktree for branch ${branchName}`);
@@ -990,7 +996,9 @@ async function spawnAgent(dispatchItem, config) {
990
996
  if (worktreePath && fs.existsSync(worktreePath)) {
991
997
  cwd = worktreePath;
992
998
  const depIds = meta?.item?.depends_on || [];
999
+ _depCountForLog = depIds.length;
993
1000
  if (depIds.length > 0) {
1001
+ _phaseT.depFetchStart = Date.now();
994
1002
  try {
995
1003
  const depBranches = resolveDependencyBranches(depIds, meta?.item?.sourcePlan, project, config);
996
1004
  let depMergeFailed = false;
@@ -1047,6 +1055,8 @@ async function spawnAgent(dispatchItem, config) {
1047
1055
  depMergeFailed = true;
1048
1056
  }
1049
1057
  }
1058
+ _phaseT.depFetchEnd = Date.now();
1059
+ _phaseT.depPreflightStart = _phaseT.depFetchEnd;
1050
1060
  // Merge successfully-fetched + recovered (local-only pushed) branches sequentially
1051
1061
  const fetched = fetchable.filter((_, i) => fetchResults[i].status === 'fulfilled' || recoveredBranches.has(fetchable[i].branch));
1052
1062
  // Ancestor pruning: remove dep branches already contained in another (#958)
@@ -1098,6 +1108,8 @@ async function spawnAgent(dispatchItem, config) {
1098
1108
  log('warn', `Pre-flight simulation failed, proceeding with real merge: ${e.message}`);
1099
1109
  }
1100
1110
  }
1111
+ _phaseT.depPreflightEnd = Date.now();
1112
+ _phaseT.depMergeStart = _phaseT.depPreflightEnd;
1101
1113
  // Stash uncommitted changes before dep merge if worktree is dirty (#973)
1102
1114
  let stashed = false;
1103
1115
  if (!depMergeFailed && !skipDepMerge && prunedDeps.length > 0) {
@@ -1182,6 +1194,7 @@ async function spawnAgent(dispatchItem, config) {
1182
1194
  log('warn', `git stash pop failed in ${branchName}: ${popErr.message} — stash preserved for agent`);
1183
1195
  }
1184
1196
  }
1197
+ _phaseT.depMergeEnd = Date.now();
1185
1198
  if (depMergeFailed) {
1186
1199
  _cleanupPromptFiles();
1187
1200
  // Build actionable failReason identifying the conflicting branch and files (#958)
@@ -1291,6 +1304,7 @@ async function spawnAgent(dispatchItem, config) {
1291
1304
  if (cwd === rootDir && ['implement', 'implement:large', 'fix', 'test', 'verify', 'plan-to-prd'].includes(type)) {
1292
1305
  log('warn', `Agent ${agentId} running ${type} task in main repo (no worktree) for ${id} — changes may land on master directly`);
1293
1306
  }
1307
+ _phaseT.afterWorktree = Date.now();
1294
1308
 
1295
1309
  // ── Stale-HEAD guard for fix-task pushes (P-c8f2d5e3) ────────────────────
1296
1310
  // When a PR branch is rebased upstream (force-push), a reused worktree can
@@ -1302,6 +1316,7 @@ async function spawnAgent(dispatchItem, config) {
1302
1316
  // Read-only and non-fix dispatches are out of scope — implement tasks cut
1303
1317
  // their own branch from main, and review/verify don't push.
1304
1318
  if (type === WORK_TYPE.FIX && branchName && worktreePath && cwd === worktreePath) {
1319
+ _phaseT.staleHeadStart = Date.now();
1305
1320
  try {
1306
1321
  const guard = await assertStaleHeadOk({
1307
1322
  branch: branchName,
@@ -1325,6 +1340,7 @@ async function spawnAgent(dispatchItem, config) {
1325
1340
  // to skipped:'fetch-failed'). Log and continue.
1326
1341
  log('warn', `Stale-HEAD guard error for ${id} (${branchName}): ${err.message}`);
1327
1342
  }
1343
+ _phaseT.staleHeadEnd = Date.now();
1328
1344
  }
1329
1345
 
1330
1346
  // ── Runtime + opts resolution (P-2a6d9c4f) ────────────────────────────────
@@ -1371,6 +1387,7 @@ async function spawnAgent(dispatchItem, config) {
1371
1387
 
1372
1388
  // MCP servers: agents inherit from ~/.claude.json directly as Claude Code processes.
1373
1389
  // No --mcp-config needed — avoids redundant config and ensures agents always have latest servers.
1390
+ _phaseT.afterRuntime = Date.now();
1374
1391
 
1375
1392
  log('info', `Spawning agent: ${agentId} (${id}) in ${cwd}`);
1376
1393
  log('info', `Task type: ${type} | Branch: ${branchName || 'none'}`);
@@ -1428,6 +1445,7 @@ async function spawnAgent(dispatchItem, config) {
1428
1445
  }
1429
1446
 
1430
1447
  let proc;
1448
+ _phaseT.spawnCallStart = Date.now();
1431
1449
  try {
1432
1450
  // `detached: true` puts the agent in its own process group (POSIX) / job
1433
1451
  // object (Windows), so when the engine dies — gracefully via stop, abruptly
@@ -1441,6 +1459,7 @@ async function spawnAgent(dispatchItem, config) {
1441
1459
  env: childEnv,
1442
1460
  detached: true,
1443
1461
  });
1462
+ _phaseT.spawnCallEnd = Date.now();
1444
1463
  } catch (spawnErr) {
1445
1464
  // Synchronous spawn failure — record it to the (already-stamped) log so the
1446
1465
  // orphan detector's "logSize > stub-only" check can tell this apart from a
@@ -1476,6 +1495,26 @@ async function spawnAgent(dispatchItem, config) {
1476
1495
  };
1477
1496
  activeProcesses.set(id, initialProcInfo);
1478
1497
 
1498
+ // Emit per-phase timing for spawn-latency analysis. One structured line per
1499
+ // dispatch; grep `[spawn-timing]` to aggregate. Null phases didn't run for
1500
+ // this dispatch (e.g. stale_head only runs for fix tasks; dep_* only when
1501
+ // the item has depends_on).
1502
+ try {
1503
+ const _diff = (a, b) => (_phaseT[a] != null && _phaseT[b] != null) ? (_phaseT[b] - _phaseT[a]) : null;
1504
+ const timings = {
1505
+ prompt: _diff('start', 'afterPrompt'),
1506
+ worktree_total: _diff('afterPrompt', 'afterWorktree'),
1507
+ dep_fetch: _diff('depFetchStart', 'depFetchEnd'),
1508
+ dep_preflight: _diff('depPreflightStart', 'depPreflightEnd'),
1509
+ dep_merge: _diff('depMergeStart', 'depMergeEnd'),
1510
+ stale_head: _diff('staleHeadStart', 'staleHeadEnd'),
1511
+ runtime: _diff('afterWorktree', 'afterRuntime'),
1512
+ spawn_call: _diff('spawnCallStart', 'spawnCallEnd'),
1513
+ total: _diff('start', 'spawnCallEnd'),
1514
+ };
1515
+ log('info', `[spawn-timing] id=${id} agent=${agentId} type=${type} runtime=${runtimeName} branch=${branchName || '-'} deps=${_depCountForLog} ${JSON.stringify(timings)}`);
1516
+ } catch { /* telemetry is best-effort */ }
1517
+
1479
1518
  const MAX_OUTPUT = 1024 * 1024; // 1MB
1480
1519
  let stdout = '';
1481
1520
  let stderr = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1925",
3
+ "version": "0.1.1926",
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"
@@ -1,5 +0,0 @@
1
- {
2
- "runtime": "copilot",
3
- "models": null,
4
- "cachedAt": "2026-05-14T02:03:25.197Z"
5
- }