@yemi33/minions 0.1.912 → 0.1.914
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 +4 -1
- package/dashboard/js/render-meetings.js +7 -0
- package/dashboard/js/render-plans.js +2 -2
- package/dashboard.js +26 -4
- package/engine.js +40 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.914 (2026-04-13)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
|
+
- fix dep re-merge failure on retry with existing commits (#977)
|
|
6
7
|
- audit and harden log buffering implementation (#971)
|
|
7
8
|
- replace magic string 'active' with PR_STATUS.ACTIVE in lifecycle.js (#969)
|
|
8
9
|
- fix dashboard plan-pause nested lock violation (#968)
|
|
@@ -16,6 +17,8 @@
|
|
|
16
17
|
- add red dot notification on CC tab when response completes (#934) (#946)
|
|
17
18
|
|
|
18
19
|
### Fixes
|
|
20
|
+
- prevent Create Plan from meeting saving doc-chat context bleed (#980)
|
|
21
|
+
- plan completion incorrectly overrides awaiting-approval status (#970) (#978)
|
|
19
22
|
- CC queued messages sent to wrong tab after tab switch
|
|
20
23
|
- pass sysPromptPath instead of steerPromptPath as system prompt in steering resume (#962)
|
|
21
24
|
- PRD item display dispatched/in-progress status conflation (closes #950) (#955)
|
|
@@ -438,12 +438,19 @@ async function _createPlanFromMeeting(id, btn) {
|
|
|
438
438
|
message: 'Create an actionable implementation plan from this meeting. Extract concrete action items from the conclusion and debates. For each item include: what to do, which files/areas to change, priority (high/medium/low), and estimated complexity (small/medium/large). Structure it as a plan ready for execution. Do NOT include preamble — start with the plan title.' + humanContext,
|
|
439
439
|
document: meetingDoc,
|
|
440
440
|
title: 'Meeting: ' + m.title,
|
|
441
|
+
freshSession: true,
|
|
441
442
|
})
|
|
442
443
|
});
|
|
443
444
|
const genData = await genRes.json();
|
|
444
445
|
if (!genRes.ok || !genData.ok) { resetBtn(); showToast('cmd-toast', 'Failed to generate plan: ' + (genData.error || 'unknown'), false); return; }
|
|
445
446
|
|
|
446
447
|
const planContent = genData.answer || '';
|
|
448
|
+
// Guard: reject doc-chat meta-responses that aren't plan content
|
|
449
|
+
if (!planContent.trim() || !/^(#|\*\*|[-*] )/.test(planContent.trim())) {
|
|
450
|
+
resetBtn();
|
|
451
|
+
showToast('cmd-toast', 'Generated content does not look like a plan — try again', false);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
447
454
|
const title = 'Meeting follow-up: ' + (m.title || id);
|
|
448
455
|
const planRes = await fetch('/api/plans/create', {
|
|
449
456
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
@@ -81,14 +81,14 @@ function derivePlanStatus(prdFile, mdFile, prdJsonStatus, workItems) {
|
|
|
81
81
|
if (prdJsonStatus === 'rejected') return 'rejected';
|
|
82
82
|
if (prdJsonStatus === 'paused' && !allDone) return 'paused';
|
|
83
83
|
if (prdJsonStatus === 'revision-requested') return 'revision-requested';
|
|
84
|
+
// PRD awaiting approval overrides WI-derived completion — new items may need materialization (#970)
|
|
85
|
+
if (prdJsonStatus === 'awaiting-approval') return 'awaiting-approval';
|
|
84
86
|
|
|
85
87
|
// Derive from work item progress
|
|
86
88
|
if (allDone && !hasActiveWork) return 'completed';
|
|
87
89
|
if (hasActiveWork) return 'dispatched';
|
|
88
90
|
if (hasPendingPrd) return 'converting';
|
|
89
91
|
if (hasFailed && !hasActiveWork) return 'has-failures';
|
|
90
|
-
|
|
91
|
-
if (prdJsonStatus === 'awaiting-approval' && implementWi.length === 0) return 'awaiting-approval';
|
|
92
92
|
if (prdJsonStatus === 'approved' && implementWi.length === 0) return 'approved';
|
|
93
93
|
|
|
94
94
|
// plan-to-prd conversion done but no implement items and PRD is gone — truly completed
|
package/dashboard.js
CHANGED
|
@@ -783,13 +783,21 @@ async function ccCall(message, { store = 'cc', sessionKey, extraContext, label =
|
|
|
783
783
|
}
|
|
784
784
|
|
|
785
785
|
// Doc-specific wrapper — adds document context, parses ---DOCUMENT---
|
|
786
|
-
async function ccDocCall({ message, document, title, filePath, selection, canEdit, isJson, model }) {
|
|
786
|
+
async function ccDocCall({ message, document, title, filePath, selection, canEdit, isJson, model, freshSession }) {
|
|
787
787
|
const sessionKey = filePath || title;
|
|
788
788
|
const docSlice = document.slice(0, 20000);
|
|
789
789
|
|
|
790
|
+
// freshSession: true → discard any prior session for this key so the call starts clean.
|
|
791
|
+
// Used by one-shot generation flows (e.g. Create Plan from meeting) that must not
|
|
792
|
+
// bleed context from earlier conversations.
|
|
793
|
+
if (freshSession && sessionKey) {
|
|
794
|
+
docSessions.delete(sessionKey);
|
|
795
|
+
// Skip persistDocSessions() here — the post-call cleanup below handles persistence.
|
|
796
|
+
}
|
|
797
|
+
|
|
790
798
|
// Skip re-sending full document on session resume if content unchanged
|
|
791
799
|
const docHash = require('crypto').createHash('md5').update(docSlice).digest('hex').slice(0, 8);
|
|
792
|
-
const existing = resolveSession('doc', sessionKey);
|
|
800
|
+
const existing = freshSession ? null : resolveSession('doc', sessionKey);
|
|
793
801
|
const docUnchanged = existing?.sessionId && existing._docHash === docHash;
|
|
794
802
|
|
|
795
803
|
let docContext;
|
|
@@ -808,8 +816,14 @@ async function ccDocCall({ message, document, title, filePath, selection, canEdi
|
|
|
808
816
|
skipStatePreamble: true,
|
|
809
817
|
...(model ? { model } : {}),
|
|
810
818
|
});
|
|
811
|
-
|
|
812
|
-
if (
|
|
819
|
+
|
|
820
|
+
if (freshSession && sessionKey) {
|
|
821
|
+
// One-shot call — discard the session ccCall just stored so it cannot
|
|
822
|
+
// bleed into future interactions under the same key.
|
|
823
|
+
docSessions.delete(sessionKey);
|
|
824
|
+
persistDocSessions();
|
|
825
|
+
} else if (result.code === 0 && result.sessionId) {
|
|
826
|
+
// Store doc hash for next call's unchanged check
|
|
813
827
|
const session = resolveSession('doc', sessionKey);
|
|
814
828
|
if (session) session._docHash = docHash;
|
|
815
829
|
}
|
|
@@ -2843,6 +2857,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
2843
2857
|
message: body.message, document: currentContent, title: body.title,
|
|
2844
2858
|
filePath: body.filePath, selection: body.selection, canEdit, isJson,
|
|
2845
2859
|
model: body.model || undefined,
|
|
2860
|
+
freshSession: !!body.freshSession,
|
|
2846
2861
|
});
|
|
2847
2862
|
|
|
2848
2863
|
if (!content) return jsonReply(res, 200, { ok: true, answer, edited: false, actions });
|
|
@@ -4105,6 +4120,13 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4105
4120
|
const { title, content, project: projectName, meetingId } = body;
|
|
4106
4121
|
if (!title || !content) return jsonReply(res, 400, { error: 'title and content required' });
|
|
4107
4122
|
|
|
4123
|
+
// Reject meta-responses that aren't actual plan content — e.g. doc-chat
|
|
4124
|
+
// summaries of prior conversations that reference existing plan files.
|
|
4125
|
+
const trimmed = content.trim();
|
|
4126
|
+
if (!/^#/.test(trimmed) && !/^\*\*/.test(trimmed) && !/^[-*] /.test(trimmed)) {
|
|
4127
|
+
return jsonReply(res, 400, { error: 'Plan content must start with a markdown heading (#), bold text (**), or a list item' });
|
|
4128
|
+
}
|
|
4129
|
+
|
|
4108
4130
|
const plansDir = path.join(MINIONS_DIR, 'plans');
|
|
4109
4131
|
if (!fs.existsSync(plansDir)) fs.mkdirSync(plansDir, { recursive: true });
|
|
4110
4132
|
const slug = shared.slugify(title);
|
package/engine.js
CHANGED
|
@@ -524,7 +524,37 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
524
524
|
}
|
|
525
525
|
// Merge successfully-fetched + recovered (local-only pushed) branches sequentially
|
|
526
526
|
const fetched = fetchable.filter((_, i) => fetchResults[i].status === 'fulfilled' || recoveredBranches.has(fetchable[i].branch));
|
|
527
|
-
if (
|
|
527
|
+
// Skip dep re-merge if worktree HEAD already contains all dep commits (#973)
|
|
528
|
+
let skipDepMerge = false;
|
|
529
|
+
if (!depMergeFailed && fetched.length > 0) {
|
|
530
|
+
const ancestorChecks = await Promise.all(
|
|
531
|
+
fetched.map(async ({ branch: depBranch }) => {
|
|
532
|
+
try {
|
|
533
|
+
await execAsync(`git merge-base --is-ancestor "origin/${depBranch}" HEAD`, { ..._gitOpts, cwd: worktreePath });
|
|
534
|
+
return true;
|
|
535
|
+
} catch (_) { return false; }
|
|
536
|
+
})
|
|
537
|
+
);
|
|
538
|
+
if (ancestorChecks.every(Boolean)) {
|
|
539
|
+
log('info', `All ${fetched.length} dep branch(es) already merged into ${branchName} — skipping dep re-merge`);
|
|
540
|
+
skipDepMerge = true;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
// Stash uncommitted changes before dep merge if worktree is dirty (#973)
|
|
544
|
+
let stashed = false;
|
|
545
|
+
if (!depMergeFailed && !skipDepMerge && fetched.length > 0) {
|
|
546
|
+
try {
|
|
547
|
+
const statusOut = (await execAsync('git status --porcelain', { ..._gitOpts, cwd: worktreePath })).stdout.toString().trim();
|
|
548
|
+
if (statusOut) {
|
|
549
|
+
await execAsync('git stash push --include-untracked -m "engine: stash before dep re-merge"', { ..._gitOpts, cwd: worktreePath });
|
|
550
|
+
stashed = true;
|
|
551
|
+
log('info', `Stashed uncommitted changes in ${branchName} before dep merge`);
|
|
552
|
+
}
|
|
553
|
+
} catch (stashErr) {
|
|
554
|
+
log('warn', `Failed to stash changes in ${branchName} before dep merge: ${stashErr.message}`);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (!depMergeFailed && !skipDepMerge) {
|
|
528
558
|
for (const { branch: depBranch, prId } of fetched) {
|
|
529
559
|
try {
|
|
530
560
|
await execAsync(`git merge "origin/${depBranch}" --no-edit`, { ..._gitOpts, cwd: worktreePath });
|
|
@@ -573,6 +603,15 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
573
603
|
}
|
|
574
604
|
}
|
|
575
605
|
}
|
|
606
|
+
// Restore stashed changes after dep merge (#973)
|
|
607
|
+
if (stashed) {
|
|
608
|
+
try {
|
|
609
|
+
await execAsync('git stash pop', { ..._gitOpts, cwd: worktreePath });
|
|
610
|
+
log('info', `Restored stashed changes in ${branchName} after dep merge`);
|
|
611
|
+
} catch (popErr) {
|
|
612
|
+
log('warn', `git stash pop failed in ${branchName}: ${popErr.message} — stash preserved for agent`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
576
615
|
if (depMergeFailed) {
|
|
577
616
|
_cleanupPromptFiles();
|
|
578
617
|
// Build actionable failReason identifying the conflicting branch and files
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.914",
|
|
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"
|