@yemi33/minions 0.1.981 → 0.1.983
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 -4
- package/dashboard.js +14 -2
- package/docs/deprecated.json +8 -0
- package/engine/cooldown.js +8 -0
- package/engine/lifecycle.js +31 -0
- package/engine/scheduler.js +6 -3
- package/engine/shared.js +14 -5
- package/engine/watches.js +1 -6
- package/engine.js +63 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.983 (2026-04-15)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
6
|
- gate build failure auto-fix behind autoFixBuilds flag
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
- add /api/work-items/reopen endpoint and reopen-work-item CC action (#982)
|
|
26
26
|
|
|
27
27
|
### Fixes
|
|
28
|
+
- Remove autoReview flag — consolidate into evalLoop (#1093)
|
|
29
|
+
- link scheduled task notes back to originating item (#1090)
|
|
30
|
+
- restore rereview and human-fix dispatch
|
|
28
31
|
- build fix sets fixDispatched to block same-tick conflict fix
|
|
29
32
|
- ENGINE_DEFAULTS undefined in tick + playbook empty-var false positives
|
|
30
33
|
- defer review setCooldown to post-gating in discoverWork
|
|
@@ -42,9 +45,6 @@
|
|
|
42
45
|
- restore queue flush after abort — queued messages are user intent
|
|
43
46
|
- CC stale lock auto-release + queue drain on abort
|
|
44
47
|
- CC streaming 'tabId' TDZ error on new tab first message
|
|
45
|
-
- fix tabId scope and close-event lock race in CC handlers
|
|
46
|
-
- defer setCooldown to post-gating in discoverWork
|
|
47
|
-
- fix CC queued message 'already processing' and thinking UX stacking
|
|
48
48
|
|
|
49
49
|
### Other
|
|
50
50
|
- refactor(watches): rename maxTriggers→stopAfter, add onNotMet per-poll action
|
package/dashboard.js
CHANGED
|
@@ -395,7 +395,13 @@ function getStatus() {
|
|
|
395
395
|
schedules: (() => {
|
|
396
396
|
const scheds = CONFIG.schedules || [];
|
|
397
397
|
const runs = shared.safeJson(path.join(MINIONS_DIR, 'engine', 'schedule-runs.json')) || {};
|
|
398
|
-
return scheds.map(s =>
|
|
398
|
+
return scheds.map(s => {
|
|
399
|
+
const runEntry = runs[s.id];
|
|
400
|
+
// Backward compat: runEntry can be a string (old format) or object (new format with back-references)
|
|
401
|
+
const _lastRun = typeof runEntry === 'string' ? runEntry : (runEntry?.lastRun || runEntry?.lastCompletedAt || null);
|
|
402
|
+
const extra = typeof runEntry === 'object' && runEntry ? { _lastWorkItemId: runEntry.lastWorkItemId, _lastResult: runEntry.lastResult, _lastCompletedAt: runEntry.lastCompletedAt } : {};
|
|
403
|
+
return { ...s, _lastRun, ...extra };
|
|
404
|
+
});
|
|
399
405
|
})(),
|
|
400
406
|
watches: watchesMod.getWatches(),
|
|
401
407
|
meetings: (() => { try { return require('./engine/meeting').getMeetings(); } catch { return []; } })(),
|
|
@@ -3683,7 +3689,13 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
3683
3689
|
reloadConfig();
|
|
3684
3690
|
const schedules = CONFIG.schedules || [];
|
|
3685
3691
|
const runs = shared.safeJson(path.join(MINIONS_DIR, 'engine', 'schedule-runs.json')) || {};
|
|
3686
|
-
const result = schedules.map(s =>
|
|
3692
|
+
const result = schedules.map(s => {
|
|
3693
|
+
const runEntry = runs[s.id];
|
|
3694
|
+
// Backward compat: runEntry can be a string (old format) or object (new format with back-references)
|
|
3695
|
+
const _lastRun = typeof runEntry === 'string' ? runEntry : (runEntry?.lastRun || runEntry?.lastCompletedAt || null);
|
|
3696
|
+
const extra = typeof runEntry === 'object' && runEntry ? { _lastWorkItemId: runEntry.lastWorkItemId, _lastResult: runEntry.lastResult, _lastCompletedAt: runEntry.lastCompletedAt } : {};
|
|
3697
|
+
return { ...s, _lastRun, ...extra };
|
|
3698
|
+
});
|
|
3687
3699
|
return jsonReply(res, 200, { schedules: result });
|
|
3688
3700
|
}
|
|
3689
3701
|
|
package/docs/deprecated.json
CHANGED
|
@@ -6,5 +6,13 @@
|
|
|
6
6
|
"reason": "Consolidated into review + evalLoop config. No separate evaluate dispatch.",
|
|
7
7
|
"locations": ["routing.md: evaluate row removed", "CLAUDE.md: evaluate references removed"],
|
|
8
8
|
"cleanup": "Already cleaned — routing.md and CLAUDE.md updated 2026-04-02"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "autoReview-flag",
|
|
12
|
+
"summary": "autoReview ENGINE_DEFAULT consolidated into evalLoop",
|
|
13
|
+
"deprecated": "2026-04-15",
|
|
14
|
+
"reason": "Redundant with evalLoop; evalLoop is the single gate for the review+fix cycle",
|
|
15
|
+
"locations": ["engine/shared.js ENGINE_DEFAULTS.autoReview", "engine.js discoverFromPrs autoReview variable"],
|
|
16
|
+
"cleanup": "Removed from ENGINE_DEFAULTS, removed autoReview variable from engine.js, replaced with reviewEnabled = evalLoopEnabled && pollEnabled"
|
|
9
17
|
}
|
|
10
18
|
]
|
package/engine/cooldown.js
CHANGED
|
@@ -90,6 +90,13 @@ function setCooldownFailure(key) {
|
|
|
90
90
|
saveCooldowns();
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
function clearCooldown(key) {
|
|
94
|
+
if (!dispatchCooldowns.has(key)) return false;
|
|
95
|
+
dispatchCooldowns.delete(key);
|
|
96
|
+
saveCooldowns();
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
93
100
|
function isAlreadyDispatched(key) {
|
|
94
101
|
const dispatch = queries.getDispatch();
|
|
95
102
|
// Check pending and active
|
|
@@ -130,6 +137,7 @@ module.exports = {
|
|
|
130
137
|
setCooldownWithContext,
|
|
131
138
|
getCoalescedContexts,
|
|
132
139
|
setCooldownFailure,
|
|
140
|
+
clearCooldown,
|
|
133
141
|
isAlreadyDispatched,
|
|
134
142
|
isBranchActive,
|
|
135
143
|
};
|
package/engine/lifecycle.js
CHANGED
|
@@ -1712,6 +1712,37 @@ async function runPostCompletionHooks(dispatchItem, agentId, code, stdout, confi
|
|
|
1712
1712
|
|
|
1713
1713
|
// Archive is manual — user archives plans from the dashboard when ready
|
|
1714
1714
|
|
|
1715
|
+
// Scheduled task back-reference: update schedule-runs.json and write linked inbox note
|
|
1716
|
+
if (meta?.item?._scheduleId) {
|
|
1717
|
+
try {
|
|
1718
|
+
const scheduleId = meta.item._scheduleId;
|
|
1719
|
+
const itemId = meta.item.id;
|
|
1720
|
+
const schedRunsPath = path.join(ENGINE_DIR, 'schedule-runs.json');
|
|
1721
|
+
mutateJsonFileLocked(schedRunsPath, (runs) => {
|
|
1722
|
+
runs[scheduleId] = {
|
|
1723
|
+
lastRun: typeof runs[scheduleId] === 'string' ? runs[scheduleId] : (runs[scheduleId]?.lastRun || ts()),
|
|
1724
|
+
lastWorkItemId: itemId,
|
|
1725
|
+
lastResult: effectiveSuccess ? DISPATCH_RESULT.SUCCESS : DISPATCH_RESULT.ERROR,
|
|
1726
|
+
lastCompletedAt: ts(),
|
|
1727
|
+
};
|
|
1728
|
+
return runs;
|
|
1729
|
+
}, { defaultValue: {} });
|
|
1730
|
+
// Write a completion note to inbox with back-references
|
|
1731
|
+
const noteSlug = `sched-completion-${scheduleId}`;
|
|
1732
|
+
const status = effectiveSuccess ? 'succeeded' : 'failed';
|
|
1733
|
+
const noteContent = `# Scheduled Task ${status}: ${meta.item.title || scheduleId}\n\n` +
|
|
1734
|
+
`**Schedule:** \`${scheduleId}\`\n` +
|
|
1735
|
+
`**Work Item:** \`${itemId}\`\n` +
|
|
1736
|
+
`**Result:** ${status}\n` +
|
|
1737
|
+
(resultSummary ? `\n## Summary\n${resultSummary}\n` : '');
|
|
1738
|
+
shared.writeToInbox('engine', noteSlug, noteContent, null, {
|
|
1739
|
+
sourceItem: itemId,
|
|
1740
|
+
scheduleId,
|
|
1741
|
+
});
|
|
1742
|
+
log('info', `Scheduled task ${scheduleId} (${itemId}) → ${status}, back-reference written`);
|
|
1743
|
+
} catch (err) { log('warn', `Scheduled task back-reference: ${err.message}`); }
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1715
1746
|
// Clean up worktree for non-shared-branch tasks after completion
|
|
1716
1747
|
if (meta?.branch && meta?.branchStrategy !== 'shared-branch') {
|
|
1717
1748
|
try {
|
package/engine/scheduler.js
CHANGED
|
@@ -116,7 +116,9 @@ function discoverScheduledWork(config) {
|
|
|
116
116
|
if (!sched.id || !sched.cron || !sched.title) continue;
|
|
117
117
|
if (!sched.enabled) continue; // truthy check — matches dashboard UI badge behavior
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
// Backward compat: runs[sched.id] can be a string (old format) or object (new format)
|
|
120
|
+
const runEntry = runs[sched.id] || null;
|
|
121
|
+
const lastRun = typeof runEntry === 'string' ? runEntry : (runEntry?.lastRun || null);
|
|
120
122
|
if (!shouldRunNow(sched, lastRun)) continue;
|
|
121
123
|
|
|
122
124
|
work.push({
|
|
@@ -133,8 +135,9 @@ function discoverScheduledWork(config) {
|
|
|
133
135
|
_scheduleId: sched.id,
|
|
134
136
|
});
|
|
135
137
|
|
|
136
|
-
// Record run time inside the lock
|
|
137
|
-
runs[sched.id]
|
|
138
|
+
// Record run time inside the lock — preserve existing fields (lastWorkItemId, lastResult, etc.)
|
|
139
|
+
const existing = typeof runs[sched.id] === 'object' && runs[sched.id] ? runs[sched.id] : {};
|
|
140
|
+
runs[sched.id] = { ...existing, lastRun: ts() };
|
|
138
141
|
}
|
|
139
142
|
}, { defaultValue: {} });
|
|
140
143
|
|
package/engine/shared.js
CHANGED
|
@@ -295,18 +295,22 @@ function parseNoteId(content) {
|
|
|
295
295
|
return m ? m[1] : null;
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
function writeToInbox(agentId, slug, content, _inboxDir) {
|
|
298
|
+
function writeToInbox(agentId, slug, content, _inboxDir, metadata) {
|
|
299
299
|
try {
|
|
300
300
|
const inboxDir = _inboxDir || path.join(MINIONS_DIR, 'notes', 'inbox');
|
|
301
301
|
const prefix = `${agentId}-${slug}-${dateStamp()}`;
|
|
302
302
|
const existing = safeReadDir(inboxDir).find(f => f.startsWith(prefix));
|
|
303
303
|
if (existing) return false;
|
|
304
304
|
const noteId = `NOTE-${uid()}`;
|
|
305
|
+
// Build optional metadata lines for frontmatter injection
|
|
306
|
+
const metaLines = (metadata && typeof metadata === 'object')
|
|
307
|
+
? Object.entries(metadata).filter(([, v]) => v != null).map(([k, v]) => `${k}: ${v}`).join('\n')
|
|
308
|
+
: '';
|
|
305
309
|
// Inject structured ID as YAML frontmatter if content doesn't already have it
|
|
306
310
|
const hasFrontmatter = /^\s*---[\r\n]/.test(content);
|
|
307
311
|
const tagged = hasFrontmatter
|
|
308
|
-
? content.replace(/^\s*---[\r\n]+/, `---\nid: ${noteId}\n`)
|
|
309
|
-
: `---\nid: ${noteId}\nagent: ${agentId}\ndate: ${dateStamp()}\n---\n\n${content}`;
|
|
312
|
+
? content.replace(/^\s*---[\r\n]+/, `---\nid: ${noteId}\n${metaLines ? metaLines + '\n' : ''}`)
|
|
313
|
+
: `---\nid: ${noteId}\nagent: ${agentId}\ndate: ${dateStamp()}\n${metaLines ? metaLines + '\n' : ''}---\n\n${content}`;
|
|
310
314
|
const filePath = path.join(inboxDir, `${prefix}.md`);
|
|
311
315
|
safeWrite(filePath, tagged);
|
|
312
316
|
return noteId;
|
|
@@ -532,7 +536,6 @@ const ENGINE_DEFAULTS = {
|
|
|
532
536
|
autoDecompose: true, // auto-decompose implement:large items into sub-tasks
|
|
533
537
|
autoApprovePlans: false, // auto-approve PRDs without waiting for human approval
|
|
534
538
|
autoArchive: false, // opt-in: auto-archive plans after verify completes (false = mark ready, user archives manually)
|
|
535
|
-
autoReview: true, // auto-dispatch review agents for new PRs (disable for manual review workflow)
|
|
536
539
|
autoFixConflicts: true, // auto-dispatch fix agents when a PR has merge conflicts
|
|
537
540
|
autoFixBuilds: true, // auto-dispatch fix agents when a PR build fails
|
|
538
541
|
meetingRoundTimeout: 600000, // 10min per meeting round before auto-advance
|
|
@@ -618,6 +621,12 @@ const PR_POLLABLE_STATUSES = new Set([PR_STATUS.ACTIVE, PR_STATUS.LINKED]);
|
|
|
618
621
|
const WATCH_STATUS = { ACTIVE: 'active', PAUSED: 'paused', TRIGGERED: 'triggered', EXPIRED: 'expired' };
|
|
619
622
|
const WATCH_TARGET_TYPE = { PR: 'pr', WORK_ITEM: 'work-item' };
|
|
620
623
|
const WATCH_CONDITION = { MERGED: 'merged', BUILD_FAIL: 'build-fail', BUILD_PASS: 'build-pass', COMPLETED: 'completed', FAILED: 'failed', STATUS_CHANGE: 'status-change', ANY: 'any' };
|
|
624
|
+
// Absolute conditions auto-expire on first trigger when stopAfter=0 (fire-once semantics).
|
|
625
|
+
// Change-based conditions (status-change, any) run forever when stopAfter=0.
|
|
626
|
+
const WATCH_ABSOLUTE_CONDITIONS = new Set([
|
|
627
|
+
WATCH_CONDITION.MERGED, WATCH_CONDITION.BUILD_FAIL, WATCH_CONDITION.BUILD_PASS,
|
|
628
|
+
WATCH_CONDITION.COMPLETED, WATCH_CONDITION.FAILED,
|
|
629
|
+
]);
|
|
621
630
|
|
|
622
631
|
/** Update per-agent review metrics (prsApproved/prsRejected). Only writes for configured agents. */
|
|
623
632
|
function trackReviewMetric(pr, newReviewStatus, config) {
|
|
@@ -1144,7 +1153,7 @@ module.exports = {
|
|
|
1144
1153
|
classifyInboxItem,
|
|
1145
1154
|
ENGINE_DEFAULTS,
|
|
1146
1155
|
WI_STATUS, DONE_STATUSES, PLAN_TERMINAL_STATUSES, WORK_TYPE, PLAN_STATUS, PRD_ITEM_STATUS, PRD_MATERIALIZABLE, PR_STATUS, PR_POLLABLE_STATUSES, DISPATCH_RESULT, trackReviewMetric, queuePlanToPrd,
|
|
1147
|
-
WATCH_STATUS, WATCH_TARGET_TYPE, WATCH_CONDITION,
|
|
1156
|
+
WATCH_STATUS, WATCH_TARGET_TYPE, WATCH_CONDITION, WATCH_ABSOLUTE_CONDITIONS,
|
|
1148
1157
|
PIPELINE_STATUS, STAGE_TYPE, MEETING_STATUS, AGENT_STATUS,
|
|
1149
1158
|
FAILURE_CLASS, ESCALATION_POLICY, COMPLETION_FIELDS,
|
|
1150
1159
|
DEFAULT_AGENT_METRICS,
|
package/engine/watches.js
CHANGED
|
@@ -237,15 +237,10 @@ function checkWatches(config, state) {
|
|
|
237
237
|
}
|
|
238
238
|
log('info', `Watch triggered: ${watch.id} — ${result.message}`);
|
|
239
239
|
|
|
240
|
-
// Expire
|
|
241
|
-
// change-based conditions (status-change, any) respect stopAfter literally (0 = run forever).
|
|
242
|
-
const isAbsolute = WATCH_ABSOLUTE_CONDITIONS.has(watch.condition);
|
|
240
|
+
// Expire when stopAfter limit is reached. stopAfter=0 means run forever (no limit).
|
|
243
241
|
if (watch.stopAfter > 0 && watch.triggerCount >= watch.stopAfter) {
|
|
244
242
|
watch.status = WATCH_STATUS.EXPIRED;
|
|
245
243
|
log('info', `Watch expired (stopAfter limit reached): ${watch.id}`);
|
|
246
|
-
} else if (isAbsolute && watch.stopAfter === 0) {
|
|
247
|
-
watch.status = WATCH_STATUS.EXPIRED;
|
|
248
|
-
log('info', `Watch expired (absolute condition auto-expire): ${watch.id}`);
|
|
249
244
|
}
|
|
250
245
|
} else if (watch.onNotMet === 'notify' && watch.owner) {
|
|
251
246
|
// Queue per-poll notification when condition is not yet met — unique key per poll
|
package/engine.js
CHANGED
|
@@ -1500,7 +1500,7 @@ function updateSnapshot(config) {
|
|
|
1500
1500
|
|
|
1501
1501
|
const { COOLDOWN_PATH, dispatchCooldowns, loadCooldowns, saveCooldowns,
|
|
1502
1502
|
isOnCooldown, setCooldown, setCooldownWithContext, getCoalescedContexts,
|
|
1503
|
-
setCooldownFailure, isAlreadyDispatched, isBranchActive } = require('./engine/cooldown');
|
|
1503
|
+
setCooldownFailure, clearCooldown, isAlreadyDispatched, isBranchActive } = require('./engine/cooldown');
|
|
1504
1504
|
|
|
1505
1505
|
|
|
1506
1506
|
|
|
@@ -1898,6 +1898,13 @@ async function discoverFromPrs(config, project) {
|
|
|
1898
1898
|
|
|
1899
1899
|
const projMeta = { name: project?.name, localPath: project?.localPath };
|
|
1900
1900
|
|
|
1901
|
+
// Resolve poll-enabled per project — stale reviewStatus is untrustworthy without poller
|
|
1902
|
+
const isAdoProject = project?.repoHost !== 'github';
|
|
1903
|
+
const pollEnabled = isAdoProject
|
|
1904
|
+
? (config.engine?.adoPollEnabled ?? DEFAULTS.adoPollEnabled)
|
|
1905
|
+
: (config.engine?.ghPollEnabled ?? DEFAULTS.ghPollEnabled);
|
|
1906
|
+
const evalLoopEnabled = config.engine?.evalLoop !== false;
|
|
1907
|
+
|
|
1901
1908
|
// Collect active PR dispatches to prevent simultaneous review+fix on same PR
|
|
1902
1909
|
const dispatch = getDispatch();
|
|
1903
1910
|
const activePrIds = new Set(
|
|
@@ -1945,10 +1952,10 @@ async function discoverFromPrs(config, project) {
|
|
|
1945
1952
|
log('warn', `PR ${pr.id}: review→fix escalated after ${evalCycles} cycles — suspending auto-dispatch`);
|
|
1946
1953
|
}
|
|
1947
1954
|
|
|
1948
|
-
// PRs needing review:
|
|
1949
|
-
const
|
|
1955
|
+
// PRs needing review: evalLoop gates the entire review+fix cycle; pollEnabled ensures reviewStatus is fresh
|
|
1956
|
+
const reviewEnabled = evalLoopEnabled && pollEnabled;
|
|
1950
1957
|
const alreadyReviewed = pr.lastReviewedAt && (!pr.lastPushedAt || pr.lastPushedAt <= pr.lastReviewedAt);
|
|
1951
|
-
const needsReview =
|
|
1958
|
+
const needsReview = reviewEnabled && reviewStatus === 'pending' && !alreadyReviewed && !evalEscalated;
|
|
1952
1959
|
if (needsReview) {
|
|
1953
1960
|
const key = `review-${project?.name || 'default'}-${pr.id}`;
|
|
1954
1961
|
if (isAlreadyDispatched(key) || isOnCooldown(key, cooldownMs)) continue;
|
|
@@ -1972,6 +1979,47 @@ async function discoverFromPrs(config, project) {
|
|
|
1972
1979
|
} catch {}
|
|
1973
1980
|
continue;
|
|
1974
1981
|
}
|
|
1982
|
+
} catch (e) { log('warn', `Pre-dispatch vote check for ${pr.id}: ${e.message} — skipping dispatch`); continue; }
|
|
1983
|
+
|
|
1984
|
+
const agentId = resolveAgent('review', config);
|
|
1985
|
+
if (!agentId) continue;
|
|
1986
|
+
|
|
1987
|
+
const item = buildPrDispatch(agentId, config, project, pr, 'review', {
|
|
1988
|
+
pr_id: pr.id, pr_number: prNumber, pr_title: pr.title || '', pr_branch: pr.branch || '',
|
|
1989
|
+
pr_author: pr.agent || '', pr_url: pr.url || '',
|
|
1990
|
+
}, `Review ${pr.id}: ${pr.title}`, { dispatchKey: key, source: 'pr', pr, branch: pr.branch, project: projMeta });
|
|
1991
|
+
if (item) { newWork.push(item); }
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
// Re-review after fix: trigger when a fix was pushed after the last minions review,
|
|
1995
|
+
// or when no minions review has completed yet (e.g. human-feedback-only fix path).
|
|
1996
|
+
const fixedAfterReview = !!(pr.minionsReview?.fixedAt &&
|
|
1997
|
+
(!pr.lastReviewedAt || pr.minionsReview.fixedAt > pr.lastReviewedAt));
|
|
1998
|
+
const needsReReview = autoReview && reviewStatus === 'waiting' &&
|
|
1999
|
+
fixedAfterReview && !evalEscalated;
|
|
2000
|
+
if (needsReReview) {
|
|
2001
|
+
const key = `rereview-${project?.name || 'default'}-${pr.id}`;
|
|
2002
|
+
// Skip isAlreadyDispatched — fixedAfterReview/lastReviewedAt already dedupe; the 1hr
|
|
2003
|
+
// completed-dispatch window would block legitimate re-reviews within the hour after a fix
|
|
2004
|
+
if (isOnCooldown(key, cooldownMs)) continue;
|
|
2005
|
+
|
|
2006
|
+
// Pre-dispatch live vote check — cached 'waiting' may be stale if reviewer already acted
|
|
2007
|
+
try {
|
|
2008
|
+
const checkFn = project.repoHost === 'github' ? ghCheckLiveReview : adoCheckLiveReview;
|
|
2009
|
+
const liveStatus = await checkFn(pr, project);
|
|
2010
|
+
if (liveStatus && liveStatus !== 'waiting') {
|
|
2011
|
+
log('info', `Pre-dispatch vote check: ${pr.id} is ${liveStatus} (cached was waiting) — skipping re-review`);
|
|
2012
|
+
if (pr.reviewStatus !== 'approved') pr.reviewStatus = liveStatus;
|
|
2013
|
+
try {
|
|
2014
|
+
mutateJsonFileLocked(projectPrPath(project), data => {
|
|
2015
|
+
if (!Array.isArray(data)) return data;
|
|
2016
|
+
const target = data.find(p => p.id === pr.id);
|
|
2017
|
+
if (target && target.reviewStatus !== 'approved') target.reviewStatus = liveStatus;
|
|
2018
|
+
return data;
|
|
2019
|
+
});
|
|
2020
|
+
} catch {}
|
|
2021
|
+
continue;
|
|
2022
|
+
}
|
|
1975
2023
|
} catch (e) { log('warn', `Pre-dispatch vote check for ${pr.id}: ${e.message}`); }
|
|
1976
2024
|
|
|
1977
2025
|
const agentId = resolveAgent('review', config);
|
|
@@ -2013,7 +2061,15 @@ async function discoverFromPrs(config, project) {
|
|
|
2013
2061
|
const hasCoalescedFeedback = (dispatchCooldowns.get(humanFixKey)?.pendingContexts || []).length > 0;
|
|
2014
2062
|
if ((pr.humanFeedback?.pendingFix || hasCoalescedFeedback) && !awaitingReReview && !fixDispatched) {
|
|
2015
2063
|
const key = humanFixKey;
|
|
2016
|
-
|
|
2064
|
+
let staleCoalesced = [];
|
|
2065
|
+
const alreadyDispatched = isAlreadyDispatched(key);
|
|
2066
|
+
const blockedByCooldown = isOnCooldown(key, cooldownMs);
|
|
2067
|
+
if (blockedByCooldown && !alreadyDispatched) {
|
|
2068
|
+
staleCoalesced = getCoalescedContexts(key);
|
|
2069
|
+
clearCooldown(key);
|
|
2070
|
+
log('info', `Cleared stale cooldown for ${key} — no matching dispatch history`);
|
|
2071
|
+
}
|
|
2072
|
+
if (alreadyDispatched || isOnCooldown(key, cooldownMs)) {
|
|
2017
2073
|
// Coalesce: save feedback for next dispatch
|
|
2018
2074
|
if (pr.humanFeedback?.feedbackContent) {
|
|
2019
2075
|
setCooldownWithContext(key, { feedbackContent: pr.humanFeedback.feedbackContent, timestamp: ts() });
|
|
@@ -2023,7 +2079,7 @@ async function discoverFromPrs(config, project) {
|
|
|
2023
2079
|
const agentId = resolveAgent('fix', config, pr.agent);
|
|
2024
2080
|
if (!agentId) continue;
|
|
2025
2081
|
|
|
2026
|
-
const coalesced = getCoalescedContexts(key);
|
|
2082
|
+
const coalesced = [...staleCoalesced, ...getCoalescedContexts(key)];
|
|
2027
2083
|
let reviewNote = pr.humanFeedback.feedbackContent || 'See PR thread comments';
|
|
2028
2084
|
if (coalesced.length > 0) {
|
|
2029
2085
|
const earlier = coalesced.map(c => c.feedbackContent).filter(Boolean).join('\n\n---\n\n');
|
|
@@ -2035,7 +2091,7 @@ async function discoverFromPrs(config, project) {
|
|
|
2035
2091
|
reviewer: 'Human Reviewer',
|
|
2036
2092
|
review_note: reviewNote,
|
|
2037
2093
|
}, `Fix ${pr.id}: ${pr.title || ''} — human feedback`, { dispatchKey: key, source: 'pr-human-feedback', pr, branch: pr.branch, project: projMeta });
|
|
2038
|
-
if (item) { newWork.push(item);
|
|
2094
|
+
if (item) { newWork.push(item); fixDispatched = true; }
|
|
2039
2095
|
}
|
|
2040
2096
|
|
|
2041
2097
|
// PRs with build failures — route to author (has session context from implementing)
|
|
@@ -3561,4 +3617,3 @@ if (require.main === module) {
|
|
|
3561
3617
|
const [cmd, ...args] = process.argv.slice(2);
|
|
3562
3618
|
handleCommand(cmd, args);
|
|
3563
3619
|
}
|
|
3564
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.983",
|
|
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"
|