@yemi33/minions 0.1.2112 → 0.1.2114
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/bin/minions.js +26 -5
- package/engine/check-status.js +1 -1
- package/engine/db/index.js +13 -4
- package/engine/pull-requests-store.js +34 -1
- package/engine/shared.js +20 -35
- package/engine/supervisor.js +11 -2
- package/engine/work-items-store.js +39 -1
- package/package.json +1 -1
package/bin/minions.js
CHANGED
|
@@ -231,6 +231,27 @@ function _openStdioLog(name) {
|
|
|
231
231
|
return shared.openAppendLogFd(name, dir, { fallback: 'ignore' }).fd;
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Node flag args required for SQLite to load. On Node 22.x, `node:sqlite`
|
|
236
|
+
* is gated behind `--experimental-sqlite`; on Node 24+ it is unflagged.
|
|
237
|
+
* Issue #3035: without this, every engine/dashboard/supervisor spawn on
|
|
238
|
+
* Node 22.x loses the SQL store and falls back to the JSON path —
|
|
239
|
+
* /api/work-items returns [] from the aggregate reader because the
|
|
240
|
+
* Phase-9-canonical SQL path is unreachable.
|
|
241
|
+
*
|
|
242
|
+
* Honors NODE_OPTIONS / MINIONS_FORCE_JSON: if the caller already has the
|
|
243
|
+
* flag set (via NODE_OPTIONS) or has opted out of SQL entirely
|
|
244
|
+
* (MINIONS_FORCE_JSON=1), we add nothing — the child inherits the env.
|
|
245
|
+
*/
|
|
246
|
+
function _sqliteSpawnFlags() {
|
|
247
|
+
if (process.env.MINIONS_FORCE_JSON === '1') return [];
|
|
248
|
+
const major = parseInt(String(process.versions.node).split('.')[0], 10);
|
|
249
|
+
if (!Number.isFinite(major) || major < 22 || major >= 24) return [];
|
|
250
|
+
const nodeOpts = String(process.env.NODE_OPTIONS || '');
|
|
251
|
+
if (nodeOpts.includes('--experimental-sqlite')) return [];
|
|
252
|
+
return ['--experimental-sqlite'];
|
|
253
|
+
}
|
|
254
|
+
|
|
234
255
|
/** Spawn a detached dashboard with self-open suppressed — the CLI decides
|
|
235
256
|
* when to open a browser based on whether a real tab reconnects post-health.
|
|
236
257
|
* stdout/stderr land in engine/dashboard-stdio.log so silent startup crashes
|
|
@@ -239,7 +260,7 @@ function spawnDashboard() {
|
|
|
239
260
|
const env = { ...process.env, MINIONS_NO_AUTO_OPEN: '1' };
|
|
240
261
|
const out = _openStdioLog('dashboard-stdio.log');
|
|
241
262
|
const err = _openStdioLog('dashboard-stdio.log');
|
|
242
|
-
const proc = spawn(process.execPath, [path.join(MINIONS_HOME, 'dashboard.js')], {
|
|
263
|
+
const proc = spawn(process.execPath, [..._sqliteSpawnFlags(), path.join(MINIONS_HOME, 'dashboard.js')], {
|
|
243
264
|
cwd: MINIONS_HOME, stdio: ['ignore', out, err], detached: true, windowsHide: true, env
|
|
244
265
|
});
|
|
245
266
|
proc.unref();
|
|
@@ -255,7 +276,7 @@ function spawnDashboard() {
|
|
|
255
276
|
function spawnSupervisor() {
|
|
256
277
|
const out = _openStdioLog('supervisor-stdio.log');
|
|
257
278
|
const err = _openStdioLog('supervisor-stdio.log');
|
|
258
|
-
const proc = spawn(process.execPath, [path.join(MINIONS_HOME, 'engine', 'supervisor.js')], {
|
|
279
|
+
const proc = spawn(process.execPath, [..._sqliteSpawnFlags(), path.join(MINIONS_HOME, 'engine', 'supervisor.js')], {
|
|
259
280
|
cwd: MINIONS_HOME, stdio: ['ignore', out, err], detached: true, windowsHide: true,
|
|
260
281
|
});
|
|
261
282
|
proc.unref();
|
|
@@ -277,7 +298,7 @@ const _supervisorPidPath = () => path.join(MINIONS_HOME, 'engine', 'supervisor.p
|
|
|
277
298
|
function spawnFullStackAndVerify({ rest, forceOpen, dashWasUp, restartStartMs }) {
|
|
278
299
|
const engineOut = _openStdioLog('engine-stdio.log');
|
|
279
300
|
const engineErr = _openStdioLog('engine-stdio.log');
|
|
280
|
-
const engineProc = spawn(process.execPath, [path.join(MINIONS_HOME, 'engine.js'), 'start', ...rest], {
|
|
301
|
+
const engineProc = spawn(process.execPath, [..._sqliteSpawnFlags(), path.join(MINIONS_HOME, 'engine.js'), 'start', ...rest], {
|
|
281
302
|
cwd: MINIONS_HOME, stdio: ['ignore', engineOut, engineErr], detached: true, windowsHide: true
|
|
282
303
|
});
|
|
283
304
|
engineProc.unref();
|
|
@@ -631,7 +652,7 @@ function init() {
|
|
|
631
652
|
console.log(isUpgrade
|
|
632
653
|
? `\n Upgrade complete (${pkgVersion}). Restarting engine and dashboard...\n`
|
|
633
654
|
: '\n Starting engine and dashboard...\n');
|
|
634
|
-
const engineProc = spawn(process.execPath, [path.join(MINIONS_HOME, 'engine.js'), 'start'], {
|
|
655
|
+
const engineProc = spawn(process.execPath, [..._sqliteSpawnFlags(), path.join(MINIONS_HOME, 'engine.js'), 'start'], {
|
|
635
656
|
cwd: MINIONS_HOME, stdio: 'ignore', detached: true, windowsHide: true
|
|
636
657
|
});
|
|
637
658
|
engineProc.unref();
|
|
@@ -840,7 +861,7 @@ function ensureInstalled() {
|
|
|
840
861
|
|
|
841
862
|
function delegate(script, args) {
|
|
842
863
|
ensureInstalled();
|
|
843
|
-
const child = spawn(process.execPath, [path.join(MINIONS_HOME, script), ...args], {
|
|
864
|
+
const child = spawn(process.execPath, [..._sqliteSpawnFlags(), path.join(MINIONS_HOME, script), ...args], {
|
|
844
865
|
stdio: 'inherit',
|
|
845
866
|
cwd: MINIONS_HOME,
|
|
846
867
|
env: { ...process.env, MINIONS_HOME },
|
package/engine/check-status.js
CHANGED
|
@@ -4,7 +4,7 @@ const dir = path.resolve(__dirname, '..');
|
|
|
4
4
|
|
|
5
5
|
console.log('=== Work Items (non-done) ===');
|
|
6
6
|
let items = [];
|
|
7
|
-
try { items =
|
|
7
|
+
try { items = require('./queries').getWorkItems() || []; } catch {}
|
|
8
8
|
items.filter(i => i.status !== 'done').forEach(i => {
|
|
9
9
|
console.log(i.id, (i.status || '').padEnd(12), (i.type || '').padEnd(12), (i.title || '').slice(0, 60));
|
|
10
10
|
});
|
package/engine/db/index.js
CHANGED
|
@@ -54,6 +54,14 @@ function _installExperimentalWarningFilter() {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
function getDb() {
|
|
57
|
+
// Hard opt-out (issue #3035, option #3): MINIONS_FORCE_JSON=1 makes
|
|
58
|
+
// every getDb() call throw, forcing the entire stack onto the JSON
|
|
59
|
+
// fallback path. Useful for users who can't enable
|
|
60
|
+
// --experimental-sqlite on Node 22.x and for regression tests that
|
|
61
|
+
// need to exercise the JSON-fallback branches deterministically.
|
|
62
|
+
if (process.env.MINIONS_FORCE_JSON === '1') {
|
|
63
|
+
throw new Error('engine/db: SQL disabled via MINIONS_FORCE_JSON=1');
|
|
64
|
+
}
|
|
57
65
|
// Re-resolve the DB path on every call so tests that swap MINIONS_TEST_DIR
|
|
58
66
|
// (or production-side configuration that changes MINIONS_HOME between
|
|
59
67
|
// operations) get a fresh connection to the new location instead of
|
|
@@ -94,10 +102,11 @@ function getDb() {
|
|
|
94
102
|
runMigrations(_db);
|
|
95
103
|
return _db;
|
|
96
104
|
} catch (e) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
`Node 22.
|
|
100
|
-
|
|
105
|
+
const nodeMajor = parseInt(String(process.versions.node).split('.')[0], 10);
|
|
106
|
+
const flagHint = nodeMajor === 22
|
|
107
|
+
? ` On Node 22.x, node:sqlite is gated behind --experimental-sqlite. Restart with NODE_OPTIONS=--experimental-sqlite, upgrade to Node 24+, or set MINIONS_FORCE_JSON=1 to bypass SQL.`
|
|
108
|
+
: ` Node 22.5+ (with --experimental-sqlite on 22.x) or 24+ required for built-in 'node:sqlite' support.`;
|
|
109
|
+
_dbInitError = new Error(`engine/db: failed to open SQLite — ${e.message}.${flagHint}`);
|
|
101
110
|
throw _dbInitError;
|
|
102
111
|
}
|
|
103
112
|
}
|
|
@@ -149,11 +149,44 @@ function readPullRequestsForScope(scope) {
|
|
|
149
149
|
return out;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
// Enumerate scopes that have a JSON file on disk — used as the
|
|
153
|
+
// SQL-unavailable fallback for `readAllPullRequests`. Mirrors
|
|
154
|
+
// work-items-store's `_enumerateJsonScopes` shape.
|
|
155
|
+
function _enumerateJsonScopes() {
|
|
156
|
+
const shared = require('./shared');
|
|
157
|
+
const scopes = [];
|
|
158
|
+
const centralPath = path.join(shared.MINIONS_DIR, 'pull-requests.json');
|
|
159
|
+
if (fs.existsSync(centralPath)) scopes.push('central');
|
|
160
|
+
const projectsDir = path.join(shared.MINIONS_DIR, 'projects');
|
|
161
|
+
try {
|
|
162
|
+
for (const d of fs.readdirSync(projectsDir, { withFileTypes: true })) {
|
|
163
|
+
if (!d.isDirectory()) continue;
|
|
164
|
+
const fp = path.join(projectsDir, d.name, 'pull-requests.json');
|
|
165
|
+
if (fs.existsSync(fp)) scopes.push(d.name);
|
|
166
|
+
}
|
|
167
|
+
} catch { /* projects dir missing on fresh install */ }
|
|
168
|
+
return scopes;
|
|
169
|
+
}
|
|
170
|
+
|
|
152
171
|
function readAllPullRequests() {
|
|
153
172
|
const { getDb } = require('./db');
|
|
154
173
|
let db;
|
|
155
174
|
try { db = getDb(); }
|
|
156
|
-
catch {
|
|
175
|
+
catch {
|
|
176
|
+
// Issue #3035: SQLite unavailable (Node 22.x without
|
|
177
|
+
// --experimental-sqlite) — read every JSON scope on disk so the
|
|
178
|
+
// aggregate readers (queries.getPullRequests, shared.getPrLinks)
|
|
179
|
+
// continue to return real data instead of [].
|
|
180
|
+
const out = [];
|
|
181
|
+
for (const scope of _enumerateJsonScopes()) {
|
|
182
|
+
for (const pr of _readJsonArrayFallback(scope)) {
|
|
183
|
+
if (!pr || typeof pr !== 'object') continue;
|
|
184
|
+
pr._scope = scope;
|
|
185
|
+
out.push(pr);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
157
190
|
|
|
158
191
|
try { _resyncScopeIfJsonDiverged(db, 'central'); } catch {}
|
|
159
192
|
const knownScopes = db.prepare('SELECT DISTINCT scope FROM pull_requests').all().map(r => r.scope);
|
package/engine/shared.js
CHANGED
|
@@ -4970,33 +4970,24 @@ function getPrLinks() {
|
|
|
4970
4970
|
if (!knownPrIdsByDisplay.has(displayId)) knownPrIdsByDisplay.set(displayId, new Set());
|
|
4971
4971
|
knownPrIdsByDisplay.get(displayId).add(pr.id);
|
|
4972
4972
|
};
|
|
4973
|
-
// Primary source: derive from all
|
|
4974
|
-
|
|
4973
|
+
// Primary source: derive from all per-project + central PR records via the
|
|
4974
|
+
// SQL-canonical store (Phase 9). `readAllPullRequests` decorates each row
|
|
4975
|
+
// with `_scope` so we can look up the owning project for legacy-ID
|
|
4976
|
+
// normalization.
|
|
4975
4977
|
const projectsByName = new Map(getProjects().map(project => [project.name || path.basename(project.localPath || ''), project]));
|
|
4976
4978
|
try {
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
registerPrId(pr);
|
|
4986
|
-
mergePrLinkItems(links, pr.id, pr.prdItems || []);
|
|
4987
|
-
}
|
|
4988
|
-
} catch { /* missing or invalid */ }
|
|
4989
|
-
}
|
|
4990
|
-
} catch { /* projects dir missing */ }
|
|
4991
|
-
try {
|
|
4992
|
-
const centralPrs = JSON.parse(fs.readFileSync(path.join(MINIONS_DIR, 'pull-requests.json'), 'utf8'));
|
|
4993
|
-
normalizePrRecords(centralPrs, null);
|
|
4994
|
-
for (const pr of centralPrs) {
|
|
4995
|
-
if (!pr.id) continue;
|
|
4979
|
+
const store = require('./pull-requests-store');
|
|
4980
|
+
const allPrs = store.readAllPullRequests() || [];
|
|
4981
|
+
for (const pr of allPrs) {
|
|
4982
|
+
if (!pr?.id) continue;
|
|
4983
|
+
const scope = pr._scope;
|
|
4984
|
+
delete pr._scope;
|
|
4985
|
+
const project = scope && scope !== 'central' ? (projectsByName.get(scope) || null) : null;
|
|
4986
|
+
normalizePrRecords([pr], project);
|
|
4996
4987
|
registerPrId(pr);
|
|
4997
4988
|
mergePrLinkItems(links, pr.id, pr.prdItems || []);
|
|
4998
4989
|
}
|
|
4999
|
-
} catch { /*
|
|
4990
|
+
} catch { /* SQL unavailable — skip primary source */ }
|
|
5000
4991
|
// Fallback: static pr-links.json for entries not covered above
|
|
5001
4992
|
try {
|
|
5002
4993
|
const static_ = JSON.parse(fs.readFileSync(PR_LINKS_PATH, 'utf8'));
|
|
@@ -5054,27 +5045,21 @@ function addPrLink(prId, itemId, { project = null, url = '', prNumber = null } =
|
|
|
5054
5045
|
if (!project) return;
|
|
5055
5046
|
const prPath = projectPrPath(project);
|
|
5056
5047
|
const effectivePrNumber = getPrNumber(prNumber ?? effectivePrId);
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
if (!Array.isArray(prs)) prs = [];
|
|
5048
|
+
// Phase 9 SQL routing: mutatePullRequests writes through pull-requests-store
|
|
5049
|
+
// so SQL stays canonical; the JSON mirror is regenerated by the store.
|
|
5050
|
+
mutatePullRequests(prPath, (prs) => {
|
|
5051
|
+
if (!Array.isArray(prs) || prs.length === 0) return prs;
|
|
5062
5052
|
normalizePrRecords(prs, project);
|
|
5063
5053
|
const existingPr = prs.find(pr =>
|
|
5064
5054
|
pr?.id === effectivePrId
|
|
5065
5055
|
|| (url && pr?.url === url)
|
|
5066
5056
|
|| (effectivePrNumber != null && getPrNumber(pr) === effectivePrNumber)
|
|
5067
5057
|
);
|
|
5068
|
-
if (!existingPr) return;
|
|
5069
|
-
const backupPath = prPath + '.backup';
|
|
5070
|
-
try { if (fs.existsSync(prPath)) fs.copyFileSync(prPath, backupPath); } catch { /* backup is best-effort */ }
|
|
5058
|
+
if (!existingPr) return prs;
|
|
5071
5059
|
existingPr.prdItems = Array.isArray(existingPr.prdItems) ? existingPr.prdItems : [];
|
|
5072
|
-
if (existingPr.prdItems.includes(itemId)) return;
|
|
5060
|
+
if (existingPr.prdItems.includes(itemId)) return prs;
|
|
5073
5061
|
existingPr.prdItems.push(itemId);
|
|
5074
|
-
|
|
5075
|
-
}, {
|
|
5076
|
-
retries: ENGINE_DEFAULTS.lockRetries,
|
|
5077
|
-
retryBackoffMs: ENGINE_DEFAULTS.lockRetryBackoffMs
|
|
5062
|
+
return prs;
|
|
5078
5063
|
});
|
|
5079
5064
|
}
|
|
5080
5065
|
|
package/engine/supervisor.js
CHANGED
|
@@ -133,10 +133,19 @@ function openAppendFd(name) {
|
|
|
133
133
|
try { return fs.openSync(path.join(dir, name), 'a'); } catch { return 'ignore'; }
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
function _sqliteSpawnFlags() {
|
|
137
|
+
if (process.env.MINIONS_FORCE_JSON === '1') return [];
|
|
138
|
+
const major = parseInt(String(process.versions.node).split('.')[0], 10);
|
|
139
|
+
if (!Number.isFinite(major) || major < 22 || major >= 24) return [];
|
|
140
|
+
const nodeOpts = String(process.env.NODE_OPTIONS || '');
|
|
141
|
+
if (nodeOpts.includes('--experimental-sqlite')) return [];
|
|
142
|
+
return ['--experimental-sqlite'];
|
|
143
|
+
}
|
|
144
|
+
|
|
136
145
|
function spawnEngine() {
|
|
137
146
|
const out = openAppendFd('engine-stdio.log');
|
|
138
147
|
const err = openAppendFd('engine-stdio.log');
|
|
139
|
-
const proc = spawn(process.execPath, [path.join(MINIONS_DIR, 'engine.js'), 'start'], {
|
|
148
|
+
const proc = spawn(process.execPath, [..._sqliteSpawnFlags(), path.join(MINIONS_DIR, 'engine.js'), 'start'], {
|
|
140
149
|
cwd: MINIONS_DIR,
|
|
141
150
|
stdio: ['ignore', out, err],
|
|
142
151
|
detached: true,
|
|
@@ -150,7 +159,7 @@ function spawnDashboard() {
|
|
|
150
159
|
const out = openAppendFd('dashboard-stdio.log');
|
|
151
160
|
const err = openAppendFd('dashboard-stdio.log');
|
|
152
161
|
const env = { ...process.env, MINIONS_NO_AUTO_OPEN: '1' };
|
|
153
|
-
const proc = spawn(process.execPath, [path.join(MINIONS_DIR, 'dashboard.js')], {
|
|
162
|
+
const proc = spawn(process.execPath, [..._sqliteSpawnFlags(), path.join(MINIONS_DIR, 'dashboard.js')], {
|
|
154
163
|
cwd: MINIONS_DIR,
|
|
155
164
|
stdio: ['ignore', out, err],
|
|
156
165
|
detached: true,
|
|
@@ -138,14 +138,52 @@ function _resyncScopeIfJsonDiverged(db, scope) {
|
|
|
138
138
|
_lastMirrorHashByScope.set(scope, currentHash);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
// Enumerate scopes that have a JSON file on disk — used as the SQL-
|
|
142
|
+
// unavailable fallback for `readAllWorkItems`. Walks `<MINIONS_DIR>/
|
|
143
|
+
// projects/*/work-items.json` plus the central `<MINIONS_DIR>/work-
|
|
144
|
+
// items.json`. Silently swallows missing dirs (fresh installs).
|
|
145
|
+
function _enumerateJsonScopes() {
|
|
146
|
+
const shared = require('./shared');
|
|
147
|
+
const scopes = [];
|
|
148
|
+
const centralPath = path.join(shared.MINIONS_DIR, 'work-items.json');
|
|
149
|
+
if (fs.existsSync(centralPath)) scopes.push('central');
|
|
150
|
+
const projectsDir = path.join(shared.MINIONS_DIR, 'projects');
|
|
151
|
+
try {
|
|
152
|
+
for (const d of fs.readdirSync(projectsDir, { withFileTypes: true })) {
|
|
153
|
+
if (!d.isDirectory()) continue;
|
|
154
|
+
const fp = path.join(projectsDir, d.name, 'work-items.json');
|
|
155
|
+
if (fs.existsSync(fp)) scopes.push(d.name);
|
|
156
|
+
}
|
|
157
|
+
} catch { /* projects dir missing on fresh install */ }
|
|
158
|
+
return scopes;
|
|
159
|
+
}
|
|
160
|
+
|
|
141
161
|
// Read all rows across all scopes — used by queries.getWorkItems which
|
|
142
162
|
// needs to surface central + every project's items in a single shot,
|
|
143
163
|
// tagged with their source scope.
|
|
164
|
+
//
|
|
165
|
+
// Issue #3035: SQL-unavailable installs (Node 22.x without
|
|
166
|
+
// --experimental-sqlite, prior to v0.1.2113) returned [] from this path
|
|
167
|
+
// because `getDb()` throws and the legacy `return null` signal was
|
|
168
|
+
// swallowed by `queries.getWorkItems`'s `|| []`. Mirror the per-scope
|
|
169
|
+
// reader's JSON-fallback shape so the aggregate API stays useful on
|
|
170
|
+
// Node versions that don't have node:sqlite enabled.
|
|
144
171
|
function readAllWorkItems() {
|
|
145
172
|
const { getDb } = require('./db');
|
|
146
173
|
let db;
|
|
147
174
|
try { db = getDb(); }
|
|
148
|
-
catch {
|
|
175
|
+
catch {
|
|
176
|
+
// SQLite unavailable — read every JSON scope on disk directly.
|
|
177
|
+
const out = [];
|
|
178
|
+
for (const scope of _enumerateJsonScopes()) {
|
|
179
|
+
for (const wi of _readJsonArrayFallback(scope)) {
|
|
180
|
+
if (!wi || typeof wi !== 'object') continue;
|
|
181
|
+
wi._source = scope;
|
|
182
|
+
out.push(wi);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
149
187
|
|
|
150
188
|
// Pick up any external JSON edits for every scope SQL knows about.
|
|
151
189
|
// Also resync 'central' explicitly so first-time reads on a JSON-only
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2114",
|
|
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"
|