@yemi33/minions 0.1.1802 → 0.1.1803

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.1803 (2026-05-08)
4
+
5
+ ### Other
6
+ - Guarantee Copilot steering delivery (#2225)
7
+
3
8
  ## 0.1.1800 (2026-05-08)
4
9
 
5
10
  ### Other
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-08T17:22:37.672Z"
4
+ "cachedAt": "2026-05-08T17:37:37.501Z"
5
5
  }
package/engine.js CHANGED
@@ -407,6 +407,50 @@ function mergePendingSteeringEntries(...groups) {
407
407
  return merged;
408
408
  }
409
409
 
410
+ function promoteCheckpointSteeringForClose(agentId, procInfo, runtime, liveOutputPath) {
411
+ if (!procInfo || procInfo._steeringMessage) return { status: 'none', entries: [] };
412
+ if (runtime?.capabilities?.midRunSessionId !== false) return { status: 'none', entries: [] };
413
+
414
+ const startedAtMs = Date.parse(procInfo.startedAt);
415
+ const runStartMs = Number.isFinite(startedAtMs) ? startedAtMs : 0;
416
+ const pendingPaths = new Set((procInfo._pendingSteeringFiles || []).map(entry => entry?.path || entry).filter(Boolean));
417
+ const deferredPaths = new Set((procInfo._deferredSteeringFiles || []).filter(Boolean));
418
+ const unread = steering.listUnreadSteeringMessages(agentId).filter(entry => entry.message.trim());
419
+ const entriesByPath = new Map(unread.map(entry => [entry.path, entry]));
420
+ const pendingDeferred = Array.from(deferredPaths)
421
+ .map(filePath => entriesByPath.get(filePath))
422
+ .filter(Boolean);
423
+ const lateCheckpoint = unread.filter(entry =>
424
+ entry.createdAtMs >= runStartMs
425
+ && !pendingPaths.has(entry.path)
426
+ && !deferredPaths.has(entry.path)
427
+ );
428
+ const checkpointEntries = mergePendingSteeringEntries(pendingDeferred, lateCheckpoint);
429
+ if (checkpointEntries.length === 0) {
430
+ delete procInfo._deferredSteeringFiles;
431
+ return { status: 'none', entries: [] };
432
+ }
433
+
434
+ if (!procInfo.sessionId) {
435
+ log('warn', `Steering: ${agentId} exited before a resumable sessionId was available - ${checkpointEntries.length} message(s) remain pending`);
436
+ try { fs.appendFileSync(liveOutputPath, `\n[steering-pending] Agent exited before a resumable session was available. Your message remains unread and will be retried on the next dispatch.\n`); } catch {}
437
+ return { status: 'pending', entries: checkpointEntries };
438
+ }
439
+
440
+ if (pendingDeferred.length > 0) {
441
+ log('info', `Steering: delivering ${pendingDeferred.length} deferred message(s) for ${agentId} at resumable checkpoint`);
442
+ }
443
+ if (lateCheckpoint.length > 0) {
444
+ log('info', `Steering: delivering ${lateCheckpoint.length} late checkpoint message(s) for ${agentId} at resumable checkpoint`);
445
+ }
446
+ procInfo._steeringMessage = checkpointEntries.map(entry => entry.message.trim()).join('\n\n');
447
+ procInfo._steeringSessionId = procInfo.sessionId;
448
+ procInfo._steeringEntry = checkpointEntries;
449
+ procInfo._steeringDeferredCheckpoint = true;
450
+ delete procInfo._deferredSteeringFiles;
451
+ return { status: 'promoted', entries: checkpointEntries };
452
+ }
453
+
410
454
  // Resolve dependency plan item IDs to their PR branches
411
455
  function resolveDependencyBranches(depIds, sourcePlan, project, config) {
412
456
  const results = []; // [{ branch, prId }]
@@ -1193,6 +1237,7 @@ async function spawnAgent(dispatchItem, config) {
1193
1237
  const MAX_OUTPUT = 1024 * 1024; // 1MB
1194
1238
  let stdout = '';
1195
1239
  let stderr = '';
1240
+ let steeringAckStdout = '';
1196
1241
  const sessionCaptureState = { sessionLineBuffer: '' };
1197
1242
  let _trustCheckDone = false;
1198
1243
  const _spawnTime = Date.now();
@@ -1207,6 +1252,7 @@ async function spawnAgent(dispatchItem, config) {
1207
1252
  const chunk = data.toString();
1208
1253
  realActivityMap.set(id, Date.now());
1209
1254
  if (stdout.length < MAX_OUTPUT) stdout += chunk.slice(0, MAX_OUTPUT - stdout.length);
1255
+ if (steeringAckStdout.length < MAX_OUTPUT) steeringAckStdout += chunk.slice(0, MAX_OUTPUT - steeringAckStdout.length);
1210
1256
  try { fs.appendFileSync(liveOutputPath, chunk); } catch { /* optional */ }
1211
1257
 
1212
1258
  // Trust gate detection: check first 30s of output for trust/permission prompts
@@ -1254,26 +1300,8 @@ async function spawnAgent(dispatchItem, config) {
1254
1300
  }
1255
1301
 
1256
1302
  const procInfo = activeProcesses.get(id);
1257
- ackPendingSteeringFiles(agentId, procInfo, stdout);
1258
-
1259
- if (procInfo?._deferredSteeringFiles?.length && procInfo.sessionId) {
1260
- const deferredPaths = new Set(procInfo._deferredSteeringFiles);
1261
- const pendingDeferred = steering.listUnreadSteeringMessages(agentId)
1262
- .filter(entry => deferredPaths.has(entry.path) && entry.message.trim());
1263
- if (pendingDeferred.length > 0) {
1264
- log('info', `Steering: delivering ${pendingDeferred.length} deferred message(s) for ${agentId} at resumable checkpoint`);
1265
- procInfo._steeringMessage = pendingDeferred.map(entry => entry.message.trim()).join('\n\n');
1266
- procInfo._steeringSessionId = procInfo.sessionId;
1267
- procInfo._steeringEntry = pendingDeferred;
1268
- procInfo._steeringDeferredCheckpoint = true;
1269
- delete procInfo._deferredSteeringFiles;
1270
- } else {
1271
- delete procInfo._deferredSteeringFiles;
1272
- }
1273
- } else if (procInfo?._deferredSteeringFiles?.length) {
1274
- log('warn', `Steering: ${agentId} exited before a resumable sessionId was available — message remains pending`);
1275
- try { fs.appendFileSync(liveOutputPath, `\n[steering-pending] Agent exited before a resumable session was available. Your message remains unread and will be retried on the next dispatch.\n`); } catch {}
1276
- }
1303
+ ackPendingSteeringFiles(agentId, procInfo, steeringAckStdout);
1304
+ promoteCheckpointSteeringForClose(agentId, procInfo, runtime, liveOutputPath);
1277
1305
 
1278
1306
  // Check if this was a steering kill — re-spawn with resume
1279
1307
  if (procInfo?._steeringMessage) {
@@ -1401,6 +1429,7 @@ async function spawnAgent(dispatchItem, config) {
1401
1429
  ),
1402
1430
  });
1403
1431
 
1432
+ steeringAckStdout = '';
1404
1433
  // Live steering kills discard partial old output. Deferred checkpoint
1405
1434
  // steering keeps the completed turn output so completion parsing still
1406
1435
  // sees the original work if the follow-up only acknowledges steering.
@@ -1414,6 +1443,7 @@ async function spawnAgent(dispatchItem, config) {
1414
1443
  const chunk = data.toString();
1415
1444
  realActivityMap.set(id, Date.now());
1416
1445
  if (stdout.length < MAX_OUTPUT) stdout += chunk.slice(0, MAX_OUTPUT - stdout.length);
1446
+ if (steeringAckStdout.length < MAX_OUTPUT) steeringAckStdout += chunk.slice(0, MAX_OUTPUT - steeringAckStdout.length);
1417
1447
  try { fs.appendFileSync(liveOutputPath, chunk); } catch { /* optional */ }
1418
1448
  const resumeInfo = activeProcesses.get(id);
1419
1449
  markRuntimeResumeOutputSeen(resumeInfo);
@@ -4719,6 +4749,7 @@ module.exports = {
4719
4749
  parseConflictFiles, pruneAncestorDeps, preflightMergeSimulation, // exported for testing
4720
4750
  isWorktreeRetryableError, removeStaleIndexLock, syncReusedWorktree, // exported for testing
4721
4751
  _maxTurnsForType, buildProjectContext, normalizeAc, _buildAgentSpawnFlags, _classifyAgentFailure, // exported for testing
4752
+ promoteCheckpointSteeringForClose, // exported for testing
4722
4753
  normalizePrBranch, resolvePrBranch, prCausePart, getPrCauseHead, getPrCauseBase, getPrAutomationCauseKey, getPrAutomationDispatchKey, // exported for testing
4723
4754
 
4724
4755
  // Playbooks
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1802",
3
+ "version": "0.1.1803",
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"