@yemi33/minions 0.1.1778 → 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 +5 -0
- package/dashboard/js/command-center.js +54 -15
- package/dashboard/js/modal-qa.js +41 -4
- package/dashboard.js +418 -36
- package/docs/command-center.md +2 -1
- package/engine/copilot-models.json +1 -1
- package/package.json +1 -1
- package/prompts/cc-system.md +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -103,15 +103,15 @@ function _ccActiveTab() {
|
|
|
103
103
|
|
|
104
104
|
// Build a plain-text transcript from a tab's stored messages — sent on every
|
|
105
105
|
// initial request so the server can carry it over if the session has to reset
|
|
106
|
-
// (runtime switch, system-prompt change)
|
|
107
|
-
//
|
|
106
|
+
// (runtime switch, system-prompt change) or if the previous turn has local
|
|
107
|
+
// action results the runtime session never saw.
|
|
108
108
|
var CC_TRANSCRIPT_MAX_TURNS = 20;
|
|
109
109
|
function _ccBuildTranscript(tab) {
|
|
110
110
|
if (!tab || !Array.isArray(tab.messages) || tab.messages.length === 0) return [];
|
|
111
111
|
var out = [];
|
|
112
112
|
for (var i = 0; i < tab.messages.length; i++) {
|
|
113
113
|
var m = tab.messages[i];
|
|
114
|
-
if (!m || (m.role !== 'user' && m.role !== 'assistant')) continue;
|
|
114
|
+
if (!m || (m.role !== 'user' && m.role !== 'assistant' && m.role !== 'action' && m.role !== 'system')) continue;
|
|
115
115
|
var html = typeof m.html === 'string' ? m.html : '';
|
|
116
116
|
var tmp = document.createElement('div');
|
|
117
117
|
tmp.innerHTML = html;
|
|
@@ -141,6 +141,23 @@ function _ccMergeStreamText(prev, incoming) {
|
|
|
141
141
|
return current + '\n\n' + next;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
var CC_DISPATCH_ACTION_ALIASES = ['fix', 'implement', 'explore', 'review', 'test'];
|
|
145
|
+
function _ccNormalizeDispatchAction(action) {
|
|
146
|
+
if (!action || typeof action !== 'object' || typeof action.type !== 'string') return action;
|
|
147
|
+
var type = action.type.trim().toLowerCase();
|
|
148
|
+
if (type === 'dispatch') {
|
|
149
|
+
if (action.type === 'dispatch') return action;
|
|
150
|
+
var dispatchAction = Object.assign({}, action);
|
|
151
|
+
dispatchAction.type = 'dispatch';
|
|
152
|
+
return dispatchAction;
|
|
153
|
+
}
|
|
154
|
+
if (CC_DISPATCH_ACTION_ALIASES.indexOf(type) < 0) return action;
|
|
155
|
+
var normalized = Object.assign({}, action);
|
|
156
|
+
normalized.type = 'dispatch';
|
|
157
|
+
if (!normalized.workType) normalized.workType = type;
|
|
158
|
+
return normalized;
|
|
159
|
+
}
|
|
160
|
+
|
|
144
161
|
async function _ccDashboardHealth() {
|
|
145
162
|
var controller = new AbortController();
|
|
146
163
|
var timer = setTimeout(function() { controller.abort(); }, 3000);
|
|
@@ -745,7 +762,7 @@ async function _ccDoSend(message, skipUserMsg, forceTabId) {
|
|
|
745
762
|
if (!isReconnect && res.status === 429 && (!activeTab._429retries || activeTab._429retries < 3)) {
|
|
746
763
|
activeTab._429retries = (activeTab._429retries || 0) + 1;
|
|
747
764
|
await new Promise(function(r) { setTimeout(r, 1500); });
|
|
748
|
-
return await _ccConsumeStream({ message: message, tabId: activeTabId, sessionId: activeTab.sessionId || null }, false);
|
|
765
|
+
return await _ccConsumeStream({ message: message, tabId: activeTabId, sessionId: activeTab.sessionId || null, transcript: _ccBuildTranscript(activeTab) }, false);
|
|
749
766
|
}
|
|
750
767
|
activeTab._429retries = 0;
|
|
751
768
|
var errText = await res.text();
|
|
@@ -963,8 +980,25 @@ function ccRetryLast(tabId, retryId) {
|
|
|
963
980
|
});
|
|
964
981
|
}
|
|
965
982
|
|
|
966
|
-
async function _ccFetch(url, body) {
|
|
967
|
-
|
|
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);
|
|
968
1002
|
if (!res.ok) {
|
|
969
1003
|
var d = await res.json().catch(function() { return {}; });
|
|
970
1004
|
var err = new Error(d.error || 'Request failed (' + res.status + ')');
|
|
@@ -994,6 +1028,7 @@ function _tagServerExecuted(actions, actionResults) {
|
|
|
994
1028
|
}
|
|
995
1029
|
|
|
996
1030
|
async function ccExecuteAction(action, targetTabId) {
|
|
1031
|
+
action = _ccNormalizeDispatchAction(action);
|
|
997
1032
|
var status = document.createElement('div');
|
|
998
1033
|
status.style.cssText = 'padding:4px 10px;border-radius:4px;font-size:10px;align-self:flex-start;border:1px dashed var(--border);color:var(--muted)';
|
|
999
1034
|
|
|
@@ -1010,7 +1045,7 @@ async function ccExecuteAction(action, targetTabId) {
|
|
|
1010
1045
|
status.style.color = action._serverDuplicate ? 'var(--orange)' : 'var(--green)';
|
|
1011
1046
|
}
|
|
1012
1047
|
ccAddMessage('action', status.outerHTML, false, targetTabId);
|
|
1013
|
-
if (['dispatch','
|
|
1048
|
+
if (['dispatch','create-meeting'].includes(action.type)) wakeEngine();
|
|
1014
1049
|
refresh();
|
|
1015
1050
|
return;
|
|
1016
1051
|
}
|
|
@@ -1023,7 +1058,7 @@ async function ccExecuteAction(action, targetTabId) {
|
|
|
1023
1058
|
case 'explore':
|
|
1024
1059
|
case 'review':
|
|
1025
1060
|
case 'test': {
|
|
1026
|
-
var workType = action.workType ||
|
|
1061
|
+
var workType = action.workType || 'implement';
|
|
1027
1062
|
// Forward both singular (`agent`) and plural (`agents`) hint shapes —
|
|
1028
1063
|
// the LLM emits either depending on phrasing ("assign to lambert" vs
|
|
1029
1064
|
// "dispatch to dallas, ralph"). The server-side handler promotes a
|
|
@@ -1050,22 +1085,25 @@ async function ccExecuteAction(action, targetTabId) {
|
|
|
1050
1085
|
if (notePageLink && !notePageLink.querySelector('.notif-badge')) { var noteCurPage = document.querySelector('.sidebar-link.active')?.getAttribute('data-page'); if (noteCurPage !== 'inbox') showNotifBadge(notePageLink); }
|
|
1051
1086
|
break;
|
|
1052
1087
|
}
|
|
1053
|
-
case 'pin':
|
|
1088
|
+
case 'pin':
|
|
1089
|
+
case 'pin-to-pinned': {
|
|
1054
1090
|
await _ccFetch('/api/pinned', { title: action.title, content: action.content || action.description, level: action.level || '' });
|
|
1055
1091
|
status.innerHTML = '📌 Pinned: <strong>' + escHtml(action.title) + '</strong> — visible to all agents';
|
|
1056
1092
|
status.style.color = 'var(--green)';
|
|
1057
1093
|
break;
|
|
1058
1094
|
}
|
|
1059
1095
|
case 'plan': {
|
|
1060
|
-
|
|
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 });
|
|
1061
1098
|
status.innerHTML = '✓ Plan queued: <strong>' + escHtml(action.title) + '</strong>';
|
|
1062
1099
|
status.style.color = 'var(--green)';
|
|
1063
1100
|
wakeEngine();
|
|
1064
1101
|
break;
|
|
1065
1102
|
}
|
|
1066
1103
|
case 'cancel': {
|
|
1067
|
-
|
|
1068
|
-
|
|
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>';
|
|
1069
1107
|
status.style.color = 'var(--orange)';
|
|
1070
1108
|
break;
|
|
1071
1109
|
}
|
|
@@ -1457,8 +1495,9 @@ async function ccExecuteAction(action, targetTabId) {
|
|
|
1457
1495
|
break;
|
|
1458
1496
|
}
|
|
1459
1497
|
case 'add-project': {
|
|
1460
|
-
|
|
1461
|
-
|
|
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>';
|
|
1462
1501
|
status.style.color = 'var(--green)';
|
|
1463
1502
|
break;
|
|
1464
1503
|
}
|
|
@@ -1489,7 +1528,7 @@ async function ccExecuteAction(action, targetTabId) {
|
|
|
1489
1528
|
default: {
|
|
1490
1529
|
// Generic fallback: if action has an `endpoint` field, call it directly (local API only)
|
|
1491
1530
|
if (action.endpoint && action.endpoint.startsWith('/api/') && !action.endpoint.includes('..') && !/\%2e/i.test(action.endpoint)) {
|
|
1492
|
-
var genRes = await _ccFetch(action.endpoint, action.params || {});
|
|
1531
|
+
var genRes = await _ccFetch(action.endpoint, action.params || {}, action.method || 'POST');
|
|
1493
1532
|
var genData = await genRes.json().catch(function() { return {}; });
|
|
1494
1533
|
status.innerHTML = '✓ ' + escHtml(action.type) + ': ' + escHtml(genData.message || genData.id || 'done');
|
|
1495
1534
|
status.style.color = 'var(--green)';
|
package/dashboard/js/modal-qa.js
CHANGED
|
@@ -28,6 +28,7 @@ let _qaAbortController = null;
|
|
|
28
28
|
let _qaQueue = []; // queued messages while processing
|
|
29
29
|
const QA_QUEUE_CAP = 10; // max queued messages
|
|
30
30
|
const QA_STREAM_STALL_MS = 6 * 60 * 1000; // allow the full doc-chat timeout before treating heartbeat-only streams as stalled
|
|
31
|
+
const QA_TRANSCRIPT_MAX_TURNS = 20;
|
|
31
32
|
let _qaSessionKey = ''; // key for current conversation (title or filePath)
|
|
32
33
|
|
|
33
34
|
const QA_STICKY_BOTTOM_PX = 80;
|
|
@@ -97,6 +98,40 @@ function _qaCloneQueue(queue) {
|
|
|
97
98
|
return Array.isArray(queue) ? queue.map(item => ({ ...item })) : [];
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
function _qaBuildTranscript(history, currentMessage) {
|
|
102
|
+
if (!Array.isArray(history) || history.length === 0) return [];
|
|
103
|
+
const current = typeof currentMessage === 'string' ? currentMessage.trim() : '';
|
|
104
|
+
const out = [];
|
|
105
|
+
for (let i = 0; i < history.length; i++) {
|
|
106
|
+
const m = history[i];
|
|
107
|
+
if (!m || (m.role !== 'user' && m.role !== 'assistant' && m.role !== 'action' && m.role !== 'system')) continue;
|
|
108
|
+
const text = typeof m.text === 'string' ? m.text.trim() : '';
|
|
109
|
+
if (!text) continue;
|
|
110
|
+
if (current && m.role === 'user' && text === current && i === history.length - 1) continue;
|
|
111
|
+
out.push({ role: m.role, text });
|
|
112
|
+
}
|
|
113
|
+
return out.slice(-QA_TRANSCRIPT_MAX_TURNS);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function _qaSummarizeActionContext(actions, actionResults) {
|
|
117
|
+
if (!Array.isArray(actions) || actions.length === 0) return '';
|
|
118
|
+
const lines = [];
|
|
119
|
+
for (let i = 0; i < actions.length; i++) {
|
|
120
|
+
const action = actions[i] || {};
|
|
121
|
+
const result = Array.isArray(actionResults) ? actionResults[i] : null;
|
|
122
|
+
const type = action.type || 'action';
|
|
123
|
+
const subject = result?.id || action.id || action.title || action.file || action.target || action.endpoint || '';
|
|
124
|
+
if (result?.error) {
|
|
125
|
+
lines.push(`${type}${subject ? ' ' + subject : ''} failed: ${result.error}`);
|
|
126
|
+
} else if (result?.ok || action._serverExecuted) {
|
|
127
|
+
lines.push(`${type}${subject ? ' ' + subject : ''} completed${result?.duplicate ? ' (duplicate)' : ''}${result?.warning ? ': ' + result.warning : ''}`);
|
|
128
|
+
} else {
|
|
129
|
+
lines.push(`${type}${subject ? ' ' + subject : ''} emitted`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return lines.join('\n');
|
|
133
|
+
}
|
|
134
|
+
|
|
100
135
|
function _qaGetRuntime(key) {
|
|
101
136
|
if (!key) return null;
|
|
102
137
|
let runtime = _qaRuntime.get(key);
|
|
@@ -609,6 +644,7 @@ async function _processQaMessage(message, selection, opts) {
|
|
|
609
644
|
filePath: capturedFilePath || null,
|
|
610
645
|
model: window._lastStatus?.autoMode?.ccModel || undefined,
|
|
611
646
|
contentHash: capturedDocContext.content ? (function(s) { const m = Math.floor(s.length / 2); return s.length + ':' + s.charCodeAt(0) + ':' + s.charCodeAt(m) + ':' + s.charCodeAt(s.length - 1); })(capturedDocContext.content) : undefined,
|
|
647
|
+
transcript: _qaBuildTranscript(runtime.history, message),
|
|
612
648
|
}),
|
|
613
649
|
});
|
|
614
650
|
let sessionDocContext = { ...capturedDocContext };
|
|
@@ -692,20 +728,21 @@ async function _processQaMessage(message, selection, opts) {
|
|
|
692
728
|
if (rawErrorHtml) _qaInsertBeforeQueued(tmp, rawErrorHtml);
|
|
693
729
|
});
|
|
694
730
|
|
|
695
|
-
runtime.history.push({ role: 'user', text: message });
|
|
696
|
-
runtime.history.push({ role: 'assistant', text: finalText || '' });
|
|
697
|
-
if (_qaIsActiveSession(sessionKey)) _qaHistory = runtime.history.slice();
|
|
698
|
-
|
|
699
731
|
_qaNotifySidebar(capturedFilePath);
|
|
732
|
+
runtime.history.push({ role: 'user', text: message });
|
|
733
|
+
runtime.history.push({ role: 'assistant', text: bodyText || finalText || '' });
|
|
700
734
|
if (evt.actions && evt.actions.length > 0) {
|
|
701
735
|
if (evt.actionResults && typeof _tagServerExecuted === 'function') _tagServerExecuted(evt.actions, evt.actionResults);
|
|
702
736
|
for (const action of evt.actions) await ccExecuteAction(action);
|
|
737
|
+
const actionContext = _qaSummarizeActionContext(evt.actions, evt.actionResults);
|
|
738
|
+
if (actionContext) runtime.history.push({ role: 'action', text: actionContext });
|
|
703
739
|
} else if (evt.actionParseError) {
|
|
704
740
|
const warning = '<div class="modal-qa-a" style="color:var(--red)">Actions block emitted but JSON could not be parsed — no actions were executed. Resend or rephrase. (' + escHtml(String(evt.actionParseError).slice(0, 200)) + ')</div>';
|
|
705
741
|
updatedThreadHtml = _qaMutateThreadHtml(sessionKey, tmp => {
|
|
706
742
|
_qaInsertBeforeQueued(tmp, warning);
|
|
707
743
|
});
|
|
708
744
|
}
|
|
745
|
+
if (_qaIsActiveSession(sessionKey)) _qaHistory = runtime.history.slice();
|
|
709
746
|
|
|
710
747
|
if (evt.edited && evt.content) {
|
|
711
748
|
const display = evt.content.replace(/^---[\s\S]*?---\n*/m, '');
|
package/dashboard.js
CHANGED
|
@@ -1366,10 +1366,36 @@ function _readCcTabSessions({ prune = true } = {}) {
|
|
|
1366
1366
|
|
|
1367
1367
|
const CC_CARRYOVER_MAX_TURNS = 20;
|
|
1368
1368
|
const CC_CARRYOVER_PER_MSG_CHARS = 2000;
|
|
1369
|
+
const CC_TRANSCRIPT_DIALOG_ROLES = new Set(['user', 'assistant']);
|
|
1370
|
+
const CC_TRANSCRIPT_CONTEXT_ROLES = new Set(['user', 'assistant', 'action', 'system']);
|
|
1369
1371
|
|
|
1370
|
-
function
|
|
1372
|
+
function _normalizeTranscriptRole(role) {
|
|
1373
|
+
const value = String(role || '').toLowerCase();
|
|
1374
|
+
return CC_TRANSCRIPT_CONTEXT_ROLES.has(value) ? value : null;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
function _transcriptHasCarryoverContext(transcript, { outOfBandOnly = false, currentMessage } = {}) {
|
|
1378
|
+
if (!Array.isArray(transcript)) return false;
|
|
1379
|
+
const current = typeof currentMessage === 'string' ? currentMessage.trim() : '';
|
|
1380
|
+
return transcript.some(m => {
|
|
1381
|
+
const role = _normalizeTranscriptRole(m?.role);
|
|
1382
|
+
if (!role || typeof m.text !== 'string' || !m.text.trim()) return false;
|
|
1383
|
+
if (outOfBandOnly && CC_TRANSCRIPT_DIALOG_ROLES.has(role)) return false;
|
|
1384
|
+
return !(current && role === 'user' && m.text.trim() === current);
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
function _buildTranscriptCarryover(transcript, { previousRuntime, currentMessage, outOfBandOnly = false } = {}) {
|
|
1371
1389
|
if (!Array.isArray(transcript) || transcript.length === 0) return '';
|
|
1372
|
-
let filtered = transcript
|
|
1390
|
+
let filtered = transcript
|
|
1391
|
+
.map(m => {
|
|
1392
|
+
const role = _normalizeTranscriptRole(m?.role);
|
|
1393
|
+
return role && typeof m?.text === 'string' && m.text.trim()
|
|
1394
|
+
? { role, text: m.text }
|
|
1395
|
+
: null;
|
|
1396
|
+
})
|
|
1397
|
+
.filter(Boolean);
|
|
1398
|
+
if (outOfBandOnly) filtered = filtered.filter(m => !CC_TRANSCRIPT_DIALOG_ROLES.has(m.role));
|
|
1373
1399
|
const current = typeof currentMessage === 'string' ? currentMessage.trim() : '';
|
|
1374
1400
|
if (current && filtered.length > 0) {
|
|
1375
1401
|
const last = filtered[filtered.length - 1];
|
|
@@ -1378,11 +1404,19 @@ function _buildTranscriptCarryover(transcript, { previousRuntime, currentMessage
|
|
|
1378
1404
|
if (filtered.length === 0) return '';
|
|
1379
1405
|
const recent = filtered.slice(-CC_CARRYOVER_MAX_TURNS);
|
|
1380
1406
|
const truncated = filtered.length > recent.length;
|
|
1381
|
-
const header =
|
|
1382
|
-
? `--- Previous
|
|
1383
|
-
:
|
|
1407
|
+
const header = outOfBandOnly
|
|
1408
|
+
? `--- Previous out-of-band UI/server events (carried over) ---`
|
|
1409
|
+
: previousRuntime
|
|
1410
|
+
? `--- Previous conversation (carried over from ${previousRuntime} session) ---`
|
|
1411
|
+
: `--- Previous conversation (carried over) ---`;
|
|
1384
1412
|
const lines = recent.map(m => {
|
|
1385
|
-
const who = m.role === 'user'
|
|
1413
|
+
const who = m.role === 'user'
|
|
1414
|
+
? 'User'
|
|
1415
|
+
: m.role === 'assistant'
|
|
1416
|
+
? 'Assistant'
|
|
1417
|
+
: m.role === 'action'
|
|
1418
|
+
? 'Action result'
|
|
1419
|
+
: 'System note';
|
|
1386
1420
|
let text = m.text.trim();
|
|
1387
1421
|
if (text.length > CC_CARRYOVER_PER_MSG_CHARS) text = text.slice(0, CC_CARRYOVER_PER_MSG_CHARS) + '… [truncated]';
|
|
1388
1422
|
return `${who}: ${text}`;
|
|
@@ -1415,6 +1449,13 @@ try {
|
|
|
1415
1449
|
let _preambleCache = null;
|
|
1416
1450
|
let _preambleCacheTs = 0;
|
|
1417
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
|
+
];
|
|
1418
1459
|
|
|
1419
1460
|
// SoT for CC's runtime API index. Captured lazily on the first HTTP request
|
|
1420
1461
|
// because ROUTES is closed over inside the request handler. Subsequent
|
|
@@ -1535,13 +1576,20 @@ function _routesAsMeta(routes) {
|
|
|
1535
1576
|
|
|
1536
1577
|
function _captureApiRoutesMeta(routes) {
|
|
1537
1578
|
if (_ccApiRoutesMeta || !Array.isArray(routes)) return;
|
|
1538
|
-
_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;
|
|
1539
1587
|
}
|
|
1540
1588
|
|
|
1541
1589
|
function _formatCcApiRoutesIndex() {
|
|
1542
1590
|
if (!Array.isArray(_ccApiRoutesMeta) || _ccApiRoutesMeta.length === 0) return '';
|
|
1543
1591
|
return _ccApiRoutesMeta
|
|
1544
|
-
.filter(r => r.path.startsWith('/api/'))
|
|
1592
|
+
.filter(r => r.path.startsWith('/api/') || r.path.startsWith('/^\\/api'))
|
|
1545
1593
|
.map(r => {
|
|
1546
1594
|
const params = r.params ? ` — params: ${r.params}` : '';
|
|
1547
1595
|
const flags = [
|
|
@@ -1601,8 +1649,8 @@ ${apiIndex || '(routes not yet captured — first request still pending)'}
|
|
|
1601
1649
|
### CLI Index (auto-generated from engine/cli.js CLI_COMMAND_DOCS — single source of truth)
|
|
1602
1650
|
${cliIndex || '(unavailable)'}
|
|
1603
1651
|
|
|
1604
|
-
For
|
|
1605
|
-
\`{"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":{...}}\`.` : '';
|
|
1606
1654
|
|
|
1607
1655
|
const result = `### Agents
|
|
1608
1656
|
${agents}
|
|
@@ -1751,6 +1799,19 @@ function _extractActionsJson(segment) {
|
|
|
1751
1799
|
return null;
|
|
1752
1800
|
}
|
|
1753
1801
|
|
|
1802
|
+
const CC_DISPATCH_ACTION_ALIASES = new Set(['fix', 'implement', 'explore', 'review', 'test']);
|
|
1803
|
+
|
|
1804
|
+
function normalizeCCAction(action) {
|
|
1805
|
+
if (!action || typeof action !== 'object') return action;
|
|
1806
|
+
if (typeof action.type !== 'string') return action;
|
|
1807
|
+
const type = action.type.trim().toLowerCase();
|
|
1808
|
+
if (type === 'dispatch') {
|
|
1809
|
+
return action.type === 'dispatch' ? action : { ...action, type: 'dispatch' };
|
|
1810
|
+
}
|
|
1811
|
+
if (!CC_DISPATCH_ACTION_ALIASES.has(type)) return action;
|
|
1812
|
+
return { ...action, type: 'dispatch', workType: action.workType || type };
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1754
1815
|
function parseCCActions(text) {
|
|
1755
1816
|
let actions = [];
|
|
1756
1817
|
let displayText = stripCCActionsForDisplay(text);
|
|
@@ -1791,6 +1852,7 @@ function parseCCActions(text) {
|
|
|
1791
1852
|
parseError = null; // legacy fallback recovered actions
|
|
1792
1853
|
}
|
|
1793
1854
|
}
|
|
1855
|
+
actions = actions.map(normalizeCCAction);
|
|
1794
1856
|
const result = { text: displayText, actions };
|
|
1795
1857
|
if (parseError && actions.length === 0) {
|
|
1796
1858
|
result._actionParseError = parseError;
|
|
@@ -2184,9 +2246,296 @@ function _ccValidateAction(action) {
|
|
|
2184
2246
|
}
|
|
2185
2247
|
}
|
|
2186
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
|
+
|
|
2187
2535
|
async function executeCCActions(actions) {
|
|
2188
2536
|
const results = [];
|
|
2189
|
-
for (const
|
|
2537
|
+
for (const rawAction of actions) {
|
|
2538
|
+
const action = normalizeCCAction(rawAction);
|
|
2190
2539
|
const validationError = _ccValidateAction(action);
|
|
2191
2540
|
if (validationError) {
|
|
2192
2541
|
results.push({ type: action?.type || 'unknown', error: validationError });
|
|
@@ -2195,7 +2544,7 @@ async function executeCCActions(actions) {
|
|
|
2195
2544
|
try {
|
|
2196
2545
|
switch (action.type) {
|
|
2197
2546
|
case 'dispatch': case 'fix': case 'implement': case 'explore': case 'review': case 'test': {
|
|
2198
|
-
const workType = routing.normalizeWorkType(action.workType ||
|
|
2547
|
+
const workType = routing.normalizeWorkType(action.workType || WORK_TYPE.IMPLEMENT, WORK_TYPE.IMPLEMENT);
|
|
2199
2548
|
const id = 'W-' + shared.uid();
|
|
2200
2549
|
const project = action.project || '';
|
|
2201
2550
|
const prRef = getWorkItemPrRef(action);
|
|
@@ -2447,10 +2796,16 @@ async function executeCCActions(actions) {
|
|
|
2447
2796
|
results.push({ type: 'resume-watch', id: action.id, ok: !!resumed });
|
|
2448
2797
|
break;
|
|
2449
2798
|
}
|
|
2450
|
-
default:
|
|
2451
|
-
|
|
2452
|
-
|
|
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
|
+
}
|
|
2453
2807
|
break;
|
|
2808
|
+
}
|
|
2454
2809
|
}
|
|
2455
2810
|
} catch (e) {
|
|
2456
2811
|
results.push({ type: action.type, error: e.message });
|
|
@@ -2661,12 +3016,14 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
|
|
|
2661
3016
|
const existing = resolveSession(store, sessionKey);
|
|
2662
3017
|
let sessionId = existing ? existing.sessionId : null;
|
|
2663
3018
|
const resumeNeedsCarryover = !!sessionId && _ccRuntimeNeedsResumeCarryover(shared.resolveCcCli(CONFIG.engine));
|
|
3019
|
+
const resumeHasOutOfBandCarryover = !!sessionId && _transcriptHasCarryoverContext(transcript, { outOfBandOnly: true, currentMessage: message });
|
|
3020
|
+
const freshNeedsCarryover = _transcriptHasCarryoverContext(transcript, { currentMessage: message });
|
|
2664
3021
|
|
|
2665
|
-
function buildPrompt({ includePreamble = true, includeCarryover = false } = {}) {
|
|
3022
|
+
function buildPrompt({ includePreamble = true, includeCarryover = false, outOfBandOnly = false } = {}) {
|
|
2666
3023
|
const parts = (!skipStatePreamble && includePreamble) ? [`## Current Minions State (${new Date().toISOString().slice(0, 16)})\n\n${buildCCStatePreamble()}`] : [];
|
|
2667
3024
|
if (extraContext) parts.push(extraContext);
|
|
2668
3025
|
if (includeCarryover) {
|
|
2669
|
-
const carryover = _buildTranscriptCarryover(transcript, { currentMessage: message });
|
|
3026
|
+
const carryover = _buildTranscriptCarryover(transcript, { currentMessage: message, outOfBandOnly });
|
|
2670
3027
|
if (carryover) parts.push(carryover);
|
|
2671
3028
|
}
|
|
2672
3029
|
parts.push(message);
|
|
@@ -2677,7 +3034,11 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
|
|
|
2677
3034
|
|
|
2678
3035
|
// Attempt 1: resume existing session — skip preamble (session already has context)
|
|
2679
3036
|
if (sessionId && maxTurns > 1) {
|
|
2680
|
-
const p1 = llm.callLLM(buildPrompt({
|
|
3037
|
+
const p1 = llm.callLLM(buildPrompt({
|
|
3038
|
+
includePreamble: false,
|
|
3039
|
+
includeCarryover: resumeNeedsCarryover || resumeHasOutOfBandCarryover,
|
|
3040
|
+
outOfBandOnly: !resumeNeedsCarryover,
|
|
3041
|
+
}), '', {
|
|
2681
3042
|
timeout, label, model, maxTurns, allowedTools, sessionId, effort: ccEffort, direct: true,
|
|
2682
3043
|
engineConfig: CONFIG.engine,
|
|
2683
3044
|
});
|
|
@@ -2714,7 +3075,7 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
|
|
|
2714
3075
|
}
|
|
2715
3076
|
|
|
2716
3077
|
// Attempt 2: fresh session (include preamble for full context)
|
|
2717
|
-
const freshPrompt = buildPrompt({ includeCarryover:
|
|
3078
|
+
const freshPrompt = buildPrompt({ includeCarryover: freshNeedsCarryover });
|
|
2718
3079
|
const p2 = llm.callLLM(freshPrompt, systemPrompt, {
|
|
2719
3080
|
timeout, label, model, maxTurns, allowedTools, effort: ccEffort, direct: true,
|
|
2720
3081
|
engineConfig: CONFIG.engine,
|
|
@@ -2762,12 +3123,14 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
|
|
|
2762
3123
|
const existing = resolveSession(store, sessionKey);
|
|
2763
3124
|
let sessionId = existing ? existing.sessionId : null;
|
|
2764
3125
|
const resumeNeedsCarryover = !!sessionId && _ccRuntimeNeedsResumeCarryover(shared.resolveCcCli(CONFIG.engine));
|
|
3126
|
+
const resumeHasOutOfBandCarryover = !!sessionId && _transcriptHasCarryoverContext(transcript, { outOfBandOnly: true, currentMessage: message });
|
|
3127
|
+
const freshNeedsCarryover = _transcriptHasCarryoverContext(transcript, { currentMessage: message });
|
|
2765
3128
|
|
|
2766
|
-
function buildPrompt({ includePreamble = true, includeCarryover = false } = {}) {
|
|
3129
|
+
function buildPrompt({ includePreamble = true, includeCarryover = false, outOfBandOnly = false } = {}) {
|
|
2767
3130
|
const parts = (!skipStatePreamble && includePreamble) ? [`## Current Minions State (${new Date().toISOString().slice(0, 16)})\n\n${buildCCStatePreamble()}`] : [];
|
|
2768
3131
|
if (extraContext) parts.push(extraContext);
|
|
2769
3132
|
if (includeCarryover) {
|
|
2770
|
-
const carryover = _buildTranscriptCarryover(transcript, { currentMessage: message });
|
|
3133
|
+
const carryover = _buildTranscriptCarryover(transcript, { currentMessage: message, outOfBandOnly });
|
|
2771
3134
|
if (carryover) parts.push(carryover);
|
|
2772
3135
|
}
|
|
2773
3136
|
parts.push(message);
|
|
@@ -2777,7 +3140,11 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
|
|
|
2777
3140
|
let result;
|
|
2778
3141
|
|
|
2779
3142
|
if (sessionId && maxTurns > 1) {
|
|
2780
|
-
const p1 = llm.callLLMStreaming(buildPrompt({
|
|
3143
|
+
const p1 = llm.callLLMStreaming(buildPrompt({
|
|
3144
|
+
includePreamble: false,
|
|
3145
|
+
includeCarryover: resumeNeedsCarryover || resumeHasOutOfBandCarryover,
|
|
3146
|
+
outOfBandOnly: !resumeNeedsCarryover,
|
|
3147
|
+
}), '', {
|
|
2781
3148
|
timeout, label, model, maxTurns, allowedTools, sessionId, effort: ccEffort, direct: true,
|
|
2782
3149
|
engineConfig: CONFIG.engine,
|
|
2783
3150
|
onChunk,
|
|
@@ -2814,7 +3181,7 @@ async function ccCallStreaming(message, { store = 'cc', sessionKey, extraContext
|
|
|
2814
3181
|
}
|
|
2815
3182
|
|
|
2816
3183
|
if (onRetry) onRetry(2);
|
|
2817
|
-
const freshPrompt = buildPrompt({ includeCarryover:
|
|
3184
|
+
const freshPrompt = buildPrompt({ includeCarryover: freshNeedsCarryover });
|
|
2818
3185
|
const p2 = llm.callLLMStreaming(freshPrompt, systemPrompt, {
|
|
2819
3186
|
timeout, label, model, maxTurns, allowedTools, effort: ccEffort, direct: true,
|
|
2820
3187
|
engineConfig: CONFIG.engine,
|
|
@@ -2998,7 +3365,7 @@ async function _retryDocChatAfterResumeFailure({ result, initialPass, freshSessi
|
|
|
2998
3365
|
function _buildDocChatErrorEnvelope(result) {
|
|
2999
3366
|
return {
|
|
3000
3367
|
code: result.code ?? null,
|
|
3001
|
-
stderr: (result.stderr || '').slice(-2048),
|
|
3368
|
+
stderr: String(result.stderr || '').slice(-2048),
|
|
3002
3369
|
errorClass: result.errorClass || null,
|
|
3003
3370
|
errorMessage: result.errorMessage || null,
|
|
3004
3371
|
runtime: result.runtime || null,
|
|
@@ -3186,7 +3553,7 @@ function _makeDocChatStreamStripper(onChunk) {
|
|
|
3186
3553
|
}
|
|
3187
3554
|
|
|
3188
3555
|
// Doc-specific wrapper — adds document context, parses ---DOCUMENT---
|
|
3189
|
-
async function ccDocCall({ message, document, title, filePath, selection, canEdit, isJson, model, freshSession, onAbortReady }) {
|
|
3556
|
+
async function ccDocCall({ message, document, title, filePath, selection, canEdit, isJson, model, freshSession, transcript, onAbortReady }) {
|
|
3190
3557
|
const sessionKey = filePath || title;
|
|
3191
3558
|
const docSlice = String(document || '');
|
|
3192
3559
|
|
|
@@ -3213,6 +3580,7 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
|
|
|
3213
3580
|
allowedTools: 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch',
|
|
3214
3581
|
skipStatePreamble: true,
|
|
3215
3582
|
systemPrompt: DOC_CHAT_SYSTEM_PROMPT,
|
|
3583
|
+
transcript,
|
|
3216
3584
|
...(model ? { model } : {}),
|
|
3217
3585
|
onAbortReady,
|
|
3218
3586
|
});
|
|
@@ -3250,7 +3618,7 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
|
|
|
3250
3618
|
return _parseDocChatResultText(result.text);
|
|
3251
3619
|
}
|
|
3252
3620
|
|
|
3253
|
-
async function ccDocCallStreaming({ message, document, title, filePath, selection, canEdit, isJson, model, freshSession, onAbortReady, onChunk, onToolUse, onRetry }) {
|
|
3621
|
+
async function ccDocCallStreaming({ message, document, title, filePath, selection, canEdit, isJson, model, freshSession, transcript, onAbortReady, onChunk, onToolUse, onRetry }) {
|
|
3254
3622
|
const sessionKey = filePath || title;
|
|
3255
3623
|
const docSlice = String(document || '');
|
|
3256
3624
|
const streamStripper = _makeDocChatStreamStripper(onChunk);
|
|
@@ -3273,6 +3641,7 @@ async function ccDocCallStreaming({ message, document, title, filePath, selectio
|
|
|
3273
3641
|
allowedTools: 'Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch',
|
|
3274
3642
|
skipStatePreamble: true,
|
|
3275
3643
|
systemPrompt: DOC_CHAT_SYSTEM_PROMPT,
|
|
3644
|
+
transcript,
|
|
3276
3645
|
...(model ? { model } : {}),
|
|
3277
3646
|
onAbortReady,
|
|
3278
3647
|
onChunk: streamStripper,
|
|
@@ -4110,7 +4479,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
4110
4479
|
id, title: body.title, type: 'plan',
|
|
4111
4480
|
priority: body.priority || 'high', description: body.description || '',
|
|
4112
4481
|
status: WI_STATUS.PENDING, created: new Date().toISOString(), createdBy: 'dashboard',
|
|
4113
|
-
branchStrategy: body.branch_strategy || 'parallel',
|
|
4482
|
+
branchStrategy: body.branch_strategy || body.branchStrategy || 'parallel',
|
|
4114
4483
|
};
|
|
4115
4484
|
if (body.project) item.project = body.project;
|
|
4116
4485
|
if (body.agent) item.agent = body.agent;
|
|
@@ -4313,14 +4682,17 @@ const server = http.createServer(async (req, res) => {
|
|
|
4313
4682
|
async function handleAgentsCancel(req, res) {
|
|
4314
4683
|
try {
|
|
4315
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' });
|
|
4316
4688
|
const dispatchPath = path.join(MINIONS_DIR, 'engine', 'dispatch.json');
|
|
4317
4689
|
const dispatch = safeJsonObj(dispatchPath);
|
|
4318
4690
|
const active = dispatch.active || [];
|
|
4319
4691
|
const cancelled = [];
|
|
4320
4692
|
|
|
4321
4693
|
for (const d of active) {
|
|
4322
|
-
const matchAgent =
|
|
4323
|
-
const matchTask =
|
|
4694
|
+
const matchAgent = requestedAgent && d.agent === requestedAgent;
|
|
4695
|
+
const matchTask = requestedTask && (d.task || '').toLowerCase().includes(String(requestedTask).toLowerCase());
|
|
4324
4696
|
if (!matchAgent && !matchTask) continue;
|
|
4325
4697
|
|
|
4326
4698
|
// Kill agent process
|
|
@@ -5433,6 +5805,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
5433
5805
|
filePath: body.filePath, selection: body.selection, canEdit, isJson,
|
|
5434
5806
|
model: body.model || undefined,
|
|
5435
5807
|
freshSession: !!body.freshSession,
|
|
5808
|
+
transcript: body.transcript,
|
|
5436
5809
|
onAbortReady: (abort) => { _docAbort = abort; },
|
|
5437
5810
|
});
|
|
5438
5811
|
const actionResults = await executeDocChatActions(actions);
|
|
@@ -5523,6 +5896,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
5523
5896
|
filePath: body.filePath, selection: body.selection, canEdit, isJson,
|
|
5524
5897
|
model: body.model || undefined,
|
|
5525
5898
|
freshSession: !!body.freshSession,
|
|
5899
|
+
transcript: body.transcript,
|
|
5526
5900
|
onAbortReady: (abort) => { _docAbort = abort; },
|
|
5527
5901
|
onChunk: (text) => { writeDocEvent({ type: 'chunk', text }); },
|
|
5528
5902
|
onToolUse: (name, input) => { writeDocEvent({ type: 'tool', name, input: _lightToolInput(input) }); },
|
|
@@ -6279,11 +6653,14 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6279
6653
|
const wasResume = !!tabSessionId;
|
|
6280
6654
|
const sessionId = tabSessionId || null;
|
|
6281
6655
|
const resumeNeedsCarryover = wasResume && _ccRuntimeNeedsResumeCarryover(currentRuntime);
|
|
6656
|
+
const resumeHasOutOfBandCarryover = wasResume && _transcriptHasCarryoverContext(body.transcript, { outOfBandOnly: true, currentMessage: body.message });
|
|
6282
6657
|
const preamble = wasResume ? '' : buildCCStatePreamble();
|
|
6283
|
-
const
|
|
6658
|
+
const includeFullCarryover = sessionReset || resumeNeedsCarryover;
|
|
6659
|
+
const carryover = (includeFullCarryover || resumeHasOutOfBandCarryover)
|
|
6284
6660
|
? _buildTranscriptCarryover(body.transcript, {
|
|
6285
6661
|
previousRuntime: sessionReset ? previousRuntime : null,
|
|
6286
6662
|
currentMessage: body.message,
|
|
6663
|
+
outOfBandOnly: !includeFullCarryover,
|
|
6287
6664
|
})
|
|
6288
6665
|
: '';
|
|
6289
6666
|
const prompt = _joinCcPromptParts(preamble, carryover, body.message);
|
|
@@ -7361,7 +7738,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7361
7738
|
{ method: 'POST', path: '/api/notes-save', desc: 'Save edited notes.md content', params: 'content, file?', handler: handleNotesSave },
|
|
7362
7739
|
|
|
7363
7740
|
// Plans
|
|
7364
|
-
{ 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 },
|
|
7365
7742
|
{ method: 'GET', path: '/api/plans', desc: 'List plan files (.md drafts + .json PRDs)', handler: handlePlansList },
|
|
7366
7743
|
{ method: 'POST', path: '/api/plans/trigger-verify', desc: 'Manually trigger verification for a completed plan', params: 'file', handler: handlePlansTriggerVerify },
|
|
7367
7744
|
{ method: 'POST', path: '/api/plans/approve', desc: 'Approve a plan for execution', params: 'file, approvedBy?', handler: handlePlansApprove },
|
|
@@ -7546,7 +7923,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7546
7923
|
inboxCount: steering.listUnreadSteeringMessages(agentId).length,
|
|
7547
7924
|
});
|
|
7548
7925
|
}},
|
|
7549
|
-
{ 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 },
|
|
7550
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 },
|
|
7551
7928
|
{ method: 'GET', path: /^\/api\/agent\/([\w-]+)\/live-stream(?:\?.*)?$/, template: '/api/agent/:id/live-stream', desc: 'SSE real-time live output streaming', handler: handleAgentLiveStream },
|
|
7552
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 },
|
|
@@ -7592,8 +7969,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7592
7969
|
{ method: 'GET', path: /^\/api\/knowledge\/([^/]+)\/([^?]+)/, template: '/api/knowledge/:category/:file', desc: 'Read a specific knowledge base entry', handler: handleKnowledgeRead },
|
|
7593
7970
|
|
|
7594
7971
|
// Doc chat
|
|
7595
|
-
{ method: 'POST', path: '/api/doc-chat', desc: 'Minions-aware doc Q&A + editing via CC session', params: 'message, document, title?, filePath?, selection?, contentHash?', handler: handleDocChat },
|
|
7596
|
-
{ method: 'POST', path: '/api/doc-chat/stream', desc: 'Streaming doc chat — SSE with text chunks and tool progress', params: 'message, document, title?, filePath?, selection?, contentHash?', handler: handleDocChatStream },
|
|
7972
|
+
{ method: 'POST', path: '/api/doc-chat', desc: 'Minions-aware doc Q&A + editing via CC session', params: 'message, document, title?, filePath?, selection?, contentHash?, transcript?', handler: handleDocChat },
|
|
7973
|
+
{ method: 'POST', path: '/api/doc-chat/stream', desc: 'Streaming doc chat — SSE with text chunks and tool progress', params: 'message, document, title?, filePath?, selection?, contentHash?, transcript?', handler: handleDocChatStream },
|
|
7597
7974
|
|
|
7598
7975
|
// Inbox
|
|
7599
7976
|
{ method: 'POST', path: '/api/inbox/persist', desc: 'Promote an inbox item to team notes', params: 'name', handler: handleInboxPersist },
|
|
@@ -7617,8 +7994,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7617
7994
|
// Command Center
|
|
7618
7995
|
{ method: 'POST', path: '/api/command-center/new-session', desc: 'Clear active CC session', handler: handleCommandCenterNewSession },
|
|
7619
7996
|
{ method: 'POST', path: '/api/command-center/abort', desc: 'Abort an in-flight CC request for a tab', params: 'tabId?', handler: handleCommandCenterAbort },
|
|
7620
|
-
{ method: 'POST', path: '/api/command-center', desc: 'Conversational command center with full minions context', params: 'message, sessionId?', handler: handleCommandCenter },
|
|
7621
|
-
{ method: 'POST', path: '/api/command-center/stream', desc: 'Streaming CC — SSE with text chunks as they arrive', params: 'message, tabId?', handler: handleCommandCenterStream },
|
|
7997
|
+
{ method: 'POST', path: '/api/command-center', desc: 'Conversational command center with full minions context', params: 'message, tabId?, sessionId?, transcript?', handler: handleCommandCenter },
|
|
7998
|
+
{ method: 'POST', path: '/api/command-center/stream', desc: 'Streaming CC — SSE with text chunks as they arrive', params: 'message, tabId?, sessionId?, transcript?', handler: handleCommandCenterStream },
|
|
7622
7999
|
{ method: 'GET', path: '/api/cc-sessions', desc: 'List CC session metadata for all tabs', handler: handleCCSessionsList },
|
|
7623
8000
|
{ method: 'DELETE', path: /^\/api\/cc-sessions\/([\w-]+)$/, template: '/api/cc-sessions/:id', desc: 'Delete a CC session by tab ID', handler: handleCCSessionDelete },
|
|
7624
8001
|
|
|
@@ -8018,10 +8395,15 @@ module.exports = {
|
|
|
8018
8395
|
_resolveWorkItemsCreateTarget: resolveWorkItemsCreateTarget,
|
|
8019
8396
|
_collectArchivedWorkItems: collectArchivedWorkItems,
|
|
8020
8397
|
_createPipelineFromAction: createPipelineFromAction,
|
|
8398
|
+
_setCcLocalApiInvokerForTest,
|
|
8399
|
+
_resetCcApiRoutesMetaForTest,
|
|
8400
|
+
_ccValidateLocalApiFallback,
|
|
8021
8401
|
executeCCActions,
|
|
8402
|
+
executeDocChatActions,
|
|
8022
8403
|
buildCCStatePreamble,
|
|
8023
8404
|
_routesAsMeta,
|
|
8024
8405
|
_buildTranscriptCarryover,
|
|
8406
|
+
_transcriptHasCarryoverContext,
|
|
8025
8407
|
_ccRuntimeNeedsResumeCarryover,
|
|
8026
8408
|
_joinCcPromptParts,
|
|
8027
8409
|
_captureApiRoutesMeta,
|
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,17 +81,17 @@ 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
|
|
|
88
|
-
- `dispatch
|
|
88
|
+
- `dispatch`: `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. Always emit `"type":"dispatch"` for dispatch-like work and preserve the semantic intent in `workType` (`fix`, `implement`, `explore`, `review`, or `test`) instead of using those words as action types.
|
|
89
89
|
- `build-and-test`: `pr` REQUIRED (number, ID, or URL).
|
|
90
90
|
- `note`: `title` and `content` (or `description`) REQUIRED.
|
|
91
91
|
- `knowledge`: `title`, `content`, and `category` REQUIRED. Valid categories: architecture, conventions, project-notes, build-reports, reviews.
|
|
92
92
|
|
|
93
93
|
Core action types:
|
|
94
|
-
- **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)
|
|
94
|
+
- **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 `workType:"implement"`.
|
|
95
95
|
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)
|
|
96
96
|
If the user wants a design/architecture artifact committed through a PR, dispatch `implement` or `docs` rather than `explore`.
|
|
97
97
|
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"`.
|
|
@@ -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.
|