@yemi33/minions 0.1.1643 → 0.1.1644
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 +6 -8
- package/dashboard/js/command-center.js +19 -0
- package/dashboard.js +105 -3
- package/engine/copilot-models.json +1 -1
- package/package.json +1 -1
- package/prompts/cc-system.md +10 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.1644 (2026-04-30)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
- harden action contracts so CC can't claim work it didn't do
|
|
7
|
+
|
|
8
|
+
## 0.1.1642 (2026-04-30)
|
|
4
9
|
|
|
5
10
|
### Features
|
|
6
|
-
- harden PR canonicalization
|
|
7
11
|
- fix PR comment auto-fix loop
|
|
8
12
|
|
|
9
|
-
### Fixes
|
|
10
|
-
- yemi33/minions#1918
|
|
11
|
-
- require explicit PR-created evidence
|
|
12
|
-
- steering messages lost when session is winding down
|
|
13
|
-
- surface missing PR branches
|
|
14
|
-
|
|
15
13
|
## 0.1.1641 (2026-04-30)
|
|
16
14
|
|
|
17
15
|
### Features
|
|
@@ -686,11 +686,30 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
|
|
|
686
686
|
if (evt.actions && evt.actions.length > 0) {
|
|
687
687
|
_tagServerExecuted(evt.actions, evt.actionResults);
|
|
688
688
|
for (var ai = 0; ai < evt.actions.length; ai++) { await ccExecuteAction(evt.actions[ai], activeTabId); }
|
|
689
|
+
// Surface per-action errors/warnings inline alongside the prose so the user can see
|
|
690
|
+
// exactly which actions failed or completed with caveats. Previously these only
|
|
691
|
+
// appeared as a small "executed" pill which gave no detail.
|
|
692
|
+
if (evt.actionResults && Array.isArray(evt.actionResults)) {
|
|
693
|
+
var failures = evt.actionResults.filter(function(r) { return r && r.error; });
|
|
694
|
+
var warnings = evt.actionResults.filter(function(r) { return r && r.warning; });
|
|
695
|
+
if (failures.length > 0) {
|
|
696
|
+
var failHtml = failures.map(function(r) { return '<li>' + escHtml(r.type || 'action') + ': ' + escHtml(r.error) + '</li>'; }).join('');
|
|
697
|
+
addMsg('system', '<div style="padding:6px 12px;font-size:11px;color:var(--red);background:var(--surface2);border-radius:6px;margin:4px 0">⚠️ ' + failures.length + ' action' + (failures.length > 1 ? 's' : '') + ' failed:<ul style="margin:4px 0 0 16px;padding:0">' + failHtml + '</ul></div>', false, activeTabId);
|
|
698
|
+
}
|
|
699
|
+
if (warnings.length > 0) {
|
|
700
|
+
var warnHtml = warnings.map(function(r) { return '<li>' + escHtml(r.type || 'action') + ': ' + escHtml(r.warning) + '</li>'; }).join('');
|
|
701
|
+
addMsg('system', '<div style="padding:6px 12px;font-size:11px;color:var(--orange);background:var(--surface2);border-radius:6px;margin:4px 0">ℹ️ ' + warnings.length + ' action' + (warnings.length > 1 ? '' : '') + ' completed with warnings:<ul style="margin:4px 0 0 16px;padding:0">' + warnHtml + '</ul></div>', false, activeTabId);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
689
704
|
} else if (evt.actionParseError) {
|
|
690
705
|
// Issue #1834: server saw ===ACTIONS=== but couldn't parse the JSON.
|
|
691
706
|
// Surface as an inline warning so the user knows actions were dropped
|
|
692
707
|
// (was previously silent — appeared as "actions failed" with no signal).
|
|
693
708
|
addMsg('system', '<div style="padding:6px 12px;font-size:11px;color:var(--red);background:var(--surface2);border-radius:6px;margin:4px 0">⚠️ Actions block emitted but JSON could not be parsed — no actions were executed. Resend or rephrase. (' + escHtml(String(evt.actionParseError).slice(0, 200)) + ')</div>', false, activeTabId);
|
|
709
|
+
} else if (evt.hallucinationWarning) {
|
|
710
|
+
// CC said it dispatched/queued/assigned something but emitted no actions block —
|
|
711
|
+
// surface the false-claim guard so the user knows nothing actually ran.
|
|
712
|
+
addMsg('system', '<div style="padding:6px 12px;font-size:11px;color:var(--orange);background:var(--surface2);border-radius:6px;margin:4px 0">⚠️ ' + escHtml(evt.hallucinationWarning) + '</div>', false, activeTabId);
|
|
694
713
|
}
|
|
695
714
|
} else if (evt.type === 'error') {
|
|
696
715
|
terminalEventSeen = true;
|
package/dashboard.js
CHANGED
|
@@ -1312,17 +1312,100 @@ function _parseWatchInterval(val) {
|
|
|
1312
1312
|
return Math.max(60000, Math.round(u === 's' ? n * 1000 : u === 'm' ? n * 60000 : n * 3600000));
|
|
1313
1313
|
}
|
|
1314
1314
|
|
|
1315
|
+
// Required-field validator for CC actions. Returns null when valid, an error string when not.
|
|
1316
|
+
// Centralises field-required checks so the model can't quietly emit a malformed action and have
|
|
1317
|
+
// the server silently fall back to placeholder values (e.g. "Untitled"). The handler invokes this
|
|
1318
|
+
// before `try` to avoid filling `results` with cryptic per-handler error messages.
|
|
1319
|
+
function _ccValidateAction(action) {
|
|
1320
|
+
if (!action || typeof action !== 'object' || !action.type) return 'action is missing required field: type';
|
|
1321
|
+
switch (action.type) {
|
|
1322
|
+
case 'dispatch': case 'fix': case 'implement': case 'explore': case 'review': case 'test':
|
|
1323
|
+
if (!action.title || typeof action.title !== 'string' || !action.title.trim()) return `${action.type} action missing required field: title`;
|
|
1324
|
+
return null;
|
|
1325
|
+
case 'build-and-test':
|
|
1326
|
+
if (!action.pr) return 'build-and-test action missing required field: pr';
|
|
1327
|
+
return null;
|
|
1328
|
+
case 'note':
|
|
1329
|
+
if (!action.title) return 'note action missing required field: title';
|
|
1330
|
+
if (!action.content && !action.description) return 'note action missing required field: content (or description)';
|
|
1331
|
+
return null;
|
|
1332
|
+
case 'knowledge':
|
|
1333
|
+
if (!action.title) return 'knowledge action missing required field: title';
|
|
1334
|
+
if (!action.content) return 'knowledge action missing required field: content';
|
|
1335
|
+
if (!action.category) return 'knowledge action missing required field: category';
|
|
1336
|
+
return null;
|
|
1337
|
+
case 'pin-to-pinned':
|
|
1338
|
+
if (!action.title || !action.content) return 'pin-to-pinned action missing title or content';
|
|
1339
|
+
return null;
|
|
1340
|
+
case 'plan':
|
|
1341
|
+
if (!action.title) return 'plan action missing required field: title';
|
|
1342
|
+
return null;
|
|
1343
|
+
default:
|
|
1344
|
+
return null; // unknown types fall through to existing handler / generic fallback
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
// Hallucination guard: detect prose like "I dispatched ..." when no ===ACTIONS=== block was emitted.
|
|
1349
|
+
// The regex is intentionally narrow — we only want affirmative claims about completed work, not
|
|
1350
|
+
// hypotheticals like "I would dispatch this" or "consider dispatching X".
|
|
1351
|
+
function _detectClaimedActionWithoutBlock(displayText, actions) {
|
|
1352
|
+
if (Array.isArray(actions) && actions.length > 0) return null; // there are actions, no false claim
|
|
1353
|
+
const triggers = /\b(dispatched|enqueued|queued|created (?:a |the )?work item|assigned (?:this |it )?(?:to|for)|spun up|kicked off|i'?ll dispatch|i (?:have )?(?:just )?dispatched)\b/i;
|
|
1354
|
+
if (!triggers.test(displayText || '')) return null;
|
|
1355
|
+
return 'CC described an action ("dispatched", "assigned", etc.) but no ===ACTIONS=== block was emitted. No work was actually queued. Resend or rephrase the request.';
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1315
1358
|
async function executeCCActions(actions) {
|
|
1316
1359
|
const results = [];
|
|
1317
1360
|
for (const action of actions) {
|
|
1361
|
+
const validationError = _ccValidateAction(action);
|
|
1362
|
+
if (validationError) {
|
|
1363
|
+
results.push({ type: action?.type || 'unknown', error: validationError });
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1318
1366
|
try {
|
|
1319
1367
|
switch (action.type) {
|
|
1320
1368
|
case 'dispatch': case 'fix': case 'implement': case 'explore': case 'review': case 'test': {
|
|
1321
1369
|
const workType = action.workType || (action.type !== 'dispatch' ? action.type : 'implement');
|
|
1322
1370
|
const id = 'W-' + shared.uid();
|
|
1323
1371
|
const project = action.project || '';
|
|
1324
|
-
|
|
1372
|
+
|
|
1373
|
+
// Strict project resolution. Silent fallback to PROJECTS[0] when the model named an unknown
|
|
1374
|
+
// project caused work items to land in the wrong repo. Now: unknown name → error; ambiguous
|
|
1375
|
+
// (multiple projects + no field) → error; single-project deployments fall through; zero
|
|
1376
|
+
// projects → root-level work-items.json (orchestration system standalone use).
|
|
1377
|
+
let targetProject = null;
|
|
1378
|
+
if (project) {
|
|
1379
|
+
targetProject = PROJECTS.find(p => p.name?.toLowerCase() === project.toLowerCase());
|
|
1380
|
+
if (!targetProject) {
|
|
1381
|
+
const known = PROJECTS.map(p => p.name).join(', ') || '(none configured)';
|
|
1382
|
+
results.push({ type: action.type, error: `Project "${project}" not found. Known projects: ${known}` });
|
|
1383
|
+
break;
|
|
1384
|
+
}
|
|
1385
|
+
} else if (PROJECTS.length > 1) {
|
|
1386
|
+
results.push({ type: action.type, error: `project field is required when ${PROJECTS.length} projects are configured: ${PROJECTS.map(p => p.name).join(', ')}` });
|
|
1387
|
+
break;
|
|
1388
|
+
} else if (PROJECTS.length === 1) {
|
|
1389
|
+
targetProject = PROJECTS[0];
|
|
1390
|
+
}
|
|
1391
|
+
// PROJECTS.length === 0 → targetProject stays null, falls back to root work-items.json (existing behavior).
|
|
1392
|
+
|
|
1325
1393
|
const wiPath = targetProject ? shared.projectWorkItemsPath(targetProject) : path.join(MINIONS_DIR, 'work-items.json');
|
|
1394
|
+
|
|
1395
|
+
// Promote `agent` (singular) → `agents` (array). Models emit either shape and the prior code
|
|
1396
|
+
// only read `action.agents`, silently dropping `agent: "lambert"` style hints.
|
|
1397
|
+
const agentHints = (() => {
|
|
1398
|
+
if (Array.isArray(action.agents) && action.agents.length > 0) return action.agents.map(String).filter(Boolean);
|
|
1399
|
+
if (typeof action.agent === 'string' && action.agent) return [action.agent];
|
|
1400
|
+
return [];
|
|
1401
|
+
})();
|
|
1402
|
+
const knownAgents = Object.keys(CONFIG.agents || {});
|
|
1403
|
+
const unknownAgent = agentHints.find(a => !knownAgents.includes(a));
|
|
1404
|
+
if (unknownAgent) {
|
|
1405
|
+
results.push({ type: action.type, error: `Unknown agent "${unknownAgent}". Configured agents: ${knownAgents.join(', ') || '(none)'}` });
|
|
1406
|
+
break;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1326
1409
|
// Issue #1772: CC review/explore/test are human-initiated one-offs.
|
|
1327
1410
|
// Mark oneShot so any discovered PR is tagged _contextOnly (skips eval loop).
|
|
1328
1411
|
const ccOneShotTypes = new Set(['review', 'explore', 'test']);
|
|
@@ -1330,16 +1413,28 @@ async function executeCCActions(actions) {
|
|
|
1330
1413
|
shared.mutateJsonFileLocked(wiPath, items => {
|
|
1331
1414
|
if (!Array.isArray(items)) items = [];
|
|
1332
1415
|
items.push({
|
|
1333
|
-
id, title: action.title
|
|
1416
|
+
id, title: action.title, type: workType,
|
|
1334
1417
|
priority: action.priority || 'medium', description: action.description || '',
|
|
1335
1418
|
status: WI_STATUS.PENDING, created: new Date().toISOString(),
|
|
1336
1419
|
createdBy: 'command-center', project,
|
|
1337
|
-
...(
|
|
1420
|
+
...(agentHints.length ? { preferred_agent: agentHints[0], agents: agentHints } : {}),
|
|
1338
1421
|
...(isOneShot ? { oneShot: true } : {}),
|
|
1339
1422
|
});
|
|
1340
1423
|
return items;
|
|
1341
1424
|
}, { defaultValue: [] });
|
|
1342
1425
|
results.push({ type: action.type, id, ok: true });
|
|
1426
|
+
|
|
1427
|
+
// Pre-flight routing check: warn the user if no agent is currently available so the new
|
|
1428
|
+
// item won't sit pending invisibly. Routing failure is non-fatal — the WI was created.
|
|
1429
|
+
try {
|
|
1430
|
+
const resolvedAgent = routing.resolveAgent(workType, CONFIG, { agentHints });
|
|
1431
|
+
if (!resolvedAgent) {
|
|
1432
|
+
const lastResult = results[results.length - 1];
|
|
1433
|
+
lastResult.warning = `Created ${id} but no agent is currently available to dispatch (routing returned no match for workType=${workType}${agentHints.length ? ', hints=' + agentHints.join(',') : ''}). Item will sit pending until an agent becomes available.`;
|
|
1434
|
+
}
|
|
1435
|
+
} catch (e) {
|
|
1436
|
+
shared.log('warn', `CC dispatch routing pre-flight: ${e.message}`);
|
|
1437
|
+
}
|
|
1343
1438
|
break;
|
|
1344
1439
|
}
|
|
1345
1440
|
case 'build-and-test': {
|
|
@@ -4769,6 +4864,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4769
4864
|
const { _actionParseError, ...parsedReply } = parsed;
|
|
4770
4865
|
const reply = { ...parsedReply, sessionId: ccSession.sessionId, newSession: !wasResume };
|
|
4771
4866
|
if (_actionParseError) reply.actionParseError = _actionParseError;
|
|
4867
|
+
const hallucinationWarning = _detectClaimedActionWithoutBlock(parsed.text, parsed.actions);
|
|
4868
|
+
if (hallucinationWarning) reply.hallucinationWarning = hallucinationWarning;
|
|
4772
4869
|
if (sessionReset) reply.sessionReset = true;
|
|
4773
4870
|
return jsonReply(res, 200, reply);
|
|
4774
4871
|
} finally {
|
|
@@ -5052,6 +5149,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
5052
5149
|
// Issue #1834: surface action JSON parse failures so the UI can warn
|
|
5053
5150
|
// instead of silently dropping. Client renders this as a small notice.
|
|
5054
5151
|
if (_actionParseError) donePayload.actionParseError = _actionParseError;
|
|
5152
|
+
const hallucinationWarning = _detectClaimedActionWithoutBlock(displayText, actions);
|
|
5153
|
+
if (hallucinationWarning) donePayload.hallucinationWarning = hallucinationWarning;
|
|
5055
5154
|
if (sessionReset) donePayload.sessionReset = true;
|
|
5056
5155
|
liveState.donePayload = donePayload;
|
|
5057
5156
|
if (liveState.writer) liveState.writer(donePayload);
|
|
@@ -6450,6 +6549,9 @@ module.exports = {
|
|
|
6450
6549
|
_linkPullRequestForTracking: linkPullRequestForTracking,
|
|
6451
6550
|
_resolveSkillReadPath,
|
|
6452
6551
|
DOC_CHAT_DOCUMENT_DELIMITER,
|
|
6552
|
+
_ccValidateAction,
|
|
6553
|
+
_detectClaimedActionWithoutBlock,
|
|
6554
|
+
executeCCActions,
|
|
6453
6555
|
};
|
|
6454
6556
|
|
|
6455
6557
|
// Start the HTTP server only when run directly (node dashboard.js).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1644",
|
|
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
|
@@ -72,8 +72,17 @@ I'll dispatch dallas to fix that bug.
|
|
|
72
72
|
|
|
73
73
|
**Generic fallback:** For any action not listed below, include `"endpoint": "/api/..."` and `"params": {...}` to call the API directly. Example: `{"type": "custom-op", "endpoint": "/api/some/endpoint", "params": {"key": "value"}}`.
|
|
74
74
|
|
|
75
|
+
**Required fields per action type — server rejects with an error if missing:**
|
|
76
|
+
|
|
77
|
+
- `dispatch` (and aliases: `fix`, `implement`, `explore`, `review`, `test`): `title` is REQUIRED. `description` recommended. `project` REQUIRED when multiple projects are configured (server returns the list of known names if you guess wrong). For agent hints emit either `agents: ["dallas"]` (array, preferred) or `agent: "dallas"` (string — auto-promoted server-side). Unknown agent names error.
|
|
78
|
+
- `build-and-test`: `pr` REQUIRED (number, ID, or URL).
|
|
79
|
+
- `note`: `title` and `content` (or `description`) REQUIRED.
|
|
80
|
+
- `knowledge`: `title`, `content`, and `category` REQUIRED. Valid categories: architecture, conventions, project-notes, build-reports, reviews.
|
|
81
|
+
|
|
82
|
+
If you describe an action in prose ("I'll dispatch this..."), you MUST emit a matching `===ACTIONS===` block. The server detects prose claims without action blocks and surfaces a warning to the user — i.e., your false claim becomes visible. Either dispatch or don't promise to.
|
|
83
|
+
|
|
75
84
|
Core action types:
|
|
76
|
-
- **dispatch**: title, workType, priority (low/medium/high), agents[] (optional), project, description
|
|
85
|
+
- **dispatch**: title (REQUIRED), workType, priority (low/medium/high), agents[] or agent (optional — both shapes accepted), project (REQUIRED when multi-project), description
|
|
77
86
|
workTypes: `explore` (research/report only, NO PR), `ask` (answer/report, NO PR), `implement` (new code, PR REQUIRED), `fix` (bug fix, PR REQUIRED), `review` (code review, NO PR), `test` (tests, PR if new), `verify` (merge/build/maintenance, NO PR)
|
|
78
87
|
If the user wants a design/architecture artifact committed through a PR, dispatch `implement` or `docs` rather than `explore`.
|
|
79
88
|
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. Use multi-agent arrays only when the user names multiple agents or asks for fan-out.
|