@yemi33/minions 0.1.1816 → 0.1.1818

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.1818 (2026-05-09)
4
+
5
+ ### Fixes
6
+ - write [process-exit] sentinel directly to live-output.log instead of bouncing it through stdout (#2266)
7
+
3
8
  ## 0.1.1816 (2026-05-09)
4
9
 
5
10
  ### Features
package/dashboard.js CHANGED
@@ -2163,7 +2163,8 @@ function _ccFallbackMissingTargetError(intent) {
2163
2163
 
2164
2164
  function _actionsWithIntentFallback(actions, opts = {}) {
2165
2165
  const { message = '', intentMetadata = null, source = 'command-center', filePath = null, title: docTitle = null, answerText = '', toolUses = [] } = opts;
2166
- const existing = Array.isArray(actions) ? actions.map(normalizeCCAction) : [];
2166
+ const existing = (Array.isArray(actions) ? actions.map(normalizeCCAction) : [])
2167
+ .filter(action => !_ccShouldSuppressAnsweredAskDispatch(action, { message, source, answerText, toolUses }));
2167
2168
  if (_messageRequestsDirectHandling(message)) return existing.filter(a => normalizeCCAction(a)?.type !== 'dispatch');
2168
2169
  if (existing.some(a => normalizeCCAction(a)?.type === 'dispatch')) return existing;
2169
2170
  if (existing.length > 0) return existing;
@@ -2199,6 +2200,7 @@ function _actionsWithIntentFallback(actions, opts = {}) {
2199
2200
  ...common,
2200
2201
  };
2201
2202
  if (!hasTarget) action._intentFallbackError = _ccFallbackMissingTargetError(intent);
2203
+ if (_ccShouldSuppressAnsweredAskDispatch(action, { message, source, answerText, toolUses })) return existing;
2202
2204
  return [action];
2203
2205
  }
2204
2206
 
@@ -2286,6 +2288,18 @@ function parseCCActions(text) {
2286
2288
  }
2287
2289
 
2288
2290
  const DELEGATION_ACTION_TERMS = ['dispatch', 'delegate', 'assign', 'queue', 'enqueue'];
2291
+ const DELEGATION_REQUEST_PREFIX_TERMS = ['please', 'can', 'could', 'would', 'will', 'should', 'you', 'cc', 'minions', 'minion'];
2292
+ const DELEGATION_PERSON_ACTION_TERMS = ['ask', 'tell', 'have'];
2293
+ // Subset of delegation action terms that double as common nouns in status
2294
+ // questions (e.g. "queue status", "dispatch contents"). When followed by a
2295
+ // status-noun follow-on these are noun usage, not imperative delegation.
2296
+ const DELEGATION_AMBIGUOUS_NOUN_TERMS = new Set(['dispatch', 'queue']);
2297
+ const DELEGATION_NON_VERB_FOLLOW_ONS = new Set([
2298
+ 'status', 'contents', 'content', 'state', 'count', 'list', 'items', 'item',
2299
+ 'size', 'length', 'depth', 'health', 'info', 'order', 'queue', 'history',
2300
+ 'log', 'logs', 'summary', 'overview', 'report', 'is', 'was', 'are', 'were',
2301
+ 'has', 'have', 'looks', 'shows',
2302
+ ]);
2289
2303
  const DELEGATION_MINIONS_PHRASES = ['have minions', 'ask minions', 'tell minions', 'hand off', 'hand it off', 'hand this off', 'send to agent', 'send it to agent', 'send this to agent'];
2290
2304
  const MEDIUM_INVESTIGATION_TERMS = ['audit', 'investigate', 'research', 'explore', 'analyze', 'analyse'];
2291
2305
  const MEDIUM_INVESTIGATION_PHRASES = ['deep dive', 'root cause'];
@@ -2316,6 +2330,15 @@ const DIRECT_HANDLING_PHRASES = [
2316
2330
  'no dispatch', 'no delegate', 'no delegation', 'no work item',
2317
2331
  'without dispatching', 'without delegating', 'without creating work item',
2318
2332
  ];
2333
+ const ANSWERED_ASK_MIN_CHARS = 80;
2334
+ const ANSWERED_ASK_INCOMPLETE_PHRASES = [
2335
+ 'i will dispatch', 'ill dispatch', 'i will delegate', 'ill delegate',
2336
+ 'i will ask', 'ill ask', 'need to dispatch', 'needs to dispatch',
2337
+ 'need to delegate', 'needs to delegate', 'need an agent', 'needs an agent',
2338
+ 'hand off', 'hand this off', 'deeper investigation', 'further investigation',
2339
+ 'cannot answer', 'cant answer', 'could not answer', 'couldnt answer',
2340
+ 'not enough information',
2341
+ ];
2319
2342
 
2320
2343
  function _isIntentWordChar(ch) {
2321
2344
  const code = ch.charCodeAt(0);
@@ -2401,6 +2424,37 @@ function _messageHasDelegationIntent(message) {
2401
2424
  return _intentHasAnyPhrase(normalized, DELEGATION_MINIONS_PHRASES);
2402
2425
  }
2403
2426
 
2427
+ function _delegationTokenIsImperativeVerb(tokens, idx) {
2428
+ const term = tokens[idx];
2429
+ if (!term) return false;
2430
+ if (!DELEGATION_AMBIGUOUS_NOUN_TERMS.has(term)) return true;
2431
+ const next = tokens[idx + 1];
2432
+ if (!next) return true;
2433
+ return !DELEGATION_NON_VERB_FOLLOW_ONS.has(next);
2434
+ }
2435
+
2436
+ function _messageExplicitlyRequestsDelegation(message) {
2437
+ const normalized = _normalizeIntentText(message);
2438
+ const tokens = _intentTokens(normalized);
2439
+ if (!tokens.length) return false;
2440
+ if (_intentHasAnyPhrase(normalized, DELEGATION_MINIONS_PHRASES)) return true;
2441
+ if (_intentHasPhrase(normalized, 'work item') && _intentHasAnyToken(normalized, ['create', 'open', 'add'])) return true;
2442
+
2443
+ let i = 0;
2444
+ while (DELEGATION_REQUEST_PREFIX_TERMS.includes(tokens[i])) i++;
2445
+ if (DELEGATION_ACTION_TERMS.includes(tokens[i]) && _delegationTokenIsImperativeVerb(tokens, i)) return true;
2446
+ if (DELEGATION_PERSON_ACTION_TERMS.includes(tokens[i])) {
2447
+ return tokens.slice(i + 1, i + 7).includes('to');
2448
+ }
2449
+ for (let j = 0; j < tokens.length && j <= 6; j++) {
2450
+ if (!DELEGATION_ACTION_TERMS.includes(tokens[j])) continue;
2451
+ if (!_delegationTokenIsImperativeVerb(tokens, j)) continue;
2452
+ const prefix = tokens.slice(0, j);
2453
+ if (prefix.includes('you') || prefix.includes('minions') || prefix.includes('minion')) return true;
2454
+ }
2455
+ return false;
2456
+ }
2457
+
2404
2458
  function _messageRequestsDirectHandling(message) {
2405
2459
  const normalized = _normalizeIntentText(message);
2406
2460
  if (!normalized.trim()) return false;
@@ -2440,6 +2494,24 @@ function _inferDelegatedWorkType(message) {
2440
2494
  return 'ask';
2441
2495
  }
2442
2496
 
2497
+ function _ccAnswerLooksCompleteForAsk(answerText, toolUses = []) {
2498
+ if (!Array.isArray(toolUses) || toolUses.length === 0) return false;
2499
+ const answer = _collapseWhitespace(stripCCActionSyntax(answerText || ''));
2500
+ if (answer.length < ANSWERED_ASK_MIN_CHARS) return false;
2501
+ const normalized = _normalizeIntentText(answer);
2502
+ if (_intentHasAnyPhrase(normalized, ANSWERED_ASK_INCOMPLETE_PHRASES)) return false;
2503
+ if (_intentHasVerbObject(normalized, ['need', 'needs', 'require', 'requires'], ['agent', 'delegation', 'investigation', 'research'])) return false;
2504
+ return true;
2505
+ }
2506
+
2507
+ function _ccShouldSuppressAnsweredAskDispatch(action, opts = {}) {
2508
+ const normalized = normalizeCCAction(action);
2509
+ if (normalized?.type !== 'dispatch') return false;
2510
+ if (String(normalized.workType || '').trim().toLowerCase() !== 'ask') return false;
2511
+ if (_messageExplicitlyRequestsDelegation(opts.message)) return false;
2512
+ return _ccAnswerLooksCompleteForAsk(opts.answerText, opts.toolUses);
2513
+ }
2514
+
2443
2515
  function _collapseWhitespace(text) {
2444
2516
  let out = '';
2445
2517
  let lastWasSpace = true;
@@ -2532,11 +2604,13 @@ function _inferDelegationActionFromMessage(message, { source = 'command-center',
2532
2604
  }
2533
2605
 
2534
2606
  function _ensureDelegationForIntent(actions, opts = {}) {
2535
- const list = Array.isArray(actions) ? actions.map(normalizeCCAction) : [];
2607
+ const list = (Array.isArray(actions) ? actions.map(normalizeCCAction) : [])
2608
+ .filter(action => !_ccShouldSuppressAnsweredAskDispatch(action, opts));
2536
2609
  if (_messageRequestsDirectHandling(opts.message)) return list.filter(a => normalizeCCAction(a)?.type !== 'dispatch');
2537
2610
  if (list.some(a => normalizeCCAction(a)?.type === 'dispatch')) return list;
2538
2611
  if (list.length > 0) return list;
2539
2612
  const inferred = _inferDelegationActionFromMessage(opts.message, opts);
2613
+ if (_ccShouldSuppressAnsweredAskDispatch(inferred, opts)) return list;
2540
2614
  return inferred ? [...list, inferred] : list;
2541
2615
  }
2542
2616
 
package/engine/cli.js CHANGED
@@ -1531,4 +1531,10 @@ module.exports = {
1531
1531
  _controlBelongsToOwner: controlBelongsToOwner,
1532
1532
  _markControlStoppingForOwner: markControlStoppingForOwner,
1533
1533
  _markControlStoppedForOwner: markControlStoppedForOwner,
1534
+ _isPidAlive: isPidAlive,
1535
+ _summarizeActiveDispatchPids: summarizeActiveDispatchPids,
1536
+ _dispatchSafeId: dispatchSafeId,
1537
+ _readDispatchPid: readDispatchPid,
1538
+ _normalizeSessionBranch: normalizeSessionBranch,
1539
+ _dispatchSessionBranch: dispatchSessionBranch,
1534
1540
  };
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-05-09T15:27:55.877Z"
4
+ "cachedAt": "2026-05-09T15:59:07.682Z"
5
5
  }
package/engine/shared.js CHANGED
@@ -3451,6 +3451,10 @@ module.exports = {
3451
3451
  legacyProjectStatePath,
3452
3452
  ensureProjectStateFiles,
3453
3453
  sameResolvedPath,
3454
+ projectStateRecordKey, // exported for testing
3455
+ mergeProjectStateArrays, // exported for testing
3456
+ realPathForComparison, // exported for testing
3457
+ prPathComparisonCandidates, // exported for testing
3454
3458
  resolveProjectForPrPath, // exported for testing
3455
3459
  getPrLinks,
3456
3460
  addPrLink,
@@ -158,55 +158,32 @@ function injectAdoTokenEnvForRepoHost(env, opts) {
158
158
  return injectAdoTokenEnv(env, opts);
159
159
  }
160
160
 
161
- const PROCESS_EXIT_SENTINEL_FLUSH_TIMEOUT_MS = 2000;
162
-
163
161
  function formatProcessExitSentinel(exitCode, signal) {
164
162
  return `\n[process-exit] code=${exitCode}${signal ? ` signal=${signal}` : ''}\n`;
165
163
  }
166
164
 
167
- function _appendSentinelFallback(outputPath, sentinel) {
168
- if (!outputPath) return false;
169
- try {
170
- fs.appendFileSync(outputPath, sentinel);
171
- return true;
172
- } catch {
173
- return false;
174
- }
175
- }
176
-
177
- function _writeStdoutWithTimeout(stdout, sentinel, timeoutMs) {
178
- return new Promise((resolve) => {
179
- let settled = false;
180
- const finish = (flushed) => {
181
- if (settled) return;
182
- settled = true;
183
- clearTimeout(timer);
184
- resolve(flushed);
185
- };
186
- const timer = setTimeout(() => finish(false), Math.max(0, timeoutMs));
187
- try {
188
- if (!stdout || typeof stdout.write !== 'function') {
189
- finish(false);
190
- return;
191
- }
192
- stdout.write(sentinel, () => finish(true));
193
- } catch {
194
- finish(false);
195
- }
196
- });
197
- }
198
-
199
- async function writeProcessExitSentinel({
165
+ // The orphan reaper recovers an agent's exit code by scanning live-output.log for
166
+ // `[process-exit] code=N`. The previous design wrote the sentinel to stdout, hoping
167
+ // the engine's stdout consumer (engine.js) would copy it into the file — but when
168
+ // the engine was force-killed during a restart on Windows, the broken pipe
169
+ // silently swallowed the sentinel and the recovery never happened (see #2265 for
170
+ // the read-side recovery; this is the upstream fix). The orphan reaper only reads
171
+ // the file, so the simplest correct thing is to write the file directly. No
172
+ // stdout, no timeouts, no callbacks — one synchronous append.
173
+ function writeProcessExitSentinel({
200
174
  exitCode,
201
175
  signal = null,
202
- stdout = process.stdout,
203
176
  outputPath = process.env.MINIONS_LIVE_OUTPUT_PATH,
204
- timeoutMs = PROCESS_EXIT_SENTINEL_FLUSH_TIMEOUT_MS,
205
177
  } = {}) {
206
178
  const sentinel = formatProcessExitSentinel(exitCode, signal);
207
- const stdoutFlushed = await _writeStdoutWithTimeout(stdout, sentinel, timeoutMs);
208
- const outputPathWritten = stdoutFlushed ? false : _appendSentinelFallback(outputPath, sentinel);
209
- return { sentinel, stdoutFlushed, outputPathWritten };
179
+ let fileWritten = false;
180
+ if (outputPath) {
181
+ try {
182
+ fs.appendFileSync(outputPath, sentinel);
183
+ fileWritten = true;
184
+ } catch { /* file unavailable; orphan reaper falls back to runtime result event (#2265) */ }
185
+ }
186
+ return { sentinel, fileWritten };
210
187
  }
211
188
 
212
189
  function _appendOutputFallback(outputPath, chunk, prefix = '') {
@@ -432,19 +409,19 @@ function main() {
432
409
  }, MCP_STARTUP_TIMEOUT);
433
410
  proc.stdout.once('data', () => { gotFirstOutput = true; clearTimeout(startupTimer); });
434
411
 
435
- proc.on('close', async (code, signal) => {
412
+ proc.on('close', (code, signal) => {
436
413
  clearTimeout(startupTimer);
437
414
  const exitCode = normalizeRuntimeExit(code, signal);
438
- const sentinelResult = await writeProcessExitSentinel({ exitCode, signal });
415
+ const sentinelResult = writeProcessExitSentinel({ exitCode, signal });
439
416
  fs.appendFileSync(debugPath, `EXIT: code=${exitCode}${signal ? ` signal=${signal}` : ''}\nSTDERR: ${stderrBuf.slice(0, 500)}\n`);
440
- if (!sentinelResult.stdoutFlushed && sentinelResult.outputPathWritten) {
441
- fs.appendFileSync(debugPath, `EXIT SENTINEL FALLBACK: ${process.env.MINIONS_LIVE_OUTPUT_PATH}\n`);
417
+ if (!sentinelResult.fileWritten) {
418
+ fs.appendFileSync(debugPath, `EXIT SENTINEL: file write failed for ${process.env.MINIONS_LIVE_OUTPUT_PATH}\n`);
442
419
  }
443
420
  process.exit(exitCode);
444
421
  });
445
- proc.on('error', async (err) => {
422
+ proc.on('error', (err) => {
446
423
  fs.appendFileSync(debugPath, `ERROR: ${err.message}\n`);
447
- await writeProcessExitSentinel({ exitCode: 1 });
424
+ writeProcessExitSentinel({ exitCode: 1 });
448
425
  process.exit(1);
449
426
  });
450
427
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.1816",
3
+ "version": "0.1.1818",
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"
@@ -98,6 +98,7 @@ I'll dispatch dallas to fix that bug.
98
98
  Core action types:
99
99
  - **dispatch**: title (REQUIRED), workType, priority (low/medium/high), agents[] or agent (optional — both shapes accepted), project (REQUIRED when multi-project unless `pr` resolves to a tracked PR), description, pr (optional PR number/id/url for work that targets an existing PR), scope (`"fan-out"` only when the user explicitly asks to fan out to all agents). Do not emit `type:"fix"` or `type:"implement"`; emit `type:"dispatch"` with `workType:"fix"` or emit `type:"dispatch"` with `workType:"implement"`.
100
100
  workTypes: `explore` (research/report only, NO PR), `ask` (answer/report, NO PR), `implement` (new code, PR REQUIRED), `fix` (standalone bug fix creates a PR; include `pr` when fixing review comments/build failures on an existing PR), `review` (code review, NO PR), `test` (tests, PR if new), `verify` (merge/build/maintenance, NO PR)
101
+ Do not dispatch an `ask` work item for a question you already answered inline in the same turn, especially after using tools. If the answer needs deeper investigation, give a brief handoff note and dispatch; do not both provide a complete answer and queue an agent to repeat it.
101
102
  If the user wants a design/architecture artifact committed through a PR, dispatch `implement` or `docs` rather than `explore`.
102
103
  When the user names a specific agent ("assign this to lambert"), put exactly that one name in `agents` (e.g. `"agents": ["lambert"]`). A single-agent assignment is hard-pinned by the server — it will queue for that agent only and skip the routing table. If the user explicitly asks for fan-out/all agents, set `scope: "fan-out"`.
103
104
  After emitting a dispatch-like action, return immediately; do not poll, monitor, watch, wait, or check until completion, and do not add follow-up status actions. Only create a watch, monitor, poll, or periodically check when the human explicitly asks for monitoring, watching, periodic checks, or notification on completion.