@yemi33/minions 0.1.2316 → 0.1.2318
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 +13 -1
- package/engine/github.js +9 -0
- package/engine/pr-clone-keep.js +2 -17
- package/engine/pr-devbox.js +2 -17
- package/engine/pr-fix-target.js +2 -16
- package/engine/pr-remote-patch.js +2 -17
- package/engine/pr-resolve.js +38 -0
- package/engine/pr-temp-clone.js +2 -17
- package/engine/pr-track.js +2 -17
- package/engine.js +13 -1
- package/package.json +1 -1
package/engine/dispatch.js
CHANGED
|
@@ -853,7 +853,19 @@ function completeDispatch(id, result = DISPATCH_RESULT.SUCCESS, reason = '', res
|
|
|
853
853
|
completedWorkItemFailure = isCompletedWorkItemForFailure(liveWi);
|
|
854
854
|
} catch (e) { log('warn', 'read live work item before retry: ' + e.message); }
|
|
855
855
|
}
|
|
856
|
-
|
|
856
|
+
// #634 — cooldown backoff bookkeeping is independent of the retry decision.
|
|
857
|
+
// `retryableFailure` (== `agentRetryable`) governs whether the engine will
|
|
858
|
+
// ever re-dispatch this work; `setCooldownFailure` governs how aggressively
|
|
859
|
+
// it backs off between attempts. Previously this was gated on
|
|
860
|
+
// `retryableFailure`, so a caller marking a failure `agentRetryable: false`
|
|
861
|
+
// (structural/non-retryable classes like LIVE_CHECKOUT_WORKTREE_CONFLICT,
|
|
862
|
+
// WORKSPACE_MANIFEST_REPO, INVALID_WORKDIR, etc.) skipped cooldown
|
|
863
|
+
// bookkeeping entirely — leaving only the flat 15-minute `isAlreadyDispatched`
|
|
864
|
+
// dedup window as the sole re-dispatch gate, so the identical doomed
|
|
865
|
+
// dispatch key looped forever every ~15 minutes with zero backoff. Always
|
|
866
|
+
// record the failure here so `isOnCooldown`'s exponential backoff applies
|
|
867
|
+
// regardless of whether the failure is retryable.
|
|
868
|
+
if (result === DISPATCH_RESULT.ERROR && item.meta?.dispatchKey && !completedWorkItemFailure) {
|
|
857
869
|
setCooldownFailure(item.meta.dispatchKey);
|
|
858
870
|
}
|
|
859
871
|
|
package/engine/github.js
CHANGED
|
@@ -690,6 +690,15 @@ async function forEachActiveGhPr(config, callback) {
|
|
|
690
690
|
for (const { before, after } of updatedCentralRecords) {
|
|
691
691
|
const idx = currentPrs.findIndex(p => p.id === after.id);
|
|
692
692
|
if (idx >= 0) {
|
|
693
|
+
// W-mr3gr4wl0004b8e3: never downgrade reviewStatus from 'approved' — it's a
|
|
694
|
+
// permanent terminal state. Mirrors the project-local write-back guard above
|
|
695
|
+
// (github.js:561-563) and ado.js's project/central write-backs (ado.js:1211-1213,
|
|
696
|
+
// :1360-1362). applyPrFieldDelta only merges fields changed during a given poll
|
|
697
|
+
// cycle, so this guard is the sole race protection against a stale/out-of-order
|
|
698
|
+
// GitHub poll response downgrading an already-approved PR.
|
|
699
|
+
if (currentPrs[idx].reviewStatus === REVIEW_STATUS.APPROVED && after.reviewStatus !== REVIEW_STATUS.APPROVED) {
|
|
700
|
+
after.reviewStatus = REVIEW_STATUS.APPROVED;
|
|
701
|
+
}
|
|
693
702
|
// W-mp5trwh60008386d: same merged-status guard as project-local PRs.
|
|
694
703
|
if (currentPrs[idx].status === PR_STATUS.MERGED && after.status !== PR_STATUS.MERGED) {
|
|
695
704
|
after.status = PR_STATUS.MERGED;
|
package/engine/pr-clone-keep.js
CHANGED
|
@@ -69,24 +69,9 @@ const GIT_OP_TIMEOUT_MS = 60 * 1000;
|
|
|
69
69
|
* `PrActionError` (400) on missing/unrecognized input — mirrors the read-only,
|
|
70
70
|
* choice-surface, and temp-clone paths so the dashboard reflects a 400, not a 500.
|
|
71
71
|
*/
|
|
72
|
-
|
|
73
|
-
if (input && typeof input === 'object') {
|
|
74
|
-
if (input.host && input.slug && input.number) return input; // already normalized
|
|
75
|
-
if (input.ref && input.ref.host) return input.ref; // plan/handle carrying a ref
|
|
76
|
-
}
|
|
77
|
-
const rawUrl = input && typeof input === 'object'
|
|
78
|
-
? (typeof input.url === 'string' ? input.url.trim() : '')
|
|
79
|
-
: (typeof input === 'string' ? input.trim() : '');
|
|
80
|
-
if (!rawUrl) throw new PrActionError('url required');
|
|
81
|
-
const ref = prResolve.normalizePrRef(rawUrl);
|
|
82
|
-
if (!ref) throw new PrActionError(`unrecognized PR reference: ${JSON.stringify(rawUrl.slice(0, 120))}`);
|
|
83
|
-
return ref;
|
|
84
|
-
}
|
|
72
|
+
const _resolveRef = (input) => prResolve.resolvePrActionRef(input, PrActionError);
|
|
85
73
|
|
|
86
|
-
|
|
87
|
-
if (!ref) return null;
|
|
88
|
-
return ref.id || (ref.host && ref.slug && ref.number ? `${ref.host}:${ref.slug}#${ref.number}` : null);
|
|
89
|
-
}
|
|
74
|
+
const _prId = prResolve.prIdFromRef;
|
|
90
75
|
|
|
91
76
|
/** Filesystem-safe slug for the persistent project dir name (from the repo name). */
|
|
92
77
|
function _safeDirSlug(name) {
|
package/engine/pr-devbox.js
CHANGED
|
@@ -60,24 +60,9 @@ const DEFAULT_REMOTE_TMP_ROOT = 'C:/minions-devbox-tmp';
|
|
|
60
60
|
* `PrActionError` (400) on missing/unrecognized input — mirrors the other
|
|
61
61
|
* projectless paths so the dashboard reflects a 400, not a 500.
|
|
62
62
|
*/
|
|
63
|
-
|
|
64
|
-
if (input && typeof input === 'object') {
|
|
65
|
-
if (input.host && input.slug && input.number) return input; // already normalized
|
|
66
|
-
if (input.ref && input.ref.host) return input.ref; // plan/handle carrying a ref
|
|
67
|
-
}
|
|
68
|
-
const rawUrl = input && typeof input === 'object'
|
|
69
|
-
? (typeof input.url === 'string' ? input.url.trim() : '')
|
|
70
|
-
: (typeof input === 'string' ? input.trim() : '');
|
|
71
|
-
if (!rawUrl) throw new PrActionError('url required');
|
|
72
|
-
const ref = prResolve.normalizePrRef(rawUrl);
|
|
73
|
-
if (!ref) throw new PrActionError(`unrecognized PR reference: ${JSON.stringify(rawUrl.slice(0, 120))}`);
|
|
74
|
-
return ref;
|
|
75
|
-
}
|
|
63
|
+
const _resolveRef = (input) => prResolve.resolvePrActionRef(input, PrActionError);
|
|
76
64
|
|
|
77
|
-
|
|
78
|
-
if (!ref) return null;
|
|
79
|
-
return ref.id || (ref.host && ref.slug && ref.number ? `${ref.host}:${ref.slug}#${ref.number}` : null);
|
|
80
|
-
}
|
|
65
|
+
const _prId = prResolve.prIdFromRef;
|
|
81
66
|
|
|
82
67
|
/** Filesystem-safe slug for a PR id (used in the remote dir name). */
|
|
83
68
|
function _safeDirSlug(prId) {
|
package/engine/pr-fix-target.js
CHANGED
|
@@ -143,21 +143,7 @@ function _routedRecord(plan, target, extra = {}) {
|
|
|
143
143
|
* a normalized PR ref. Throws `PrActionError` (400) on missing/unrecognized input
|
|
144
144
|
* so the dashboard handler reflects it as a 400 (matching the read-only path).
|
|
145
145
|
*/
|
|
146
|
-
|
|
147
|
-
if (input && typeof input === 'object' && input.host && input.slug && input.number) {
|
|
148
|
-
return input; // already normalized
|
|
149
|
-
}
|
|
150
|
-
if (input && typeof input === 'object' && input.ref && input.ref.host) {
|
|
151
|
-
return input.ref; // a plan/handle carrying a ref
|
|
152
|
-
}
|
|
153
|
-
const rawUrl = input && typeof input === 'object'
|
|
154
|
-
? (typeof input.url === 'string' ? input.url.trim() : '')
|
|
155
|
-
: (typeof input === 'string' ? input.trim() : '');
|
|
156
|
-
if (!rawUrl) throw new PrActionError('url required');
|
|
157
|
-
const ref = prResolve.normalizePrRef(rawUrl);
|
|
158
|
-
if (!ref) throw new PrActionError(`unrecognized PR reference: ${JSON.stringify(rawUrl.slice(0, 120))}`);
|
|
159
|
-
return ref;
|
|
160
|
-
}
|
|
146
|
+
const _resolveRef = (input) => prResolve.resolvePrActionRef(input, PrActionError);
|
|
161
147
|
|
|
162
148
|
// ── Configured-project detection ──────────────────────────────────────────────
|
|
163
149
|
|
|
@@ -309,7 +295,7 @@ function buildPrFixTargetModal(plan) {
|
|
|
309
295
|
function planPrFix(input = {}, opts = {}) {
|
|
310
296
|
const ref = _resolveRef(input);
|
|
311
297
|
const prUrl = opts.prUrl || (input && typeof input === 'object' ? input.url : undefined) || null;
|
|
312
|
-
const prId =
|
|
298
|
+
const prId = prResolve.prIdFromRef(ref);
|
|
313
299
|
|
|
314
300
|
const project = findConfiguredProjectForRef(ref, opts.config);
|
|
315
301
|
if (project) {
|
|
@@ -59,24 +59,9 @@ const REMOTE_PATCH_NO_VALIDATION_LABEL =
|
|
|
59
59
|
* `PrActionError` (400) on missing/unrecognized input — mirrors the read-only,
|
|
60
60
|
* choice-surface, and temp-clone paths so the dashboard reflects a 400, not 500.
|
|
61
61
|
*/
|
|
62
|
-
|
|
63
|
-
if (input && typeof input === 'object') {
|
|
64
|
-
if (input.host && input.slug && input.number) return input; // already normalized
|
|
65
|
-
if (input.ref && input.ref.host) return input.ref; // plan/handle carrying a ref
|
|
66
|
-
}
|
|
67
|
-
const rawUrl = input && typeof input === 'object'
|
|
68
|
-
? (typeof input.url === 'string' ? input.url.trim() : '')
|
|
69
|
-
: (typeof input === 'string' ? input.trim() : '');
|
|
70
|
-
if (!rawUrl) throw new PrActionError('url required');
|
|
71
|
-
const ref = prResolve.normalizePrRef(rawUrl);
|
|
72
|
-
if (!ref) throw new PrActionError(`unrecognized PR reference: ${JSON.stringify(rawUrl.slice(0, 120))}`);
|
|
73
|
-
return ref;
|
|
74
|
-
}
|
|
62
|
+
const _resolveRef = (input) => prResolve.resolvePrActionRef(input, PrActionError);
|
|
75
63
|
|
|
76
|
-
|
|
77
|
-
if (!ref) return null;
|
|
78
|
-
return ref.id || (ref.host && ref.slug && ref.number ? `${ref.host}:${ref.slug}#${ref.number}` : null);
|
|
79
|
-
}
|
|
64
|
+
const _prId = prResolve.prIdFromRef;
|
|
80
65
|
|
|
81
66
|
// ── Patch normalization (the trivial-single-file guardrail) ───────────────────
|
|
82
67
|
|
package/engine/pr-resolve.js
CHANGED
|
@@ -102,6 +102,42 @@ function normalizePrRef(input) {
|
|
|
102
102
|
return _normalizeFromScope(parsed.scope, parsed.prNumber);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
// ── Shared ref resolution for the PR-action executors ───────────────────────
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Resolve `{ url }` (raw PR URL / canonical id), an already-normalized ref, or a
|
|
109
|
+
* plan/handle carrying `.ref` into a normalized PR ref. Throws `PrActionError`
|
|
110
|
+
* (400) on missing/unrecognized input so callers reflect a 400, not a 500.
|
|
111
|
+
*
|
|
112
|
+
* Shared by the 6 PR-action executor modules (pr-clone-keep, pr-devbox,
|
|
113
|
+
* pr-remote-patch, pr-temp-clone, pr-track, pr-fix-target), which previously
|
|
114
|
+
* each defined a byte-identical copy of this function. `PrActionError` must be
|
|
115
|
+
* passed in by the caller — pr-resolve.js does not import engine/pr-action.js
|
|
116
|
+
* (that would introduce a new cross-module dependency in that direction).
|
|
117
|
+
*/
|
|
118
|
+
function resolvePrActionRef(input, PrActionError) {
|
|
119
|
+
if (input && typeof input === 'object') {
|
|
120
|
+
if (input.host && input.slug && input.number) return input; // already normalized
|
|
121
|
+
if (input.ref && input.ref.host) return input.ref; // plan/handle carrying a ref
|
|
122
|
+
}
|
|
123
|
+
const rawUrl = input && typeof input === 'object'
|
|
124
|
+
? (typeof input.url === 'string' ? input.url.trim() : '')
|
|
125
|
+
: (typeof input === 'string' ? input.trim() : '');
|
|
126
|
+
if (!rawUrl) throw new PrActionError('url required');
|
|
127
|
+
const ref = normalizePrRef(rawUrl);
|
|
128
|
+
if (!ref) throw new PrActionError(`unrecognized PR reference: ${JSON.stringify(rawUrl.slice(0, 120))}`);
|
|
129
|
+
return ref;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Derive the canonical `host:slug#number` PR id string from a normalized ref
|
|
134
|
+
* (or its already-computed `.id`). Returns null when `ref` is falsy.
|
|
135
|
+
*/
|
|
136
|
+
function prIdFromRef(ref) {
|
|
137
|
+
if (!ref) return null;
|
|
138
|
+
return ref.id || (ref.host && ref.slug && ref.number ? `${ref.host}:${ref.slug}#${ref.number}` : null);
|
|
139
|
+
}
|
|
140
|
+
|
|
105
141
|
// ── Config (for the unknown-slug account fallback) ───────────────────────────
|
|
106
142
|
|
|
107
143
|
function _readConfig(opts = {}) {
|
|
@@ -432,6 +468,8 @@ async function fetchPrPayload(refOrInput, opts = {}) {
|
|
|
432
468
|
|
|
433
469
|
module.exports = {
|
|
434
470
|
normalizePrRef,
|
|
471
|
+
resolvePrActionRef,
|
|
472
|
+
prIdFromRef,
|
|
435
473
|
fetchPrPayload,
|
|
436
474
|
fetchPrBranches,
|
|
437
475
|
// Exported for testing.
|
package/engine/pr-temp-clone.js
CHANGED
|
@@ -57,24 +57,9 @@ const GIT_OP_TIMEOUT_MS = 60 * 1000;
|
|
|
57
57
|
* `PrActionError` (400) on missing/unrecognized input — mirrors the read-only
|
|
58
58
|
* and choice-surface paths so the dashboard reflects a 400, not a 500.
|
|
59
59
|
*/
|
|
60
|
-
|
|
61
|
-
if (input && typeof input === 'object') {
|
|
62
|
-
if (input.host && input.slug && input.number) return input; // already normalized
|
|
63
|
-
if (input.ref && input.ref.host) return input.ref; // plan/handle carrying a ref
|
|
64
|
-
}
|
|
65
|
-
const rawUrl = input && typeof input === 'object'
|
|
66
|
-
? (typeof input.url === 'string' ? input.url.trim() : '')
|
|
67
|
-
: (typeof input === 'string' ? input.trim() : '');
|
|
68
|
-
if (!rawUrl) throw new PrActionError('url required');
|
|
69
|
-
const ref = prResolve.normalizePrRef(rawUrl);
|
|
70
|
-
if (!ref) throw new PrActionError(`unrecognized PR reference: ${JSON.stringify(rawUrl.slice(0, 120))}`);
|
|
71
|
-
return ref;
|
|
72
|
-
}
|
|
60
|
+
const _resolveRef = (input) => prResolve.resolvePrActionRef(input, PrActionError);
|
|
73
61
|
|
|
74
|
-
|
|
75
|
-
if (!ref) return null;
|
|
76
|
-
return ref.id || (ref.host && ref.slug && ref.number ? `${ref.host}:${ref.slug}#${ref.number}` : null);
|
|
77
|
-
}
|
|
62
|
+
const _prId = prResolve.prIdFromRef;
|
|
78
63
|
|
|
79
64
|
/** Filesystem-safe slug for a PR id (used in the ephemeral dir name). */
|
|
80
65
|
function _safeDirSlug(prId) {
|
package/engine/pr-track.js
CHANGED
|
@@ -56,24 +56,9 @@ const TRACK_RECOMMENDED_TARGET = 'clone-keep';
|
|
|
56
56
|
* missing/unrecognized input so the dashboard reflects a 400 — mirrors the
|
|
57
57
|
* read-only and choice-surface paths.
|
|
58
58
|
*/
|
|
59
|
-
|
|
60
|
-
if (input && typeof input === 'object') {
|
|
61
|
-
if (input.host && input.slug && input.number) return input; // already normalized
|
|
62
|
-
if (input.ref && input.ref.host) return input.ref; // plan/handle carrying a ref
|
|
63
|
-
}
|
|
64
|
-
const rawUrl = input && typeof input === 'object'
|
|
65
|
-
? (typeof input.url === 'string' ? input.url.trim() : '')
|
|
66
|
-
: (typeof input === 'string' ? input.trim() : '');
|
|
67
|
-
if (!rawUrl) throw new PrActionError('url required');
|
|
68
|
-
const ref = prResolve.normalizePrRef(rawUrl);
|
|
69
|
-
if (!ref) throw new PrActionError(`unrecognized PR reference: ${JSON.stringify(rawUrl.slice(0, 120))}`);
|
|
70
|
-
return ref;
|
|
71
|
-
}
|
|
59
|
+
const _resolveRef = (input) => prResolve.resolvePrActionRef(input, PrActionError);
|
|
72
60
|
|
|
73
|
-
|
|
74
|
-
if (!ref) return null;
|
|
75
|
-
return ref.id || (ref.host && ref.slug && ref.number ? `${ref.host}:${ref.slug}#${ref.number}` : null);
|
|
76
|
-
}
|
|
61
|
+
const _prId = prResolve.prIdFromRef;
|
|
77
62
|
|
|
78
63
|
// ── Choice surface (Clone & keep implied) ─────────────────────────────────────
|
|
79
64
|
|
package/engine.js
CHANGED
|
@@ -7185,7 +7185,19 @@ async function discoverFromPrs(config, project) {
|
|
|
7185
7185
|
coalesceCurrentHumanFeedback();
|
|
7186
7186
|
continue;
|
|
7187
7187
|
}
|
|
7188
|
-
|
|
7188
|
+
// #634 — only clear the cooldown when it holds no real failure history
|
|
7189
|
+
// (i.e. it was created purely by `setCooldownWithContext` to park
|
|
7190
|
+
// coalesced feedback while `fixThrottled`, and `failures` is still 0).
|
|
7191
|
+
// A cooldown with `failures > 0` came from `setCooldownFailure` after an
|
|
7192
|
+
// actual dispatch error — including non-retryable structural failures,
|
|
7193
|
+
// which now always record a failure (see dispatch.js#completeDispatch).
|
|
7194
|
+
// Clearing it just because the 15-min `isAlreadyDispatched` window
|
|
7195
|
+
// closed would erase the exponential backoff and re-dispatch the same
|
|
7196
|
+
// doomed key every ~15 minutes, defeating the whole point of the
|
|
7197
|
+
// backoff. Only genuinely orphaned (never-attempted) cooldowns are
|
|
7198
|
+
// cleared as stale.
|
|
7199
|
+
const isGenuineFailureCooldown = (dispatchCooldowns.get(key)?.failures || 0) > 0;
|
|
7200
|
+
if (blockedByCooldown && !alreadyDispatched && !isGenuineFailureCooldown) {
|
|
7189
7201
|
staleCoalesced = drainCoalescedContexts(key);
|
|
7190
7202
|
clearCooldown(key);
|
|
7191
7203
|
log('info', `Cleared stale cooldown for ${key} — no matching dispatch history`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2318",
|
|
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"
|