@yemi33/minions 0.1.1739 → 0.1.1741
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 +9 -0
- package/dashboard.js +177 -71
- package/engine/cli.js +35 -17
- package/engine/copilot-models.json +1 -1
- package/engine/meeting.js +26 -1
- package/engine/runtimes/claude.js +23 -1
- package/engine/runtimes/copilot.js +23 -1
- package/engine/shared.js +2 -1
- package/engine/spawn-agent.js +8 -5
- package/package.json +1 -1
- package/prompts/cc-system.md +25 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1741 (2026-05-06)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
- hard-timeout backstop prevents permanent round stalls
|
|
7
|
+
- auto-index API + CLI catalog into preamble; threshold-based delegation gate
|
|
8
|
+
|
|
9
|
+
### Other
|
|
10
|
+
- chore(gitignore): ignore .vscode/, engine/completions/, pull-requests.json.backup
|
|
11
|
+
|
|
3
12
|
## 0.1.1739 (2026-05-06)
|
|
4
13
|
|
|
5
14
|
### Features
|
package/dashboard.js
CHANGED
|
@@ -952,9 +952,10 @@ const CC_STREAM_REATTACH_GRACE_MS = 60000; // keep CC job alive briefly after di
|
|
|
952
952
|
const CC_STREAM_DONE_RETENTION_MS = 30000; // retain final payload briefly so reconnect can still receive it
|
|
953
953
|
const CC_LIVE_STREAM_MAX_AGE_MS = shared.ENGINE_DEFAULTS.ccLiveStreamMaxAgeMs;
|
|
954
954
|
// Doc-chat is interactive — long-doc edits with multi-step Read+Write tool use can run
|
|
955
|
-
//
|
|
956
|
-
// edits mid-stream
|
|
957
|
-
|
|
955
|
+
// well past 5 min on `canEdit:true` paths. Bumped to 1 hour (matching CC) so legitimate
|
|
956
|
+
// edits aren't killed mid-stream and the backend timeout never beats the user's reading
|
|
957
|
+
// time. The doc-chat handlers still abort on client disconnect.
|
|
958
|
+
const DOC_CHAT_TIMEOUT_MS = 60 * 60 * 1000;
|
|
958
959
|
function _releaseCCTab(tabId) { ccInFlightTabs.delete(tabId); ccInFlightAborts.delete(tabId); }
|
|
959
960
|
function _getCcLiveStream(tabId) {
|
|
960
961
|
return ccLiveStreams.get(tabId) || null;
|
|
@@ -1175,6 +1176,50 @@ let _preambleCache = null;
|
|
|
1175
1176
|
let _preambleCacheTs = 0;
|
|
1176
1177
|
const PREAMBLE_TTL = 30000; // 30s — longer TTL since preamble is lightweight orientation, not real-time data
|
|
1177
1178
|
|
|
1179
|
+
// SoT for CC's runtime API index. Captured lazily on the first HTTP request
|
|
1180
|
+
// because ROUTES is closed over inside the request handler. Subsequent
|
|
1181
|
+
// captures no-op via the truthy guard.
|
|
1182
|
+
let _ccApiRoutesMeta = null;
|
|
1183
|
+
|
|
1184
|
+
function _routesAsMeta(routes) {
|
|
1185
|
+
return routes.map(r => ({
|
|
1186
|
+
method: r.method,
|
|
1187
|
+
path: typeof r.path === 'string' ? r.path : String(r.path),
|
|
1188
|
+
desc: r.desc || '',
|
|
1189
|
+
params: r.params || null,
|
|
1190
|
+
}));
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
function _captureApiRoutesMeta(routes) {
|
|
1194
|
+
if (_ccApiRoutesMeta || !Array.isArray(routes)) return;
|
|
1195
|
+
_ccApiRoutesMeta = _routesAsMeta(routes);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function _formatCcApiRoutesIndex() {
|
|
1199
|
+
if (!Array.isArray(_ccApiRoutesMeta) || _ccApiRoutesMeta.length === 0) return '';
|
|
1200
|
+
return _ccApiRoutesMeta
|
|
1201
|
+
.filter(r => r.path.startsWith('/api/'))
|
|
1202
|
+
.map(r => {
|
|
1203
|
+
const params = r.params ? ` — params: ${r.params}` : '';
|
|
1204
|
+
return `- \`${r.method} ${r.path}\` — ${r.desc}${params}`;
|
|
1205
|
+
})
|
|
1206
|
+
.join('\n');
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
const { CLI_COMMAND_DOCS: _CC_CLI_DOCS } = require('./engine/cli');
|
|
1210
|
+
|
|
1211
|
+
function _formatCcCliCommandsIndex() {
|
|
1212
|
+
if (!_CC_CLI_DOCS) return '';
|
|
1213
|
+
return Object.entries(_CC_CLI_DOCS)
|
|
1214
|
+
.map(([name, { args, summary }]) => `- \`minions ${name}${args ? ' ' + args : ''}\` — ${summary}`)
|
|
1215
|
+
.join('\n');
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
function _resetPreambleCache() {
|
|
1219
|
+
_preambleCache = null;
|
|
1220
|
+
_preambleCacheTs = 0;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1178
1223
|
function buildCCStatePreamble() {
|
|
1179
1224
|
const now = Date.now();
|
|
1180
1225
|
if (_preambleCache && now - _preambleCacheTs < PREAMBLE_TTL) return _preambleCache;
|
|
@@ -1197,6 +1242,19 @@ function buildCCStatePreamble() {
|
|
|
1197
1242
|
let pipelineCount = 0;
|
|
1198
1243
|
try { pipelineCount = require('./engine/pipeline').getPipelines().length; } catch {}
|
|
1199
1244
|
|
|
1245
|
+
const apiIndex = _formatCcApiRoutesIndex();
|
|
1246
|
+
const cliIndex = _formatCcCliCommandsIndex();
|
|
1247
|
+
const indexSection = (apiIndex || cliIndex) ? `
|
|
1248
|
+
|
|
1249
|
+
### API Index (auto-generated from dashboard.js ROUTES — single source of truth)
|
|
1250
|
+
${apiIndex || '(routes not yet captured — first request still pending)'}
|
|
1251
|
+
|
|
1252
|
+
### CLI Index (auto-generated from engine/cli.js CLI_COMMAND_DOCS — single source of truth)
|
|
1253
|
+
${cliIndex || '(unavailable)'}
|
|
1254
|
+
|
|
1255
|
+
For any \`/api/...\` endpoint not covered by a named CC action, use the generic fallback:
|
|
1256
|
+
\`{"type":"<descriptive>","endpoint":"/api/...","params":{...}}\`.` : '';
|
|
1257
|
+
|
|
1200
1258
|
const result = `### Agents
|
|
1201
1259
|
${agents}
|
|
1202
1260
|
|
|
@@ -1210,8 +1268,8 @@ PRs: ${prCount} | Work items: ${wiCount} | Plans/PRDs: ${planFiles.length} | Sch
|
|
|
1210
1268
|
### Projects
|
|
1211
1269
|
${projects}
|
|
1212
1270
|
|
|
1213
|
-
Use tools to read \`config.json\` (schedules), \`pipelines/\` dir
|
|
1214
|
-
For all state files, look under \`${MINIONS_DIR}
|
|
1271
|
+
Use tools to read \`config.json\` (schedules), \`pipelines/\` dir for details.
|
|
1272
|
+
For all state files, look under \`${MINIONS_DIR}\`.${indexSection}`;
|
|
1215
1273
|
_preambleCache = result;
|
|
1216
1274
|
_preambleCacheTs = now;
|
|
1217
1275
|
return result;
|
|
@@ -2466,6 +2524,56 @@ function _formatDocChatContext({ document, title, filePath, selection, canEdit,
|
|
|
2466
2524
|
return context;
|
|
2467
2525
|
}
|
|
2468
2526
|
|
|
2527
|
+
// Build the doc-chat extraContext for a single ccCall pass — refreshed on retry
|
|
2528
|
+
// so a fresh-session retry includes the full document instead of relying on the
|
|
2529
|
+
// dead session's prior turn for context.
|
|
2530
|
+
function _buildDocChatPass({ docSlice, title, filePath, selection, canEdit, isJson, sessionKey, freshSession }) {
|
|
2531
|
+
const existing = freshSession ? null : resolveSession('doc', sessionKey);
|
|
2532
|
+
const docHash = require('crypto').createHash('md5').update(docSlice).digest('hex').slice(0, 8);
|
|
2533
|
+
const docUnchanged = existing?.sessionId && existing._docHash === docHash;
|
|
2534
|
+
const extraContext = _formatDocChatContext({
|
|
2535
|
+
document: docSlice, title, filePath, selection, canEdit, isJson, docUnchanged,
|
|
2536
|
+
});
|
|
2537
|
+
return { extraContext, docHash, hadSession: !!existing?.sessionId };
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
// One-shot recovery from a stuck resumed session: if the initial call rode an
|
|
2541
|
+
// existing session and came back empty / non-zero, invalidate the persisted
|
|
2542
|
+
// session and re-run via the supplied closure. ccCall already retries fresh
|
|
2543
|
+
// when the runtime can't extract a sessionId, but on adapter-still-alive
|
|
2544
|
+
// failures it preserves the session — and from doc-chat's perspective that's
|
|
2545
|
+
// the failure mode the user actually sees ("Failed to process request").
|
|
2546
|
+
async function _retryDocChatAfterResumeFailure({ result, initialPass, freshSession, sessionKey, runOnce }) {
|
|
2547
|
+
if (!initialPass.hadSession || freshSession || result.missingRuntime) return result;
|
|
2548
|
+
if (result.code === 0 && result.text) return result;
|
|
2549
|
+
console.log(`[doc-chat] Resumed session call failed (code=${result.code}, empty=${!result.text}) — invalidating session and retrying fresh for ${sessionKey || 'untitled'}`);
|
|
2550
|
+
if (sessionKey) {
|
|
2551
|
+
docSessions.delete(sessionKey);
|
|
2552
|
+
schedulePersistDocSessions();
|
|
2553
|
+
}
|
|
2554
|
+
return runOnce();
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
// Build the {error} envelope returned to the dashboard when doc-chat ultimately
|
|
2558
|
+
// fails. Keeps the polite user-facing message but exposes the runtime's real
|
|
2559
|
+
// stderr / exit code / errorClass so the UI can render the cause and so future
|
|
2560
|
+
// failures are debuggable from logs.
|
|
2561
|
+
function _docChatFailureResponse(label, filePath, result) {
|
|
2562
|
+
const stderrTail = (result.stderr || '').slice(-2048);
|
|
2563
|
+
console.error(`[${label}] Failed: code=${result.code}, empty=${!result.text}, filePath=${filePath}, errorClass=${result.errorClass || 'null'}, stderr=${stderrTail.slice(0, 200)}`);
|
|
2564
|
+
return {
|
|
2565
|
+
answer: 'Failed to process request. Try again.',
|
|
2566
|
+
content: null,
|
|
2567
|
+
actions: [],
|
|
2568
|
+
error: {
|
|
2569
|
+
code: result.code ?? null,
|
|
2570
|
+
stderr: stderrTail,
|
|
2571
|
+
errorClass: result.errorClass || null,
|
|
2572
|
+
runtime: result.runtime || null,
|
|
2573
|
+
},
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2469
2577
|
// Doc-specific wrapper — adds document context, parses ---DOCUMENT---
|
|
2470
2578
|
async function ccDocCall({ message, document, title, filePath, selection, canEdit, isJson, model, freshSession, onAbortReady }) {
|
|
2471
2579
|
const sessionKey = filePath || title;
|
|
@@ -2479,34 +2587,30 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
|
|
|
2479
2587
|
// Skip persistDocSessions() here — the post-call cleanup below handles persistence.
|
|
2480
2588
|
}
|
|
2481
2589
|
|
|
2482
|
-
// Skip re-sending full document on session resume if content unchanged
|
|
2483
|
-
const docHash = require('crypto').createHash('md5').update(docSlice).digest('hex').slice(0, 8);
|
|
2484
|
-
const existing = freshSession ? null : resolveSession('doc', sessionKey);
|
|
2485
|
-
const docUnchanged = existing?.sessionId && existing._docHash === docHash;
|
|
2486
|
-
|
|
2487
|
-
const docContext = _formatDocChatContext({
|
|
2488
|
-
document: docSlice,
|
|
2489
|
-
title,
|
|
2490
|
-
filePath,
|
|
2491
|
-
selection,
|
|
2492
|
-
canEdit,
|
|
2493
|
-
isJson,
|
|
2494
|
-
docUnchanged,
|
|
2495
|
-
});
|
|
2496
2590
|
const allowActions = _messageRequestsOrchestration(message);
|
|
2497
2591
|
|
|
2498
|
-
const
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2592
|
+
const runOnce = async () => {
|
|
2593
|
+
const { extraContext } = _buildDocChatPass({
|
|
2594
|
+
docSlice, title, filePath, selection, canEdit, isJson, sessionKey, freshSession,
|
|
2595
|
+
});
|
|
2596
|
+
return ccCall(message, {
|
|
2597
|
+
store: 'doc', sessionKey,
|
|
2598
|
+
extraContext, label: 'doc-chat',
|
|
2599
|
+
timeout: DOC_CHAT_TIMEOUT_MS,
|
|
2600
|
+
allowedTools: canEdit ? 'Read,Write,Edit,Glob,Grep' : 'Read,Glob,Grep',
|
|
2601
|
+
maxTurns: canEdit ? 25 : 10,
|
|
2602
|
+
skipStatePreamble: true,
|
|
2603
|
+
systemPrompt: DOC_CHAT_SYSTEM_PROMPT,
|
|
2604
|
+
...(model ? { model } : {}),
|
|
2605
|
+
onAbortReady,
|
|
2606
|
+
});
|
|
2607
|
+
};
|
|
2608
|
+
|
|
2609
|
+
const initialPass = _buildDocChatPass({
|
|
2610
|
+
docSlice, title, filePath, selection, canEdit, isJson, sessionKey, freshSession,
|
|
2509
2611
|
});
|
|
2612
|
+
let result = await runOnce();
|
|
2613
|
+
result = await _retryDocChatAfterResumeFailure({ result, initialPass, freshSession, sessionKey, runOnce });
|
|
2510
2614
|
|
|
2511
2615
|
if (freshSession && sessionKey) {
|
|
2512
2616
|
// One-shot call — discard the session ccCall just stored so it cannot
|
|
@@ -2516,7 +2620,7 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
|
|
|
2516
2620
|
} else if (result.code === 0 && result.sessionId) {
|
|
2517
2621
|
// Store doc hash for next call's unchanged check
|
|
2518
2622
|
const session = resolveSession('doc', sessionKey);
|
|
2519
|
-
if (session) session._docHash = docHash;
|
|
2623
|
+
if (session) session._docHash = initialPass.docHash;
|
|
2520
2624
|
}
|
|
2521
2625
|
|
|
2522
2626
|
if (result.missingRuntime) {
|
|
@@ -2524,8 +2628,7 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
|
|
|
2524
2628
|
}
|
|
2525
2629
|
|
|
2526
2630
|
if (result.code !== 0 || !result.text) {
|
|
2527
|
-
|
|
2528
|
-
return { answer: 'Failed to process request. Try again.', content: null, actions: [] };
|
|
2631
|
+
return _docChatFailureResponse('doc-chat', filePath, result);
|
|
2529
2632
|
}
|
|
2530
2633
|
|
|
2531
2634
|
return _parseDocChatResultText(result.text, { allowActions });
|
|
@@ -2539,42 +2642,39 @@ async function ccDocCallStreaming({ message, document, title, filePath, selectio
|
|
|
2539
2642
|
docSessions.delete(sessionKey);
|
|
2540
2643
|
}
|
|
2541
2644
|
|
|
2542
|
-
const docHash = require('crypto').createHash('md5').update(docSlice).digest('hex').slice(0, 8);
|
|
2543
|
-
const existing = freshSession ? null : resolveSession('doc', sessionKey);
|
|
2544
|
-
const docUnchanged = existing?.sessionId && existing._docHash === docHash;
|
|
2545
|
-
|
|
2546
|
-
const docContext = _formatDocChatContext({
|
|
2547
|
-
document: docSlice,
|
|
2548
|
-
title,
|
|
2549
|
-
filePath,
|
|
2550
|
-
selection,
|
|
2551
|
-
canEdit,
|
|
2552
|
-
isJson,
|
|
2553
|
-
docUnchanged,
|
|
2554
|
-
});
|
|
2555
2645
|
const allowActions = _messageRequestsOrchestration(message);
|
|
2556
2646
|
|
|
2557
|
-
const
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2647
|
+
const runOnce = async () => {
|
|
2648
|
+
const { extraContext } = _buildDocChatPass({
|
|
2649
|
+
docSlice, title, filePath, selection, canEdit, isJson, sessionKey, freshSession,
|
|
2650
|
+
});
|
|
2651
|
+
return ccCallStreaming(message, {
|
|
2652
|
+
store: 'doc', sessionKey,
|
|
2653
|
+
extraContext, label: 'doc-chat',
|
|
2654
|
+
timeout: DOC_CHAT_TIMEOUT_MS,
|
|
2655
|
+
allowedTools: canEdit ? 'Read,Write,Edit,Glob,Grep' : 'Read,Glob,Grep',
|
|
2656
|
+
maxTurns: canEdit ? 25 : 10,
|
|
2657
|
+
skipStatePreamble: true,
|
|
2658
|
+
systemPrompt: DOC_CHAT_SYSTEM_PROMPT,
|
|
2659
|
+
...(model ? { model } : {}),
|
|
2660
|
+
onAbortReady,
|
|
2661
|
+
onChunk: (text) => { if (onChunk) onChunk(_docChatDisplayText(text, { allowActions })); },
|
|
2662
|
+
onToolUse,
|
|
2663
|
+
});
|
|
2664
|
+
};
|
|
2665
|
+
|
|
2666
|
+
const initialPass = _buildDocChatPass({
|
|
2667
|
+
docSlice, title, filePath, selection, canEdit, isJson, sessionKey, freshSession,
|
|
2570
2668
|
});
|
|
2669
|
+
let result = await runOnce();
|
|
2670
|
+
result = await _retryDocChatAfterResumeFailure({ result, initialPass, freshSession, sessionKey, runOnce });
|
|
2571
2671
|
|
|
2572
2672
|
if (freshSession && sessionKey) {
|
|
2573
2673
|
docSessions.delete(sessionKey);
|
|
2574
2674
|
schedulePersistDocSessions();
|
|
2575
2675
|
} else if (result.code === 0 && result.sessionId) {
|
|
2576
2676
|
const session = resolveSession('doc', sessionKey);
|
|
2577
|
-
if (session) session._docHash = docHash;
|
|
2677
|
+
if (session) session._docHash = initialPass.docHash;
|
|
2578
2678
|
}
|
|
2579
2679
|
|
|
2580
2680
|
if (result.missingRuntime) {
|
|
@@ -2582,8 +2682,7 @@ async function ccDocCallStreaming({ message, document, title, filePath, selectio
|
|
|
2582
2682
|
}
|
|
2583
2683
|
|
|
2584
2684
|
if (result.code !== 0 || !result.text) {
|
|
2585
|
-
|
|
2586
|
-
return { answer: 'Failed to process request. Try again.', content: null, actions: [] };
|
|
2685
|
+
return _docChatFailureResponse('doc-chat-stream', filePath, result);
|
|
2587
2686
|
}
|
|
2588
2687
|
|
|
2589
2688
|
return _parseDocChatResultText(result.text, { allowActions });
|
|
@@ -4680,7 +4779,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4680
4779
|
}
|
|
4681
4780
|
}
|
|
4682
4781
|
|
|
4683
|
-
const { answer, content, actions, actionParseError } = await ccDocCall({
|
|
4782
|
+
const { answer, content, actions, actionParseError, error: ccError } = await ccDocCall({
|
|
4684
4783
|
message: body.message, document: currentContent, title: body.title,
|
|
4685
4784
|
filePath: body.filePath, selection: body.selection, canEdit, isJson,
|
|
4686
4785
|
model: body.model || undefined,
|
|
@@ -4689,11 +4788,12 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4689
4788
|
});
|
|
4690
4789
|
const actionResults = await executeDocChatActions(actions);
|
|
4691
4790
|
const baseReply = (extra = {}) => ({
|
|
4692
|
-
ok:
|
|
4791
|
+
ok: !ccError,
|
|
4693
4792
|
answer,
|
|
4694
4793
|
actions,
|
|
4695
4794
|
...(actionResults ? { actionResults } : {}),
|
|
4696
4795
|
...(actionParseError ? { actionParseError } : {}),
|
|
4796
|
+
...(ccError ? { error: ccError } : {}),
|
|
4697
4797
|
...extra,
|
|
4698
4798
|
});
|
|
4699
4799
|
|
|
@@ -4795,7 +4895,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4795
4895
|
|
|
4796
4896
|
try {
|
|
4797
4897
|
|
|
4798
|
-
const { answer, content, actions, actionParseError } = await ccDocCallStreaming({
|
|
4898
|
+
const { answer, content, actions, actionParseError, error: ccError } = await ccDocCallStreaming({
|
|
4799
4899
|
message: body.message, document: currentContent, title: body.title,
|
|
4800
4900
|
filePath: body.filePath, selection: body.selection, canEdit, isJson,
|
|
4801
4901
|
model: body.model || undefined,
|
|
@@ -4811,6 +4911,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4811
4911
|
actions,
|
|
4812
4912
|
...(actionResults ? { actionResults } : {}),
|
|
4813
4913
|
...(actionParseError ? { actionParseError } : {}),
|
|
4914
|
+
...(ccError ? { error: ccError } : {}),
|
|
4814
4915
|
...extra,
|
|
4815
4916
|
});
|
|
4816
4917
|
|
|
@@ -6502,12 +6603,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
6502
6603
|
});
|
|
6503
6604
|
}},
|
|
6504
6605
|
{ method: 'GET', path: '/api/routes', desc: 'List all available API endpoints', handler: (req, res) => {
|
|
6505
|
-
const list = ROUTES.map(
|
|
6506
|
-
method: r.method,
|
|
6507
|
-
path: typeof r.path === 'string' ? r.path : r.path.toString(),
|
|
6508
|
-
description: r.desc,
|
|
6509
|
-
params: r.params || null
|
|
6510
|
-
}));
|
|
6606
|
+
const list = _routesAsMeta(ROUTES).map(({ desc, ...rest }) => ({ ...rest, description: desc }));
|
|
6511
6607
|
return jsonReply(res, 200, { routes: list });
|
|
6512
6608
|
}},
|
|
6513
6609
|
|
|
@@ -7140,6 +7236,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7140
7236
|
|
|
7141
7237
|
// ── Route Dispatcher ────────────────────────────────────────────────────────
|
|
7142
7238
|
|
|
7239
|
+
// Capture ROUTES metadata into the module-level snapshot so CC's preamble
|
|
7240
|
+
// renders the live API surface (single source of truth — adding any new
|
|
7241
|
+
// route to ROUTES above auto-surfaces it in CC's index).
|
|
7242
|
+
_captureApiRoutesMeta(ROUTES);
|
|
7243
|
+
|
|
7143
7244
|
const pathname = req.url.split('?')[0];
|
|
7144
7245
|
const _reqStart = Date.now();
|
|
7145
7246
|
for (const route of ROUTES) {
|
|
@@ -7225,6 +7326,11 @@ module.exports = {
|
|
|
7225
7326
|
_resolveWorkItemsCreateTarget: resolveWorkItemsCreateTarget,
|
|
7226
7327
|
_createPipelineFromAction: createPipelineFromAction,
|
|
7227
7328
|
executeCCActions,
|
|
7329
|
+
buildCCStatePreamble,
|
|
7330
|
+
_captureApiRoutesMeta,
|
|
7331
|
+
_formatCcApiRoutesIndex,
|
|
7332
|
+
_formatCcCliCommandsIndex,
|
|
7333
|
+
_resetPreambleCache,
|
|
7228
7334
|
};
|
|
7229
7335
|
|
|
7230
7336
|
// Start the HTTP server only when run directly (node dashboard.js).
|
package/engine/cli.js
CHANGED
|
@@ -97,27 +97,41 @@ function handleCommand(cmd, args) {
|
|
|
97
97
|
} else {
|
|
98
98
|
console.log(`Unknown command: ${cmd}`);
|
|
99
99
|
console.log('Commands:');
|
|
100
|
-
|
|
101
|
-
console.log(' stop Stop engine');
|
|
102
|
-
console.log(' pause / resume Pause/resume dispatching');
|
|
103
|
-
console.log(' status Show engine + agent state + fleet config');
|
|
104
|
-
console.log(' queue Show dispatch queue');
|
|
105
|
-
console.log(' sources Show work source status');
|
|
106
|
-
console.log(' discover Dry-run work discovery');
|
|
107
|
-
console.log(' dispatch Force a dispatch cycle');
|
|
108
|
-
console.log(' spawn <a> <p> Manually spawn agent with prompt');
|
|
109
|
-
console.log(' work <title> [o] Add to work-items.json queue');
|
|
110
|
-
console.log(' plan <src> [p] Generate PRD from a plan (file or text)');
|
|
111
|
-
console.log(' kill Kill all active agents, reset to pending');
|
|
112
|
-
console.log(' complete <id> Mark a dispatch as done');
|
|
113
|
-
console.log(' cleanup Clean temp files, worktrees, zombies');
|
|
114
|
-
console.log(' mcp-sync Sync MCP servers from ~/.claude.json');
|
|
115
|
-
console.log(' doctor Check prerequisites and runtime health');
|
|
116
|
-
console.log(' config set-cli <R> [--model M] Persist defaultCli/defaultModel without starting');
|
|
100
|
+
for (const line of formatCliCommandHelpLines()) console.log(line);
|
|
117
101
|
process.exit(1);
|
|
118
102
|
}
|
|
119
103
|
}
|
|
120
104
|
|
|
105
|
+
// SoT for engine-CLI metadata: drives handleCommand's help text and the
|
|
106
|
+
// CC preamble's CLI index in dashboard.js. Drift-checked against `commands`.
|
|
107
|
+
const CLI_COMMAND_DOCS = Object.freeze({
|
|
108
|
+
start: { args: '[--cli R] [--model M]', summary: 'Start engine daemon (R = registered runtime)' },
|
|
109
|
+
stop: { args: '', summary: 'Stop engine' },
|
|
110
|
+
pause: { args: '', summary: 'Pause dispatching' },
|
|
111
|
+
resume: { args: '', summary: 'Resume dispatching' },
|
|
112
|
+
status: { args: '', summary: 'Show engine + agent state + fleet config' },
|
|
113
|
+
queue: { args: '', summary: 'Show dispatch queue' },
|
|
114
|
+
sources: { args: '', summary: 'Show work source status per project' },
|
|
115
|
+
discover: { args: '', summary: 'Dry-run work discovery' },
|
|
116
|
+
dispatch: { args: '', summary: 'Force a dispatch cycle' },
|
|
117
|
+
spawn: { args: '<agent> <prompt>', summary: 'Manually spawn an agent with prompt' },
|
|
118
|
+
work: { args: '<title> [opts]', summary: 'Add to work-items.json queue' },
|
|
119
|
+
plan: { args: '<file|text> [project]', summary: 'Generate PRD from a plan (file or text)' },
|
|
120
|
+
kill: { args: '', summary: 'Kill all active agents, reset to pending' },
|
|
121
|
+
complete: { args: '<dispatch-id>', summary: 'Mark a dispatch as done' },
|
|
122
|
+
cleanup: { args: '', summary: 'Clean temp files, worktrees, zombies' },
|
|
123
|
+
'mcp-sync': { args: '', summary: 'Sync MCP servers from ~/.claude.json' },
|
|
124
|
+
doctor: { args: '', summary: 'Check prerequisites and runtime health' },
|
|
125
|
+
config: { args: 'set-cli <R> [--model M]', summary: 'Persist defaultCli/defaultModel without starting' },
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
function formatCliCommandHelpLines() {
|
|
129
|
+
const entries = Object.entries(CLI_COMMAND_DOCS);
|
|
130
|
+
const lefts = entries.map(([name, { args }]) => ' ' + name + (args ? ' ' + args : ''));
|
|
131
|
+
const padTo = Math.max(...lefts.map(s => s.length)) + 2;
|
|
132
|
+
return entries.map(([, { summary }], i) => lefts[i].padEnd(padTo) + summary);
|
|
133
|
+
}
|
|
134
|
+
|
|
121
135
|
// ─── Runtime fleet flags (--cli / --model / --effort) ────────────────────────
|
|
122
136
|
//
|
|
123
137
|
// Shared by `start`, `restart`, and `config set-cli`. Single source of truth
|
|
@@ -1461,7 +1475,11 @@ const commands = {
|
|
|
1461
1475
|
|
|
1462
1476
|
module.exports = {
|
|
1463
1477
|
handleCommand,
|
|
1478
|
+
// exported for downstream indexing (dashboard CC preamble)
|
|
1479
|
+
CLI_COMMAND_DOCS,
|
|
1480
|
+
formatCliCommandHelpLines,
|
|
1464
1481
|
// exported for testing
|
|
1482
|
+
_listCommandKeys: () => Object.keys(commands),
|
|
1465
1483
|
_parseRuntimeFlags,
|
|
1466
1484
|
_modelLooksIncompatible,
|
|
1467
1485
|
_applyRuntimeFlags,
|
package/engine/meeting.js
CHANGED
|
@@ -730,6 +730,8 @@ function checkMeetingTimeouts(config) {
|
|
|
730
730
|
const meetings = getMeetings();
|
|
731
731
|
const timeout = (config.engine || {}).meetingRoundTimeout
|
|
732
732
|
|| ENGINE_DEFAULTS.meetingRoundTimeout;
|
|
733
|
+
const hardTimeout = (config.engine || {}).meetingRoundHardTimeout
|
|
734
|
+
|| ENGINE_DEFAULTS.meetingRoundHardTimeout;
|
|
733
735
|
|
|
734
736
|
for (const meeting of meetings) {
|
|
735
737
|
if (isTerminalMeetingStatus(meeting.status)) continue;
|
|
@@ -760,11 +762,34 @@ function checkMeetingTimeouts(config) {
|
|
|
760
762
|
meeting.transcript.push({ round: meeting.round, agent: 'system', type: 'timeout', content: `Round ${meeting.round} timed out after all participants finished`, at: ts() });
|
|
761
763
|
advanceMeetingIfRoundComplete(meeting, roundName, meeting.id, config);
|
|
762
764
|
saveMeeting(meeting);
|
|
765
|
+
} else if (elapsed >= hardTimeout) {
|
|
766
|
+
const failures = getRoundFailures(meeting, roundName, meeting.round, true);
|
|
767
|
+
const stalled = (meeting.participants || []).filter(p => !hasRoundTerminalOutcome(meeting, roundName, p, meeting.round));
|
|
768
|
+
const reason = `Hard meeting timeout after ${Math.round(elapsed / 60000)}min — agent did not produce ${roundName} output`;
|
|
769
|
+
for (const agentId of stalled) {
|
|
770
|
+
failures[agentId] = { reason, content: '', submittedAt: ts() };
|
|
771
|
+
meeting.transcript.push({ round: meeting.round, agent: agentId, type: 'failure', content: reason, at: ts() });
|
|
772
|
+
}
|
|
773
|
+
log('warn', `Meeting ${meeting.id}: round ${meeting.round} hit hard timeout after ${Math.round(elapsed / 60000)}min — marking ${stalled.length}/${totalCount} non-responders as failed and advancing`);
|
|
774
|
+
meeting.transcript.push({ round: meeting.round, agent: 'system', type: 'timeout', content: `Round ${meeting.round} hard timeout — ${stalled.length} non-responder(s) marked failed`, at: ts() });
|
|
775
|
+
advanceMeetingIfRoundComplete(meeting, roundName, meeting.id, config);
|
|
776
|
+
saveMeeting(meeting);
|
|
763
777
|
} else {
|
|
764
778
|
log('warn', `Meeting ${meeting.id}: round ${meeting.round} timed out after ${Math.round(elapsed / 60000)}min — waiting for all participants to finish (${respondedCount}/${totalCount} succeeded)`);
|
|
765
779
|
}
|
|
766
780
|
} else if (meeting.status === 'concluding') {
|
|
767
|
-
|
|
781
|
+
if (elapsed >= hardTimeout) {
|
|
782
|
+
const reason = `Hard meeting timeout after ${Math.round(elapsed / 60000)}min — conclusion agent did not produce output`;
|
|
783
|
+
const failures = getRoundFailures(meeting, 'conclude', meeting.round, true);
|
|
784
|
+
const conclusionAgent = (meeting.participants || []).find(p => !hasRoundTerminalOutcome(meeting, 'conclude', p, meeting.round)) || meeting.participants?.[0] || 'system';
|
|
785
|
+
failures[conclusionAgent] = { reason, content: '', submittedAt: ts() };
|
|
786
|
+
meeting.transcript.push({ round: meeting.round, agent: conclusionAgent, type: 'failure', content: reason, at: ts() });
|
|
787
|
+
log('warn', `Meeting ${meeting.id}: conclusion round hit hard timeout after ${Math.round(elapsed / 60000)}min — synthesising fallback conclusion`);
|
|
788
|
+
advanceMeetingIfRoundComplete(meeting, 'conclude', meeting.id, config);
|
|
789
|
+
saveMeeting(meeting);
|
|
790
|
+
} else {
|
|
791
|
+
log('warn', `Meeting ${meeting.id}: conclusion round timed out after ${Math.round(elapsed / 60000)}min — waiting for the conclusion agent to finish`);
|
|
792
|
+
}
|
|
768
793
|
}
|
|
769
794
|
}
|
|
770
795
|
}
|
|
@@ -287,12 +287,33 @@ function getSkillWriteTargets({ homeDir = os.homedir(), project = null } = {}) {
|
|
|
287
287
|
};
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
+
// Stamped into every session.json this adapter writes so the pre-spawn resume
|
|
291
|
+
// path can detect "session was produced by a different runtime" — Claude
|
|
292
|
+
// rejects Copilot session IDs (and vice versa) with "No conversation found",
|
|
293
|
+
// which would otherwise burn a retry slot before the post-failure cleanup at
|
|
294
|
+
// engine.js:1195 fires. See W-mot9fwya000d09cb.
|
|
295
|
+
const RUNTIME_NAME = 'claude';
|
|
296
|
+
|
|
290
297
|
function getResumeSessionId({ agentId, branchName, agentsDir, maxAgeMs = 2 * 60 * 60 * 1000, logger = console } = {}) {
|
|
291
298
|
if (!agentId || agentId.startsWith('temp-') || !agentsDir) return null;
|
|
299
|
+
const sessionPath = path.join(agentsDir, agentId, 'session.json');
|
|
292
300
|
try {
|
|
293
|
-
const sessionPath = path.join(agentsDir, agentId, 'session.json');
|
|
294
301
|
const sessionFile = _safeJson(sessionPath);
|
|
295
302
|
if (!sessionFile?.sessionId || !sessionFile.savedAt) return null;
|
|
303
|
+
|
|
304
|
+
// Runtime-mismatch invalidation. Distinct from stale-by-age: the session is
|
|
305
|
+
// structurally unusable on this runtime, so drop it AND clear session.json
|
|
306
|
+
// so the next dispatch starts fresh instead of failing with --resume.
|
|
307
|
+
// Legacy sessions (no `runtime` field) are treated as compatible — opt-in
|
|
308
|
+
// check, no false invalidations on first deploy.
|
|
309
|
+
if (sessionFile.runtime && sessionFile.runtime !== RUNTIME_NAME) {
|
|
310
|
+
if (logger && typeof logger.info === 'function') {
|
|
311
|
+
logger.info(`Skipping resume for ${agentId}: runtime mismatch (session: ${sessionFile.runtime}, current: ${RUNTIME_NAME}) — clearing session.json`);
|
|
312
|
+
}
|
|
313
|
+
try { fs.unlinkSync(sessionPath); } catch {}
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
|
|
296
317
|
const sessionAge = Date.now() - new Date(sessionFile.savedAt).getTime();
|
|
297
318
|
const sameBranch = branchName && sessionFile.branch && sessionFile.branch === branchName;
|
|
298
319
|
if (sessionAge < maxAgeMs && sameBranch) {
|
|
@@ -315,6 +336,7 @@ function saveSession({ agentId, dispatchId, branch, sessionId, agentsDir, now =
|
|
|
315
336
|
dispatchId,
|
|
316
337
|
savedAt: typeof now === 'function' ? now() : new Date().toISOString(),
|
|
317
338
|
branch: branch || null,
|
|
339
|
+
runtime: RUNTIME_NAME,
|
|
318
340
|
});
|
|
319
341
|
return true;
|
|
320
342
|
} catch (err) {
|
|
@@ -306,12 +306,33 @@ function getSkillWriteTargets({ homeDir = os.homedir(), project = null } = {}) {
|
|
|
306
306
|
};
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
// Stamped into every session.json this adapter writes so the pre-spawn resume
|
|
310
|
+
// path can detect "session was produced by a different runtime" — Copilot
|
|
311
|
+
// rejects Claude session IDs (and vice versa) with "No conversation found",
|
|
312
|
+
// which would otherwise burn a retry slot before the post-failure cleanup at
|
|
313
|
+
// engine.js:1195 fires. See W-mot9fwya000d09cb.
|
|
314
|
+
const RUNTIME_NAME = 'copilot';
|
|
315
|
+
|
|
309
316
|
function getResumeSessionId({ agentId, branchName, agentsDir, maxAgeMs = 2 * 60 * 60 * 1000, logger = console } = {}) {
|
|
310
317
|
if (!agentId || agentId.startsWith('temp-') || !agentsDir) return null;
|
|
318
|
+
const sessionPath = path.join(agentsDir, agentId, 'session.json');
|
|
311
319
|
try {
|
|
312
|
-
const sessionPath = path.join(agentsDir, agentId, 'session.json');
|
|
313
320
|
const sessionFile = _safeJson(sessionPath);
|
|
314
321
|
if (!sessionFile?.sessionId || !sessionFile.savedAt) return null;
|
|
322
|
+
|
|
323
|
+
// Runtime-mismatch invalidation. Distinct from stale-by-age: the session is
|
|
324
|
+
// structurally unusable on this runtime, so drop it AND clear session.json
|
|
325
|
+
// so the next dispatch starts fresh instead of failing with --resume.
|
|
326
|
+
// Legacy sessions (no `runtime` field) are treated as compatible — opt-in
|
|
327
|
+
// check, no false invalidations on first deploy.
|
|
328
|
+
if (sessionFile.runtime && sessionFile.runtime !== RUNTIME_NAME) {
|
|
329
|
+
if (logger && typeof logger.info === 'function') {
|
|
330
|
+
logger.info(`Skipping resume for ${agentId}: runtime mismatch (session: ${sessionFile.runtime}, current: ${RUNTIME_NAME}) — clearing session.json`);
|
|
331
|
+
}
|
|
332
|
+
try { fs.unlinkSync(sessionPath); } catch {}
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
|
|
315
336
|
const sessionAge = Date.now() - new Date(sessionFile.savedAt).getTime();
|
|
316
337
|
const sameBranch = branchName && sessionFile.branch && sessionFile.branch === branchName;
|
|
317
338
|
if (sessionAge < maxAgeMs && sameBranch) {
|
|
@@ -334,6 +355,7 @@ function saveSession({ agentId, dispatchId, branch, sessionId, agentsDir, now =
|
|
|
334
355
|
dispatchId,
|
|
335
356
|
savedAt: typeof now === 'function' ? now() : new Date().toISOString(),
|
|
336
357
|
branch: branch || null,
|
|
358
|
+
runtime: RUNTIME_NAME,
|
|
337
359
|
});
|
|
338
360
|
return true;
|
|
339
361
|
} catch (err) {
|
package/engine/shared.js
CHANGED
|
@@ -861,7 +861,8 @@ const ENGINE_DEFAULTS = {
|
|
|
861
861
|
prNoOpFixPauseAttempts: 2, // pause one PR automation cause after repeated no-op fixes for unchanged evidence
|
|
862
862
|
completionReportRetentionDays: 90, // retain completion report sidecars beyond capped dispatch history
|
|
863
863
|
completionReportMaxFiles: 5000, // hard cap for completion report sidecars during cleanup
|
|
864
|
-
meetingRoundTimeout: 900000, // 15min per meeting round
|
|
864
|
+
meetingRoundTimeout: 900000, // 15min per meeting round — soft signal; logs a "still waiting" warning each tick
|
|
865
|
+
meetingRoundHardTimeout: 3600000, // 60min hard backstop — non-terminal participants are marked failed and the round advances. Prevents permanent stalls if an agent's dispatch never spawns or its completion gets dropped.
|
|
865
866
|
evalLoop: true, // enable review→fix loop after implementation completes
|
|
866
867
|
evalMaxIterations: 3, // legacy UI/config field; engine discovery no longer enforces review→fix cycle caps
|
|
867
868
|
evalMaxCost: null, // USD ceiling per work item across all eval iterations; null = no limit (gather baseline data first)
|
package/engine/spawn-agent.js
CHANGED
|
@@ -355,11 +355,14 @@ function main() {
|
|
|
355
355
|
|
|
356
356
|
// Clean up sys tmp (only created for non-resume sessions on adapters that
|
|
357
357
|
// use --system-prompt-file).
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
//
|
|
358
|
+
//
|
|
359
|
+
// We deliberately DO NOT use a fixed-delay setTimeout here. Claude CLI reads
|
|
360
|
+
// `--system-prompt-file` lazily during init; on cold starts (MCP boot,
|
|
361
|
+
// `--add-dir` traversal, Windows process startup) the read can land well
|
|
362
|
+
// after any short timer fires, leaving Claude to bail with
|
|
363
|
+
// "System prompt file not found". The exit/SIGTERM handler below — plus the
|
|
364
|
+
// matching unlinks in engine/dispatch.js and engine/meeting.js on dispatch
|
|
365
|
+
// completion — are sufficient to keep tmp files from leaking.
|
|
363
366
|
function _cleanupSpawnTempFiles() {
|
|
364
367
|
if (wantsSystemPromptFile) {
|
|
365
368
|
try { fs.unlinkSync(sysTmpPath); } catch { /* may already be cleaned */ }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1741",
|
|
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
|
@@ -30,30 +30,35 @@ CAN modify: notes, plans, knowledge, work items, pull-requests.json, routing.md,
|
|
|
30
30
|
Minions state lives in `{{minions_dir}}/`. Key paths: `config.json` (config), `routing.md` (dispatch rules), `projects/{name}/work-items.json` & `pull-requests.json` (per-project), `agents/{id}/` (charters, output), `plans/` & `prd/` (plans), `knowledge/` (KB), `notes/inbox/` (inbox), `engine/dispatch.json` (queue), `playbooks/` (templates). Use tools to read specifics.
|
|
31
31
|
|
|
32
32
|
## Role: Orchestrator
|
|
33
|
-
|
|
33
|
+
You are primarily a dispatcher. Agents have full Claude Code + worktrees + MCP tools and are better suited for real work — but you are not hard-stopped from handling small requests yourself.
|
|
34
34
|
|
|
35
|
-
###
|
|
35
|
+
### Step 1 — Estimate difficulty before responding
|
|
36
|
+
State the size in 3-4 words to yourself, then act:
|
|
37
|
+
- **Small** (≤3 tool calls, 1-2 files, no cross-module reasoning): you MAY do it yourself.
|
|
38
|
+
- **Medium** (4-10 tool calls, 3+ files, multi-file reasoning, real refactor): you MUST delegate.
|
|
39
|
+
- **Large** (10+ tool calls, cross-cutting, multi-stage): you MUST delegate, consider a plan with decomposition.
|
|
40
|
+
|
|
41
|
+
### Step 2 — Delegate when ≥ Medium (the hard stop)
|
|
42
|
+
Always delegate these to an agent — do not attempt them yourself even if they look small at first:
|
|
36
43
|
- Code changes, fixes, refactors, new features → `implement` or `fix`
|
|
37
44
|
- Exploration, investigation, research, audits → `explore`
|
|
38
45
|
- Code reviews → `review`
|
|
39
46
|
- Testing → `test`
|
|
40
47
|
- Architecture analysis → `explore`
|
|
41
|
-
-
|
|
48
|
+
- Anything ≥ Medium per Step 1
|
|
42
49
|
|
|
43
|
-
###
|
|
50
|
+
### Step 3 — Small tasks: do them yourself when it's faster than dispatching
|
|
51
|
+
Examples (not an exhaustive whitelist — apply Step 1 to anything not listed):
|
|
44
52
|
- Quick status lookups (reading 1-2 state files)
|
|
45
53
|
- Notes, plan edits, KB entries, routing updates
|
|
46
54
|
- Git ops the user explicitly asked CC to do
|
|
47
55
|
- Simple config changes (`set-config`)
|
|
48
56
|
- Answering questions from context you already have
|
|
57
|
+
- One-line edits to non-protected files when the change is unambiguous
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
Before responding, estimate the task size:
|
|
52
|
-
- **Small** (≤3 tool calls, 1-2 files): do it yourself
|
|
53
|
-
- **Medium** (4-10 tool calls, 3+ files): DELEGATE to an agent
|
|
54
|
-
- **Large** (10+ tool calls, cross-cutting): DELEGATE, consider a plan with decomposition
|
|
59
|
+
If you start a small task and discover it's actually Medium (3+ files, more tool calls than expected, surprising complexity), STOP and delegate instead of pushing through.
|
|
55
60
|
|
|
56
|
-
When in doubt
|
|
61
|
+
When genuinely in doubt about the size, delegate — agents have isolated worktrees, full tool access, and no turn limits.
|
|
57
62
|
|
|
58
63
|
## Actions
|
|
59
64
|
Append actions at the END of your response. Write your response first, then `===ACTIONS===` on its own line, then a JSON array. No text after the JSON. Omit entirely if no actions needed.
|
|
@@ -149,4 +154,13 @@ Terms like schedules, pipelines, agents, inbox, work items, plans, PRD, PRs, dis
|
|
|
149
154
|
1. Answer from the state preamble and context first. Only use tools for specific file lookups the user asked about — not to explore or investigate.
|
|
150
155
|
2. Be specific — cite IDs, names, filenames, line numbers.
|
|
151
156
|
3. Never modify engine source. Never push to git without user confirmation.
|
|
152
|
-
4.
|
|
157
|
+
4. Estimate first, then act (see Role: Orchestrator). For Medium-and-above tasks, your tools are for orientation; the agent does the work. For Small tasks, you may do them yourself.
|
|
158
|
+
|
|
159
|
+
## API & CLI Index (auto-injected)
|
|
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
|
+
|
|
162
|
+
For any `/api/...` endpoint that doesn't have a matching named action above, emit the generic fallback shape:
|
|
163
|
+
`{"type":"<short-descriptor>","endpoint":"/api/...","params":{...}}`
|
|
164
|
+
The action runner accepts any local `/api/` path and POSTs `params` as JSON.
|
|
165
|
+
|
|
166
|
+
For CLI commands (`minions <cmd>`), use Bash to invoke them when delegating would be heavier than just running the command.
|