@yemi33/minions 0.1.983 → 0.1.985
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 -5
- package/dashboard/js/render-watches.js +4 -0
- package/dashboard/js/settings.js +4 -2
- package/dashboard.js +18 -0
- package/engine/lifecycle.js +3 -4
- package/engine/shared.js +1 -1
- package/engine/watches.js +14 -3
- package/engine.js +3 -2
- package/package.json +1 -1
- package/prompts/cc-system.md +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.985 (2026-04-15)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
|
+
- complete watches management — new conditions, CC actions, PROJECTS fix (#1103)
|
|
7
|
+
- fix misleading poll frequency field labels in settings modal (#1099)
|
|
8
|
+
- add Auto-complete PRs toggle to settings modal (#1095)
|
|
6
9
|
- gate build failure auto-fix behind autoFixBuilds flag
|
|
7
10
|
- free-form interval input + CC create-watch action
|
|
8
11
|
- flush queued CC messages as single combined request
|
|
@@ -20,11 +23,9 @@
|
|
|
20
23
|
- Update command-center.js to capture tool inputs and use formatted summaries
|
|
21
24
|
- certificate-based auth for Teams integration (#1027)
|
|
22
25
|
- Create render-utils.js with shared formatting helpers
|
|
23
|
-
- add adoPollEnabled/ghPollEnabled engine settings
|
|
24
|
-
- doc-chat abort kills LLM process + queued messages auto-process
|
|
25
|
-
- add /api/work-items/reopen endpoint and reopen-work-item CC action (#982)
|
|
26
26
|
|
|
27
27
|
### Fixes
|
|
28
|
+
- fix syncPrsFromOutput skipping tool_result lines where PR URLs live (#1100)
|
|
28
29
|
- Remove autoReview flag — consolidate into evalLoop (#1093)
|
|
29
30
|
- link scheduled task notes back to originating item (#1090)
|
|
30
31
|
- restore rereview and human-fix dispatch
|
|
@@ -44,7 +45,6 @@
|
|
|
44
45
|
- skip orphaned LLM retry when client disconnected during CC stream
|
|
45
46
|
- restore queue flush after abort — queued messages are user intent
|
|
46
47
|
- CC stale lock auto-release + queue drain on abort
|
|
47
|
-
- CC streaming 'tabId' TDZ error on new tab first message
|
|
48
48
|
|
|
49
49
|
### Other
|
|
50
50
|
- refactor(watches): rename maxTriggers→stopAfter, add onNotMet per-poll action
|
|
@@ -22,6 +22,8 @@ const _WATCH_CONDITION_LABELS = {
|
|
|
22
22
|
failed: 'Failed',
|
|
23
23
|
'status-change': 'Status Change',
|
|
24
24
|
any: 'Any Change',
|
|
25
|
+
'new-comments': 'New Comments',
|
|
26
|
+
'vote-change': 'Vote Change',
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
function _intervalToHuman(ms) {
|
|
@@ -222,6 +224,8 @@ function _watchFormHtml() {
|
|
|
222
224
|
{ value: 'failed', label: 'Failed' },
|
|
223
225
|
{ value: 'status-change', label: 'Status Change' },
|
|
224
226
|
{ value: 'any', label: 'Any Change' },
|
|
227
|
+
{ value: 'new-comments', label: 'New Comments' },
|
|
228
|
+
{ value: 'vote-change', label: 'Vote Change' },
|
|
225
229
|
];
|
|
226
230
|
var ttOpts = targetTypes.map(function(t) { return '<option value="' + t.value + '">' + t.label + '</option>'; }).join('');
|
|
227
231
|
var condOpts = conditions.map(function(c) { return '<option value="' + c.value + '">' + c.label + '</option>'; }).join('');
|
package/dashboard/js/settings.js
CHANGED
|
@@ -51,12 +51,13 @@ async function openSettings() {
|
|
|
51
51
|
settingsToggle('Auto-archive Plans', 'set-autoArchive', !!e.autoArchive, 'Automatically archive plans after verify completes (off = manual archive via dashboard)') +
|
|
52
52
|
settingsToggle('Auto-fix Builds', 'set-autoFixBuilds', e.autoFixBuilds !== false, 'Auto-dispatch fix agents when a PR build fails') +
|
|
53
53
|
settingsToggle('Auto-fix Conflicts', 'set-autoFixConflicts', e.autoFixConflicts !== false, 'Auto-dispatch fix agents when a PR has merge conflicts') +
|
|
54
|
+
settingsToggle('Auto-complete PRs', 'set-autoCompletePrs', !!e.autoCompletePrs, 'Auto-merge PRs when builds pass and review is approved (opt-in)') +
|
|
54
55
|
settingsToggle('ADO Polling', 'set-adoPollEnabled', e.adoPollEnabled !== false, 'Poll ADO PR status and comments each tick (reconciliation always runs)') +
|
|
55
56
|
settingsToggle('GitHub Polling', 'set-ghPollEnabled', e.ghPollEnabled !== false, 'Poll GitHub PR status and comments each tick (reconciliation always runs)') +
|
|
56
57
|
'</div>' +
|
|
57
58
|
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px">' +
|
|
58
|
-
settingsField('
|
|
59
|
-
settingsField('
|
|
59
|
+
settingsField('PR Status Poll Frequency', 'set-adoPollStatusEvery', e.adoPollStatusEvery || 6, 'ticks', 'Poll PR build/review/merge status every N ticks for both ADO and GitHub (~6 min at default tick rate)') +
|
|
60
|
+
settingsField('PR Comments Poll Frequency', 'set-adoPollCommentsEvery', e.adoPollCommentsEvery || 12, 'ticks', 'Poll PR human comments every N ticks for both ADO and GitHub (~12 min at default tick rate)') +
|
|
60
61
|
'</div>' +
|
|
61
62
|
'<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px">' +
|
|
62
63
|
settingsField('Eval Max Iterations', 'set-evalMaxIterations', e.evalMaxIterations || 3, '', 'Max review→fix cycles before escalating (1-10)') +
|
|
@@ -242,6 +243,7 @@ async function saveSettings() {
|
|
|
242
243
|
autoArchive: document.getElementById('set-autoArchive').checked,
|
|
243
244
|
autoFixBuilds: document.getElementById('set-autoFixBuilds').checked,
|
|
244
245
|
autoFixConflicts: document.getElementById('set-autoFixConflicts').checked,
|
|
246
|
+
autoCompletePrs: document.getElementById('set-autoCompletePrs').checked,
|
|
245
247
|
adoPollEnabled: document.getElementById('set-adoPollEnabled').checked,
|
|
246
248
|
ghPollEnabled: document.getElementById('set-ghPollEnabled').checked,
|
|
247
249
|
adoPollStatusEvery: document.getElementById('set-adoPollStatusEvery').value,
|
package/dashboard.js
CHANGED
|
@@ -709,6 +709,24 @@ async function executeCCActions(actions) {
|
|
|
709
709
|
results.push({ type: 'create-watch', id: watch.id, ok: true });
|
|
710
710
|
break;
|
|
711
711
|
}
|
|
712
|
+
case 'delete-watch': {
|
|
713
|
+
const deleted = watchesMod.deleteWatch(action.id);
|
|
714
|
+
if (deleted) invalidateStatusCache();
|
|
715
|
+
results.push({ type: 'delete-watch', id: action.id, ok: deleted });
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
case 'pause-watch': {
|
|
719
|
+
const paused = watchesMod.updateWatch(action.id, { status: shared.WATCH_STATUS.PAUSED });
|
|
720
|
+
if (paused) invalidateStatusCache();
|
|
721
|
+
results.push({ type: 'pause-watch', id: action.id, ok: !!paused });
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
724
|
+
case 'resume-watch': {
|
|
725
|
+
const resumed = watchesMod.updateWatch(action.id, { status: shared.WATCH_STATUS.ACTIVE });
|
|
726
|
+
if (resumed) invalidateStatusCache();
|
|
727
|
+
results.push({ type: 'resume-watch', id: action.id, ok: !!resumed });
|
|
728
|
+
break;
|
|
729
|
+
}
|
|
712
730
|
default:
|
|
713
731
|
// Server didn't handle — frontend must execute
|
|
714
732
|
results.push({ type: action.type });
|
package/engine/lifecycle.js
CHANGED
|
@@ -707,15 +707,14 @@ function syncPrsFromOutput(output, agentId, meta, config) {
|
|
|
707
707
|
const lines = output.split('\n');
|
|
708
708
|
for (const line of lines) {
|
|
709
709
|
try {
|
|
710
|
-
if (!line.includes('"type":"assistant"') && !line.includes('"type":"result"')) continue;
|
|
710
|
+
if (!line.includes('"type":"assistant"') && !line.includes('"type":"result"') && !line.includes('"type":"user"')) continue;
|
|
711
711
|
const parsed = JSON.parse(line);
|
|
712
712
|
const content = parsed.message?.content || [];
|
|
713
713
|
for (const block of content) {
|
|
714
|
+
// Scan tool_result blocks in user messages for PR URLs (gh pr create output lands here)
|
|
714
715
|
if (block.type === 'tool_result' && block.content) {
|
|
715
716
|
const text = typeof block.content === 'string' ? block.content : JSON.stringify(block.content);
|
|
716
|
-
|
|
717
|
-
while ((match = urlPattern.exec(text)) !== null) prMatches.add(match[1] || match[2]);
|
|
718
|
-
}
|
|
717
|
+
while ((match = urlPattern.exec(text)) !== null) prMatches.add(match[1] || match[2]);
|
|
719
718
|
}
|
|
720
719
|
// Also scan assistant text blocks for PR URLs and "PR created" patterns
|
|
721
720
|
if (block.type === 'text' && block.text) {
|
package/engine/shared.js
CHANGED
|
@@ -620,7 +620,7 @@ const PR_POLLABLE_STATUSES = new Set([PR_STATUS.ACTIVE, PR_STATUS.LINKED]);
|
|
|
620
620
|
// Watch statuses — engine-level persistent watches that survive restarts
|
|
621
621
|
const WATCH_STATUS = { ACTIVE: 'active', PAUSED: 'paused', TRIGGERED: 'triggered', EXPIRED: 'expired' };
|
|
622
622
|
const WATCH_TARGET_TYPE = { PR: 'pr', WORK_ITEM: 'work-item' };
|
|
623
|
-
const WATCH_CONDITION = { MERGED: 'merged', BUILD_FAIL: 'build-fail', BUILD_PASS: 'build-pass', COMPLETED: 'completed', FAILED: 'failed', STATUS_CHANGE: 'status-change', ANY: 'any' };
|
|
623
|
+
const WATCH_CONDITION = { MERGED: 'merged', BUILD_FAIL: 'build-fail', BUILD_PASS: 'build-pass', COMPLETED: 'completed', FAILED: 'failed', STATUS_CHANGE: 'status-change', ANY: 'any', NEW_COMMENTS: 'new-comments', VOTE_CHANGE: 'vote-change' };
|
|
624
624
|
// Absolute conditions auto-expire on first trigger when stopAfter=0 (fire-once semantics).
|
|
625
625
|
// Change-based conditions (status-change, any) run forever when stopAfter=0.
|
|
626
626
|
const WATCH_ABSOLUTE_CONDITIONS = new Set([
|
package/engine/watches.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const shared = require('./shared');
|
|
13
13
|
const { safeJson, mutateJsonFileLocked, ts, uid, log, writeToInbox,
|
|
14
|
-
WATCH_STATUS, WATCH_TARGET_TYPE, WATCH_CONDITION
|
|
14
|
+
WATCH_STATUS, WATCH_TARGET_TYPE, WATCH_CONDITION } = shared;
|
|
15
15
|
|
|
16
16
|
// Dynamic path — respects MINIONS_TEST_DIR for test isolation
|
|
17
17
|
function _watchesPath() { return path.join(shared.MINIONS_DIR, 'engine', 'watches.json'); }
|
|
@@ -158,6 +158,16 @@ function evaluateWatch(watch, state) {
|
|
|
158
158
|
);
|
|
159
159
|
return { triggered: anyChanged, message: anyChanged ? `PR ${target} changed` : '' };
|
|
160
160
|
}
|
|
161
|
+
case WATCH_CONDITION.NEW_COMMENTS: {
|
|
162
|
+
const lastCommentDate = pr.humanFeedback?.lastProcessedCommentDate || null;
|
|
163
|
+
const prevCommentDate = prevState.lastCommentDate || null;
|
|
164
|
+
const hasNew = lastCommentDate && lastCommentDate !== prevCommentDate;
|
|
165
|
+
return { triggered: !!hasNew, message: hasNew ? `PR ${target} has a new comment (${lastCommentDate})` : '' };
|
|
166
|
+
}
|
|
167
|
+
case WATCH_CONDITION.VOTE_CHANGE: {
|
|
168
|
+
const changed = prevState.reviewStatus !== undefined && prevState.reviewStatus !== pr.reviewStatus;
|
|
169
|
+
return { triggered: changed, message: changed ? `PR ${target} vote changed: ${prevState.reviewStatus} → ${pr.reviewStatus}` : '' };
|
|
170
|
+
}
|
|
161
171
|
default:
|
|
162
172
|
return { triggered: false, message: `Unknown condition: ${condition}` };
|
|
163
173
|
}
|
|
@@ -237,7 +247,8 @@ function checkWatches(config, state) {
|
|
|
237
247
|
}
|
|
238
248
|
log('info', `Watch triggered: ${watch.id} — ${result.message}`);
|
|
239
249
|
|
|
240
|
-
// Expire when stopAfter
|
|
250
|
+
// Expire when stopAfter > 0 and trigger count reaches the limit.
|
|
251
|
+
// stopAfter: 0 means "run forever" for all condition types.
|
|
241
252
|
if (watch.stopAfter > 0 && watch.triggerCount >= watch.stopAfter) {
|
|
242
253
|
watch.status = WATCH_STATUS.EXPIRED;
|
|
243
254
|
log('info', `Watch expired (stopAfter limit reached): ${watch.id}`);
|
|
@@ -279,7 +290,7 @@ function _captureState(watch, state) {
|
|
|
279
290
|
const pr = (state.pullRequests || []).find(p =>
|
|
280
291
|
String(p.prNumber) === String(watch.target) || p.id === watch.target
|
|
281
292
|
);
|
|
282
|
-
if (pr) return { status: pr.status, buildStatus: pr.buildStatus, reviewStatus: pr.reviewStatus };
|
|
293
|
+
if (pr) return { status: pr.status, buildStatus: pr.buildStatus, reviewStatus: pr.reviewStatus, lastCommentDate: pr.humanFeedback?.lastProcessedCommentDate || null };
|
|
283
294
|
}
|
|
284
295
|
if (watch.targetType === WATCH_TARGET_TYPE.WORK_ITEM) {
|
|
285
296
|
const wi = (state.workItems || []).find(w => w.id === watch.target);
|
package/engine.js
CHANGED
|
@@ -3188,11 +3188,12 @@ async function tickInner() {
|
|
|
3188
3188
|
if (tickCount % 3 === 0) {
|
|
3189
3189
|
safe('checkWatches', () => {
|
|
3190
3190
|
const { checkWatches } = require('./engine/watches');
|
|
3191
|
-
const
|
|
3191
|
+
const projects = getProjects(config);
|
|
3192
|
+
const pullRequests = projects.flatMap(p => {
|
|
3192
3193
|
const prPath = path.join(MINIONS_DIR, 'projects', p.name, 'pull-requests.json');
|
|
3193
3194
|
return safeJson(prPath) || [];
|
|
3194
3195
|
});
|
|
3195
|
-
const workItems =
|
|
3196
|
+
const workItems = projects.flatMap(p => {
|
|
3196
3197
|
const wiPath = path.join(MINIONS_DIR, 'projects', p.name, 'work-items.json');
|
|
3197
3198
|
return safeJson(wiPath) || [];
|
|
3198
3199
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.985",
|
|
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
|
@@ -82,9 +82,15 @@ Additional actions (all take `id` or `file` as primary key):
|
|
|
82
82
|
- Meetings: add-meeting-note (id, note), advance-meeting (id), end-meeting (id), archive-meeting (id), unarchive-meeting (id), delete-meeting (id)
|
|
83
83
|
- Work item ops: delete-work-item (id, source), archive-work-item (id), work-item-feedback (id, rating: up/down, comment), reopen-work-item (id, project[, description] — reopen a done/failed item back to pending)
|
|
84
84
|
- PRD ops: edit-prd-item, remove-prd-item, reopen-prd-item (id, file — re-dispatches on existing branch)
|
|
85
|
-
- **create-watch**: target, targetType (pr/work-item
|
|
85
|
+
- **create-watch**: target, targetType (pr/work-item), condition (merged/build-fail/build-pass/completed/failed/status-change/any/new-comments/vote-change), interval ("15m", "1h", "30s" — default "5m"), owner (agent id or "human"), description, project, stopAfter (0=run forever, 1=expire on first match, N=expire after N matches), onNotMet (null=do nothing, "notify"=write to inbox each poll while condition not met)
|
|
86
86
|
Example: user says "check PR 1065 build every 15 min until green" → `{"type":"create-watch","target":"1065","targetType":"pr","condition":"build-pass","interval":"15m","stopAfter":1,"description":"Watch PR 1065 build until green"}`
|
|
87
87
|
Example: user says "ping me every 15 min while build is still failing" → `{"type":"create-watch","target":"1065","targetType":"pr","condition":"build-pass","interval":"15m","stopAfter":1,"onNotMet":"notify","description":"Watch PR 1065 build — notify each poll"}`
|
|
88
|
+
- **delete-watch**: id — remove a watch permanently
|
|
89
|
+
Example: user says "stop watching PR 1065" → `{"type":"delete-watch","id":"watch-abc123"}`
|
|
90
|
+
- **pause-watch**: id — pause a watch without deleting it (can be resumed later)
|
|
91
|
+
Example: user says "pause the PR 1065 watch" → `{"type":"pause-watch","id":"watch-abc123"}`
|
|
92
|
+
- **resume-watch**: id — resume a paused watch
|
|
93
|
+
Example: user says "resume watching PR 1065" → `{"type":"resume-watch","id":"watch-abc123"}`
|
|
88
94
|
- KB/Inbox: promote-to-kb (file, category), kb-sweep, toggle-kb-pin (key)
|
|
89
95
|
- Plan lifecycle: revise-plan (file, feedback — dispatches agent to revise)
|
|
90
96
|
- Pipeline: continue-pipeline (id — resume past wait stage)
|