@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 +5 -0
- package/dashboard.js +76 -2
- package/engine/cli.js +6 -0
- package/engine/copilot-models.json +1 -1
- package/engine/shared.js +4 -0
- package/engine/spawn-agent.js +23 -46
- package/package.json +1 -1
- package/prompts/cc-system.md +1 -0
package/CHANGELOG.md
CHANGED
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
|
};
|
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,
|
package/engine/spawn-agent.js
CHANGED
|
@@ -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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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',
|
|
412
|
+
proc.on('close', (code, signal) => {
|
|
436
413
|
clearTimeout(startupTimer);
|
|
437
414
|
const exitCode = normalizeRuntimeExit(code, signal);
|
|
438
|
-
const sentinelResult =
|
|
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.
|
|
441
|
-
fs.appendFileSync(debugPath, `EXIT SENTINEL
|
|
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',
|
|
422
|
+
proc.on('error', (err) => {
|
|
446
423
|
fs.appendFileSync(debugPath, `ERROR: ${err.message}\n`);
|
|
447
|
-
|
|
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.
|
|
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"
|
package/prompts/cc-system.md
CHANGED
|
@@ -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.
|