@yemi33/minions 0.1.2383 → 0.1.2384
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/dashboard/js/refresh.js +2 -1
- package/dashboard/js/settings.js +1 -1
- package/dashboard.js +11 -7
- package/docs/keep-processes.md +7 -2
- package/docs/live-checkout-mode.md +7 -7
- package/docs/managed-spawn.md +14 -1
- package/docs/runtime-adapters.md +7 -4
- package/docs/worktree-lifecycle.md +22 -0
- package/engine/acp-transport.js +216 -29
- package/engine/agent-worker-pool.js +268 -51
- package/engine/cc-worker-pool.js +8 -1
- package/engine/cleanup.js +15 -11
- package/engine/cli.js +47 -27
- package/engine/create-pr-worktree.js +57 -79
- package/engine/keep-process-sweep.js +131 -9
- package/engine/lifecycle.js +1 -1
- package/engine/live-checkout.js +4 -2
- package/engine/managed-spawn.js +112 -5
- package/engine/pooled-agent-process.js +486 -21
- package/engine/pr-action.js +6 -8
- package/engine/process-utils.js +154 -18
- package/engine/runtimes/copilot.js +22 -4
- package/engine/shared.js +254 -61
- package/engine/spawn-agent.js +6 -3
- package/engine/timeout.js +14 -10
- package/engine/worktree-gc.js +55 -46
- package/engine.js +237 -134
- package/package.json +1 -1
- package/playbooks/shared-rules.md +2 -2
- package/prompts/cc-system.md +11 -11
package/engine/managed-spawn.js
CHANGED
|
@@ -302,7 +302,22 @@ function _validateSpec(spec, index, limits, opts) {
|
|
|
302
302
|
if (typeof spec.cwd === 'string' && spec.cwd.length > 500) {
|
|
303
303
|
return { ok: false, reason: 'cwd-too-long' };
|
|
304
304
|
}
|
|
305
|
-
if (
|
|
305
|
+
if (opts.allowedCwdRoot) {
|
|
306
|
+
if (typeof spec.cwd !== 'string' || spec.cwd.length === 0) {
|
|
307
|
+
return { ok: false, reason: INVALID_WORKDIR_REASON_PREFIX + 'cwd-required-for-dispatch-confinement' };
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
const cwdReal = shared.realPathForComparison(spec.cwd);
|
|
311
|
+
const rootReal = shared.realPathForComparison(opts.allowedCwdRoot);
|
|
312
|
+
if (!shared.isPathInsideOrEqual(cwdReal, rootReal)) {
|
|
313
|
+
return { ok: false, reason: INVALID_WORKDIR_REASON_PREFIX + 'cwd-outside-dispatch-worktree' };
|
|
314
|
+
}
|
|
315
|
+
} catch (err) {
|
|
316
|
+
return { ok: false, reason: INVALID_WORKDIR_REASON_PREFIX + 'cwd-containment-check-failed (' + err.message + ')' };
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
let normalizedCwd = typeof spec.cwd === 'string' ? spec.cwd : '';
|
|
320
|
+
if (_resolveRequireGitWorkdir(opts) && normalizedCwd.length > 0) {
|
|
306
321
|
const wt = shared.isValidGitWorktree(spec.cwd, { memo: opts._gitWorktreeMemo });
|
|
307
322
|
if (!wt.ok) {
|
|
308
323
|
// W-mpbpa01y000qcdc2 — enrich the reject reason so the dispatcher prompt
|
|
@@ -319,6 +334,12 @@ function _validateSpec(spec, index, limits, opts) {
|
|
|
319
334
|
return { ok: false, reason: INVALID_WORKDIR_REASON_PREFIX + detail };
|
|
320
335
|
}
|
|
321
336
|
}
|
|
337
|
+
if (normalizedCwd) {
|
|
338
|
+
try { normalizedCwd = shared.realPathForComparison(normalizedCwd); }
|
|
339
|
+
catch (err) {
|
|
340
|
+
return { ok: false, reason: INVALID_WORKDIR_REASON_PREFIX + 'cwd-canonicalization-failed (' + err.message + ')' };
|
|
341
|
+
}
|
|
342
|
+
}
|
|
322
343
|
|
|
323
344
|
// env (optional). Validation has three independent layers:
|
|
324
345
|
// 1. Shape guard: key matches /^[A-Za-z_][A-Za-z0-9_]*$/ (argv-smuggling
|
|
@@ -339,7 +360,7 @@ function _validateSpec(spec, index, limits, opts) {
|
|
|
339
360
|
if (envKeys.length > maxEnv) {
|
|
340
361
|
return { ok: false, reason: 'env-too-many (>' + maxEnv + ')' };
|
|
341
362
|
}
|
|
342
|
-
const projectForSpec = _resolveProjectForCwd(
|
|
363
|
+
const projectForSpec = opts.project || _resolveProjectForCwd(
|
|
343
364
|
typeof spec.cwd === 'string' ? spec.cwd : '',
|
|
344
365
|
opts && Array.isArray(opts.projects) ? opts.projects : null,
|
|
345
366
|
);
|
|
@@ -411,7 +432,7 @@ function _validateSpec(spec, index, limits, opts) {
|
|
|
411
432
|
name: spec.name,
|
|
412
433
|
cmd: spec.cmd,
|
|
413
434
|
args: args,
|
|
414
|
-
cwd:
|
|
435
|
+
cwd: normalizedCwd,
|
|
415
436
|
env: env,
|
|
416
437
|
ports: ports,
|
|
417
438
|
ttl_minutes: ttlMinutes,
|
|
@@ -585,7 +606,7 @@ function buildManagedSpawnHint(opts) {
|
|
|
585
606
|
' "name": "constellation-host",',
|
|
586
607
|
' "cmd": "bun",',
|
|
587
608
|
' "args": ["run", "dev"],',
|
|
588
|
-
' "cwd": "
|
|
609
|
+
' "cwd": "<absolute path at or below MINIONS_AGENT_CWD>",',
|
|
589
610
|
' "env": { "VITE_HOST": "127.0.0.1" },',
|
|
590
611
|
' "ports": [3001],',
|
|
591
612
|
' "ttl_minutes": ' + ttl + ',',
|
|
@@ -624,6 +645,7 @@ function buildManagedSpawnHint(opts) {
|
|
|
624
645
|
'- Ports: 1024–65535, ≤ 20 per spec',
|
|
625
646
|
'- TTL: ≤ ' + maxTtl + ' minutes (hard cap), defaults to ' + defaultTtl + ' if omitted',
|
|
626
647
|
'- `attrs` serialized: ≤ 2048 bytes (opaque blob the engine surfaces to downstream agents)',
|
|
648
|
+
'- `cwd`: must resolve at or below this dispatch\'s `MINIONS_AGENT_CWD`. A worktree-mode project\'s operator checkout is rejected even though it is a valid Git checkout.',
|
|
627
649
|
'',
|
|
628
650
|
'If your file is invalid the engine marks this dispatch ERROR with `failure_class: invalid-managed-spawn` (non-retryable) — fix the file shape, don\'t retry blindly.',
|
|
629
651
|
'',
|
|
@@ -875,6 +897,9 @@ function _toStateRecord(spec, runtime, ctx) {
|
|
|
875
897
|
owner_agent: (ctx && ctx.owner_agent) || '',
|
|
876
898
|
owner_wi: (ctx && ctx.owner_wi) || '',
|
|
877
899
|
owner_project: (ctx && ctx.owner_project) || '',
|
|
900
|
+
owner_work_type: (ctx && ctx.owner_work_type) || '',
|
|
901
|
+
dispatch_root: (ctx && ctx.dispatch_root) || '',
|
|
902
|
+
checkout_mode: (ctx && ctx.checkout_mode) || '',
|
|
878
903
|
cmd: spec.cmd,
|
|
879
904
|
args: Array.isArray(spec.args) ? spec.args.slice() : [],
|
|
880
905
|
cwd: typeof spec.cwd === 'string' ? spec.cwd : '',
|
|
@@ -1265,7 +1290,69 @@ function computeManagedSpecsEtag(opts) {
|
|
|
1265
1290
|
return crypto.createHash('sha1').update(json).digest('hex').slice(0, 16);
|
|
1266
1291
|
}
|
|
1267
1292
|
|
|
1268
|
-
function
|
|
1293
|
+
function validateManagedSpecExecutionContext(record, opts) {
|
|
1294
|
+
opts = opts || {};
|
|
1295
|
+
if (!record || typeof record.cwd !== 'string' || !record.cwd) {
|
|
1296
|
+
return { ok: false, reason: 'cwd-missing' };
|
|
1297
|
+
}
|
|
1298
|
+
let projects;
|
|
1299
|
+
try {
|
|
1300
|
+
projects = Array.isArray(opts.projects) ? opts.projects : shared.getProjects(opts.config);
|
|
1301
|
+
} catch {
|
|
1302
|
+
return { ok: false, reason: 'project-config-unavailable' };
|
|
1303
|
+
}
|
|
1304
|
+
const project = record.owner_project
|
|
1305
|
+
? shared.findProjectByName(projects, record.owner_project)
|
|
1306
|
+
: null;
|
|
1307
|
+
for (const candidate of projects) {
|
|
1308
|
+
if (!candidate?.localPath) continue;
|
|
1309
|
+
const candidateMode = shared.resolveCheckoutMode(
|
|
1310
|
+
candidate,
|
|
1311
|
+
record.owner_work_type || shared.WORK_TYPE.IMPLEMENT,
|
|
1312
|
+
);
|
|
1313
|
+
if (candidateMode !== shared.CHECKOUT_MODES.WORKTREE) continue;
|
|
1314
|
+
try {
|
|
1315
|
+
if (shared.pathsOverlap(record.cwd, candidate.localPath)) {
|
|
1316
|
+
return { ok: false, reason: 'cwd-overlaps-worktree-mode-operator-checkout' };
|
|
1317
|
+
}
|
|
1318
|
+
} catch {
|
|
1319
|
+
return { ok: false, reason: 'cwd-overlap-check-failed' };
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
const mode = project
|
|
1323
|
+
? shared.resolveCheckoutMode(project, record.owner_work_type || shared.WORK_TYPE.IMPLEMENT)
|
|
1324
|
+
: shared.CHECKOUT_MODES.WORKTREE;
|
|
1325
|
+
if (project && mode === shared.CHECKOUT_MODES.LIVE) {
|
|
1326
|
+
if (record.dispatch_root && !shared.sameResolvedPath(record.dispatch_root, project.localPath)) {
|
|
1327
|
+
return { ok: false, reason: 'live-dispatch-root-mismatch' };
|
|
1328
|
+
}
|
|
1329
|
+
try {
|
|
1330
|
+
if (!shared.isPathInsideOrEqual(record.cwd, project.localPath)) {
|
|
1331
|
+
return { ok: false, reason: 'live-cwd-outside-project' };
|
|
1332
|
+
}
|
|
1333
|
+
} catch {
|
|
1334
|
+
return { ok: false, reason: 'live-cwd-unavailable' };
|
|
1335
|
+
}
|
|
1336
|
+
return { ok: true, project, mode };
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
if (!record.dispatch_root) {
|
|
1340
|
+
return record.checkout_mode
|
|
1341
|
+
? { ok: false, reason: 'dispatch-root-missing' }
|
|
1342
|
+
: { ok: true, legacy: true, project, mode };
|
|
1343
|
+
}
|
|
1344
|
+
try {
|
|
1345
|
+
shared.assertWorktreeOutsideProjects(record.dispatch_root, projects);
|
|
1346
|
+
if (!shared.isPathInsideOrEqual(record.cwd, record.dispatch_root)) {
|
|
1347
|
+
return { ok: false, reason: 'cwd-outside-dispatch-root' };
|
|
1348
|
+
}
|
|
1349
|
+
} catch {
|
|
1350
|
+
return { ok: false, reason: 'dispatch-root-inside-operator-checkout' };
|
|
1351
|
+
}
|
|
1352
|
+
return { ok: true, project, mode };
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
function restartManagedSpec(name, opts) {
|
|
1269
1356
|
if (typeof name !== 'string' || name.length === 0) {
|
|
1270
1357
|
throw new Error('restartManagedSpec: name required');
|
|
1271
1358
|
}
|
|
@@ -1273,6 +1360,11 @@ function restartManagedSpec(name) {
|
|
|
1273
1360
|
if (!existing) {
|
|
1274
1361
|
throw new Error('restartManagedSpec: spec not found: ' + name);
|
|
1275
1362
|
}
|
|
1363
|
+
const executionContext = validateManagedSpecExecutionContext(existing, opts);
|
|
1364
|
+
if (!executionContext.ok) {
|
|
1365
|
+
try { removeManagedSpec(name); } catch {}
|
|
1366
|
+
throw new Error('restartManagedSpec: unsafe persisted execution context (' + executionContext.reason + ')');
|
|
1367
|
+
}
|
|
1276
1368
|
// Reconstruct a sidecar-shaped spec from the persisted state row. The
|
|
1277
1369
|
// state shape keeps cmd/args/cwd/env/ports/attrs/healthcheck verbatim
|
|
1278
1370
|
// (see _toStateRecord), so this is a direct projection.
|
|
@@ -1299,6 +1391,9 @@ function restartManagedSpec(name) {
|
|
|
1299
1391
|
owner_agent: existing.owner_agent || '',
|
|
1300
1392
|
owner_wi: existing.owner_wi || '',
|
|
1301
1393
|
owner_project: existing.owner_project || '',
|
|
1394
|
+
owner_work_type: existing.owner_work_type || '',
|
|
1395
|
+
dispatch_root: existing.dispatch_root || '',
|
|
1396
|
+
checkout_mode: existing.checkout_mode || '',
|
|
1302
1397
|
};
|
|
1303
1398
|
const runtime = spawnManagedSpec(spec, ctx);
|
|
1304
1399
|
// recordManagedSpec replaces by name (item 2 idempotency contract) and
|
|
@@ -1380,6 +1475,12 @@ function _runManagedReconcile(opts) {
|
|
|
1380
1475
|
const killBatch = typeof opts.killBatch === 'function'
|
|
1381
1476
|
? opts.killBatch
|
|
1382
1477
|
: shared.killByPidsImmediate;
|
|
1478
|
+
let projects;
|
|
1479
|
+
try {
|
|
1480
|
+
projects = Array.isArray(opts.projects) ? opts.projects : shared.getProjects(opts.config);
|
|
1481
|
+
} catch {
|
|
1482
|
+
projects = [];
|
|
1483
|
+
}
|
|
1383
1484
|
const stats = {
|
|
1384
1485
|
scanned: 0,
|
|
1385
1486
|
ttlExpired: 0,
|
|
@@ -1413,6 +1514,11 @@ function _runManagedReconcile(opts) {
|
|
|
1413
1514
|
stats.deadDropped++;
|
|
1414
1515
|
continue; // dead + not expired → drop
|
|
1415
1516
|
}
|
|
1517
|
+
const executionContext = validateManagedSpecExecutionContext(rec, { projects });
|
|
1518
|
+
if (!executionContext.ok) {
|
|
1519
|
+
ttlPidsToKill.push(rec.pid);
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1416
1522
|
kept.push(rec);
|
|
1417
1523
|
survivors.push({ name: rec.name, log_path: rec.log_path || '', healthy: rec.healthy === true, last_health_at: rec.last_health_at || 0 });
|
|
1418
1524
|
}
|
|
@@ -1550,6 +1656,7 @@ module.exports = {
|
|
|
1550
1656
|
// Item 4 (P-4b8d2e57): discovery API (etag + state-driven respawn).
|
|
1551
1657
|
computeManagedSpecsEtag: computeManagedSpecsEtag,
|
|
1552
1658
|
restartManagedSpec: restartManagedSpec,
|
|
1659
|
+
validateManagedSpecExecutionContext: validateManagedSpecExecutionContext,
|
|
1553
1660
|
// Item 7 (P-8a4d6f29): TTL sweep + boot reconcile + project cleanup.
|
|
1554
1661
|
sweepManagedSpawn: sweepManagedSpawn,
|
|
1555
1662
|
bootReconcileManagedSpawn: bootReconcileManagedSpawn,
|