@yemi33/minions 0.1.1779 → 0.1.1780
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 -3
- package/dashboard/js/command-center.js +30 -9
- package/dashboard.js +326 -13
- package/docs/command-center.md +2 -1
- package/engine/copilot-models.json +1 -1
- package/package.json +1 -1
- package/prompts/cc-system.md +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.1780 (2026-05-07)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
- fix command center action parity (#2174)
|
|
7
|
+
|
|
8
|
+
## 0.1.1778 (2026-05-07)
|
|
4
9
|
|
|
5
10
|
### Features
|
|
6
|
-
- fix cc doc chat resume continuity (#2184)
|
|
7
|
-
- consolidate CC dispatch action type (#2183)
|
|
8
11
|
- harden dashboard state mutations (#2175)
|
|
9
12
|
|
|
10
13
|
## 0.1.1777 (2026-05-07)
|
|
@@ -980,8 +980,25 @@ function ccRetryLast(tabId, retryId) {
|
|
|
980
980
|
});
|
|
981
981
|
}
|
|
982
982
|
|
|
983
|
-
async function _ccFetch(url, body) {
|
|
984
|
-
|
|
983
|
+
async function _ccFetch(url, body, method) {
|
|
984
|
+
method = (method || 'POST').toUpperCase();
|
|
985
|
+
var fetchUrl = url;
|
|
986
|
+
var opts = { method: method, headers: { 'Content-Type': 'application/json' } };
|
|
987
|
+
if (method === 'GET') {
|
|
988
|
+
var qs = new URLSearchParams();
|
|
989
|
+
Object.entries(body || {}).forEach(function(entry) {
|
|
990
|
+
var key = entry[0], value = entry[1];
|
|
991
|
+
if (value === undefined || value === null) return;
|
|
992
|
+
if (Array.isArray(value)) value.forEach(function(v) { qs.append(key, String(v)); });
|
|
993
|
+
else if (typeof value === 'object') qs.append(key, JSON.stringify(value));
|
|
994
|
+
else qs.append(key, String(value));
|
|
995
|
+
});
|
|
996
|
+
var text = qs.toString();
|
|
997
|
+
if (text) fetchUrl += (fetchUrl.includes('?') ? '&' : '?') + text;
|
|
998
|
+
} else {
|
|
999
|
+
opts.body = JSON.stringify(body || {});
|
|
1000
|
+
}
|
|
1001
|
+
var res = await fetch(fetchUrl, opts);
|
|
985
1002
|
if (!res.ok) {
|
|
986
1003
|
var d = await res.json().catch(function() { return {}; });
|
|
987
1004
|
var err = new Error(d.error || 'Request failed (' + res.status + ')');
|
|
@@ -1068,22 +1085,25 @@ async function ccExecuteAction(action, targetTabId) {
|
|
|
1068
1085
|
if (notePageLink && !notePageLink.querySelector('.notif-badge')) { var noteCurPage = document.querySelector('.sidebar-link.active')?.getAttribute('data-page'); if (noteCurPage !== 'inbox') showNotifBadge(notePageLink); }
|
|
1069
1086
|
break;
|
|
1070
1087
|
}
|
|
1071
|
-
case 'pin':
|
|
1088
|
+
case 'pin':
|
|
1089
|
+
case 'pin-to-pinned': {
|
|
1072
1090
|
await _ccFetch('/api/pinned', { title: action.title, content: action.content || action.description, level: action.level || '' });
|
|
1073
1091
|
status.innerHTML = '📌 Pinned: <strong>' + escHtml(action.title) + '</strong> — visible to all agents';
|
|
1074
1092
|
status.style.color = 'var(--green)';
|
|
1075
1093
|
break;
|
|
1076
1094
|
}
|
|
1077
1095
|
case 'plan': {
|
|
1078
|
-
|
|
1096
|
+
var branchStrategy = action.branch_strategy || action.branchStrategy || 'parallel';
|
|
1097
|
+
await _ccFetch('/api/plan', { title: action.title, description: action.description, project: action.project, branch_strategy: branchStrategy, branchStrategy: branchStrategy });
|
|
1079
1098
|
status.innerHTML = '✓ Plan queued: <strong>' + escHtml(action.title) + '</strong>';
|
|
1080
1099
|
status.style.color = 'var(--green)';
|
|
1081
1100
|
wakeEngine();
|
|
1082
1101
|
break;
|
|
1083
1102
|
}
|
|
1084
1103
|
case 'cancel': {
|
|
1085
|
-
|
|
1086
|
-
|
|
1104
|
+
var cancelAgent = action.agent || action.agentId || '';
|
|
1105
|
+
await _ccFetch('/api/agents/cancel', { agent: cancelAgent, agentId: cancelAgent, task: action.task || action.cancelTask || '', reason: action.reason || 'Cancelled via command center' });
|
|
1106
|
+
status.innerHTML = '✓ Cancelled agent: <strong>' + escHtml(cancelAgent || action.task || action.cancelTask || '') + '</strong>';
|
|
1087
1107
|
status.style.color = 'var(--orange)';
|
|
1088
1108
|
break;
|
|
1089
1109
|
}
|
|
@@ -1475,8 +1495,9 @@ async function ccExecuteAction(action, targetTabId) {
|
|
|
1475
1495
|
break;
|
|
1476
1496
|
}
|
|
1477
1497
|
case 'add-project': {
|
|
1478
|
-
|
|
1479
|
-
|
|
1498
|
+
var projectPath = action.path || action.localPath;
|
|
1499
|
+
await _ccFetch('/api/projects/add', { path: projectPath, localPath: projectPath, name: action.name || '', repoHost: action.repoHost || 'github', allowNonRepo: action.allowNonRepo, confirmToken: action.confirmToken });
|
|
1500
|
+
status.innerHTML = '✓ Project added: <strong>' + escHtml(action.name || projectPath) + '</strong>';
|
|
1480
1501
|
status.style.color = 'var(--green)';
|
|
1481
1502
|
break;
|
|
1482
1503
|
}
|
|
@@ -1507,7 +1528,7 @@ async function ccExecuteAction(action, targetTabId) {
|
|
|
1507
1528
|
default: {
|
|
1508
1529
|
// Generic fallback: if action has an `endpoint` field, call it directly (local API only)
|
|
1509
1530
|
if (action.endpoint && action.endpoint.startsWith('/api/') && !action.endpoint.includes('..') && !/\%2e/i.test(action.endpoint)) {
|
|
1510
|
-
var genRes = await _ccFetch(action.endpoint, action.params || {});
|
|
1531
|
+
var genRes = await _ccFetch(action.endpoint, action.params || {}, action.method || 'POST');
|
|
1511
1532
|
var genData = await genRes.json().catch(function() { return {}; });
|
|
1512
1533
|
status.innerHTML = '✓ ' + escHtml(action.type) + ': ' + escHtml(genData.message || genData.id || 'done');
|
|
1513
1534
|
status.style.color = 'var(--green)';
|
package/dashboard.js
CHANGED
|
@@ -1449,6 +1449,13 @@ try {
|
|
|
1449
1449
|
let _preambleCache = null;
|
|
1450
1450
|
let _preambleCacheTs = 0;
|
|
1451
1451
|
const PREAMBLE_TTL = 30000; // 30s — longer TTL since preamble is lightweight orientation, not real-time data
|
|
1452
|
+
const CC_API_FALLBACK_TIMEOUT_MS = 15000;
|
|
1453
|
+
const CC_API_FALLBACK_METHODS = new Set(['GET', 'POST', 'DELETE']);
|
|
1454
|
+
const CC_API_FALLBACK_BLOCKED_PREFIXES = [
|
|
1455
|
+
'/api/command-center',
|
|
1456
|
+
'/api/doc-chat',
|
|
1457
|
+
'/api/bot',
|
|
1458
|
+
];
|
|
1452
1459
|
|
|
1453
1460
|
// SoT for CC's runtime API index. Captured lazily on the first HTTP request
|
|
1454
1461
|
// because ROUTES is closed over inside the request handler. Subsequent
|
|
@@ -1569,13 +1576,20 @@ function _routesAsMeta(routes) {
|
|
|
1569
1576
|
|
|
1570
1577
|
function _captureApiRoutesMeta(routes) {
|
|
1571
1578
|
if (_ccApiRoutesMeta || !Array.isArray(routes)) return;
|
|
1572
|
-
_ccApiRoutesMeta =
|
|
1579
|
+
_ccApiRoutesMeta = routes.map(r => ({
|
|
1580
|
+
..._routesAsMeta([r])[0],
|
|
1581
|
+
_pathRegex: r.path instanceof RegExp ? r.path : null,
|
|
1582
|
+
}));
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
function _resetCcApiRoutesMetaForTest() {
|
|
1586
|
+
_ccApiRoutesMeta = null;
|
|
1573
1587
|
}
|
|
1574
1588
|
|
|
1575
1589
|
function _formatCcApiRoutesIndex() {
|
|
1576
1590
|
if (!Array.isArray(_ccApiRoutesMeta) || _ccApiRoutesMeta.length === 0) return '';
|
|
1577
1591
|
return _ccApiRoutesMeta
|
|
1578
|
-
.filter(r => r.path.startsWith('/api/'))
|
|
1592
|
+
.filter(r => r.path.startsWith('/api/') || r.path.startsWith('/^\\/api'))
|
|
1579
1593
|
.map(r => {
|
|
1580
1594
|
const params = r.params ? ` — params: ${r.params}` : '';
|
|
1581
1595
|
const flags = [
|
|
@@ -1635,8 +1649,8 @@ ${apiIndex || '(routes not yet captured — first request still pending)'}
|
|
|
1635
1649
|
### CLI Index (auto-generated from engine/cli.js CLI_COMMAND_DOCS — single source of truth)
|
|
1636
1650
|
${cliIndex || '(unavailable)'}
|
|
1637
1651
|
|
|
1638
|
-
For
|
|
1639
|
-
\`{"type":"<descriptive>","endpoint":"/api/...","params":{...}}\`.` : '';
|
|
1652
|
+
For any safe local \`/api/...\` endpoint not covered by a named CC action, use the generic fallback:
|
|
1653
|
+
\`{"type":"<descriptive>","endpoint":"/api/...","method":"GET|POST|DELETE","params":{...}}\`.` : '';
|
|
1640
1654
|
|
|
1641
1655
|
const result = `### Agents
|
|
1642
1656
|
${agents}
|
|
@@ -2232,6 +2246,292 @@ function _ccValidateAction(action) {
|
|
|
2232
2246
|
}
|
|
2233
2247
|
}
|
|
2234
2248
|
|
|
2249
|
+
let _ccLocalApiInvokerForTest = null;
|
|
2250
|
+
|
|
2251
|
+
function _setCcLocalApiInvokerForTest(fn) {
|
|
2252
|
+
_ccLocalApiInvokerForTest = typeof fn === 'function' ? fn : null;
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
function _ccRouteMethodsForPath(pathname) {
|
|
2256
|
+
if (!Array.isArray(_ccApiRoutesMeta) || _ccApiRoutesMeta.length === 0) return null;
|
|
2257
|
+
const methods = new Set();
|
|
2258
|
+
for (const route of _ccApiRoutesMeta) {
|
|
2259
|
+
if (route._pathRegex instanceof RegExp) {
|
|
2260
|
+
route._pathRegex.lastIndex = 0;
|
|
2261
|
+
if (route._pathRegex.test(pathname)) methods.add(String(route.method || '').toUpperCase());
|
|
2262
|
+
} else if (route.path === pathname) {
|
|
2263
|
+
methods.add(String(route.method || '').toUpperCase());
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
return methods;
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
function _ccValidateLocalApiFallback(endpoint, method) {
|
|
2270
|
+
if (typeof endpoint !== 'string' || !endpoint.trim()) return 'generic API fallback requires endpoint';
|
|
2271
|
+
const raw = endpoint.trim();
|
|
2272
|
+
if (!(raw === '/api' || raw.startsWith('/api/'))) return 'generic API fallback endpoint must be a local /api/ path';
|
|
2273
|
+
if (/[\0\r\n\\]/.test(raw) || raw.includes('..') || /%2e/i.test(raw) || /%5c/i.test(raw)) {
|
|
2274
|
+
return 'generic API fallback endpoint is unsafe';
|
|
2275
|
+
}
|
|
2276
|
+
let parsed;
|
|
2277
|
+
try {
|
|
2278
|
+
parsed = new URL(raw, 'http://127.0.0.1');
|
|
2279
|
+
} catch {
|
|
2280
|
+
return 'generic API fallback endpoint is invalid';
|
|
2281
|
+
}
|
|
2282
|
+
if (parsed.origin !== 'http://127.0.0.1' || !(parsed.pathname === '/api' || parsed.pathname.startsWith('/api/'))) {
|
|
2283
|
+
return 'generic API fallback endpoint must be a local /api/ path';
|
|
2284
|
+
}
|
|
2285
|
+
if (CC_API_FALLBACK_BLOCKED_PREFIXES.some(prefix => parsed.pathname === prefix || parsed.pathname.startsWith(prefix + '/'))) {
|
|
2286
|
+
return 'generic API fallback cannot call Command Center, doc-chat, or bot endpoints';
|
|
2287
|
+
}
|
|
2288
|
+
if (/stream/i.test(parsed.pathname) || parsed.pathname === '/api/hot-reload') {
|
|
2289
|
+
return 'generic API fallback cannot call streaming endpoints';
|
|
2290
|
+
}
|
|
2291
|
+
const normalizedMethod = String(method || 'POST').toUpperCase();
|
|
2292
|
+
if (!CC_API_FALLBACK_METHODS.has(normalizedMethod)) {
|
|
2293
|
+
return `generic API fallback method ${normalizedMethod} is not allowed`;
|
|
2294
|
+
}
|
|
2295
|
+
const routeMethods = _ccRouteMethodsForPath(parsed.pathname);
|
|
2296
|
+
if (routeMethods && routeMethods.size > 0 && !routeMethods.has(normalizedMethod)) {
|
|
2297
|
+
return `API endpoint ${parsed.pathname} does not allow ${normalizedMethod}; allowed methods: ${[...routeMethods].join(', ')}`;
|
|
2298
|
+
}
|
|
2299
|
+
if (routeMethods && routeMethods.size === 0) {
|
|
2300
|
+
return `API endpoint ${parsed.pathname} is not in the local API index`;
|
|
2301
|
+
}
|
|
2302
|
+
return null;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
function _ccBuildQueryString(params) {
|
|
2306
|
+
if (!params || typeof params !== 'object' || Array.isArray(params)) return '';
|
|
2307
|
+
const search = new URLSearchParams();
|
|
2308
|
+
for (const [key, value] of Object.entries(params)) {
|
|
2309
|
+
if (value === undefined || value === null) continue;
|
|
2310
|
+
if (Array.isArray(value)) {
|
|
2311
|
+
for (const item of value) search.append(key, String(item));
|
|
2312
|
+
} else if (typeof value === 'object') {
|
|
2313
|
+
search.append(key, JSON.stringify(value));
|
|
2314
|
+
} else {
|
|
2315
|
+
search.append(key, String(value));
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
const text = search.toString();
|
|
2319
|
+
return text ? '?' + text : '';
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
function _ccRequestPath(endpoint, method, params) {
|
|
2323
|
+
const parsed = new URL(endpoint, 'http://127.0.0.1');
|
|
2324
|
+
if (method === 'GET') {
|
|
2325
|
+
const extra = _ccBuildQueryString(params);
|
|
2326
|
+
if (extra) {
|
|
2327
|
+
const glue = parsed.search ? '&' : '?';
|
|
2328
|
+
return parsed.pathname + parsed.search + glue + extra.slice(1);
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
return parsed.pathname + parsed.search;
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
async function _ccInvokeLocalApi({ method, endpoint, params }) {
|
|
2335
|
+
if (_ccLocalApiInvokerForTest) return _ccLocalApiInvokerForTest({ method, endpoint, params });
|
|
2336
|
+
const requestPath = _ccRequestPath(endpoint, method, params);
|
|
2337
|
+
return new Promise((resolve, reject) => {
|
|
2338
|
+
const body = method === 'GET' ? null : JSON.stringify(params || {});
|
|
2339
|
+
const req = http.request({
|
|
2340
|
+
hostname: '127.0.0.1',
|
|
2341
|
+
port: PORT,
|
|
2342
|
+
method,
|
|
2343
|
+
path: requestPath,
|
|
2344
|
+
timeout: CC_API_FALLBACK_TIMEOUT_MS,
|
|
2345
|
+
headers: body ? {
|
|
2346
|
+
'Content-Type': 'application/json',
|
|
2347
|
+
'Content-Length': Buffer.byteLength(body),
|
|
2348
|
+
} : {},
|
|
2349
|
+
}, res => {
|
|
2350
|
+
let text = '';
|
|
2351
|
+
res.setEncoding('utf8');
|
|
2352
|
+
res.on('data', chunk => { text += chunk; });
|
|
2353
|
+
res.on('end', () => {
|
|
2354
|
+
let data = text;
|
|
2355
|
+
try { data = text ? JSON.parse(text) : {}; } catch { /* non-JSON API response */ }
|
|
2356
|
+
resolve({ status: res.statusCode || 0, data });
|
|
2357
|
+
});
|
|
2358
|
+
});
|
|
2359
|
+
req.on('timeout', () => {
|
|
2360
|
+
req.destroy(new Error(`local API fallback timed out after ${CC_API_FALLBACK_TIMEOUT_MS}ms`));
|
|
2361
|
+
});
|
|
2362
|
+
req.on('error', reject);
|
|
2363
|
+
if (body) req.write(body);
|
|
2364
|
+
req.end();
|
|
2365
|
+
});
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
function _ccApiRequest(endpoint, params = {}, method = 'POST') {
|
|
2369
|
+
return { endpoint, params, method };
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
function _ccMappedApiRequests(action) {
|
|
2373
|
+
switch (action.type) {
|
|
2374
|
+
case 'pin':
|
|
2375
|
+
case 'pin-to-pinned':
|
|
2376
|
+
return _ccApiRequest('/api/pinned', { title: action.title, content: action.content || action.description, level: action.level || '' });
|
|
2377
|
+
case 'plan': {
|
|
2378
|
+
const branchStrategy = action.branch_strategy || action.branchStrategy || 'parallel';
|
|
2379
|
+
return _ccApiRequest('/api/plan', {
|
|
2380
|
+
title: action.title, description: action.description || '', priority: action.priority,
|
|
2381
|
+
project: action.project, agent: action.agent, branch_strategy: branchStrategy,
|
|
2382
|
+
});
|
|
2383
|
+
}
|
|
2384
|
+
case 'cancel':
|
|
2385
|
+
return _ccApiRequest('/api/agents/cancel', {
|
|
2386
|
+
agent: action.agent || action.agentId,
|
|
2387
|
+
task: action.task || action.cancelTask,
|
|
2388
|
+
reason: action.reason || 'Cancelled via command center',
|
|
2389
|
+
});
|
|
2390
|
+
case 'retry':
|
|
2391
|
+
return (action.ids || []).map(id => _ccApiRequest('/api/work-items/retry', { id, source: action.source || '' }));
|
|
2392
|
+
case 'pause-plan':
|
|
2393
|
+
return _ccApiRequest('/api/plans/pause', { file: action.file });
|
|
2394
|
+
case 'approve-plan':
|
|
2395
|
+
return _ccApiRequest('/api/plans/approve', { file: action.file });
|
|
2396
|
+
case 'reject-plan':
|
|
2397
|
+
return _ccApiRequest('/api/plans/reject', { file: action.file, reason: action.reason || '' });
|
|
2398
|
+
case 'archive-plan':
|
|
2399
|
+
return _ccApiRequest('/api/plans/archive', { file: action.file });
|
|
2400
|
+
case 'unarchive-plan':
|
|
2401
|
+
return _ccApiRequest('/api/plans/unarchive', { file: action.file });
|
|
2402
|
+
case 'execute-plan':
|
|
2403
|
+
return _ccApiRequest('/api/plans/execute', { file: action.file, project: action.project || '' });
|
|
2404
|
+
case 'trigger-verify':
|
|
2405
|
+
return _ccApiRequest('/api/plans/trigger-verify', { file: action.file });
|
|
2406
|
+
case 'regenerate-plan':
|
|
2407
|
+
return _ccApiRequest('/api/plans/approve', { file: action.file, forceRegen: true });
|
|
2408
|
+
case 'revise-plan':
|
|
2409
|
+
return _ccApiRequest('/api/plans/revise', { file: action.file, feedback: action.feedback || action.description, requestedBy: 'command-center' });
|
|
2410
|
+
case 'edit-prd-item':
|
|
2411
|
+
return _ccApiRequest('/api/prd-items/update', {
|
|
2412
|
+
source: action.source, itemId: action.itemId, name: action.name, description: action.description,
|
|
2413
|
+
priority: action.priority, estimated_complexity: action.estimated_complexity || action.complexity,
|
|
2414
|
+
});
|
|
2415
|
+
case 'remove-prd-item':
|
|
2416
|
+
return _ccApiRequest('/api/prd-items/remove', { source: action.source, itemId: action.itemId });
|
|
2417
|
+
case 'reopen-prd-item':
|
|
2418
|
+
return _ccApiRequest('/api/prd-items/update', { source: action.file, itemId: action.id, status: 'updated' });
|
|
2419
|
+
case 'delete-work-item':
|
|
2420
|
+
return _ccApiRequest('/api/work-items/delete', { id: action.id, source: action.source || '' });
|
|
2421
|
+
case 'cancel-work-item':
|
|
2422
|
+
return _ccApiRequest('/api/work-items/cancel', { id: action.id, source: action.source || '', reason: action.reason || 'cc' });
|
|
2423
|
+
case 'archive-work-item':
|
|
2424
|
+
return _ccApiRequest('/api/work-items/archive', { id: action.id });
|
|
2425
|
+
case 'work-item-feedback':
|
|
2426
|
+
return _ccApiRequest('/api/work-items/feedback', { id: action.id, rating: action.rating || 'up', comment: action.comment || '' });
|
|
2427
|
+
case 'schedule':
|
|
2428
|
+
return _ccApiRequest(action._update ? '/api/schedules/update' : '/api/schedules', {
|
|
2429
|
+
id: action.id, title: action.title, cron: action.cron, type: action.workType || 'implement',
|
|
2430
|
+
project: action.project, agent: action.agent, description: action.description,
|
|
2431
|
+
priority: action.priority, enabled: action.enabled !== false,
|
|
2432
|
+
});
|
|
2433
|
+
case 'delete-schedule':
|
|
2434
|
+
return _ccApiRequest('/api/schedules/delete', { id: action.id });
|
|
2435
|
+
case 'edit-pipeline':
|
|
2436
|
+
return _ccApiRequest('/api/pipelines/update', {
|
|
2437
|
+
id: action.id, title: action.title, stages: action.stages,
|
|
2438
|
+
trigger: action.trigger, enabled: action.enabled, stopWhen: action.stopWhen,
|
|
2439
|
+
monitoredResources: action.monitoredResources,
|
|
2440
|
+
});
|
|
2441
|
+
case 'delete-pipeline':
|
|
2442
|
+
return _ccApiRequest('/api/pipelines/delete', { id: action.id });
|
|
2443
|
+
case 'trigger-pipeline':
|
|
2444
|
+
return _ccApiRequest('/api/pipelines/trigger', { id: action.id });
|
|
2445
|
+
case 'continue-pipeline':
|
|
2446
|
+
return _ccApiRequest('/api/pipelines/continue', { id: action.id, stageId: action.stageId });
|
|
2447
|
+
case 'abort-pipeline':
|
|
2448
|
+
return _ccApiRequest('/api/pipelines/abort', { id: action.id });
|
|
2449
|
+
case 'retrigger-pipeline':
|
|
2450
|
+
return _ccApiRequest('/api/pipelines/retrigger', { id: action.id });
|
|
2451
|
+
case 'add-meeting-note':
|
|
2452
|
+
return _ccApiRequest('/api/meetings/note', { id: action.id, note: action.note || action.content });
|
|
2453
|
+
case 'advance-meeting':
|
|
2454
|
+
return _ccApiRequest('/api/meetings/advance', { id: action.id });
|
|
2455
|
+
case 'end-meeting':
|
|
2456
|
+
return _ccApiRequest('/api/meetings/end', { id: action.id });
|
|
2457
|
+
case 'archive-meeting':
|
|
2458
|
+
return _ccApiRequest('/api/meetings/archive', { id: action.id });
|
|
2459
|
+
case 'unarchive-meeting':
|
|
2460
|
+
return _ccApiRequest('/api/meetings/unarchive', { id: action.id });
|
|
2461
|
+
case 'delete-meeting':
|
|
2462
|
+
return _ccApiRequest('/api/meetings/delete', { id: action.id });
|
|
2463
|
+
case 'set-config':
|
|
2464
|
+
return _ccApiRequest('/api/settings', { engine: { [action.setting]: action.value } });
|
|
2465
|
+
case 'update-routing':
|
|
2466
|
+
return _ccApiRequest('/api/settings/routing', { content: action.content });
|
|
2467
|
+
case 'steer-agent':
|
|
2468
|
+
return _ccApiRequest('/api/agents/steer', { agent: action.agent, message: action.message || action.content });
|
|
2469
|
+
case 'link-pr':
|
|
2470
|
+
return _ccApiRequest('/api/pull-requests/link', { url: action.url, title: action.title || '', project: action.project || '', autoObserve: action.autoObserve !== false });
|
|
2471
|
+
case 'delete-pr':
|
|
2472
|
+
return _ccApiRequest('/api/pull-requests/delete', { id: action.id, project: action.project || '' });
|
|
2473
|
+
case 'file-bug':
|
|
2474
|
+
return _ccApiRequest('/api/issues/create', { title: action.title, description: action.description, labels: action.labels });
|
|
2475
|
+
case 'promote-to-kb':
|
|
2476
|
+
return _ccApiRequest('/api/inbox/promote-kb', { name: action.file, category: action.category || 'project-notes' });
|
|
2477
|
+
case 'kb-sweep':
|
|
2478
|
+
return _ccApiRequest('/api/knowledge/sweep', {});
|
|
2479
|
+
case 'toggle-kb-pin':
|
|
2480
|
+
return _ccApiRequest('/api/kb-pins/toggle', { key: action.key });
|
|
2481
|
+
case 'unpin':
|
|
2482
|
+
return _ccApiRequest('/api/pinned' + '/remove', { title: action.title });
|
|
2483
|
+
case 'add-project':
|
|
2484
|
+
return _ccApiRequest('/api/projects/add', {
|
|
2485
|
+
path: action.path || action.localPath, name: action.name || '',
|
|
2486
|
+
repoHost: action.repoHost || 'github', allowNonRepo: action.allowNonRepo,
|
|
2487
|
+
confirmToken: action.confirmToken,
|
|
2488
|
+
});
|
|
2489
|
+
case 'restart-engine':
|
|
2490
|
+
return _ccApiRequest('/api/engine/restart', {});
|
|
2491
|
+
case 'reset-settings':
|
|
2492
|
+
return _ccApiRequest('/api/settings/reset', {});
|
|
2493
|
+
default:
|
|
2494
|
+
if (action.endpoint) return _ccApiRequest(action.endpoint, action.params || {}, action.method || 'POST');
|
|
2495
|
+
return null;
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
async function _ccExecuteLocalApiAction(action) {
|
|
2500
|
+
const mapped = _ccMappedApiRequests(action);
|
|
2501
|
+
if (!mapped) return null;
|
|
2502
|
+
const requests = Array.isArray(mapped) ? mapped : [mapped];
|
|
2503
|
+
if (requests.length === 0) throw new Error(`${action.type} action has no API requests to execute`);
|
|
2504
|
+
const apiResults = [];
|
|
2505
|
+
for (const request of requests) {
|
|
2506
|
+
const method = String(request.method || 'POST').toUpperCase();
|
|
2507
|
+
const endpoint = String(request.endpoint || '').trim();
|
|
2508
|
+
const params = request.params || {};
|
|
2509
|
+
const validationError = _ccValidateLocalApiFallback(endpoint, method);
|
|
2510
|
+
if (validationError) throw new Error(validationError);
|
|
2511
|
+
const response = await _ccInvokeLocalApi({ method, endpoint, params });
|
|
2512
|
+
const status = Number(response?.status) || 0;
|
|
2513
|
+
const data = response?.data === undefined ? {} : response.data;
|
|
2514
|
+
if (status < 200 || status >= 300) {
|
|
2515
|
+
const detail = data && typeof data === 'object' && data.error ? data.error : `HTTP ${status}`;
|
|
2516
|
+
throw new Error(`${method} ${endpoint} failed: ${detail}`);
|
|
2517
|
+
}
|
|
2518
|
+
if (data && typeof data === 'object' && data.error) throw new Error(`${method} ${endpoint} failed: ${data.error}`);
|
|
2519
|
+
apiResults.push({ status, data, endpoint, method });
|
|
2520
|
+
}
|
|
2521
|
+
const firstData = apiResults[0]?.data && typeof apiResults[0].data === 'object' ? apiResults[0].data : {};
|
|
2522
|
+
return {
|
|
2523
|
+
type: action.type,
|
|
2524
|
+
ok: true,
|
|
2525
|
+
endpoint: apiResults[0]?.endpoint,
|
|
2526
|
+
method: apiResults[0]?.method,
|
|
2527
|
+
status: apiResults[0]?.status,
|
|
2528
|
+
...(firstData.id ? { id: firstData.id } : {}),
|
|
2529
|
+
...(firstData.file ? { file: firstData.file } : {}),
|
|
2530
|
+
...(firstData.message ? { message: firstData.message } : {}),
|
|
2531
|
+
...(apiResults.length > 1 ? { count: apiResults.length, results: apiResults.map(r => r.data) } : { data: firstData }),
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2235
2535
|
async function executeCCActions(actions) {
|
|
2236
2536
|
const results = [];
|
|
2237
2537
|
for (const rawAction of actions) {
|
|
@@ -2496,10 +2796,16 @@ async function executeCCActions(actions) {
|
|
|
2496
2796
|
results.push({ type: 'resume-watch', id: action.id, ok: !!resumed });
|
|
2497
2797
|
break;
|
|
2498
2798
|
}
|
|
2499
|
-
default:
|
|
2500
|
-
|
|
2501
|
-
|
|
2799
|
+
default: {
|
|
2800
|
+
const apiResult = await _ccExecuteLocalApiAction(action);
|
|
2801
|
+
if (apiResult) {
|
|
2802
|
+
results.push(apiResult);
|
|
2803
|
+
} else {
|
|
2804
|
+
// Server didn't handle — frontend must execute.
|
|
2805
|
+
results.push({ type: action.type });
|
|
2806
|
+
}
|
|
2502
2807
|
break;
|
|
2808
|
+
}
|
|
2503
2809
|
}
|
|
2504
2810
|
} catch (e) {
|
|
2505
2811
|
results.push({ type: action.type, error: e.message });
|
|
@@ -3059,7 +3365,7 @@ async function _retryDocChatAfterResumeFailure({ result, initialPass, freshSessi
|
|
|
3059
3365
|
function _buildDocChatErrorEnvelope(result) {
|
|
3060
3366
|
return {
|
|
3061
3367
|
code: result.code ?? null,
|
|
3062
|
-
stderr: (result.stderr || '').slice(-2048),
|
|
3368
|
+
stderr: String(result.stderr || '').slice(-2048),
|
|
3063
3369
|
errorClass: result.errorClass || null,
|
|
3064
3370
|
errorMessage: result.errorMessage || null,
|
|
3065
3371
|
runtime: result.runtime || null,
|
|
@@ -4173,7 +4479,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
4173
4479
|
id, title: body.title, type: 'plan',
|
|
4174
4480
|
priority: body.priority || 'high', description: body.description || '',
|
|
4175
4481
|
status: WI_STATUS.PENDING, created: new Date().toISOString(), createdBy: 'dashboard',
|
|
4176
|
-
branchStrategy: body.branch_strategy || 'parallel',
|
|
4482
|
+
branchStrategy: body.branch_strategy || body.branchStrategy || 'parallel',
|
|
4177
4483
|
};
|
|
4178
4484
|
if (body.project) item.project = body.project;
|
|
4179
4485
|
if (body.agent) item.agent = body.agent;
|
|
@@ -4376,14 +4682,17 @@ const server = http.createServer(async (req, res) => {
|
|
|
4376
4682
|
async function handleAgentsCancel(req, res) {
|
|
4377
4683
|
try {
|
|
4378
4684
|
const body = await readBody(req);
|
|
4685
|
+
const requestedAgent = body.agent || body.agentId;
|
|
4686
|
+
const requestedTask = body.task || body.cancelTask;
|
|
4687
|
+
if (!requestedAgent && !requestedTask) return jsonReply(res, 400, { error: 'agent or task required' });
|
|
4379
4688
|
const dispatchPath = path.join(MINIONS_DIR, 'engine', 'dispatch.json');
|
|
4380
4689
|
const dispatch = safeJsonObj(dispatchPath);
|
|
4381
4690
|
const active = dispatch.active || [];
|
|
4382
4691
|
const cancelled = [];
|
|
4383
4692
|
|
|
4384
4693
|
for (const d of active) {
|
|
4385
|
-
const matchAgent =
|
|
4386
|
-
const matchTask =
|
|
4694
|
+
const matchAgent = requestedAgent && d.agent === requestedAgent;
|
|
4695
|
+
const matchTask = requestedTask && (d.task || '').toLowerCase().includes(String(requestedTask).toLowerCase());
|
|
4387
4696
|
if (!matchAgent && !matchTask) continue;
|
|
4388
4697
|
|
|
4389
4698
|
// Kill agent process
|
|
@@ -7429,7 +7738,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7429
7738
|
{ method: 'POST', path: '/api/notes-save', desc: 'Save edited notes.md content', params: 'content, file?', handler: handleNotesSave },
|
|
7430
7739
|
|
|
7431
7740
|
// Plans
|
|
7432
|
-
{ method: 'POST', path: '/api/plan', desc: 'Create a plan work item that chains to PRD on completion', params: 'title, description?, priority?, project?, agent?, branch_strategy?', handler: handlePlanCreate },
|
|
7741
|
+
{ method: 'POST', path: '/api/plan', desc: 'Create a plan work item that chains to PRD on completion', params: 'title, description?, priority?, project?, agent?, branch_strategy? or branchStrategy?', handler: handlePlanCreate },
|
|
7433
7742
|
{ method: 'GET', path: '/api/plans', desc: 'List plan files (.md drafts + .json PRDs)', handler: handlePlansList },
|
|
7434
7743
|
{ method: 'POST', path: '/api/plans/trigger-verify', desc: 'Manually trigger verification for a completed plan', params: 'file', handler: handlePlansTriggerVerify },
|
|
7435
7744
|
{ method: 'POST', path: '/api/plans/approve', desc: 'Approve a plan for execution', params: 'file, approvedBy?', handler: handlePlansApprove },
|
|
@@ -7614,7 +7923,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7614
7923
|
inboxCount: steering.listUnreadSteeringMessages(agentId).length,
|
|
7615
7924
|
});
|
|
7616
7925
|
}},
|
|
7617
|
-
{ method: 'POST', path: '/api/agents/cancel', desc: 'Cancel an active agent by ID or task substring', params: 'agent?, task?', handler: handleAgentsCancel },
|
|
7926
|
+
{ method: 'POST', path: '/api/agents/cancel', desc: 'Cancel an active agent by ID or task substring', params: 'agent? or agentId?, task?', handler: handleAgentsCancel },
|
|
7618
7927
|
{ method: 'POST', path: /^\/api\/agent\/([\w-]+)\/kill$/, template: '/api/agent/:id/kill', desc: 'Kill a running agent: stop process, clear dispatch, reset work items to pending', handler: handleAgentKill },
|
|
7619
7928
|
{ method: 'GET', path: /^\/api\/agent\/([\w-]+)\/live-stream(?:\?.*)?$/, template: '/api/agent/:id/live-stream', desc: 'SSE real-time live output streaming', handler: handleAgentLiveStream },
|
|
7620
7929
|
{ method: 'GET', path: /^\/api\/agent\/([\w-]+)\/live(?:\?.*)?$/, template: '/api/agent/:id/live', desc: 'Tail live output for a working agent', params: 'tail? (bytes, default 8192)', handler: handleAgentLive },
|
|
@@ -8086,7 +8395,11 @@ module.exports = {
|
|
|
8086
8395
|
_resolveWorkItemsCreateTarget: resolveWorkItemsCreateTarget,
|
|
8087
8396
|
_collectArchivedWorkItems: collectArchivedWorkItems,
|
|
8088
8397
|
_createPipelineFromAction: createPipelineFromAction,
|
|
8398
|
+
_setCcLocalApiInvokerForTest,
|
|
8399
|
+
_resetCcApiRoutesMetaForTest,
|
|
8400
|
+
_ccValidateLocalApiFallback,
|
|
8089
8401
|
executeCCActions,
|
|
8402
|
+
executeDocChatActions,
|
|
8090
8403
|
buildCCStatePreamble,
|
|
8091
8404
|
_routesAsMeta,
|
|
8092
8405
|
_buildTranscriptCarryover,
|
package/docs/command-center.md
CHANGED
|
@@ -93,6 +93,8 @@ When you ask CC to *do* something, it includes structured action blocks in its r
|
|
|
93
93
|
| `remove-prd-item` | Remove a PRD item | "Remove P011 from the plan" |
|
|
94
94
|
| `delete-work-item` | Delete a work item | "Delete work item W025" |
|
|
95
95
|
|
|
96
|
+
For endpoints without a named action, CC may emit a local API fallback action with `endpoint`, `method`, and `params`. The server only invokes safe local `/api/...` paths, validates the requested method against the route index when available, sends GET params as query strings, and rejects streaming/recursive CC/doc-chat endpoints.
|
|
97
|
+
|
|
96
98
|
## Error Handling
|
|
97
99
|
|
|
98
100
|
- **Frontend timeout**: 10-minute `AbortSignal` on the fetch — prevents infinite "thinking" spinner
|
|
@@ -155,4 +157,3 @@ Frontend
|
|
|
155
157
|
## Command Bar
|
|
156
158
|
|
|
157
159
|
The command bar at the top of the dashboard routes all input to the CC panel. Typing in the command bar opens the CC drawer and sends the message as a CC turn.
|
|
158
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1780",
|
|
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
|
@@ -81,7 +81,7 @@ I'll dispatch dallas to fix that bug.
|
|
|
81
81
|
===ACTIONS===
|
|
82
82
|
[{"type": "dispatch", "title": "Fix login bug", "workType": "fix", "agents": ["dallas"], "project": "MyApp", "description": "..."}]
|
|
83
83
|
|
|
84
|
-
**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"}}`.
|
|
84
|
+
**Generic fallback:** For any action not listed below, include `"endpoint": "/api/..."`, `"method": "GET|POST|DELETE"`, and `"params": {...}` to call the API directly. Omit `method` only for POST endpoints. Example: `{"type": "custom-op", "endpoint": "/api/some/endpoint", "method": "POST", "params": {"key": "value"}}`.
|
|
85
85
|
|
|
86
86
|
**Required fields per action type — server rejects with an error if missing:**
|
|
87
87
|
|
|
@@ -143,7 +143,7 @@ Additional actions (all take `id` or `file` as primary key):
|
|
|
143
143
|
- KB/Inbox: promote-to-kb (file, category), kb-sweep, toggle-kb-pin (key)
|
|
144
144
|
- Plan lifecycle: revise-plan (file, feedback — dispatches agent to revise)
|
|
145
145
|
- Pipeline: continue-pipeline (id — resume past wait stage)
|
|
146
|
-
- Projects: add-project (localPath, name, repoHost)
|
|
146
|
+
- Projects: add-project (path or localPath, name, repoHost)
|
|
147
147
|
- Engine: restart-engine, reset-settings
|
|
148
148
|
- Other: unpin (title), link-pr (url, title, project, autoObserve), delete-pr (id, project), update-routing (content), file-bug (title, description, labels)
|
|
149
149
|
|
|
@@ -159,8 +159,8 @@ Terms like schedules, pipelines, agents, inbox, work items, plans, PRD, PRs, dis
|
|
|
159
159
|
## API & CLI Index (auto-injected)
|
|
160
160
|
Your state preamble (delivered alongside this prompt at session start) carries an auto-generated **API Index** rendered from `dashboard.js` `ROUTES` and a **CLI Index** rendered from `engine/cli.js` `CLI_COMMAND_DOCS`. Both are single-source-of-truth — adding a new HTTP endpoint or CLI command auto-surfaces it in your preamble; do not memorize the named action shorthand list above as exhaustive.
|
|
161
161
|
|
|
162
|
-
For
|
|
163
|
-
`{"type":"<short-descriptor>","endpoint":"/api/...","params":{...}}`
|
|
164
|
-
The action runner
|
|
162
|
+
For any safe local `/api/...` endpoint that doesn't have a matching named action above, emit the generic fallback shape:
|
|
163
|
+
`{"type":"<short-descriptor>","endpoint":"/api/...","method":"GET|POST|DELETE","params":{...}}`
|
|
164
|
+
The action runner enforces the endpoint method from the API index when available, sends GET params as query strings, sends POST/DELETE params as JSON, and rejects Command Center, doc-chat, bot, or streaming endpoints.
|
|
165
165
|
|
|
166
166
|
For CLI commands (`minions <cmd>`), use Bash to invoke them when delegating would be heavier than just running the command.
|