@yemi33/minions 0.1.2177 → 0.1.2179
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 +24 -11
- package/dashboard/js/command-parser.js +1 -1
- package/dashboard/js/memory-panel.js +262 -0
- package/dashboard/js/qa.js +2 -2
- package/dashboard/js/refresh.js +9 -1
- package/dashboard/js/render-dispatch.js +92 -0
- package/dashboard/js/render-other.js +1 -1
- package/dashboard/js/render-plans.js +82 -13
- package/dashboard/js/render-prs.js +2 -1
- package/dashboard/js/render-schedules.js +1 -1
- package/dashboard/js/render-watches.js +1 -1
- package/dashboard/js/settings.js +100 -11
- package/dashboard/layout.html +6 -0
- package/dashboard/pages/engine-memory-panel.html +49 -0
- package/dashboard/pages/engine.html +1 -0
- package/dashboard/slim/js/link-pr.js +5 -5
- package/dashboard/slim/js/modals-tiles.js +44 -3
- package/dashboard/slim/js/projects.js +8 -6
- package/dashboard/slim/styles.css +20 -0
- package/dashboard/styles.css +39 -0
- package/dashboard-build.js +17 -2
- package/dashboard.js +469 -21
- package/docs/README.md +8 -1
- package/docs/auto-discovery.md +40 -0
- package/docs/branch-derivation.md +13 -1
- package/docs/cross-repo-plans.md +292 -0
- package/docs/deprecated.json +4 -4
- package/docs/pr-auto-fix-dispatch.md +64 -0
- package/docs/pr-review-fix-loop.md +1 -1
- package/docs/watches.md +1 -0
- package/engine/ado.js +1 -10
- package/engine/diagnostics-memory.js +190 -0
- package/engine/dispatch.js +53 -0
- package/engine/lifecycle.js +155 -191
- package/engine/meeting.js +30 -0
- package/engine/playbook.js +15 -0
- package/engine/queries.js +165 -5
- package/engine/runtimes/copilot.js +19 -0
- package/engine/shared.js +303 -3
- package/engine/watchdog.js +6 -0
- package/engine.js +576 -113
- package/package.json +2 -2
- package/playbooks/plan-to-prd.md +25 -2
- package/playbooks/plan.md +4 -2
package/engine/shared.js
CHANGED
|
@@ -484,18 +484,99 @@ function resolveEngineCacheDir(fallbackEngineDir) {
|
|
|
484
484
|
// Cross-platform URL opener. Uses execSync so failures fall through the
|
|
485
485
|
// try/catch and the caller sees them. Dashboard self-open and `minions dash`
|
|
486
486
|
// / `minions restart` post-health open all funnel through here.
|
|
487
|
-
|
|
487
|
+
//
|
|
488
|
+
// Every call writes a structured `event: 'browser-open'` entry to log.json
|
|
489
|
+
// (via shared.log) and bumps a per-reason counter in `_engine.browserOpens`
|
|
490
|
+
// in metrics.json. Callers MUST pass `opts.reason` so the log/metric is
|
|
491
|
+
// attributable; a missing reason is logged at `warn` level with
|
|
492
|
+
// `reason: 'unknown'` to make a regression loud rather than silent.
|
|
493
|
+
//
|
|
494
|
+
// `MINIONS_NO_AUTO_OPEN=1` short-circuits the open and writes a debug-level
|
|
495
|
+
// SUPPRESSED entry instead — this lets us prove the kill-switch is firing
|
|
496
|
+
// in production (absence of opens otherwise looks identical whether the
|
|
497
|
+
// guard worked, nobody tried to open, or the guard regressed).
|
|
498
|
+
//
|
|
499
|
+
// Note: browser windows opened by external MCP servers (e.g. @playwright/mcp)
|
|
500
|
+
// do NOT funnel through this primitive and are therefore NOT logged. See
|
|
501
|
+
// MTG-mqam2mjs000e39d7.
|
|
502
|
+
//
|
|
503
|
+
// @param {string} url - URL to open in the user's default browser.
|
|
504
|
+
// @param {Object} [opts]
|
|
505
|
+
// @param {string} [opts.reason] - Why we are opening (e.g. 'cli-dash-warm',
|
|
506
|
+
// 'cli-start-force-open', 'dashboard-self-open'). Required-in-practice;
|
|
507
|
+
// missing values are logged at warn level.
|
|
508
|
+
// @param {string} [opts.callerHint] - Optional `file:line` to disambiguate
|
|
509
|
+
// when one reason has multiple call sites.
|
|
510
|
+
// @returns {{ok: boolean, error?: string, suppressed?: boolean}}
|
|
511
|
+
function openUrlInBrowser(url, opts = {}) {
|
|
512
|
+
const reason = (opts && typeof opts.reason === 'string' && opts.reason) ? opts.reason : 'unknown';
|
|
513
|
+
const callerHint = (opts && typeof opts.callerHint === 'string') ? opts.callerHint : null;
|
|
514
|
+
// Heuristic-driven opens (no explicit user action this dispatch) log at warn
|
|
515
|
+
// so a leak shows up as a warn cluster in the dashboard log filter.
|
|
516
|
+
const HEURISTIC_REASONS = new Set(['cli-restart-no-beacon', 'dashboard-self-open']);
|
|
517
|
+
let level = HEURISTIC_REASONS.has(reason) ? 'warn' : 'info';
|
|
518
|
+
if (reason === 'unknown') level = 'warn';
|
|
519
|
+
const callerStack = (new Error()).stack.split('\n').slice(2, 5).map(s => s.trim()).join(' | ');
|
|
520
|
+
const baseMeta = {
|
|
521
|
+
event: 'browser-open',
|
|
522
|
+
url,
|
|
523
|
+
reason,
|
|
524
|
+
callerHint,
|
|
525
|
+
callerStack,
|
|
526
|
+
pid: process.pid,
|
|
527
|
+
platform: process.platform,
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
// Kill-switch: log SUPPRESSED at debug level and skip execSync. Centralizing
|
|
531
|
+
// the check here means every caller (existing + future) is observable, and
|
|
532
|
+
// outer guards (e.g. ralph's W-mqb9y83o000le41e leak fixes) become belt-and-
|
|
533
|
+
// suspenders without breaking the log audit trail.
|
|
534
|
+
if (process.env.MINIONS_NO_AUTO_OPEN) {
|
|
535
|
+
log('debug', `[browser-open] SUPPRESSED reason=${reason} url=${url} (MINIONS_NO_AUTO_OPEN=1)`, {
|
|
536
|
+
...baseMeta,
|
|
537
|
+
suppressed: true,
|
|
538
|
+
});
|
|
539
|
+
_bumpBrowserOpenMetric(reason, { suppressed: true });
|
|
540
|
+
return { ok: false, suppressed: true };
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
log(level, `[browser-open] reason=${reason} url=${url}`, baseMeta);
|
|
544
|
+
_bumpBrowserOpenMetric(reason);
|
|
545
|
+
|
|
488
546
|
const { execSync } = require('child_process');
|
|
489
547
|
try {
|
|
490
548
|
if (process.platform === 'win32') execSync(`start "" "${url}"`, { stdio: 'ignore', windowsHide: true });
|
|
491
549
|
else if (process.platform === 'darwin') execSync(`open "${url}"`, { stdio: 'ignore' });
|
|
492
550
|
else execSync(`xdg-open "${url}"`, { stdio: 'ignore' });
|
|
551
|
+
log(level, `[browser-open] ok reason=${reason}`, { ...baseMeta, ok: true });
|
|
493
552
|
return { ok: true };
|
|
494
553
|
} catch (e) {
|
|
495
|
-
|
|
554
|
+
const errMsg = e && e.message ? e.message : String(e);
|
|
555
|
+
log(level, `[browser-open] failed reason=${reason} error=${errMsg}`, { ...baseMeta, ok: false, error: errMsg });
|
|
556
|
+
return { ok: false, error: errMsg };
|
|
496
557
|
}
|
|
497
558
|
}
|
|
498
559
|
|
|
560
|
+
// Bump per-reason counter under metrics._engine.browserOpens. Best-effort —
|
|
561
|
+
// metrics.json writes must never throw out of openUrlInBrowser. Schema:
|
|
562
|
+
// _engine.browserOpens.<reason> = { count, suppressedCount, lastAt }
|
|
563
|
+
function _bumpBrowserOpenMetric(reason, { suppressed = false } = {}) {
|
|
564
|
+
try {
|
|
565
|
+
mutateMetrics((metrics) => {
|
|
566
|
+
if (!metrics._engine || typeof metrics._engine !== 'object') metrics._engine = {};
|
|
567
|
+
if (!metrics._engine.browserOpens || typeof metrics._engine.browserOpens !== 'object') {
|
|
568
|
+
metrics._engine.browserOpens = {};
|
|
569
|
+
}
|
|
570
|
+
const slot = metrics._engine.browserOpens[reason] || { count: 0, suppressedCount: 0, lastAt: null };
|
|
571
|
+
if (suppressed) slot.suppressedCount = (slot.suppressedCount || 0) + 1;
|
|
572
|
+
else slot.count = (slot.count || 0) + 1;
|
|
573
|
+
slot.lastAt = new Date().toISOString();
|
|
574
|
+
metrics._engine.browserOpens[reason] = slot;
|
|
575
|
+
return metrics;
|
|
576
|
+
});
|
|
577
|
+
} catch { /* metric best-effort — never throw out of openUrlInBrowser */ }
|
|
578
|
+
}
|
|
579
|
+
|
|
499
580
|
function _flushLogBuffer() {
|
|
500
581
|
if (_logBuffer.length === 0) return;
|
|
501
582
|
const drained = _logBuffer.splice(0);
|
|
@@ -2369,9 +2450,31 @@ const ENGINE_DEFAULTS = {
|
|
|
2369
2450
|
autoFixConflicts: true, // auto-dispatch fix agents when a PR has merge conflicts
|
|
2370
2451
|
autoFixBuilds: true, // auto-dispatch fix agents when a PR build fails
|
|
2371
2452
|
autoReviewPrs: true, // auto-dispatch review agents for newly opened agent PRs
|
|
2453
|
+
// P-a1f3c2d4 — hard-stop polling kill-switch / master override. When true,
|
|
2454
|
+
// section 2.6 (pollPrStatus) and 2.7 (pollPrHumanComments) skip both
|
|
2455
|
+
// providers regardless of adoPollEnabled / ghPollEnabled, and
|
|
2456
|
+
// discoverFromPrs treats pollEnabled as false for every project so the
|
|
2457
|
+
// downstream auto-dispatch gates (autoFixBuilds / autoFixConflicts /
|
|
2458
|
+
// autoReviewPrs / autoFixReviewFeedback / autoFixHumanComments) all
|
|
2459
|
+
// become inert. Default false so a fresh install behaves identically to
|
|
2460
|
+
// before this knob existed. Flip via Dashboard → Settings → Polling, or
|
|
2461
|
+
// set `engine.pollingPaused: true` in config.json. Reconciliation still
|
|
2462
|
+
// runs (it's a recovery sweep, not a convenience poll) — see section 2.7.
|
|
2463
|
+
pollingPaused: false, // hard-stop kill-switch / master override (see comment above)
|
|
2372
2464
|
autoReReviewPrs: true, // auto-dispatch review agents after a PR fix is pushed
|
|
2373
2465
|
autoFixReviewFeedback: true, // auto-dispatch fix agents for minions review changes-requested verdicts
|
|
2374
2466
|
autoFixHumanComments: true, // auto-dispatch fix agents for actionable human PR comments
|
|
2467
|
+
// P-b2e5d8c7 — hard-stop auto-fix kill-switch / master override. When true,
|
|
2468
|
+
// discoverFromPrs treats every auto-fix gate (autoFixBuilds /
|
|
2469
|
+
// autoFixConflicts / autoFixReviewFeedback / autoFixHumanComments) as false
|
|
2470
|
+
// for every project, so no fix agent is auto-dispatched against any PR.
|
|
2471
|
+
// Review polling, review dispatch (autoReviewPrs / autoReReviewPrs), and
|
|
2472
|
+
// reconciliation are intentionally NOT gated — operators can pause the
|
|
2473
|
+
// fix-storm during an incident while still seeing fresh PR status and
|
|
2474
|
+
// review verdicts. Default false so a fresh install behaves identically
|
|
2475
|
+
// to before this knob existed. Flip via Dashboard → Settings → Auto-fix
|
|
2476
|
+
// & Review Loop, or set `engine.autoFixPaused: true` in config.json.
|
|
2477
|
+
autoFixPaused: false, // hard-stop kill-switch / master override (see comment above)
|
|
2375
2478
|
autoConsolidateMemory: false, // opt-in: periodically spawn engine/kb-sweep-runner.js from the tick loop (4h cadence). Inbox→notes consolidation already runs every tick via consolidateInbox; this flag only controls the KB sweep.
|
|
2376
2479
|
prNoOpFixPauseAttempts: 2, // pause one PR automation cause after repeated no-op fixes for unchanged evidence
|
|
2377
2480
|
quarantineAutoRecoveryMax: 2, // #2996 follow-up: cap on auto-flipping WORKTREE_DIRTY/WORKTREE_DIVERGENT failures back to pending (the quarantine is self-healing so the next dispatch starts clean; the cap prevents infinite loops if quarantine itself keeps failing).
|
|
@@ -2468,6 +2571,39 @@ const ENGINE_DEFAULTS = {
|
|
|
2468
2571
|
maxBuildFixRetries: 3,
|
|
2469
2572
|
adoPollEnabled: true, // poll ADO PR status, comments, and reconciliation on each tick cycle
|
|
2470
2573
|
ghPollEnabled: true, // poll GitHub PR status, comments, and reconciliation on each tick cycle
|
|
2574
|
+
// P-c4d8e1a3 — granular per-poller flags for the 6 PR-poll axes + rebase
|
|
2575
|
+
// processor. Each one defaults true and silently inherits a `false` from
|
|
2576
|
+
// its legacy bundle macro (adoPollEnabled / ghPollEnabled) for backward
|
|
2577
|
+
// compat, so existing config.json knobs keep working. New behavior:
|
|
2578
|
+
// setting `adoPollEnabled: false` (or `ghPollEnabled: false`) alone now
|
|
2579
|
+
// also silences the matching reconcile sweep — operators who want the
|
|
2580
|
+
// recovery sweep back must explicitly set `adoPrReconcileEnabled: true`
|
|
2581
|
+
// (or `ghPrReconcileEnabled: true`). processPendingRebasesEnabled has no
|
|
2582
|
+
// legacy counterpart; the rebase processor was previously ungated. See
|
|
2583
|
+
// shared.resolvePollFlag for the resolution order and the per-axis
|
|
2584
|
+
// surfaces in engine.js section 2.6 / 2.7.
|
|
2585
|
+
adoPrStatusPollEnabled: true,
|
|
2586
|
+
adoPrCommentsPollEnabled: true,
|
|
2587
|
+
adoPrReconcileEnabled: true,
|
|
2588
|
+
ghPrStatusPollEnabled: true,
|
|
2589
|
+
ghPrCommentsPollEnabled: true,
|
|
2590
|
+
ghPrReconcileEnabled: true,
|
|
2591
|
+
processPendingRebasesEnabled: true,
|
|
2592
|
+
// P-d6f0a2b5 — granular work-discovery flags. Each one gates a single
|
|
2593
|
+
// discovery phase inside `engine.discoverWork()`. Default true so the
|
|
2594
|
+
// out-of-box tick behaviour is unchanged; flip OFF via
|
|
2595
|
+
// `config.engine.<flag>` to silence a single discovery source without
|
|
2596
|
+
// touching the per-project `project.workSources.*.enabled` toggles
|
|
2597
|
+
// (those keep composing independently inside the matching discoverFrom*
|
|
2598
|
+
// helper — a `false` at either level skips the call). Wired inline as
|
|
2599
|
+
// `config.engine?.<flag> !== false` at each call site —
|
|
2600
|
+
// shared.resolvePollFlag is intentionally NOT used here because no
|
|
2601
|
+
// legacy macro bundle ever covered these phases.
|
|
2602
|
+
prDiscoveryEnabled: true, // discoverFromPrs (per-project PR-driven fix/review/test discovery)
|
|
2603
|
+
workItemsDiscoveryEnabled: true, // discoverFromWorkItems (per-project work-items.json scan)
|
|
2604
|
+
centralWorkDiscoveryEnabled: true, // discoverCentralWorkItems (top-level work-items.json scan)
|
|
2605
|
+
scheduledWorkDiscoveryEnabled: true, // discoverScheduledWork (cron-style scheduled tasks + meetings)
|
|
2606
|
+
planMaterializationEnabled: true, // reconcilePrdStatuses + materializePlansAsWorkItems pair
|
|
2471
2607
|
prPollStatusEvery: 72, // poll PR build/review/merge status every N ticks for both ADO and GitHub (~12 min at default 10s tick)
|
|
2472
2608
|
prPollCommentsEvery: 72, // poll PR human comments every N ticks for both ADO and GitHub (~12 min at default 10s tick)
|
|
2473
2609
|
// W-mpxpckey000laa4f: lifted inline tick-counter literals to named constants
|
|
@@ -2478,6 +2614,11 @@ const ENGINE_DEFAULTS = {
|
|
|
2478
2614
|
cleanupEvery: 60, // runCleanup + MCP sync every N ticks (~10 min at default 10s tick)
|
|
2479
2615
|
planCompletionScanEvery: 60, // periodic PRD completion sweep (~10 min at default 10s tick) — catches plans completed while engine was down
|
|
2480
2616
|
watchPollEvery: 18, // checkWatches every N ticks (~3 min at default 10s tick)
|
|
2617
|
+
// P-b2c3d4e5: cadence for MEMORY_BASELINE log line + diagnostics-memory.json
|
|
2618
|
+
// sidecar write driven from engine.js tickInner. Defaults to 6 ticks ≈ 60s
|
|
2619
|
+
// at the default 10s tickInterval. Set to 0 (or any non-positive integer)
|
|
2620
|
+
// to disable both the log emission and sidecar write cleanly (operator opt-out).
|
|
2621
|
+
memoryBaselineEveryTicks: 6,
|
|
2481
2622
|
stalledDispatchSweepEvery: 120, // stalled-dispatch retry sweep (~20 min at default 10s tick) — only fires when all agents idle
|
|
2482
2623
|
// W-mp5trwh60008386d: per-PR 404 must repeat across N consecutive successful base-repo probes
|
|
2483
2624
|
// before flipping a PR to `abandoned`. A single 404 on `repos/{slug}/pulls/{n}` can be a transient
|
|
@@ -2522,6 +2663,26 @@ const ENGINE_DEFAULTS = {
|
|
|
2522
2663
|
agentBusyReassignMs: 600000, // 10min — reassign work item to another agent if preferred agent is busy beyond this threshold
|
|
2523
2664
|
ccEffort: null, // effort level for CC/doc-chat (null, 'low', 'medium', 'high')
|
|
2524
2665
|
enablePreDispatchEval: true, // P-d2a9f6e5: cheap LLM gate before queueing — on by default. See engine/pre-dispatch-eval.js (Ripley §3 recommendation, 2026-05-11 architecture review). Validates from acceptance_criteria when present, falls back to description when criteria are absent but description is rich (≥80 chars). Fail-open on any validator error.
|
|
2666
|
+
// W-mq9acoo800177bcb — bounded-concurrency for the pre-dispatch validator. The
|
|
2667
|
+
// discoverWork loop in engine.js used to run validateAcceptanceCriteria
|
|
2668
|
+
// sequentially, costing ~25-30s per item × N items per tick (an 11-item
|
|
2669
|
+
// approved PRD took ~5 min to queue). This caps the parallel LLM calls per
|
|
2670
|
+
// tick — 6 is a balance between LLM-provider concurrency tolerance and not
|
|
2671
|
+
// starving the rest of the tick budget. Clamped to [1, 20] in
|
|
2672
|
+
// dashboard.js#handleSettingsUpdate; engine.js#resolvePreDispatchEvalConcurrency
|
|
2673
|
+
// applies the same clamp at read time so a hand-edited config can't bypass it.
|
|
2674
|
+
preDispatchEvalConcurrency: 6,
|
|
2675
|
+
// W-mq9acoo800177bcb — short-circuit the validator for items materialized
|
|
2676
|
+
// from an approved/active PRD. plan-to-prd already LLM-vets every item that
|
|
2677
|
+
// lands in a PRD, so re-validating each item per discovery tick is the
|
|
2678
|
+
// dominant cost of the workflow that triggered this optimization (user-
|
|
2679
|
+
// observed: 2 PRDs / 11 items → ~5 min queueing). Items keep the
|
|
2680
|
+
// `_preDispatchEvalSkipped: 'prd-sourced'` marker on their dispatch entry for
|
|
2681
|
+
// observability. The bypass fires only when item.meta.item.sourcePlan is set
|
|
2682
|
+
// AND the parent PRD's status is approved/active; rejected/paused/revision-
|
|
2683
|
+
// requested/completed PRDs still flow through the normal validator path so a
|
|
2684
|
+
// stale PRD reopen can't smuggle in unvetted items.
|
|
2685
|
+
preDispatchEvalSkipPrdSourced: true,
|
|
2525
2686
|
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".
|
|
2526
2687
|
autoApplyReviewVote: false, // W-mpea9fyb0010febf / P-f7splitgate: gates POSITIVE auto-actions only — when true, the engine mirrors live platform vote state into local pull-requests.json reviewStatus (so platform-side approves auto-promote local status). When false (default), the verdict is recorded in pull-requests.json reviewStatus from the agent's completion only — informational. NEGATIVE-correction (dismissing the engine's own prior REQUEST_CHANGES / resetting its own prior negative ADO vote on a verdict flip to APPROVE) is correctness and runs UNCONDITIONALLY regardless of this flag.
|
|
2527
2688
|
|
|
@@ -2541,6 +2702,20 @@ const ENGINE_DEFAULTS = {
|
|
|
2541
2702
|
copilotSuppressAgentsMd: true, // Copilot --no-custom-instructions: stop AGENTS.md auto-load from fighting Minions playbook prompts
|
|
2542
2703
|
copilotStreamMode: 'on', // Copilot --stream <on|off>: 'on' streams assistant.message_delta events live; 'off' batches them
|
|
2543
2704
|
copilotReasoningSummaries: false, // Copilot --enable-reasoning-summaries (Anthropic-family models only)
|
|
2705
|
+
// P-mcp-storm — Copilot loads `~/.copilot/mcp-config.json` (the operator's
|
|
2706
|
+
// user-scope MCP servers) UNCONDITIONALLY on every process start: it is NOT
|
|
2707
|
+
// gated by `--add-dir`, cwd, or `hermeticHarness` (proven empirically — a
|
|
2708
|
+
// copilot spawned from a neutral dir with no `--add-dir ~/.copilot` still
|
|
2709
|
+
// connects every server). So every spawned Copilot agent boots the operator's
|
|
2710
|
+
// full interactive MCP stack; on Windows each `type:local`/`stdio` server
|
|
2711
|
+
// (e.g. playwright, maestro) launches its own visible console window and
|
|
2712
|
+
// playwright opens browser tabs. The only lever Copilot exposes is
|
|
2713
|
+
// `--disable-mcp-server <name>` (repeatable). This list names the servers the
|
|
2714
|
+
// engine disables for autonomous agent dispatches. Default [] = inherit ALL
|
|
2715
|
+
// (no behavior change). Per-agent override `agent.copilotAgentDisabledMcpServers`.
|
|
2716
|
+
// Resolved through `shared.resolveCopilotAgentDisabledMcpServers(agent, engine)`.
|
|
2717
|
+
// NOTE: `hermeticHarness` does NOT suppress these (it only strips `--add-dir`).
|
|
2718
|
+
copilotAgentDisabledMcpServers: [],
|
|
2544
2719
|
ccUseWorkerPool: false, // Sub-task C of W-mp2w003600196c51 (CC perf): when true AND CC runtime is copilot, _invokeCcStream routes through engine/cc-worker-pool.js (persistent `copilot --acp` per CC tab) instead of spawning a fresh CLI per turn. Off by default — opt-in feature flag. **Structurally copilot-only**: the pool spawns `copilot --acp` (Agent Client Protocol); Claude Code does not implement ACP, so resolveCcUseWorkerPool returns false on non-copilot CC runtimes even with explicit-true (W-mphlriic00095f69 — prevents silent runtime switch). Engine/agent dispatch path stays per-process regardless.
|
|
2545
2720
|
maxBudgetUsd: undefined, // fleet USD ceiling for --max-budget-usd (per-agent override: agents.<id>.maxBudgetUsd). Honors 0 via ?? so a literal cap of $0 works
|
|
2546
2721
|
disableModelDiscovery: false, // skip runtime.listModels() REST calls fleet-wide (settings UI falls back to free-text)
|
|
@@ -2770,6 +2945,31 @@ const ENGINE_DEFAULTS = {
|
|
|
2770
2945
|
operatorLogin: null,
|
|
2771
2946
|
};
|
|
2772
2947
|
|
|
2948
|
+
// ── P-c4d8e1a3: granular per-poller flag resolution ──────────────────────────
|
|
2949
|
+
//
|
|
2950
|
+
// Helper for the 6 PR-poll axes + processPendingRebases. Each axis has a
|
|
2951
|
+
// granular `*Enabled` flag in ENGINE_DEFAULTS (default true) plus an
|
|
2952
|
+
// optional legacy bundle macro (`adoPollEnabled` / `ghPollEnabled`) that
|
|
2953
|
+
// silences the matching ADO/GitHub trio when set to false. Resolution
|
|
2954
|
+
// order, in priority:
|
|
2955
|
+
//
|
|
2956
|
+
// 1. granular key explicitly present on engineCfg → use it
|
|
2957
|
+
// 2. legacyMacroKey is set on engineCfg AND === false → false
|
|
2958
|
+
// (legacy users keep working — and now reconcile is silenced too)
|
|
2959
|
+
// 3. fall through to ENGINE_DEFAULTS[granularKey] (default true)
|
|
2960
|
+
//
|
|
2961
|
+
// legacyMacroKey may be `null` for axes that have no legacy counterpart
|
|
2962
|
+
// (today: processPendingRebasesEnabled).
|
|
2963
|
+
function resolvePollFlag(engineCfg, granularKey, legacyMacroKey) {
|
|
2964
|
+
if (engineCfg && Object.prototype.hasOwnProperty.call(engineCfg, granularKey)) {
|
|
2965
|
+
return engineCfg[granularKey] !== false;
|
|
2966
|
+
}
|
|
2967
|
+
if (legacyMacroKey && engineCfg && engineCfg[legacyMacroKey] === false) {
|
|
2968
|
+
return false;
|
|
2969
|
+
}
|
|
2970
|
+
return ENGINE_DEFAULTS[granularKey] !== false;
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2773
2973
|
// ─── Runtime Fleet Resolution (P-3b8e5f1d) ──────────────────────────────────
|
|
2774
2974
|
//
|
|
2775
2975
|
// Six helpers that are the single source of truth for "which CLI runtime + model
|
|
@@ -2921,6 +3121,36 @@ function resolveAgentBareMode(agent, engine) {
|
|
|
2921
3121
|
return false;
|
|
2922
3122
|
}
|
|
2923
3123
|
|
|
3124
|
+
// P-mcp-storm — Resolve the list of MCP server names to disable for spawned
|
|
3125
|
+
// Copilot agents (emitted as `--disable-mcp-server <name>`). Chain: per-agent
|
|
3126
|
+
// `agent.copilotAgentDisabledMcpServers` → `engine.copilotAgentDisabledMcpServers`
|
|
3127
|
+
// → `[]` (inherit all). Tolerant of either an array OR a comma/whitespace-
|
|
3128
|
+
// separated string (Settings UI / `MINIONS_*` env deliver strings). Always
|
|
3129
|
+
// returns a deduped array of trimmed, non-empty strings. See ENGINE_DEFAULTS
|
|
3130
|
+
// for why this exists (Copilot loads ~/.copilot MCP config unconditionally;
|
|
3131
|
+
// `hermeticHarness` does NOT suppress it).
|
|
3132
|
+
function _normalizeMcpServerList(v) {
|
|
3133
|
+
if (v == null) return null;
|
|
3134
|
+
const raw = Array.isArray(v) ? v : String(v).split(/[\s,]+/);
|
|
3135
|
+
const out = [];
|
|
3136
|
+
const seen = new Set();
|
|
3137
|
+
for (const item of raw) {
|
|
3138
|
+
const name = String(item == null ? '' : item).trim();
|
|
3139
|
+
if (!name || seen.has(name)) continue;
|
|
3140
|
+
seen.add(name);
|
|
3141
|
+
out.push(name);
|
|
3142
|
+
}
|
|
3143
|
+
return out;
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
function resolveCopilotAgentDisabledMcpServers(agent, engine) {
|
|
3147
|
+
const a = agent ? _normalizeMcpServerList(agent.copilotAgentDisabledMcpServers) : null;
|
|
3148
|
+
if (a) return a;
|
|
3149
|
+
const e = engine ? _normalizeMcpServerList(engine.copilotAgentDisabledMcpServers) : null;
|
|
3150
|
+
if (e) return e;
|
|
3151
|
+
return [];
|
|
3152
|
+
}
|
|
3153
|
+
|
|
2924
3154
|
// ─── Legacy ccModel → defaultModel Migration ─────────────────────────────────
|
|
2925
3155
|
//
|
|
2926
3156
|
// Pre-P-3b8e5f1d, `engine.ccModel` was the single fleet-wide model knob (it
|
|
@@ -3704,6 +3934,49 @@ function extractPlanDeclaredProject(planContent) {
|
|
|
3704
3934
|
}
|
|
3705
3935
|
return '';
|
|
3706
3936
|
}
|
|
3937
|
+
|
|
3938
|
+
// P-7a3f1c08: Parse a cross-repo plan's list of target projects.
|
|
3939
|
+
// Primary signal: `<!-- minions:targetProjects=a, b, c -->` HTML comment.
|
|
3940
|
+
// Fallback (human): `**Projects:** a, b | c` markdown line in the first 80 lines.
|
|
3941
|
+
// Both forms accept `,`, `|`, or `;` as separators with surrounding whitespace.
|
|
3942
|
+
// Returns string[] of trimmed project names (empty array when nothing parseable).
|
|
3943
|
+
function extractPlanTargetProjects(planContent) {
|
|
3944
|
+
if (typeof planContent !== 'string' || !planContent.trim()) return [];
|
|
3945
|
+
|
|
3946
|
+
const split = (raw) => {
|
|
3947
|
+
if (typeof raw !== 'string') return [];
|
|
3948
|
+
return raw
|
|
3949
|
+
.split(/[,|;]/)
|
|
3950
|
+
.map(s => s.trim().replace(/^["'`]+|["'`]+$/g, '').trim())
|
|
3951
|
+
.filter(Boolean);
|
|
3952
|
+
};
|
|
3953
|
+
|
|
3954
|
+
// Primary signal: structured HTML comment marker (case-insensitive on the key).
|
|
3955
|
+
const markerMatch = planContent.match(/<!--\s*minions:targetProjects\s*=\s*([^>]*?)\s*-->/i);
|
|
3956
|
+
if (markerMatch) {
|
|
3957
|
+
const parsed = split(markerMatch[1]);
|
|
3958
|
+
if (parsed.length > 0) return parsed;
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3961
|
+
// Fallback signal: human-readable plural `Projects:` line — distinct from the
|
|
3962
|
+
// singular `Project:` line consumed by extractPlanDeclaredProject. Scan the
|
|
3963
|
+
// first 80 lines, matching the same leading-bullet / bold-markdown tolerance.
|
|
3964
|
+
const lines = planContent.split(/\r?\n/).slice(0, 80);
|
|
3965
|
+
for (const rawLine of lines) {
|
|
3966
|
+
const line = rawLine
|
|
3967
|
+
.replace(/^\s*[-*]\s+/, '')
|
|
3968
|
+
.replace(/\*\*/g, '')
|
|
3969
|
+
.trim();
|
|
3970
|
+
const match = line.match(/^Projects\s*:\s*(.+)$/i);
|
|
3971
|
+
if (!match) continue;
|
|
3972
|
+
let value = match[1].trim();
|
|
3973
|
+
value = value.replace(/\s+#.*$/, '').trim();
|
|
3974
|
+
const parsed = split(value);
|
|
3975
|
+
if (parsed.length > 0) return parsed;
|
|
3976
|
+
}
|
|
3977
|
+
|
|
3978
|
+
return [];
|
|
3979
|
+
}
|
|
3707
3980
|
const DISPATCH_RESULT = { SUCCESS: 'success', ERROR: 'error', TIMEOUT: 'timeout' };
|
|
3708
3981
|
const PIPELINE_STATUS = {
|
|
3709
3982
|
PENDING: 'pending', RUNNING: 'running', COMPLETED: 'completed',
|
|
@@ -4097,6 +4370,30 @@ function getProjects(config) {
|
|
|
4097
4370
|
return [];
|
|
4098
4371
|
}
|
|
4099
4372
|
|
|
4373
|
+
// Generic, ambiguous project-name tokens that read poorly as a bare label in
|
|
4374
|
+
// the dashboard. For these, projectDisplayName derives a `<parentFolder>/<name>`
|
|
4375
|
+
// label from the project's localPath. `src` is the required case. DISPLAY ONLY —
|
|
4376
|
+
// the project `name` field is a load-bearing identifier and is never mutated.
|
|
4377
|
+
const GENERIC_PROJECT_NAME_TOKENS = new Set(['src', 'source', 'repo']);
|
|
4378
|
+
|
|
4379
|
+
// Human-facing label for a project. Returns `project.name` unchanged for normal
|
|
4380
|
+
// names. When the name is a generic token (e.g. `src`), derives the parent
|
|
4381
|
+
// folder one level up from localPath and returns `<parentBasename>/<name>` —
|
|
4382
|
+
// e.g. name `src` at localPath `C:/office/src` → `office/src`. Falls back to the
|
|
4383
|
+
// bare name when localPath is missing or the parent can't be derived. Never
|
|
4384
|
+
// mutates the project; preserves the original name casing.
|
|
4385
|
+
function projectDisplayName(project) {
|
|
4386
|
+
if (!project || typeof project !== 'object') return '';
|
|
4387
|
+
const name = project.name == null ? '' : String(project.name);
|
|
4388
|
+
if (!GENERIC_PROJECT_NAME_TOKENS.has(name.trim().toLowerCase())) return name;
|
|
4389
|
+
const localPath = project.localPath == null ? '' : String(project.localPath);
|
|
4390
|
+
const normalized = localPath.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
4391
|
+
if (!normalized) return name;
|
|
4392
|
+
const parent = path.basename(path.dirname(normalized));
|
|
4393
|
+
if (!parent || parent === '.') return name;
|
|
4394
|
+
return `${parent}/${name}`;
|
|
4395
|
+
}
|
|
4396
|
+
|
|
4100
4397
|
function formatUnknownProjectError(projectName, projects = []) {
|
|
4101
4398
|
const known = projects.map(p => p.name).filter(Boolean).join(', ') || '(none configured)';
|
|
4102
4399
|
return `Project "${projectName}" not found. Known projects: ${known}`;
|
|
@@ -7504,14 +7801,16 @@ module.exports = {
|
|
|
7504
7801
|
KB_READABLE_CATEGORIES,
|
|
7505
7802
|
classifyInboxItem,
|
|
7506
7803
|
ENGINE_DEFAULTS,
|
|
7804
|
+
resolvePollFlag, // P-c4d8e1a3 — granular per-poller flag resolution
|
|
7507
7805
|
resolveAgentCli, resolveCcCli, resolveCcUseWorkerPool, resolveAgentModel, resolveCcModel,
|
|
7508
7806
|
resolveAgentMaxBudget, resolveAgentBareMode,
|
|
7807
|
+
resolveCopilotAgentDisabledMcpServers,
|
|
7509
7808
|
applyLegacyCcModelMigration, _resetLegacyCcModelMigrationFlag,
|
|
7510
7809
|
applyCcWorkerPoolForceOnMigration,
|
|
7511
7810
|
runtimeConfigWarnings,
|
|
7512
7811
|
projectWorkSourceWarnings,
|
|
7513
7812
|
backfillProjectWorkSourceDefaults,
|
|
7514
|
-
WI_STATUS, DONE_STATUSES, PLAN_TERMINAL_STATUSES, WORK_TYPE, WORKTREE_REQUIRING_TYPES, VALID_WORK_TYPES, resolveWorkItemTypeFromPrdItem, PLAN_STATUS, PRD_ITEM_STATUS, PRD_MATERIALIZABLE, PR_STATUS, PR_POLLABLE_STATUSES, PR_PENDING_REASON, BUILD_STATUS, REVIEW_STATUS, FETCH_TIMEOUT_MS, RETRY_DELAY_MS, ADO_TOKEN_REFRESH_MAX_RETRIES, DISPATCH_RESULT, mutateMetrics, mutateWatches, mutateScheduleRuns, mutatePipelineRuns, mutateManagedProcesses, mutateWorktreePool, mutateQaRuns, mutateQaSessions, trackReviewMetric, queuePlanToPrd, extractPlanDeclaredProject,
|
|
7813
|
+
WI_STATUS, DONE_STATUSES, PLAN_TERMINAL_STATUSES, WORK_TYPE, WORKTREE_REQUIRING_TYPES, VALID_WORK_TYPES, resolveWorkItemTypeFromPrdItem, PLAN_STATUS, PRD_ITEM_STATUS, PRD_MATERIALIZABLE, PR_STATUS, PR_POLLABLE_STATUSES, PR_PENDING_REASON, BUILD_STATUS, REVIEW_STATUS, FETCH_TIMEOUT_MS, RETRY_DELAY_MS, ADO_TOKEN_REFRESH_MAX_RETRIES, DISPATCH_RESULT, mutateMetrics, mutateWatches, mutateScheduleRuns, mutatePipelineRuns, mutateManagedProcesses, mutateWorktreePool, mutateQaRuns, mutateQaSessions, trackReviewMetric, queuePlanToPrd, extractPlanDeclaredProject, extractPlanTargetProjects,
|
|
7515
7814
|
WATCH_STATUS, WATCH_TARGET_TYPE, WATCH_CONDITION, WATCH_ABSOLUTE_CONDITIONS, WATCH_ACTION_TYPE,
|
|
7516
7815
|
WATCH_STALLED_DEFAULT_TICKS, WATCH_STUCK_STAGE_DEFAULT_TICKS,
|
|
7517
7816
|
PIPELINE_STATUS, STAGE_TYPE, MEETING_STATUS, AGENT_STATUS,
|
|
@@ -7532,6 +7831,7 @@ module.exports = {
|
|
|
7532
7831
|
mergeManifestAllowedTools,
|
|
7533
7832
|
formatManifestRejection,
|
|
7534
7833
|
getProjects,
|
|
7834
|
+
projectDisplayName,
|
|
7535
7835
|
formatUnknownProjectError,
|
|
7536
7836
|
findProjectByName,
|
|
7537
7837
|
findProjectByNameOrPath,
|
package/engine/watchdog.js
CHANGED
|
@@ -146,6 +146,12 @@ async function tick(opts) {
|
|
|
146
146
|
detached: true,
|
|
147
147
|
stdio: 'ignore',
|
|
148
148
|
windowsHide: true,
|
|
149
|
+
// W-mqb9y83o — suppress auto-open in the respawned dashboard. The
|
|
150
|
+
// watchdog has no user-intent signal (it fires on health failure, not
|
|
151
|
+
// user action), so the post-restart hook MUST NOT pop a browser tab.
|
|
152
|
+
// The CLI's spawnFullStackAndVerify reads this env var as a hard
|
|
153
|
+
// kill-switch via MINIONS_NO_AUTO_OPEN=1.
|
|
154
|
+
env: { ...process.env, MINIONS_NO_AUTO_OPEN: '1' },
|
|
149
155
|
});
|
|
150
156
|
if (child && typeof child.unref === 'function') child.unref();
|
|
151
157
|
logLine(minionsHome, `spawned minions ${action} pid=${child && child.pid} (detached)`);
|