@yemi33/minions 0.1.2357 → 0.1.2359
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/engine/dispatch.js +37 -5
- package/engine/pipeline.js +33 -24
- package/engine/shared.js +12 -0
- package/package.json +1 -1
package/engine/dispatch.js
CHANGED
|
@@ -310,25 +310,53 @@ function addToDispatch(item) {
|
|
|
310
310
|
function _persistInvalidWorkItem(item, evaluation) {
|
|
311
311
|
const meta = item?.meta;
|
|
312
312
|
const itemId = meta?.item?.id;
|
|
313
|
-
if (!itemId) return;
|
|
313
|
+
if (!itemId) return { stuck: false };
|
|
314
314
|
let wiPath;
|
|
315
315
|
try { wiPath = lifecycle().resolveWorkItemPath(meta); } catch { wiPath = null; }
|
|
316
|
-
if (!wiPath) return;
|
|
316
|
+
if (!wiPath) return { stuck: false };
|
|
317
|
+
// W-mrcrh5hj0017d22f — repeat-rejection cap. Compare against the
|
|
318
|
+
// description snapshot from the LAST rejection so an operator edit to the
|
|
319
|
+
// WI resets the counter (it deserves a fresh evaluation), while an
|
|
320
|
+
// unchanged, permanently-unactionable description accumulates toward
|
|
321
|
+
// the cap instead of being silently re-evaluated forever.
|
|
322
|
+
const maxRejections = queries.getConfig()?.engine?.preDispatchEvalMaxRejections
|
|
323
|
+
?? ENGINE_DEFAULTS.preDispatchEvalMaxRejections;
|
|
324
|
+
const currentDescription = String((meta.item && meta.item.description) || '');
|
|
325
|
+
let stuck = false;
|
|
317
326
|
try {
|
|
318
327
|
mutateWorkItems(wiPath, (items) => {
|
|
319
328
|
if (!Array.isArray(items)) return items;
|
|
320
329
|
const idx = items.findIndex(w => w && w.id === itemId);
|
|
321
330
|
if (idx === -1) return items;
|
|
322
|
-
items[idx]
|
|
331
|
+
const wi = items[idx];
|
|
332
|
+
const prior = wi._preDispatchEval;
|
|
333
|
+
const sameDescription = prior && prior.description === currentDescription;
|
|
334
|
+
const rejectCount = (sameDescription ? (prior.rejectCount || 0) : 0) + 1;
|
|
335
|
+
wi._preDispatchEval = {
|
|
323
336
|
valid: false,
|
|
324
337
|
reason: evaluation.reason || '',
|
|
325
338
|
evaluatedAt: ts(),
|
|
339
|
+
rejectCount,
|
|
340
|
+
description: currentDescription,
|
|
326
341
|
};
|
|
342
|
+
if (rejectCount >= maxRejections) {
|
|
343
|
+
stuck = true;
|
|
344
|
+
wi.status = WI_STATUS.FAILED;
|
|
345
|
+
wi._failureClass = FAILURE_CLASS.PRE_DISPATCH_EVAL_STUCK;
|
|
346
|
+
wi.failReason = `pre-dispatch-eval rejected this work item ${rejectCount} times in a row ` +
|
|
347
|
+
`with an unchanged description — last reason: ${evaluation.reason || 'criteria not actionable'}`;
|
|
348
|
+
wi.failedAt = ts();
|
|
349
|
+
wi._pendingReason = 'pre_dispatch_eval_stuck';
|
|
350
|
+
}
|
|
327
351
|
return items;
|
|
328
352
|
}, { skipWriteIfUnchanged: true });
|
|
329
353
|
} catch (e) {
|
|
330
354
|
log('warn', `pre-dispatch-eval: failed to persist reason on ${itemId}: ${e.message}`);
|
|
331
355
|
}
|
|
356
|
+
if (stuck) {
|
|
357
|
+
log('warn', `pre-dispatch-eval: ${itemId} rejected ${maxRejections}+ times with unchanged description — flipped to failed (PRE_DISPATCH_EVAL_STUCK) instead of re-evaluating forever`);
|
|
358
|
+
}
|
|
359
|
+
return { stuck };
|
|
332
360
|
}
|
|
333
361
|
|
|
334
362
|
function _routeToReviewQueue(item, evaluation) {
|
|
@@ -455,8 +483,12 @@ async function addToDispatchWithValidation(item, opts = {}) {
|
|
|
455
483
|
|
|
456
484
|
if (!evaluation || evaluation.valid !== false) return addToDispatch(item);
|
|
457
485
|
|
|
458
|
-
_persistInvalidWorkItem(item, evaluation);
|
|
459
|
-
|
|
486
|
+
const { stuck } = _persistInvalidWorkItem(item, evaluation);
|
|
487
|
+
// W-mrcrh5hj0017d22f: once the WI has been flipped to failed for
|
|
488
|
+
// repeat-rejection, don't also push it into the review queue — the
|
|
489
|
+
// failed status already stops future discovery ticks from re-evaluating
|
|
490
|
+
// it, and a review-queue entry would just be a stale duplicate signal.
|
|
491
|
+
if (!stuck) _routeToReviewQueue(item, evaluation);
|
|
460
492
|
log('warn', `pre-dispatch-eval: blocked work item ${wi.id} — ${evaluation.reason || 'criteria not actionable'}`);
|
|
461
493
|
return null;
|
|
462
494
|
}
|
package/engine/pipeline.js
CHANGED
|
@@ -77,6 +77,23 @@ function getPipelineRuns() {
|
|
|
77
77
|
return safeJsonObj(PIPELINE_RUNS_PATH);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
// W-mrcrh5hj0017d22f: SQL is the canonical (and only) work-items store post
|
|
81
|
+
// Phase 9.4 — the JSON files (central work-items.json + each
|
|
82
|
+
// projects/<name>/work-items.json) are passive, best-effort mirrors that can
|
|
83
|
+
// legitimately lag behind a just-completed write (mirror failures are
|
|
84
|
+
// swallowed by design so they never block the SQL write; see
|
|
85
|
+
// work-items-store.js's `_mirrorJsonFromSql` comments). Reading those JSON
|
|
86
|
+
// files directly (via safeJson/safeJsonArr) can therefore observe a work
|
|
87
|
+
// item whose `status` mirrored to DONE but whose `resultSummary` write
|
|
88
|
+
// hasn't landed in the mirror yet — which silently degraded stage-output
|
|
89
|
+
// template substitution ({{stages.<id>.output}}) to the stage's own work
|
|
90
|
+
// item id instead of its real completion text. Route every work-item read
|
|
91
|
+
// in this module through the SQL-backed store so pipeline state always sees
|
|
92
|
+
// the authoritative record.
|
|
93
|
+
function _allWorkItems() {
|
|
94
|
+
return require('./work-items-store').readAllWorkItems();
|
|
95
|
+
}
|
|
96
|
+
|
|
80
97
|
function getActiveRun(pipelineId) {
|
|
81
98
|
const runs = getPipelineRuns();
|
|
82
99
|
const pipelineRuns = runs[pipelineId] || [];
|
|
@@ -338,12 +355,7 @@ function evaluateCondition(condition, ctx) {
|
|
|
338
355
|
case 'noFailedItems': {
|
|
339
356
|
// True when all work items created by the pipeline are done (not failed)
|
|
340
357
|
if (!run) return false;
|
|
341
|
-
const
|
|
342
|
-
const workItems = safeJsonArr(wiPath);
|
|
343
|
-
const allProjectWi = shared.getProjects(config).reduce((acc, p) => {
|
|
344
|
-
return acc.concat(safeJson(shared.projectWorkItemsPath(p)) || []);
|
|
345
|
-
}, []);
|
|
346
|
-
const all = [...workItems, ...allProjectWi];
|
|
358
|
+
const all = _allWorkItems();
|
|
347
359
|
const pipelineWis = all.filter(w => w._pipelineRun === run.runId);
|
|
348
360
|
if (pipelineWis.length === 0) return true; // no items = nothing failed
|
|
349
361
|
return pipelineWis.every(w => w.status !== WI_STATUS.FAILED);
|
|
@@ -945,13 +957,8 @@ function isStageComplete(stage, stageState, run, config) {
|
|
|
945
957
|
|
|
946
958
|
switch (stage.type) {
|
|
947
959
|
case STAGE_TYPE.TASK: {
|
|
948
|
-
// Check
|
|
949
|
-
const
|
|
950
|
-
const workItems = safeJsonArr(wiPath);
|
|
951
|
-
const allProjectWi = shared.getProjects(config).reduce((acc, p) => {
|
|
952
|
-
return acc.concat(safeJson(shared.projectWorkItemsPath(p)) || []);
|
|
953
|
-
}, []);
|
|
954
|
-
const all = [...workItems, ...allProjectWi];
|
|
960
|
+
// Check central + all project work items (WIs may live in project scopes)
|
|
961
|
+
const all = _allWorkItems();
|
|
955
962
|
const ids = artifacts.workItems || [];
|
|
956
963
|
if (ids.length === 0) return false;
|
|
957
964
|
return ids.every(id => {
|
|
@@ -970,12 +977,7 @@ function isStageComplete(stage, stageState, run, config) {
|
|
|
970
977
|
}
|
|
971
978
|
case STAGE_TYPE.PLAN: {
|
|
972
979
|
// Plan stage completion: PRD conversion done + all materialized work items done
|
|
973
|
-
const
|
|
974
|
-
const workItems = safeJsonArr(wiPath);
|
|
975
|
-
const allProjectWi = shared.getProjects(config).reduce((acc, p) => {
|
|
976
|
-
return acc.concat(safeJson(shared.projectWorkItemsPath(p)) || []);
|
|
977
|
-
}, []);
|
|
978
|
-
const all = [...workItems, ...allProjectWi];
|
|
980
|
+
const all = _allWorkItems();
|
|
979
981
|
|
|
980
982
|
// Check if plan-to-prd work item is done
|
|
981
983
|
const prdWiIds = artifacts.workItems || [];
|
|
@@ -1138,13 +1140,20 @@ async function discoverPipelineWork(config) {
|
|
|
1138
1140
|
// Collect output
|
|
1139
1141
|
let output = '';
|
|
1140
1142
|
if (stage.type === STAGE_TYPE.TASK) {
|
|
1141
|
-
const
|
|
1142
|
-
const workItems = safeJsonArr(wiPath);
|
|
1143
|
-
const projWi = shared.getProjects(config).reduce((acc, p) => acc.concat(safeJson(shared.projectWorkItemsPath(p)) || []), []);
|
|
1144
|
-
const allWi = [...workItems, ...projWi];
|
|
1143
|
+
const allWi = _allWorkItems();
|
|
1145
1144
|
output = (stageState.artifacts?.workItems || []).map(id => {
|
|
1146
1145
|
const wi = allWi.find(w => w.id === id);
|
|
1147
|
-
|
|
1146
|
+
// W-mrcrh5hj0017d22f: never fall back to the stage's own work
|
|
1147
|
+
// item id — a bare id substituted into {{stages.<id>.output}}
|
|
1148
|
+
// produces a nonsensical downstream description (e.g.
|
|
1149
|
+
// "Confirmed bugs:\nPL-mrcqpzit-verify-0-minions-opg") that
|
|
1150
|
+
// pre-dispatch-eval correctly (but silently, forever) rejects.
|
|
1151
|
+
// If the real resultSummary/title text isn't available yet
|
|
1152
|
+
// (WI missing, or a lagged/failed mirror momentarily hides a
|
|
1153
|
+
// just-completed resultSummary), fall back to an empty string
|
|
1154
|
+
// — a blank substitution is harmless where an opaque id is
|
|
1155
|
+
// actively misleading.
|
|
1156
|
+
return wi?.resultSummary || wi?.title || '';
|
|
1148
1157
|
}).join('\n');
|
|
1149
1158
|
} else if (stage.type === STAGE_TYPE.MEETING) {
|
|
1150
1159
|
const { getMeeting } = require('./meeting');
|
package/engine/shared.js
CHANGED
|
@@ -3497,6 +3497,17 @@ const ENGINE_DEFAULTS = {
|
|
|
3497
3497
|
// requested/completed PRDs still flow through the normal validator path so a
|
|
3498
3498
|
// stale PRD reopen can't smuggle in unvetted items.
|
|
3499
3499
|
preDispatchEvalSkipPrdSourced: true,
|
|
3500
|
+
// W-mrcrh5hj0017d22f — repeat-rejection cap. addToDispatchWithValidation
|
|
3501
|
+
// re-runs the LLM validator on EVERY discovery tick for a WI that's still
|
|
3502
|
+
// `pending` — routing to the review queue (_routeToReviewQueue) dedupes
|
|
3503
|
+
// that queue but never changes the WI's own status, so an item whose
|
|
3504
|
+
// description the validator will never accept (see stage-output
|
|
3505
|
+
// substitution bug above) got re-evaluated forever, once per tick,
|
|
3506
|
+
// invisible to the dashboard. Once the SAME (unchanged) description is
|
|
3507
|
+
// rejected this many consecutive times, the WI is flipped to `failed`
|
|
3508
|
+
// with FAILURE_CLASS.PRE_DISPATCH_EVAL_STUCK instead of being silently
|
|
3509
|
+
// re-queued into review again.
|
|
3510
|
+
preDispatchEvalMaxRejections: 5,
|
|
3500
3511
|
completionNonceRequired: false, // P-d2a8f6c1 (agent trust boundary F8): when true, a missing `nonce` field in the completion JSON hard-fails the dispatch with failure_class:'completion-nonce-mismatch'. Default false for one release so older agents/runtime caches that haven't picked up the prompt change degrade with a warning instead of breaking. Mismatched nonces hard-fail regardless of this flag. See docs/completion-reports.md → "Trust boundary".
|
|
3501
3512
|
autoApplyReviewVote: false, // W-mpea9fyb0010febf / P-f7splitgate: master gate for ALL engine platform vote mutations. When true, the engine (a) mirrors live platform vote state into local pull-requests.json reviewStatus, (b) re-applies its own ADO +10 approval when ADO resets votes on target-branch advance (pollPrStatus), (c) reconciles its own prior negative on a verdict flip to APPROVE (ADO resetReviewerNegativeVote / GitHub dismissPriorViewerChangesRequestedReviews), and (d) instructs ADO review agents to cast `az repos pr set-vote`. When false (default), the verdict is recorded in pull-requests.json reviewStatus from the agent's completion ONLY — informational — and Minions never cast, re-cast, or clear a platform vote/review (ADO agents post the verdict as a comment only, parity with GitHub self-approval block).
|
|
3502
3513
|
|
|
@@ -4958,6 +4969,7 @@ const FAILURE_CLASS = {
|
|
|
4958
4969
|
OUTPUT_TRUNCATED: 'output-truncated', // P-8e4c2a17: the agent streamed more stdout than the engine's hard capture cap (engine.js AGENT_OUTPUT_CAP_BYTES, 1MB) BEFORE the terminal `result` event arrived. The result (session id, completion block, final text) lives at the END of the stream, so it fell outside the captured window and parseOutput found nothing — the dispatch would otherwise fail as an opaque UNKNOWN and retry to death. Surfaced loudly with an actionable message; non-retryable (mechanical retry just reproduces the overflow — the agent must reduce output volume or the task must be split).
|
|
4959
4970
|
VERIFY_MISSING_PR: 'verify-missing-pr', // W-mqsk1ip00006cbae: a verify WI exited done but no PR was attached (neither _prUrl/_pr on the item nor a matching pull-requests.json entry for the plan). Flipped to failed so the plan doesn't silently advance without an E2E PR. Retryable — agent may have phantom-crashed before pushing the branch.
|
|
4960
4971
|
PROJECT_NOT_FOUND: 'project_not_found', // W-mqv2wsy30002e090: a work item's `project` field names a project that is not configured (resolveConfiguredProject / formatUnknownProjectError → `Project "<name>" not found. Known projects: …`). Stamped at discovery time so the dashboard PR-column renderer surfaces the red failReason snippet like other non-retryable failures instead of burying it in the Agent column. Non-retryable — operator must fix the WI's project field or configure the project.
|
|
4972
|
+
PRE_DISPATCH_EVAL_STUCK: 'pre-dispatch-eval-stuck', // W-mrcrh5hj0017d22f: pre-dispatch-eval rejected the SAME (unchanged) description ENGINE_DEFAULTS.preDispatchEvalMaxRejections times in a row without the WI status ever leaving pending — previously this burned an LLM validation call every discovery tick forever with nothing surfaced to the dashboard (constant-bug-bash's file-fixes stage wedged 16+ consecutive rejections, one/minute, until a human manually cancelled it). Engine now flips the WI to failed with this class so the stalled item is visible and stops silently re-evaluating; non-retryable until the description changes.
|
|
4961
4973
|
UNKNOWN: 'unknown', // Unclassified failure
|
|
4962
4974
|
};
|
|
4963
4975
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2359",
|
|
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"
|