@yemi33/minions 0.1.1985 → 0.1.1987
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/bin/minions.js +3 -1
- package/dashboard/js/qa.js +53 -0
- package/dashboard/js/refresh.js +4 -2
- package/dashboard/js/render-managed.js +43 -9
- package/dashboard/js/render-other.js +41 -11
- package/dashboard/layout.html +1 -0
- package/dashboard/pages/qa.html +23 -0
- package/dashboard-build.js +2 -2
- package/dashboard.js +135 -24
- package/docs/README.md +2 -0
- package/docs/constellation-bridge.md +94 -0
- package/docs/security.md +177 -0
- package/engine/bridge.js +124 -0
- package/engine/cc-worker-pool.js +48 -1
- package/engine/cleanup.js +72 -23
- package/engine/cli.js +126 -12
- package/engine/dispatch.js +24 -11
- package/engine/github.js +79 -26
- package/engine/issues.js +14 -3
- package/engine/lifecycle.js +47 -11
- package/engine/llm.js +16 -9
- package/engine/meeting.js +16 -5
- package/engine/queries.js +123 -52
- package/engine/shared.js +265 -5
- package/engine/spawn-agent.js +13 -5
- package/engine/timeout.js +4 -2
- package/engine.js +59 -15
- package/package.json +1 -1
package/engine.js
CHANGED
|
@@ -586,7 +586,16 @@ async function syncReusedWorktree(rootDir, worktreePath, branchName, gitOpts = {
|
|
|
586
586
|
async function findExistingWorktree(repoDir, branchName) {
|
|
587
587
|
try {
|
|
588
588
|
const out = await shared.shellSafeGit(['worktree', 'list', '--porcelain'], { cwd: repoDir, timeout: 10000 });
|
|
589
|
-
|
|
589
|
+
// Skip worktrees at or inside `repoDir` itself: the main checkout (always
|
|
590
|
+
// first in the porcelain output) sits AT repoDir, and nested worktrees
|
|
591
|
+
// under it would trip assertWorktreeOutsideProject downstream anyway.
|
|
592
|
+
// Returning either as "reusable" creates an unresolvable preflight
|
|
593
|
+
// rejection loop when the branch happens to be checked out at the
|
|
594
|
+
// project root (common when an operator works on the minions repo
|
|
595
|
+
// locally while the engine tries to review that same branch).
|
|
596
|
+
const found = shared.parseWorktreePorcelain(out).find(w =>
|
|
597
|
+
w.branch === branchName && !shared.isPathInsideOrEqual(w.path, repoDir)
|
|
598
|
+
);
|
|
590
599
|
if (found && fs.existsSync(found.path)) return found.path;
|
|
591
600
|
} catch (e) { log('warn', 'git: ' + e.message); }
|
|
592
601
|
return null;
|
|
@@ -926,14 +935,34 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
926
935
|
: taskPromptWithReport;
|
|
927
936
|
};
|
|
928
937
|
let fullTaskPrompt = buildFullTaskPrompt(taskPrompt);
|
|
929
|
-
const tmpDir = path.join(ENGINE_DIR, 'tmp');
|
|
930
|
-
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, { recursive: true });
|
|
931
938
|
const safeId = id.replace(/[:\\/*?"<>|]/g, '-');
|
|
932
|
-
|
|
939
|
+
// P-f6-tmp-toctou: per-dispatch unique tmp dir closes the symlink-plant
|
|
940
|
+
// window on engine/tmp/prompt-<id>.md (mkdtempSync + chmod 0o700). Filename
|
|
941
|
+
// pattern inside the dir is unchanged so spawn-agent.js's prompt→pid regex
|
|
942
|
+
// derivation still works.
|
|
943
|
+
const dispatchTmpDir = shared.createDispatchTmpDir(id);
|
|
944
|
+
const promptPath = path.join(dispatchTmpDir, `prompt-${safeId}.md`);
|
|
933
945
|
safeWrite(promptPath, fullTaskPrompt);
|
|
934
|
-
const sysPromptPath = path.join(
|
|
946
|
+
const sysPromptPath = path.join(dispatchTmpDir, `sysprompt-${safeId}.md`);
|
|
935
947
|
safeWrite(sysPromptPath, systemPrompt);
|
|
936
|
-
|
|
948
|
+
// Stamp the tmpDir on the dispatch entry so cleanup/orphan-reap/kill paths
|
|
949
|
+
// (dispatch.js, meeting.js, cli.js, cleanup.js, timeout.js) can find the
|
|
950
|
+
// dir via shared.dispatchPidCandidates / findDispatchPidFile. Best-effort —
|
|
951
|
+
// backward-compat fallbacks still scan dispatch-<safeId>-* dirs by id.
|
|
952
|
+
try {
|
|
953
|
+
dispatchItem.tmpDir = dispatchTmpDir;
|
|
954
|
+
const dispatchPath = path.join(MINIONS_DIR, 'engine', 'dispatch.json');
|
|
955
|
+
mutateJsonFileLocked(dispatchPath, (dispatch) => {
|
|
956
|
+
for (const queue of ['pending', 'active', 'completed']) {
|
|
957
|
+
const arr = Array.isArray(dispatch?.[queue]) ? dispatch[queue] : null;
|
|
958
|
+
if (!arr) continue;
|
|
959
|
+
const found = arr.find(d => d && d.id === id);
|
|
960
|
+
if (found) found.tmpDir = dispatchTmpDir;
|
|
961
|
+
}
|
|
962
|
+
return dispatch;
|
|
963
|
+
}, { defaultValue: { pending: [], active: [], completed: [] } });
|
|
964
|
+
} catch (e) { log('warn', `spawnAgent: failed to persist tmpDir for ${id}: ${e.message}`); }
|
|
965
|
+
const _cleanupPromptFiles = () => { shared.removeDispatchTmpDir(dispatchTmpDir); };
|
|
937
966
|
// Convert a WORKTREE_NESTED_IN_PROJECT throw into a fail-fast non-retryable
|
|
938
967
|
// dispatch failure (W-mp62taw2000ubcc3). The error's `.code` is set by
|
|
939
968
|
// shared.assertWorktreeOutsideProject so we don't have to parse the message.
|
|
@@ -1834,6 +1863,9 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1834
1863
|
if (pidFilePath) {
|
|
1835
1864
|
try { safeUnlink(pidFilePath); } catch { /* may not exist yet */ }
|
|
1836
1865
|
}
|
|
1866
|
+
// P-f6-tmp-toctou: rm the per-dispatch tmp dir too so prompt/sysprompt
|
|
1867
|
+
// sidecars don't leak when runFile throws before onAgentClose can run.
|
|
1868
|
+
try { _cleanupPromptFiles(); } catch { /* cleanup is best-effort */ }
|
|
1837
1869
|
cleanupTempAgent(agentId);
|
|
1838
1870
|
throw spawnErr;
|
|
1839
1871
|
}
|
|
@@ -1974,7 +2006,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1974
2006
|
const pendingForResume = steering.buildPendingSteeringPrompt(agentId);
|
|
1975
2007
|
const steerPromptBody = pendingForResume.prompt || steerMsg;
|
|
1976
2008
|
const steerPrompt = `Message from your human teammate:\n\n${steerPromptBody}\n\nRespond to this, then continue working on your current task.`;
|
|
1977
|
-
const steerPromptPath = path.join(
|
|
2009
|
+
const steerPromptPath = path.join(dispatchTmpDir, `prompt-steer-${safeId}.md`);
|
|
1978
2010
|
try { safeWrite(steerPromptPath, steerPrompt); } catch (e) {
|
|
1979
2011
|
log('warn', `Steering: failed to write prompt for ${agentId}: ${e.message}`);
|
|
1980
2012
|
try { fs.appendFileSync(liveOutputPath, `\n[steering-failed] Could not write prompt. Message was: ${steerMsg}\n`); } catch {}
|
|
@@ -2832,10 +2864,11 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
2832
2864
|
} catch (e) { log('warn', `keep-processes acceptance: failed to set _pendingReason: ${e.message}`); }
|
|
2833
2865
|
}
|
|
2834
2866
|
|
|
2835
|
-
// Cleanup temp files (
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2867
|
+
// Cleanup temp files (whole per-dispatch dir, including PID/sysprompt
|
|
2868
|
+
// tmp/prompt — P-f6-tmp-toctou). removeDispatchTmpDir validates the path
|
|
2869
|
+
// resolves under engine/tmp/dispatch-* before rmSync, so a corrupted
|
|
2870
|
+
// dispatch.json field can't redirect this to an arbitrary path.
|
|
2871
|
+
try { shared.removeDispatchTmpDir(dispatchTmpDir); } catch { /* cleanup */ }
|
|
2839
2872
|
|
|
2840
2873
|
log('info', `Agent ${agentId} completed. Output saved to ${archivePath}`);
|
|
2841
2874
|
|
|
@@ -5028,10 +5061,19 @@ function materializeSpecsAsWorkItems(config, project) {
|
|
|
5028
5061
|
let recentSpecs = [];
|
|
5029
5062
|
for (const pattern of filePatterns) {
|
|
5030
5063
|
try {
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5064
|
+
// P-f7-git-log: argv form via execFileSync (shell:false) so file patterns
|
|
5065
|
+
// from operator config.json reach git as literal pathspec args, never as
|
|
5066
|
+
// shell tokens. Spread the pattern after the '--' pathspec separator.
|
|
5067
|
+
const args = [
|
|
5068
|
+
'log',
|
|
5069
|
+
'--diff-filter=AM',
|
|
5070
|
+
'--name-only',
|
|
5071
|
+
'--pretty=format:COMMIT:%H|%s',
|
|
5072
|
+
`--since=${sinceDate}`,
|
|
5073
|
+
'--',
|
|
5074
|
+
pattern,
|
|
5075
|
+
];
|
|
5076
|
+
const result = shared.shellSafeGitSync(args, { cwd: root, timeout: 10000 }).trim();
|
|
5035
5077
|
if (!result) continue;
|
|
5036
5078
|
|
|
5037
5079
|
let currentCommit = null;
|
|
@@ -6539,6 +6581,7 @@ module.exports = {
|
|
|
6539
6581
|
// Discovery
|
|
6540
6582
|
discoverWork, discoverFromPrs, discoverFromWorkItems, discoverCentralWorkItems,
|
|
6541
6583
|
materializePlansAsWorkItems,
|
|
6584
|
+
materializeSpecsAsWorkItems, // exported for testing (P-f7-git-log)
|
|
6542
6585
|
reservePrdFilename, // exported for testing (P-9b7e5d3c)
|
|
6543
6586
|
sweepStaleArchivedPrdBackups, // exported for testing
|
|
6544
6587
|
|
|
@@ -6548,6 +6591,7 @@ module.exports = {
|
|
|
6548
6591
|
buildDepConflictFixItem, // exported for testing (W-mpcwojgr000a0244)
|
|
6549
6592
|
isWorktreeRetryableError, removeStaleIndexLock, syncReusedWorktree, assertCleanSharedWorktree, // exported for testing
|
|
6550
6593
|
pruneStaleWorktreeForBranch, // exported for testing
|
|
6594
|
+
findExistingWorktree, // exported for testing
|
|
6551
6595
|
_maxTurnsForType, buildProjectContext, normalizeAc, _buildAgentSpawnFlags, _classifyAgentFailure, // exported for testing
|
|
6552
6596
|
promoteCheckpointSteeringForClose, // exported for testing
|
|
6553
6597
|
normalizePrBranch, resolvePrBranch, prCausePart, getPrCauseHead, getPrCauseBase, getPrAutomationCauseKey, getPrAutomationDispatchKey, // exported for testing
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1987",
|
|
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"
|