@yemi33/minions 0.1.163 → 0.1.164

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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.164 (2026-04-02)
4
+
5
+ ### Engine
6
+ - engine/lifecycle.js
7
+
3
8
  ## 0.1.163 (2026-04-02)
4
9
 
5
10
  ### Engine
@@ -1188,6 +1188,11 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
1188
1188
  ? path.join(MINIONS_DIR, 'work-items.json')
1189
1189
  : meta.project?.name ? path.join(MINIONS_DIR, 'projects', meta.project.name, 'work-items.json') : null;
1190
1190
  if (wiPath) {
1191
+ // Cost ceiling circuit breaker config — resolved outside lock for clarity
1192
+ const engineCfg = config?.engine || {};
1193
+ const evalMaxCost = engineCfg.evalMaxCost != null ? engineCfg.evalMaxCost : shared.ENGINE_DEFAULTS.evalMaxCost;
1194
+
1195
+ // Single lock: accumulate cost AND check ceiling atomically (no TOCTOU)
1191
1196
  mutateJsonFileLocked(wiPath, (items) => {
1192
1197
  if (!Array.isArray(items)) return items;
1193
1198
  const wi = items.find(i => i.id === meta.item.id);
@@ -1195,29 +1200,17 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
1195
1200
  wi._totalCostUsd = (wi._totalCostUsd || 0) + (taskUsage.costUsd || 0);
1196
1201
  wi._totalInputTokens = (wi._totalInputTokens || 0) + (taskUsage.inputTokens || 0);
1197
1202
  wi._totalOutputTokens = (wi._totalOutputTokens || 0) + (taskUsage.outputTokens || 0);
1203
+
1204
+ // Cost ceiling circuit breaker — treat like evalMaxIterations exceeded
1205
+ if (evalMaxCost != null && evalMaxCost > 0 &&
1206
+ wi._totalCostUsd > evalMaxCost && wi.status !== 'needs-human-review') {
1207
+ wi.status = 'needs-human-review';
1208
+ wi.failReason = `Cumulative cost $${wi._totalCostUsd.toFixed(2)} exceeds evalMaxCost ceiling $${evalMaxCost.toFixed(2)}`;
1209
+ log('warn', `Work item ${meta.item.id} exceeded cost ceiling ($${wi._totalCostUsd.toFixed(2)} > $${evalMaxCost.toFixed(2)}) — needs-human-review`);
1210
+ }
1198
1211
  }
1199
1212
  return items;
1200
1213
  }, { defaultValue: [] });
1201
-
1202
- // Cost ceiling circuit breaker — treat like evalMaxIterations exceeded
1203
- const engineCfg = config?.engine || {};
1204
- const evalMaxCost = engineCfg.evalMaxCost != null ? engineCfg.evalMaxCost : shared.ENGINE_DEFAULTS.evalMaxCost;
1205
- if (evalMaxCost != null && evalMaxCost > 0) {
1206
- const freshItems = safeJson(wiPath) || [];
1207
- const wi = freshItems.find(i => i.id === meta.item.id);
1208
- if (wi && wi._totalCostUsd > evalMaxCost && wi.status !== 'needs-human-review') {
1209
- mutateJsonFileLocked(wiPath, (items) => {
1210
- if (!Array.isArray(items)) return items;
1211
- const target = items.find(i => i.id === meta.item.id);
1212
- if (target) {
1213
- target.status = 'needs-human-review';
1214
- target.failReason = `Cumulative cost $${wi._totalCostUsd.toFixed(2)} exceeds evalMaxCost ceiling $${evalMaxCost.toFixed(2)}`;
1215
- log('warn', `Work item ${meta.item.id} exceeded cost ceiling ($${wi._totalCostUsd.toFixed(2)} > $${evalMaxCost.toFixed(2)}) — needs-human-review`);
1216
- }
1217
- return items;
1218
- }, { defaultValue: [] });
1219
- }
1220
- }
1221
1214
  }
1222
1215
  } catch (err) { log('warn', `Cost accumulation: ${err.message}`); }
1223
1216
  }
@@ -1358,15 +1351,15 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
1358
1351
  }
1359
1352
 
1360
1353
  if (!isSuccess && meta?.item?.id) {
1361
- const wiPath = resolveWiPath(meta);
1362
- if (wiPath) {
1363
- let finalStatus = null;
1364
- try {
1354
+ // Auto-retry with atomic read-modify-write (no TOCTOU race)
1355
+ try {
1356
+ const wiPath = resolveWiPath(meta);
1357
+ if (wiPath) {
1358
+ let retriesExhausted = false;
1365
1359
  mutateJsonFileLocked(wiPath, (items) => {
1366
1360
  if (!Array.isArray(items)) return items;
1367
1361
  const wi = items.find(i => i.id === meta.item.id);
1368
1362
  if (!wi) return items;
1369
-
1370
1363
  const retries = wi._retryCount || 0;
1371
1364
  if (retries < 3) {
1372
1365
  log('info', `Agent failed for ${meta.item.id} — auto-retry ${retries + 1}/3`);
@@ -1374,22 +1367,28 @@ function runPostCompletionHooks(dispatchItem, agentId, code, stdout, config) {
1374
1367
  wi.status = 'pending';
1375
1368
  delete wi.dispatched_at;
1376
1369
  delete wi.dispatched_to;
1377
- finalStatus = 'pending';
1370
+ if (type === 'decompose') delete wi._decomposing;
1378
1371
  } else {
1379
- wi.status = 'failed';
1380
- wi.failReason = 'Agent failed (3 retries exhausted)';
1381
- wi.failedAt = ts();
1382
- finalStatus = 'failed';
1372
+ retriesExhausted = true;
1383
1373
  }
1374
+ // Clear _decomposing flag on any decompose failure to prevent permanent stuck
1384
1375
  if (type === 'decompose') delete wi._decomposing;
1385
1376
  return items;
1386
- });
1387
- } catch (err) { log('warn', `Retry update: ${err.message}`); }
1388
- // Sync status to PRD outside the work-items lock
1389
- if (finalStatus) {
1390
- syncPrdItemStatus(meta.item.id, finalStatus, meta.item?.sourcePlan);
1377
+ }, { defaultValue: [] });
1378
+ if (retriesExhausted) {
1379
+ updateWorkItemStatus(meta, 'failed', 'Agent failed (3 retries exhausted)');
1380
+ }
1381
+ } else {
1382
+ // No wiPath — can't read retries, fall back to meta snapshot
1383
+ const retries = meta.item._retryCount || 0;
1384
+ if (retries < 3) {
1385
+ log('info', `Agent failed for ${meta.item.id} — auto-retry ${retries + 1}/3`);
1386
+ updateWorkItemStatus(meta, 'pending', '');
1387
+ } else {
1388
+ updateWorkItemStatus(meta, 'failed', 'Agent failed (3 retries exhausted)');
1389
+ }
1391
1390
  }
1392
- }
1391
+ } catch (err) { log('warn', `Retry/decompose update: ${err.message}`); }
1393
1392
  }
1394
1393
  // Meeting post-completion: collect findings/debate/conclusion
1395
1394
  if (type === 'meeting' && meta?.meetingId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.163",
3
+ "version": "0.1.164",
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"