@yemi33/minions 0.1.2447 → 0.1.2449
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/cli-api-client.js +1 -1
- package/bin/install-internal-minions.js +1382 -44
- package/bin/install-layout.js +150 -0
- package/bin/minions.js +460 -167
- package/dashboard/docs/typography.md +65 -12
- package/dashboard/js/command-center.js +66 -7
- package/dashboard/js/detail-panel.js +36 -0
- package/dashboard/js/memory-panel.js +59 -12
- package/dashboard/js/qa.js +186 -45
- package/dashboard/js/refresh.js +148 -12
- package/dashboard/js/render-dispatch.js +3 -4
- package/dashboard/js/render-inbox.js +2 -2
- package/dashboard/js/render-other.js +3 -3
- package/dashboard/js/render-pipelines.js +14 -0
- package/dashboard/js/render-plans.js +57 -9
- package/dashboard/js/render-prd.js +132 -23
- package/dashboard/js/render-prs.js +195 -166
- package/dashboard/js/render-schedules.js +63 -3
- package/dashboard/js/render-utils.js +3 -3
- package/dashboard/js/render-watches.js +19 -3
- package/dashboard/js/render-work-items.js +238 -30
- package/dashboard/js/settings.js +205 -54
- package/dashboard/js/utils.js +51 -1
- package/dashboard/pages/home.html +1 -1
- package/dashboard/pages/qa.html +1 -16
- package/dashboard/pages/work.html +40 -0
- package/dashboard/shared/cc-limits.js +79 -0
- package/dashboard/shared/pr-filters.js +21 -38
- package/dashboard/shared/project-git-summary.js +1 -1
- package/dashboard/shared/record-filters.js +169 -0
- package/dashboard/shared/watches-source.js +1 -1
- package/dashboard/shared/welcome-popup.js +1 -1
- package/dashboard/shared/wi-filters.js +302 -0
- package/dashboard/slim/body.html +1 -0
- package/dashboard/slim/js/command-send.js +26 -0
- package/dashboard/slim/js/modals-tiles.js +380 -39
- package/dashboard/slim/js/status.js +13 -21
- package/dashboard/slim/layout.html +1 -0
- package/dashboard/slim/panel-bootstrap.js +6 -2
- package/dashboard/slim/styles.css +38 -0
- package/dashboard/styles.css +159 -55
- package/dashboard-build.js +52 -6
- package/dashboard.js +956 -423
- package/docs/README.md +11 -6
- package/docs/api-errors.md +2 -2
- package/docs/architecture-review-2026-07-09.md +1 -1
- package/docs/architecture.excalidraw +2 -2
- package/docs/auto-discovery.md +18 -9
- package/docs/branch-derivation.md +4 -4
- package/docs/capture-demos.js +39 -2
- package/docs/ci-runner-canary.md +123 -0
- package/docs/claude-md-propagation.md +2 -2
- package/docs/cloud-agent-dispatch.md +204 -0
- package/docs/command-center.md +7 -7
- package/docs/completion-reports.md +43 -20
- package/docs/constants.md +10 -3
- package/docs/constellation-bridge.md +134 -6
- package/docs/constellation-style-telemetry.md +4 -4
- package/docs/contracts/capability-protocol.v1.json +165 -0
- package/docs/cooldown-merge-semantics.md +12 -12
- package/docs/copilot-cli-schema.md +7 -7
- package/docs/cross-repo-plans.md +10 -10
- package/docs/dead-code-audit-retractions.md +5 -5
- package/docs/default-branch-ci.md +173 -0
- package/docs/deprecated.json +31 -31
- package/docs/design-inbox-entries-schema.md +3 -3
- package/docs/design-language.md +1051 -0
- package/docs/design-state-storage.md +11 -11
- package/docs/diagnostics-crash-reports.md +9 -9
- package/docs/diagnostics-memory.md +5 -5
- package/docs/documentation-audit-2026-07-09.md +7 -7
- package/docs/engine-restart.md +90 -5
- package/docs/harness-mode.md +1 -1
- package/docs/internal-install.md +338 -39
- package/docs/kb-dedup-duplicate-pair-investigation.md +5 -5
- package/docs/kb-pr3223-cascade-archiving.md +1 -1
- package/docs/kb-pr696-merge-conflict-docs.md +6 -6
- package/docs/kb-sweep.md +35 -35
- package/docs/keep-processes.md +1 -1
- package/docs/live-checkout-mode.md +30 -30
- package/docs/managed-spawn.md +18 -14
- package/docs/named-agents.md +7 -7
- package/docs/plan-lifecycle.md +69 -2
- package/docs/pr-author-identity.md +114 -0
- package/docs/pr-auto-fix-dispatch.md +19 -4
- package/docs/pr-comment-followup.md +6 -6
- package/docs/pr-review-fix-loop.md +59 -10
- package/docs/process-termination.md +40 -0
- package/docs/proposals/repo-pool-for-live-checkout.md +13 -13
- package/docs/qa-runbook-lifecycle.md +367 -17
- package/docs/qa-runbooks.md +3 -3
- package/docs/rfc-completion-json.md +18 -18
- package/docs/runtime-adapters.md +26 -21
- package/docs/security.md +6 -6
- package/docs/self-improvement.md +4 -4
- package/docs/shared-lifecycle-module-map.md +473 -472
- package/docs/skills.md +52 -3
- package/docs/slim-ux/concepts.md +121 -116
- package/docs/specs/agent-configurability.md +18 -18
- package/docs/specs/agent-rename.md +18 -18
- package/docs/team-memory.md +38 -21
- package/docs/timeouts-and-liveness.md +118 -10
- package/docs/tutorials/01-install-and-connect.md +1 -1
- package/docs/watches.md +40 -39
- package/docs/workspace-manifests.md +4 -4
- package/docs/worktree-lifecycle.md +293 -14
- package/engine/README.md +46 -0
- package/engine/{ado-comment.js → ado/comment.js} +8 -8
- package/engine/{ado-git-auth.js → ado/git-auth.js} +4 -4
- package/engine/{ado.js → ado/index.js} +417 -63
- package/engine/{ado-status.js → ado/status.js} +6 -8
- package/engine/{ado-token.js → ado/token.js} +1 -1
- package/engine/{acp-transport.js → agents/acp-transport.js} +62 -22
- package/engine/{agent-worker-pool.js → agents/agent-worker-pool.js} +17 -8
- package/engine/{cc-worker-pool.js → agents/cc-worker-pool.js} +16 -6
- package/engine/{claude-md-context.js → agents/claude-md-context.js} +5 -5
- package/engine/{harness-context.js → agents/harness-context.js} +5 -5
- package/engine/{harness.js → agents/harness.js} +3 -3
- package/engine/{llm.js → agents/llm.js} +18 -14
- package/engine/{model-discovery.js → agents/model-discovery.js} +2 -2
- package/engine/{playbook.js → agents/playbook.js} +155 -22
- package/engine/{pooled-agent-process.js → agents/pooled-agent-process.js} +14 -12
- package/engine/{preflight.js → agents/preflight.js} +29 -10
- package/engine/{spawn-agent.js → agents/spawn-agent.js} +25 -14
- package/engine/{spawn-phase-watchdog.js → agents/spawn-phase-watchdog.js} +16 -7
- package/engine/{steering.js → agents/steering.js} +5 -5
- package/engine/{tools-inventory.js → agents/tools-inventory.js} +2 -2
- package/engine/{agent-api-validation.js → api/agent-api-validation.js} +2 -2
- package/engine/{api-validation.js → api/api-validation.js} +1 -1
- package/engine/api/bridge.js +787 -0
- package/engine/{cc-api-validation.js → api/cc-api-validation.js} +1 -1
- package/engine/api/companion.js +560 -0
- package/engine/{content-api-validation.js → api/content-api-validation.js} +2 -2
- package/engine/{pr-issue-validation.js → api/pr-issue-validation.js} +33 -6
- package/engine/{settings-validation.js → api/settings-validation.js} +32 -4
- package/engine/api-contracts/agent-content.js +4 -4
- package/engine/api-contracts/capability-manifest.js +236 -0
- package/engine/api-contracts/capability-protocol.js +333 -0
- package/engine/api-contracts/cc-ops.js +1 -1
- package/engine/api-contracts/config-runtime.js +5 -0
- package/engine/api-contracts/core.js +28 -1
- package/engine/api-contracts/index.js +100 -0
- package/engine/api-contracts/orchestration.js +18 -5
- package/engine/api-contracts/pull-requests.js +37 -6
- package/engine/api-contracts/qa-process.js +29 -6
- package/engine/api-contracts/work-plan-prd.js +21 -1
- package/engine/cloud/contract.js +212 -0
- package/engine/cloud/index.js +159 -0
- package/engine/{execution-model.js → core/execution-model.js} +1 -1
- package/engine/{features.js → core/features.js} +4 -4
- package/engine/{operator-identity.js → core/operator-identity.js} +1 -1
- package/engine/{queries.js → core/queries.js} +201 -36
- package/engine/{safe-expr.js → core/safe-expr.js} +1 -1
- package/engine/{shared.js → core/shared.js} +1637 -175
- package/engine/{stdio-timestamps.js → core/stdio-timestamps.js} +1 -1
- package/engine/{untrusted-fence.js → core/untrusted-fence.js} +3 -3
- package/engine/db/index.js +11 -2
- package/engine/db/migrations/002-dispatches.js +3 -3
- package/engine/db/migrations/003-work-items.js +1 -1
- package/engine/db/migrations/004-pull-requests.js +1 -1
- package/engine/db/migrations/006-metrics.js +1 -1
- package/engine/db/migrations/007-watches.js +2 -2
- package/engine/db/migrations/008-small-state.js +1 -1
- package/engine/db/migrations/009-qa.js +1 -1
- package/engine/db/migrations/010-pr-links.js +1 -1
- package/engine/db/migrations/011-remaining-state.js +1 -1
- package/engine/db/migrations/012-steering-deliveries.js +2 -2
- package/engine/db/migrations/013-backfill-broken-note-links.js +1 -1
- package/engine/db/migrations/014-pr-fix-target-prefs.js +2 -2
- package/engine/db/migrations/015-plans-prds.js +0 -0
- package/engine/db/migrations/018-sql-only-cutover.js +2 -2
- package/engine/db/migrations/021-archived-work-items.js +1 -1
- package/engine/db/migrations/022-global-cc-session.js +1 -1
- package/engine/db/migrations/023-engine-state.js +1 -1
- package/engine/db/migrations/025-malformed-work-item-phantoms.js +1 -1
- package/engine/db/migrations/027-review-learning-lifecycle.js +1 -1
- package/engine/db/migrations/029-repair-reused-versions.js +20 -0
- package/engine/db/migrations/031-pr-author-identity.js +137 -0
- package/engine/{consolidation.js → memory/consolidation.js} +6 -6
- package/engine/{kb-sweep-runner.js → memory/kb-sweep-runner.js} +2 -2
- package/engine/{kb-sweep.js → memory/kb-sweep.js} +9 -7
- package/engine/{memory-retrieval.js → memory/memory-retrieval.js} +46 -4
- package/engine/{memory-store.js → memory/memory-store.js} +3 -3
- package/engine/{promotion.js → memory/promotion.js} +3 -3
- package/engine/{review-learning-backfill.js → memory/review-learning-backfill.js} +6 -6
- package/engine/{review-learning.js → memory/review-learning.js} +10 -5
- package/engine/{diagnostics-memory.js → observability/diagnostics-memory.js} +1 -1
- package/engine/{logs-store.js → observability/logs-store.js} +5 -5
- package/engine/{metrics-store.js → observability/metrics-store.js} +4 -4
- package/engine/{check-status.js → operations/check-status.js} +3 -3
- package/engine/{cli.js → operations/cli.js} +271 -113
- package/engine/{distribution.js → operations/distribution.js} +5 -6
- package/engine/{cleanup.js → orchestration/cleanup.js} +72 -45
- package/engine/{cooldown.js → orchestration/cooldown.js} +5 -5
- package/engine/{dispatch-events.js → orchestration/dispatch-events.js} +2 -2
- package/engine/{dispatch.js → orchestration/dispatch.js} +129 -36
- package/engine/orchestration/failed-scheduled-cleanup.js +274 -0
- package/engine/{lifecycle.js → orchestration/lifecycle.js} +198 -90
- package/engine/{meeting.js → orchestration/meeting.js} +6 -16
- package/engine/{pipeline.js → orchestration/pipeline.js} +12 -12
- package/engine/{pre-dispatch-eval.js → orchestration/pre-dispatch-eval.js} +10 -9
- package/engine/{routing.js → orchestration/routing.js} +3 -3
- package/engine/{schedule-bootstrap.js → orchestration/schedule-bootstrap.js} +4 -4
- package/engine/{scheduler.js → orchestration/scheduler.js} +38 -8
- package/engine/{timeout.js → orchestration/timeout.js} +158 -109
- package/engine/{db-events.js → persistence/db-events.js} +2 -2
- package/engine/{dispatch-store.js → persistence/dispatch-store.js} +7 -7
- package/engine/{inbox-store.js → persistence/inbox-store.js} +2 -2
- package/engine/{note-link-backfill.js → persistence/note-link-backfill.js} +4 -4
- package/engine/{pr-fix-target-store.js → persistence/pr-fix-target-store.js} +8 -8
- package/engine/{pull-requests-store.js → persistence/pull-requests-store.js} +21 -7
- package/engine/{small-state-store.js → persistence/small-state-store.js} +31 -31
- package/engine/persistence/state-operations.js +350 -0
- package/engine/{steering-store.js → persistence/steering-store.js} +6 -6
- package/engine/{issues.js → planning/issues.js} +2 -2
- package/engine/{plan-prd-validation.js → planning/plan-prd-validation.js} +8 -2
- package/engine/planning/prd-result-sidecar.js +190 -0
- package/engine/{prd-store.js → planning/prd-store.js} +17 -17
- package/engine/{project-discovery.js → planning/project-discovery.js} +5 -5
- package/engine/{projects.js → planning/projects.js} +10 -10
- package/engine/{resolve-area.js → planning/resolve-area.js} +1 -1
- package/engine/{work-item-validation.js → planning/work-item-validation.js} +39 -3
- package/engine/{work-items-store.js → planning/work-items-store.js} +29 -21
- package/engine/{keep-process-sweep.js → processes/keep-process-sweep.js} +57 -17
- package/engine/{managed-spawn-launcher.js → processes/managed-spawn-launcher.js} +3 -3
- package/engine/{managed-spawn.js → processes/managed-spawn.js} +97 -46
- package/engine/{process-utils.js → processes/process-utils.js} +599 -55
- package/engine/{abandoned-pr-reconciliation.js → providers/abandoned-pr-reconciliation.js} +17 -7
- package/engine/{comment-classifier.js → providers/comment-classifier.js} +85 -17
- package/engine/{comment-format.js → providers/comment-format.js} +5 -5
- package/engine/{gh-comment.js → providers/gh-comment.js} +15 -15
- package/engine/{gh-token.js → providers/gh-token.js} +4 -4
- package/engine/{github.js → providers/github.js} +131 -54
- package/engine/{pr-action.js → providers/pr-action.js} +13 -12
- package/engine/{pr-clone-keep.js → providers/pr-clone-keep.js} +7 -7
- package/engine/{pr-devbox.js → providers/pr-devbox.js} +6 -6
- package/engine/{pr-fix-target.js → providers/pr-fix-target.js} +13 -13
- package/engine/{pr-remote-patch.js → providers/pr-remote-patch.js} +4 -4
- package/engine/{pr-resolve.js → providers/pr-resolve.js} +7 -7
- package/engine/{pr-temp-clone.js → providers/pr-temp-clone.js} +5 -5
- package/engine/{pr-track.js → providers/pr-track.js} +11 -13
- package/engine/{shared-branch-pr-reconcile.js → providers/shared-branch-pr-reconcile.js} +4 -4
- package/engine/qa/auto-prd-qa.js +313 -0
- package/engine/{qa-from-prd.js → qa/from-prd.js} +42 -12
- package/engine/qa/prd-session.js +240 -0
- package/engine/{qa-process-validation.js → qa/process-validation.js} +14 -9
- package/engine/{qa-runbooks.js → qa/runbooks.js} +1 -1
- package/engine/{qa-runs.js → qa/runs.js} +286 -15
- package/engine/{qa-sessions.js → qa/sessions.js} +595 -49
- package/engine/qa/visual-journey.js +654 -0
- package/engine/{qa-runners.js → qa-runners/index.js} +7 -7
- package/engine/qa-runners/maestro.js +3 -3
- package/engine/qa-runners/playwright.js +2 -2
- package/engine/{restart-health.js → recovery/restart-health.js} +48 -4
- package/engine/recovery/stop-stack.js +607 -0
- package/engine/{supervisor.js → recovery/supervisor.js} +105 -175
- package/engine/{watchdog.js → recovery/watchdog.js} +136 -13
- package/engine/runtimes/claude.js +14 -12
- package/engine/runtimes/codex.js +8 -6
- package/engine/runtimes/copilot.js +17 -16
- package/engine/{watch-actions.js → watches/actions.js} +13 -13
- package/engine/{watches.js → watches/index.js} +43 -32
- package/engine/{watches-store.js → watches/store.js} +4 -4
- package/engine/{create-pr-worktree.js → worktrees/create-pr.js} +1 -1
- package/engine/{worktree-gc.js → worktrees/gc.js} +70 -22
- package/engine/worktrees/inventory.js +671 -0
- package/engine/{live-checkout.js → worktrees/live-checkout.js} +4 -4
- package/engine/{worktree-pool.js → worktrees/pool.js} +2 -2
- package/engine/{worktree-preflight.js → worktrees/preflight.js} +1 -0
- package/engine/worktrees/quarantine-refs.js +173 -0
- package/engine.js +1137 -208
- package/minions.js +147 -77
- package/package.json +10 -6
- package/playbooks/_pr-description-audit.md +110 -78
- package/playbooks/build-fix-complex.md +2 -0
- package/playbooks/fix.md +16 -12
- package/playbooks/implement-shared.md +2 -0
- package/playbooks/implement.md +19 -20
- package/playbooks/plan-to-prd.md +18 -3
- package/playbooks/qa-session-draft.md +136 -1
- package/playbooks/qa-session-execute.md +80 -2
- package/playbooks/qa-session-setup.md +17 -1
- package/playbooks/qa-validate.md +1 -1
- package/playbooks/setup.md +2 -0
- package/playbooks/shared-rules.md +25 -32
- package/playbooks/templates/followup-dispatch.md +4 -3
- package/playbooks/verify.md +1 -1
- package/prompts/cc-system.md +19 -27
- package/watch-plugins/README.md +92 -0
- package/watch-plugins/ado-author-prs.js +336 -0
- package/watch-plugins/gh-author-prs.js +375 -0
- package/watch-plugins/http.js +474 -0
- package/watch-plugins/teams-channel.js +869 -0
- package/docs/dev-composite-workflow.md +0 -101
- package/docs/pr-screenshots/pr-886/after-single-header.png +0 -0
- package/docs/pr-screenshots/pr-886/before-duplicate-header.png +0 -0
- package/docs/pr-screenshots/pr-895/01-cancellation-reason-detail.png +0 -0
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-901/projects-tab-default.png +0 -0
- package/docs/pr-screenshots/pr-901/projects-tab-fmf-selected.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-AFTER-crop.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-BEFORE-crop.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-916/model-picker-dropdown-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
- package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
- package/docs/visual-evidence-ci.md +0 -103
- package/engine/bridge.js +0 -379
- package/engine/quarantine-refs.js +0 -103
- package/engine/state-operations.js +0 -178
- /package/engine/{steering-constraints.js → agents/steering-constraints.js} +0 -0
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* engine/cli.js — CLI command handlers for Minions engine.
|
|
2
|
+
* engine/operations/cli.js — CLI command handlers for Minions engine.
|
|
3
3
|
* Extracted from engine.js to reduce monolith size.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
|
-
const shared = require('
|
|
8
|
+
const shared = require('../core/shared');
|
|
9
9
|
const { safeRead, safeJson, safeJsonArr, safeWrite, readWorkItems, readPullRequests, mutateControl, mutateWorkItems, ts, WI_STATUS, WORK_TYPE, PLAN_STATUS, PR_STATUS, REVIEW_STATUS, DISPATCH_RESULT, FAILURE_CLASS } = shared;
|
|
10
|
-
const queries = require('
|
|
11
|
-
const agentWorkerPool = require('
|
|
12
|
-
const { resolveCommentExecutionModel } = require('
|
|
10
|
+
const queries = require('../core/queries');
|
|
11
|
+
const agentWorkerPool = require('../agents/agent-worker-pool');
|
|
12
|
+
const { resolveCommentExecutionModel } = require('../core/execution-model');
|
|
13
13
|
const { getConfig, getControl, getDispatch, getAgentStatus,
|
|
14
14
|
MINIONS_DIR, ENGINE_DIR, AGENTS_DIR, PLANS_DIR, PRD_DIR, CONTROL_PATH } = queries;
|
|
15
15
|
|
|
16
16
|
// Lazy require — only for engine-specific functions (log, ts, tick, addToDispatch, etc.)
|
|
17
17
|
let _engine = null;
|
|
18
18
|
function engine() {
|
|
19
|
-
if (!_engine) _engine = require('
|
|
19
|
+
if (!_engine) _engine = require('../../engine');
|
|
20
20
|
return _engine;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
let _dispatchModule = null;
|
|
24
|
-
function dispatchModule() { if (!_dispatchModule) _dispatchModule = require('
|
|
24
|
+
function dispatchModule() { if (!_dispatchModule) _dispatchModule = require('../orchestration/dispatch'); return _dispatchModule; }
|
|
25
25
|
|
|
26
26
|
function spawnPendingDispatch(e, item, config) {
|
|
27
27
|
return e._withSpawnAttemptLease(
|
|
@@ -81,13 +81,54 @@ function readDispatchPid(itemId) {
|
|
|
81
81
|
catch { return null; }
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
84
|
+
// W-ms6dmv99005o26f4 — routed through the tri-state probe in engine/process-utils.js
|
|
85
|
+
// so an EPERM (process EXISTS, signal refused) is no longer read as death. A false
|
|
86
|
+
// here zeroes `agentPid` at reattach, which is the `hadPid && !agentPid` condition
|
|
87
|
+
// that marks a dispatch confirmed-dead and exempts it from the orphan grace window.
|
|
88
|
+
function isPidAlive(pid, opts = {}) {
|
|
89
|
+
let value;
|
|
90
|
+
try { value = shared.validatePid(pid); }
|
|
91
|
+
catch { return false; }
|
|
92
|
+
return shared.inspectPidLiveness(value, opts).alive;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// W-ms6dmv99005o26f4 — restart reattach evidence, exported for testing.
|
|
96
|
+
//
|
|
97
|
+
// Decides whether a surviving OS PID may be reattached to a dispatch, and — just
|
|
98
|
+
// as importantly — whether a refusal is backed by evidence. `conclusive: false`
|
|
99
|
+
// means "we could not observe this process", which is NOT the same as "this
|
|
100
|
+
// process is gone" and must never be escalated to a confirmed-dead marking.
|
|
101
|
+
function evaluateReattachEvidence({ pid, runtimePidObservedAt, snapshot, probes = {} } = {}) {
|
|
102
|
+
const inspectIdentity = probes.inspectProcessIdentity || shared.inspectProcessIdentity;
|
|
103
|
+
const inspectCommandLine = probes.inspectProcessCommandLineForAgent || shared.inspectProcessCommandLineForAgent;
|
|
104
|
+
const observedAt = Number(runtimePidObservedAt);
|
|
105
|
+
|
|
106
|
+
// Recycled-PID guard: a process that started AFTER we recorded the launch cannot
|
|
107
|
+
// be our agent. Only meaningful when a launch timestamp was persisted.
|
|
108
|
+
if (observedAt > 0) {
|
|
109
|
+
const identity = inspectIdentity(pid, { processes: snapshot });
|
|
110
|
+
if (!identity.conclusive) {
|
|
111
|
+
return { reattach: false, conclusive: false, reason: `PID ${pid} identity evidence unavailable (${identity.reason})` };
|
|
112
|
+
}
|
|
113
|
+
if (!shared.isProcessIdentityCompatible(identity.identity, observedAt, 2000)) {
|
|
114
|
+
return { reattach: false, conclusive: true, reason: `PID ${pid} identity does not match the recorded launch` };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const cmdline = inspectCommandLine(pid, { processes: snapshot });
|
|
119
|
+
if (!cmdline.conclusive) {
|
|
120
|
+
return { reattach: false, conclusive: false, reason: `PID ${pid} command-line evidence unavailable (${cmdline.reason})` };
|
|
121
|
+
}
|
|
122
|
+
if (!cmdline.match) {
|
|
123
|
+
return { reattach: false, conclusive: true, reason: `PID ${pid} command line does not match an agent runtime` };
|
|
90
124
|
}
|
|
125
|
+
return { reattach: true, conclusive: true, reason: 'verified' };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// W-ms6dmv99005o26f4 — the single decision that turns a restart-time observation
|
|
129
|
+
// into an instant orphan. Exported for testing.
|
|
130
|
+
function shouldMarkConfirmedDeadAtRestart({ hadPid, agentPid, inconclusive } = {}) {
|
|
131
|
+
return !!hadPid && !agentPid && !inconclusive;
|
|
91
132
|
}
|
|
92
133
|
|
|
93
134
|
function summarizeActiveDispatchPids(activeItems = []) {
|
|
@@ -356,7 +397,7 @@ function _parseRuntimeFlags(args) {
|
|
|
356
397
|
function _modelLooksIncompatible(runtime, model) {
|
|
357
398
|
if (!model) return false;
|
|
358
399
|
let adapter;
|
|
359
|
-
try { adapter = require('
|
|
400
|
+
try { adapter = require('../runtimes').resolveRuntime(runtime); }
|
|
360
401
|
catch { return false; } // unknown runtime → no opinion
|
|
361
402
|
if (typeof adapter.modelLooksFamiliar !== 'function') return false; // adapter doesn't claim a model namespace → no opinion
|
|
362
403
|
return !adapter.modelLooksFamiliar(model);
|
|
@@ -381,7 +422,7 @@ function _applyRuntimeFlags({ cli, model, modelExplicit }) {
|
|
|
381
422
|
// Validate the runtime name BEFORE touching disk so typos fail loudly with
|
|
382
423
|
// a list of registered runtimes — same UX shape as resolveRuntime() throws.
|
|
383
424
|
let registered = [];
|
|
384
|
-
try { registered = require('
|
|
425
|
+
try { registered = require('../runtimes').listRuntimes(); }
|
|
385
426
|
catch { /* registry missing during partial install — skip validation */ }
|
|
386
427
|
if (cli !== undefined && registered.length > 0 && !registered.includes(cli)) {
|
|
387
428
|
const err = new Error(`Unknown CLI runtime "${cli}". Registered runtimes: ${registered.join(', ')}`);
|
|
@@ -449,7 +490,7 @@ const commands = {
|
|
|
449
490
|
|
|
450
491
|
// Run preflight checks (warn but don't block — engine may still be useful)
|
|
451
492
|
try {
|
|
452
|
-
const { runPreflight, printPreflight } = require('
|
|
493
|
+
const { runPreflight, printPreflight } = require('../agents/preflight');
|
|
453
494
|
// Pass config so runtime-fleet warnings (P-3b8e5f1d) can fire pre-start.
|
|
454
495
|
// getConfig() is cheap (cached); failure here is non-fatal — preflight
|
|
455
496
|
// simply skips the runtime-config warnings when config is missing.
|
|
@@ -497,9 +538,9 @@ const commands = {
|
|
|
497
538
|
// Bust require cache so npm updates are detected after minions restart
|
|
498
539
|
let codeVersion = null;
|
|
499
540
|
try {
|
|
500
|
-
const pkgPath = require.resolve('
|
|
541
|
+
const pkgPath = require.resolve('../../package.json');
|
|
501
542
|
delete require.cache[pkgPath];
|
|
502
|
-
codeVersion = require('
|
|
543
|
+
codeVersion = require('../../package.json').version;
|
|
503
544
|
} catch {}
|
|
504
545
|
if (!codeVersion) {
|
|
505
546
|
try {
|
|
@@ -509,7 +550,7 @@ const commands = {
|
|
|
509
550
|
} catch {}
|
|
510
551
|
}
|
|
511
552
|
let codeCommit = null;
|
|
512
|
-
try { codeCommit = require('child_process').execSync('git rev-parse --short HEAD', { cwd: path.resolve(__dirname, '..'), encoding: 'utf8', timeout: 5000, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] }).trim(); } catch {}
|
|
553
|
+
try { codeCommit = require('child_process').execSync('git rev-parse --short HEAD', { cwd: path.resolve(__dirname, '..', '..'), encoding: 'utf8', timeout: 5000, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] }).trim(); } catch {}
|
|
513
554
|
const controlOwner = createControlOwner();
|
|
514
555
|
mutateControl(() => ({
|
|
515
556
|
state: 'running',
|
|
@@ -556,7 +597,7 @@ const commands = {
|
|
|
556
597
|
// through mutateWorkItems so the repair is transactional — a no-op
|
|
557
598
|
// once healed, so it's safe to run on every boot.
|
|
558
599
|
try {
|
|
559
|
-
const res = require('
|
|
600
|
+
const res = require('../persistence/note-link-backfill').runNoteLinkBackfill({ config });
|
|
560
601
|
if (res.fixedLinks > 0) {
|
|
561
602
|
e.log('info', `Repointed ${res.fixedLinks} broken note link(s) across ${res.fixedItems} work item(s)`);
|
|
562
603
|
console.log(` Repaired ${res.fixedLinks} broken note link(s) in ${res.fixedItems} work item(s).`);
|
|
@@ -565,7 +606,7 @@ const commands = {
|
|
|
565
606
|
|
|
566
607
|
// Backfill work_items.prd_item_id for rows created before FK stamping.
|
|
567
608
|
try {
|
|
568
|
-
const fk = require('
|
|
609
|
+
const fk = require('../planning/prd-store').backfillWorkItemPrdItemIds();
|
|
569
610
|
const filled = (fk.byWorkItemId || 0) + (fk.bySourcePlan || 0);
|
|
570
611
|
if (fk.ok && filled > 0) {
|
|
571
612
|
e.log('info', `Backfilled prd_item_id on ${filled} work item(s) (${fk.byWorkItemId} by workItemId, ${fk.bySourcePlan} by sourcePlan)`);
|
|
@@ -596,7 +637,7 @@ const commands = {
|
|
|
596
637
|
} catch (err) { e.log('warn', `workSources auto-heal failed: ${err.message}`); }
|
|
597
638
|
const interval = config.engine?.tickInterval || shared.ENGINE_DEFAULTS.tickInterval;
|
|
598
639
|
|
|
599
|
-
const { getProjects } = require('
|
|
640
|
+
const { getProjects } = require('../core/shared');
|
|
600
641
|
const projects = getProjects(config);
|
|
601
642
|
if (projects.length === 0) {
|
|
602
643
|
console.log(' \x1b[33mNo projects configured.\x1b[0m Link one with: minions add <path-to-repo>');
|
|
@@ -624,6 +665,15 @@ const commands = {
|
|
|
624
665
|
const dispatch = getDispatch();
|
|
625
666
|
const activeOnStart = (dispatch.active || []);
|
|
626
667
|
if (activeOnStart.length > 0) {
|
|
668
|
+
let processSnapshot = shared.captureProcessSnapshot();
|
|
669
|
+
if (!processSnapshot.ok) {
|
|
670
|
+
// W-ms6dmv99005o26f4 — a failed enumeration used to be swallowed into an
|
|
671
|
+
// empty array that then "proved" every active dispatch was gone. Log it
|
|
672
|
+
// loudly: the reattach verdicts below now degrade to inconclusive, which
|
|
673
|
+
// keeps the fleet on the normal bounded orphan grace window instead of
|
|
674
|
+
// reaping it on the next tick.
|
|
675
|
+
e.log('warn', `Restart: process enumeration unavailable (${processSnapshot.reason}) — reattach evidence will be re-probed per PID`);
|
|
676
|
+
}
|
|
627
677
|
let reattached = 0;
|
|
628
678
|
for (const item of activeOnStart) {
|
|
629
679
|
const agentId = item.agent;
|
|
@@ -631,10 +681,10 @@ const commands = {
|
|
|
631
681
|
// P-3f5b7d26 — a pooled dispatch (engine.js#spawnAgent, item.pooled
|
|
632
682
|
// persisted at dispatch-time) can NEVER be reattached, regardless of
|
|
633
683
|
// what the PID file says. The ACP worker child
|
|
634
|
-
// (engine/acp-transport.js#spawnAcp) is not spawned with
|
|
684
|
+
// (engine/agents/acp-transport.js#spawnAcp) is not spawned with
|
|
635
685
|
// `detached: true` like the cold-spawn path, and even on the rare
|
|
636
686
|
// chance its OS process survived the restart, the freshly-started
|
|
637
|
-
// engine's engine/agent-worker-pool.js always cold-starts with an
|
|
687
|
+
// engine's engine/agents/agent-worker-pool.js always cold-starts with an
|
|
638
688
|
// empty worker set — there is no in-process handle whose stdio could
|
|
639
689
|
// be reconnected to that PID. Treating a live-but-orphaned worker PID
|
|
640
690
|
// as "still running" here would silently hang the dispatch forever
|
|
@@ -688,17 +738,50 @@ const commands = {
|
|
|
688
738
|
}
|
|
689
739
|
}
|
|
690
740
|
|
|
741
|
+
const runtimePidObservedAt = Number(item.runtimePidObservedAt);
|
|
691
742
|
if (agentPid && agentPid > 0 && !isPidAlive(agentPid)) agentPid = null;
|
|
743
|
+
// W-ms6dmv99005o26f4 — `inconclusiveEvidence` records that we refused the
|
|
744
|
+
// reattach because we could not OBSERVE the process, not because we saw it
|
|
745
|
+
// gone. Refusing is still correct (never reattach to an unverified PID),
|
|
746
|
+
// but the confirmed-dead marking below must not follow from it.
|
|
747
|
+
let inconclusiveEvidence = false;
|
|
748
|
+
if (agentPid && agentPid > 0) {
|
|
749
|
+
const verdict = evaluateReattachEvidence({
|
|
750
|
+
pid: agentPid,
|
|
751
|
+
runtimePidObservedAt,
|
|
752
|
+
snapshot: processSnapshot,
|
|
753
|
+
});
|
|
754
|
+
if (!verdict.reattach) {
|
|
755
|
+
e.log('warn', `Reattach: refusing ${item.id} — ${verdict.reason}`);
|
|
756
|
+
if (!verdict.conclusive) inconclusiveEvidence = true;
|
|
757
|
+
agentPid = null;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
692
760
|
if (agentPid && !shared.isDispatchExecutionPathSafe(item, config)) {
|
|
693
761
|
e.log('warn', `Reattach: refusing ${item.id} — persisted execution path is not safe under current checkout config`);
|
|
694
|
-
try {
|
|
762
|
+
try {
|
|
763
|
+
shared.terminateProcess('engine.cli.unsafe-reattach', { pid: agentPid }, {
|
|
764
|
+
recordedAtMs: runtimePidObservedAt,
|
|
765
|
+
processes: processSnapshot,
|
|
766
|
+
skewMs: 2000,
|
|
767
|
+
});
|
|
768
|
+
}
|
|
695
769
|
catch (err) { e.log('warn', `Reattach: failed to terminate unsafe dispatch ${item.id}: ${err.message}`); }
|
|
696
770
|
agentPid = null;
|
|
697
771
|
}
|
|
698
772
|
|
|
699
|
-
// PID was found but confirmed dead — exempt from restart grace period (#869)
|
|
700
|
-
|
|
773
|
+
// PID was found but confirmed dead — exempt from restart grace period (#869).
|
|
774
|
+
// W-ms6dmv99005o26f4 — the exemption sets `confirmedDeadAtRestart` in
|
|
775
|
+
// engine/timeout.js, which bypasses BOTH minAliveBeforeOrphanMs and the
|
|
776
|
+
// engine-restart grace window, so the dispatch is orphaned on the very next
|
|
777
|
+
// tick after only seconds of silence. That is the correct handling for a PID
|
|
778
|
+
// we OBSERVED to be dead, and catastrophic for one we merely failed to
|
|
779
|
+
// observe — hence the conclusive-evidence gate.
|
|
780
|
+
if (shouldMarkConfirmedDeadAtRestart({ hadPid, agentPid, inconclusive: inconclusiveEvidence })) {
|
|
701
781
|
e.engineRestartGraceExempt.add(item.id);
|
|
782
|
+
e.log('info', `Restart: ${agentId} (${item.id}) runtime PID confirmed dead or mismatched — marking for infrastructure recovery`);
|
|
783
|
+
} else if (hadPid && !agentPid) {
|
|
784
|
+
e.log('warn', `Restart: ${agentId} (${item.id}) liveness evidence was unavailable — NOT marking confirmed dead; the standard orphan grace window applies`);
|
|
702
785
|
}
|
|
703
786
|
|
|
704
787
|
if (agentPid) {
|
|
@@ -723,7 +806,15 @@ const commands = {
|
|
|
723
806
|
}
|
|
724
807
|
} catch {}
|
|
725
808
|
const runtimeName = item.runtimeName || item.runtime || item.meta?.runtimeName || item.meta?.runtime || null;
|
|
726
|
-
e.activeProcesses.set(item.id, {
|
|
809
|
+
e.activeProcesses.set(item.id, {
|
|
810
|
+
proc: { pid: agentPid > 0 ? agentPid : null },
|
|
811
|
+
agentId,
|
|
812
|
+
startedAt: item.created_at,
|
|
813
|
+
reattached: true,
|
|
814
|
+
sessionId,
|
|
815
|
+
runtimeName,
|
|
816
|
+
runtimePidObservedAt: item.runtimePidObservedAt,
|
|
817
|
+
});
|
|
727
818
|
// Sync work item status to dispatched — atomic write to avoid lifecycle lazy init issues
|
|
728
819
|
if (item.meta?.item?.id && item.meta?.project?.localPath) {
|
|
729
820
|
try {
|
|
@@ -761,8 +852,8 @@ const commands = {
|
|
|
761
852
|
// check if the agent actually completed by scanning its output file.
|
|
762
853
|
// If it did, run the post-completion hooks now so work items get updated.
|
|
763
854
|
(function detectOrphanCompletions() {
|
|
764
|
-
const shared = require('
|
|
765
|
-
const lifecycle = require('
|
|
855
|
+
const shared = require('../core/shared');
|
|
856
|
+
const lifecycle = require('../orchestration/lifecycle');
|
|
766
857
|
let recovered = 0;
|
|
767
858
|
|
|
768
859
|
for (const item of activeOnStart) {
|
|
@@ -859,7 +950,7 @@ const commands = {
|
|
|
859
950
|
// originalRef fields.
|
|
860
951
|
if (shared.isLiveCheckoutDispatchRecord(item, config)) {
|
|
861
952
|
try {
|
|
862
|
-
require('
|
|
953
|
+
require('../worktrees/live-checkout').maybeRestoreLiveCheckoutFromRecord({
|
|
863
954
|
item,
|
|
864
955
|
config,
|
|
865
956
|
isTerminalFailure: !isSuccess,
|
|
@@ -887,7 +978,7 @@ const commands = {
|
|
|
887
978
|
|
|
888
979
|
// Recovery sweep
|
|
889
980
|
(function recoverBrokenState() {
|
|
890
|
-
const shared = require('
|
|
981
|
+
const shared = require('../core/shared');
|
|
891
982
|
const projects = shared.getProjects(config);
|
|
892
983
|
let fixes = 0;
|
|
893
984
|
|
|
@@ -927,7 +1018,7 @@ const commands = {
|
|
|
927
1018
|
// operates on `active`/`linked`, so there is no per-record race.
|
|
928
1019
|
(function startupReconcileAbandonedPrs() {
|
|
929
1020
|
try {
|
|
930
|
-
const reconciler = require('
|
|
1021
|
+
const reconciler = require('../providers/abandoned-pr-reconciliation');
|
|
931
1022
|
Promise.resolve(reconciler.runStartupReconciliation(config))
|
|
932
1023
|
.then(result => {
|
|
933
1024
|
if (!result.skipped && result.totals.flipped > 0) {
|
|
@@ -948,7 +1039,7 @@ const commands = {
|
|
|
948
1039
|
// dead). Run once at boot before the first tick. Idempotent.
|
|
949
1040
|
(function startupReconcileKeepProcesses() {
|
|
950
1041
|
try {
|
|
951
|
-
const { sweepKeepProcesses } = require('
|
|
1042
|
+
const { sweepKeepProcesses } = require('../processes/keep-process-sweep');
|
|
952
1043
|
const stats = sweepKeepProcesses();
|
|
953
1044
|
if (stats.scanned > 0 && (stats.expiredFiles || stats.deadFiles)) {
|
|
954
1045
|
console.log(` Keep-processes boot reconcile: ${stats.expiredFiles} expired, ${stats.deadFiles} dead, ${stats.killedPids} killed`);
|
|
@@ -966,8 +1057,8 @@ const commands = {
|
|
|
966
1057
|
// fire-and-forget — the first tick is allowed to start regardless.
|
|
967
1058
|
(function startupReconcileManagedSpawn() {
|
|
968
1059
|
try {
|
|
969
|
-
const managedSpawn = require('
|
|
970
|
-
const limits = (require('
|
|
1060
|
+
const managedSpawn = require('../processes/managed-spawn');
|
|
1061
|
+
const limits = (require('../core/shared').ENGINE_DEFAULTS.managedSpawn || {});
|
|
971
1062
|
if (limits.enabled === false) return;
|
|
972
1063
|
const maxMs = Math.max(1, Number(limits.bootReconcileMaxMs) || 2000);
|
|
973
1064
|
let settled = false;
|
|
@@ -1004,11 +1095,11 @@ const commands = {
|
|
|
1004
1095
|
// boot prune just keeps re-attach sane.
|
|
1005
1096
|
(function startupReconcileWorktreePool() {
|
|
1006
1097
|
try {
|
|
1007
|
-
const worktreePool = require('
|
|
1008
|
-
const dispatchSnap = require('
|
|
1098
|
+
const worktreePool = require('../worktrees/pool');
|
|
1099
|
+
const dispatchSnap = require('../core/queries').getDispatch();
|
|
1009
1100
|
const activeIds = new Set((dispatchSnap.active || []).map(d => d.id));
|
|
1010
|
-
const cfg = require('
|
|
1011
|
-
const idleTtlMs = cfg?.engine?.worktreePoolIdleTtlMs ?? require('
|
|
1101
|
+
const cfg = require('../core/queries').getConfig();
|
|
1102
|
+
const idleTtlMs = cfg?.engine?.worktreePoolIdleTtlMs ?? require('../core/shared').ENGINE_DEFAULTS.worktreePoolIdleTtlMs;
|
|
1012
1103
|
const result = worktreePool.pruneStale({ activeDispatchIds: activeIds, idleTtlMs });
|
|
1013
1104
|
if (result.evicted > 0) {
|
|
1014
1105
|
console.log(` Worktree-pool boot reconcile: evicted ${result.evicted} stale entr${result.evicted === 1 ? 'y' : 'ies'}`);
|
|
@@ -1029,10 +1120,10 @@ const commands = {
|
|
|
1029
1120
|
// about to be pruned but still appeared as a pool member).
|
|
1030
1121
|
(function startupReconcileOrphanWorktrees() {
|
|
1031
1122
|
try {
|
|
1032
|
-
const sharedMod = require('
|
|
1033
|
-
const worktreeGc = require('
|
|
1034
|
-
const dispatchSnap = require('
|
|
1035
|
-
const cfg = require('
|
|
1123
|
+
const sharedMod = require('../core/shared');
|
|
1124
|
+
const worktreeGc = require('../worktrees/gc');
|
|
1125
|
+
const dispatchSnap = require('../core/queries').getDispatch();
|
|
1126
|
+
const cfg = require('../core/queries').getConfig();
|
|
1036
1127
|
const projects = sharedMod.getProjects(cfg);
|
|
1037
1128
|
if (projects.length === 0) return;
|
|
1038
1129
|
const worktreeProjects = projects.filter((project) => {
|
|
@@ -1092,7 +1183,7 @@ const commands = {
|
|
|
1092
1183
|
// /api/knowledge/sweep/status until someone POSTs a new sweep. Idempotent.
|
|
1093
1184
|
(function startupReconcileKbSweep() {
|
|
1094
1185
|
try {
|
|
1095
|
-
const { reconcileSweepStateOnBoot } = require('
|
|
1186
|
+
const { reconcileSweepStateOnBoot } = require('../memory/kb-sweep');
|
|
1096
1187
|
const stats = reconcileSweepStateOnBoot();
|
|
1097
1188
|
if (stats.released > 0) {
|
|
1098
1189
|
console.log(` KB-sweep boot reconcile: released stale ${stats.prevStatus} record (pid=${stats.prevPid}, reason=${stats.reason})`);
|
|
@@ -1109,7 +1200,7 @@ const commands = {
|
|
|
1109
1200
|
// shares the same promise.
|
|
1110
1201
|
(function warmKnowledgeBaseCache() {
|
|
1111
1202
|
try {
|
|
1112
|
-
const queries = require('
|
|
1203
|
+
const queries = require('../core/queries');
|
|
1113
1204
|
Promise.resolve(queries.getKnowledgeBaseEntries())
|
|
1114
1205
|
.catch(err => e.log('warn', `KB cache warm failed: ${err && err.message}`));
|
|
1115
1206
|
} catch (err) {
|
|
@@ -1140,7 +1231,7 @@ const commands = {
|
|
|
1140
1231
|
|
|
1141
1232
|
// Fast poll: check steering every 1s (lightweight — just fs.stat per agent)
|
|
1142
1233
|
// and wakeup signals every 1s (control.json read)
|
|
1143
|
-
const { checkSteering } = require('
|
|
1234
|
+
const { checkSteering } = require('../orchestration/timeout');
|
|
1144
1235
|
const fastPollTimer = setInterval(() => {
|
|
1145
1236
|
try { checkSteering(); } catch {}
|
|
1146
1237
|
const ctrl = getControl();
|
|
@@ -1188,7 +1279,7 @@ const commands = {
|
|
|
1188
1279
|
// a stderr line if a flush fails so a refactor mistake (typo, missing
|
|
1189
1280
|
// export) doesn't go silent.
|
|
1190
1281
|
function drainBuffers() {
|
|
1191
|
-
try { require('
|
|
1282
|
+
try { require('../agents/llm').flushMetricsBuffer(); }
|
|
1192
1283
|
catch (err) { console.error(`[shutdown] flushMetricsBuffer failed: ${err.message}`); }
|
|
1193
1284
|
try { shared.flushLogs(); }
|
|
1194
1285
|
catch (err) { console.error(`[shutdown] flushLogs failed: ${err.message}`); }
|
|
@@ -1262,7 +1353,7 @@ const commands = {
|
|
|
1262
1353
|
const procInfo = e.activeProcesses.get(id);
|
|
1263
1354
|
if (!procInfo?._pooled) continue;
|
|
1264
1355
|
try {
|
|
1265
|
-
procInfo.proc
|
|
1356
|
+
shared.terminateProcess('engine.cli.shutdown-pooled', procInfo.proc);
|
|
1266
1357
|
if (typeof procInfo.proc.waitForClose === 'function') {
|
|
1267
1358
|
closing.push(procInfo.proc.waitForClose());
|
|
1268
1359
|
}
|
|
@@ -1282,7 +1373,12 @@ const commands = {
|
|
|
1282
1373
|
DISPATCH_RESULT.ERROR,
|
|
1283
1374
|
'Pooled dispatch cancelled after graceful shutdown timeout',
|
|
1284
1375
|
'',
|
|
1285
|
-
{
|
|
1376
|
+
{
|
|
1377
|
+
failureClass: FAILURE_CLASS.ENGINE_RESTART,
|
|
1378
|
+
retryable: true,
|
|
1379
|
+
retryBudget: 'infrastructure',
|
|
1380
|
+
countAgentRetryFailure: false,
|
|
1381
|
+
},
|
|
1286
1382
|
);
|
|
1287
1383
|
}
|
|
1288
1384
|
finishShutdown(`Shutdown timeout (${timeout / 1000}s) — cancelled ${ids.length} pooled agent(s) for retry.`, 1);
|
|
@@ -1343,7 +1439,7 @@ const commands = {
|
|
|
1343
1439
|
// Write stop-intent BEFORE killing so the external supervisor (engine/
|
|
1344
1440
|
// supervisor.js) sees the flag on its next tick and stands down instead
|
|
1345
1441
|
// of respawning. Cleared by `minions start`/`restart` AND by every
|
|
1346
|
-
// successful engine boot (engine/cli.js#start) — so a direct
|
|
1442
|
+
// successful engine boot (engine/operations/cli.js#start) — so a direct
|
|
1347
1443
|
// `node engine.js start` after this stop still clears the flag.
|
|
1348
1444
|
if (!shared.writeStopIntent('engine.js stop')) {
|
|
1349
1445
|
e.log('warn', 'Failed to write stop-intent');
|
|
@@ -1393,7 +1489,7 @@ const commands = {
|
|
|
1393
1489
|
const dispatch = getDispatch();
|
|
1394
1490
|
const agents = config.agents || {};
|
|
1395
1491
|
|
|
1396
|
-
const { getProjects } = require('
|
|
1492
|
+
const { getProjects } = require('../core/shared');
|
|
1397
1493
|
const projects = getProjects(config);
|
|
1398
1494
|
|
|
1399
1495
|
// Version info
|
|
@@ -1646,7 +1742,7 @@ const commands = {
|
|
|
1646
1742
|
}
|
|
1647
1743
|
|
|
1648
1744
|
const config = getConfig();
|
|
1649
|
-
const { getProjects, resolveProjectSource, uid } = require('
|
|
1745
|
+
const { getProjects, resolveProjectSource, uid } = require('../core/shared');
|
|
1650
1746
|
const projects = getProjects(config);
|
|
1651
1747
|
const target = opts.project ? resolveProjectSource(opts.project, projects, { allowCentral: false }) : null;
|
|
1652
1748
|
if (target?.error) {
|
|
@@ -1670,7 +1766,7 @@ const commands = {
|
|
|
1670
1766
|
}
|
|
1671
1767
|
requestedId = opts.id.trim();
|
|
1672
1768
|
const liveItems = readWorkItems(scope);
|
|
1673
|
-
const archivedItems = require('
|
|
1769
|
+
const archivedItems = require('../planning/work-items-store').readArchivedWorkItemsForScope(scope);
|
|
1674
1770
|
if (liveItems.some(w => w?.id === requestedId) || archivedItems.some(w => w?.id === requestedId)) {
|
|
1675
1771
|
console.log(`Error: work item id "${requestedId}" already exists in scope "${scope}" or its archive`);
|
|
1676
1772
|
process.exit(1);
|
|
@@ -1737,7 +1833,7 @@ const commands = {
|
|
|
1737
1833
|
}
|
|
1738
1834
|
|
|
1739
1835
|
const config = getConfig();
|
|
1740
|
-
const { getProjects, resolveProjectSource } = require('
|
|
1836
|
+
const { getProjects, resolveProjectSource } = require('../core/shared');
|
|
1741
1837
|
const projects = getProjects(config);
|
|
1742
1838
|
const target = projectName ? resolveProjectSource(projectName, projects, { allowCentral: false }) : null;
|
|
1743
1839
|
if (target?.error) {
|
|
@@ -1833,7 +1929,7 @@ const commands = {
|
|
|
1833
1929
|
sources() {
|
|
1834
1930
|
const e = engine();
|
|
1835
1931
|
const config = getConfig();
|
|
1836
|
-
const shared = require('
|
|
1932
|
+
const shared = require('../core/shared');
|
|
1837
1933
|
const projects = shared.getProjects(config);
|
|
1838
1934
|
|
|
1839
1935
|
console.log('\n=== Work Sources ===\n');
|
|
@@ -1897,12 +1993,13 @@ const commands = {
|
|
|
1897
1993
|
const e = engine();
|
|
1898
1994
|
console.log('\n=== Kill All Active Work ===\n');
|
|
1899
1995
|
const config = getConfig();
|
|
1900
|
-
const shared = require('
|
|
1996
|
+
const shared = require('../core/shared');
|
|
1901
1997
|
|
|
1902
1998
|
// Kill processes via PID files (expensive — outside dispatch lock).
|
|
1903
1999
|
// PID files live in engine/tmp/ — both legacy flat layout
|
|
1904
2000
|
// (`pid-<id>.pid`) and per-dispatch dirs (`dispatch-<id>-XXX/pid-<id>.pid`)
|
|
1905
2001
|
// post-P-f6-tmp-toctou. shared.forEachPidFile visits both.
|
|
2002
|
+
const processSnapshot = shared.listAllProcesses();
|
|
1906
2003
|
shared.forEachPidFile((pidPath, fileName, layout) => {
|
|
1907
2004
|
const raw = safeRead(pidPath).trim();
|
|
1908
2005
|
// Guard against falsy/zero/NaN PIDs. Empty pid files would resolve to
|
|
@@ -1910,14 +2007,25 @@ const commands = {
|
|
|
1910
2007
|
// calling process group — which would kill the engine itself.
|
|
1911
2008
|
let pidNum = NaN;
|
|
1912
2009
|
try { pidNum = shared.validatePid(raw); } catch { /* invalid — skip */ }
|
|
1913
|
-
|
|
2010
|
+
let pidFileMtimeMs = 0;
|
|
2011
|
+
try { pidFileMtimeMs = fs.statSync(pidPath).mtimeMs; } catch {}
|
|
2012
|
+
const terminated = pidNum > 0 && shared.terminateProcess('engine.cli.kill-command', {
|
|
2013
|
+
pid: pidNum,
|
|
2014
|
+
}, {
|
|
2015
|
+
recordedAtMs: pidFileMtimeMs,
|
|
2016
|
+
processes: processSnapshot,
|
|
2017
|
+
skewMs: 2000,
|
|
2018
|
+
});
|
|
2019
|
+
if (terminated) {
|
|
1914
2020
|
// killImmediate force-kills the whole process tree (bare process.kill
|
|
1915
2021
|
// defaults to a non-recursing SIGTERM that is Windows-broken and
|
|
1916
2022
|
// leaks child processes — CLAUDE.md Footgun #4).
|
|
1917
|
-
try {
|
|
2023
|
+
try {
|
|
2024
|
+
console.log(`Killed process ${pidNum} (${fileName})`);
|
|
2025
|
+
}
|
|
1918
2026
|
catch { console.log(`Process ${pidNum} already dead`); }
|
|
1919
2027
|
} else {
|
|
1920
|
-
console.log(`Skipping ${fileName}: invalid or
|
|
2028
|
+
console.log(`Skipping ${fileName}: invalid, stale, or non-agent PID`);
|
|
1921
2029
|
}
|
|
1922
2030
|
if (layout === 'dispatch-dir') {
|
|
1923
2031
|
try { shared.removeDispatchTmpDir(path.dirname(pidPath)); } catch { /* may not exist */ }
|
|
@@ -1977,7 +2085,7 @@ const commands = {
|
|
|
1977
2085
|
|
|
1978
2086
|
'mcp-sync'() {
|
|
1979
2087
|
// P-90a37c52 — repurposed as a harness diagnostic printer. Same source
|
|
1980
|
-
// as `minions doctor --harness` (engine/preflight.js → runHarnessDoctor)
|
|
2088
|
+
// as `minions doctor --harness` (engine/agents/preflight.js → runHarnessDoctor)
|
|
1981
2089
|
// but stays exit 0 regardless of the diagnostic's hardError signal, so
|
|
1982
2090
|
// callers that scripted around the legacy `mcp-sync` (which was a pure
|
|
1983
2091
|
// print + exit 0) don't have their automation flipped. For an exit
|
|
@@ -1986,12 +2094,12 @@ const commands = {
|
|
|
1986
2094
|
// Stays a no-op: no writes to ~/.claude.json, no state mutation. The
|
|
1987
2095
|
// legacy "Sync MCP servers from ~/.claude.json — no sync needed."
|
|
1988
2096
|
// message is replaced by the structured per-runtime propagation survey.
|
|
1989
|
-
const { runHarnessDoctor } = require('
|
|
2097
|
+
const { runHarnessDoctor } = require('../agents/preflight');
|
|
1990
2098
|
runHarnessDoctor(MINIONS_DIR);
|
|
1991
2099
|
},
|
|
1992
2100
|
|
|
1993
2101
|
doctor(...doctorArgs) {
|
|
1994
|
-
const { doctor, runHarnessDoctor } = require('
|
|
2102
|
+
const { doctor, runHarnessDoctor } = require('../agents/preflight');
|
|
1995
2103
|
if (doctorArgs.includes('--harness')) {
|
|
1996
2104
|
// Read-only runtime harness inventory. See
|
|
1997
2105
|
// docs/harness-propagation.md and the parallel branch in bin/minions.js.
|
|
@@ -2012,7 +2120,7 @@ const commands = {
|
|
|
2012
2120
|
if (!cliName || cliName.startsWith('--')) {
|
|
2013
2121
|
console.error('Usage: minions config set-cli <runtime> [--model <model>]');
|
|
2014
2122
|
let registered = [];
|
|
2015
|
-
try { registered = require('
|
|
2123
|
+
try { registered = require('../runtimes').listRuntimes(); } catch {}
|
|
2016
2124
|
if (registered.length > 0) console.error(`Registered runtimes: ${registered.join(', ')}`);
|
|
2017
2125
|
process.exit(2);
|
|
2018
2126
|
}
|
|
@@ -2074,7 +2182,7 @@ const commands = {
|
|
|
2074
2182
|
console.log('');
|
|
2075
2183
|
},
|
|
2076
2184
|
|
|
2077
|
-
// `minions pr <subcommand> ...` — wraps engine/gh-comment.js so playbook-
|
|
2185
|
+
// `minions pr <subcommand> ...` — wraps engine/providers/gh-comment.js so playbook-
|
|
2078
2186
|
// driven shells have a single command that automatically prepends the hidden
|
|
2079
2187
|
// minions marker (so engine classifiers can identify agent-authored PR
|
|
2080
2188
|
// comments by structure, not body shape).
|
|
@@ -2178,7 +2286,7 @@ const commands = {
|
|
|
2178
2286
|
process.exit(2);
|
|
2179
2287
|
}
|
|
2180
2288
|
const orgBase = flags['org-base'] || `https://dev.azure.com/${adoOrg}`;
|
|
2181
|
-
const adoComment = require('
|
|
2289
|
+
const adoComment = require('../ado/comment');
|
|
2182
2290
|
adoComment.postAdoPrComment({
|
|
2183
2291
|
orgBase, project, repositoryId, prNumber, body, agentId, kind, workItemId,
|
|
2184
2292
|
model: executionModel.model,
|
|
@@ -2195,7 +2303,7 @@ const commands = {
|
|
|
2195
2303
|
}
|
|
2196
2304
|
|
|
2197
2305
|
// ── GitHub: <repo> <prNumber> ──
|
|
2198
|
-
const ghComment = require('
|
|
2306
|
+
const ghComment = require('../providers/gh-comment');
|
|
2199
2307
|
// GitHub conversation (issue) comments have no "resolved" concept — only
|
|
2200
2308
|
// inline review threads can be resolved, and that needs a GraphQL
|
|
2201
2309
|
// resolveReviewThread round-trip the agent runs itself (see shared-rules.md
|
|
@@ -2237,18 +2345,19 @@ const commands = {
|
|
|
2237
2345
|
// Subcommands:
|
|
2238
2346
|
// status Print enabled flag + last-seen Constellation agent timestamp.
|
|
2239
2347
|
// health Probe the versioned local snapshot endpoint and print the same
|
|
2240
|
-
// metadata summary the Constellation bridge consumes
|
|
2348
|
+
// metadata summary the Constellation bridge consumes, preceded by
|
|
2349
|
+
// the Constellation companion presence/compatibility report.
|
|
2241
2350
|
// enable Atomically set engine.constellationBridge.enabled = true.
|
|
2242
2351
|
// disable Atomically set engine.constellationBridge.enabled = false.
|
|
2243
2352
|
bridge(subcmd, ...rest) {
|
|
2244
|
-
const bridge = require('
|
|
2353
|
+
const bridge = require('../api/bridge');
|
|
2245
2354
|
const BRIDGE_USAGE = 'Usage: minions bridge <status|health|enable|disable>';
|
|
2246
2355
|
|
|
2247
2356
|
if (!subcmd || subcmd === 'help' || subcmd === '--help' || subcmd === '-h') {
|
|
2248
2357
|
console.log(BRIDGE_USAGE);
|
|
2249
2358
|
console.log('');
|
|
2250
2359
|
console.log(' status Show enabled flag + last-seen Constellation agent timestamp');
|
|
2251
|
-
console.log(' health Probe the local versioned Constellation snapshot');
|
|
2360
|
+
console.log(' health Probe the local versioned Constellation snapshot + report companion presence');
|
|
2252
2361
|
console.log(' enable Set engine.constellationBridge.enabled = true');
|
|
2253
2362
|
console.log(' disable Set engine.constellationBridge.enabled = false');
|
|
2254
2363
|
return;
|
|
@@ -2289,49 +2398,30 @@ const commands = {
|
|
|
2289
2398
|
}
|
|
2290
2399
|
|
|
2291
2400
|
if (subcmd === 'health') {
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
console.log(' snapshot (same metadata the Constellation bridge mirrors):');
|
|
2317
|
-
for (const [k, v] of Object.entries(projection)) {
|
|
2318
|
-
console.log(` ${k}: ${v === null ? '(unknown)' : v}`);
|
|
2319
|
-
}
|
|
2320
|
-
});
|
|
2321
|
-
}
|
|
2322
|
-
);
|
|
2323
|
-
req.on('timeout', () => {
|
|
2324
|
-
req.destroy(new Error('connect timeout'));
|
|
2325
|
-
});
|
|
2326
|
-
req.on('error', (err) => {
|
|
2327
|
-
if (err.code === 'ECONNREFUSED') {
|
|
2328
|
-
console.error('error: dashboard not running on :7331 — start it with `minions dash`');
|
|
2329
|
-
} else {
|
|
2330
|
-
console.error(`error: ${err.message}`);
|
|
2331
|
-
}
|
|
2332
|
-
process.exit(1);
|
|
2333
|
-
});
|
|
2334
|
-
return;
|
|
2401
|
+
// Companion first: it is the one part of `bridge health` that still says
|
|
2402
|
+
// something useful when the dashboard is down. Bounded by construction
|
|
2403
|
+
// (marker read + optional loopback probe with an explicit timeout) and
|
|
2404
|
+
// never fatal — an absent or incompatible companion does not change the
|
|
2405
|
+
// exit code, which stays a pure dashboard-reachability signal.
|
|
2406
|
+
const companion = require('../api/companion');
|
|
2407
|
+
let companionConfig = null;
|
|
2408
|
+
try { companionConfig = getConfig(); } catch { /* config problems are the dashboard probe's business */ }
|
|
2409
|
+
return Promise.resolve()
|
|
2410
|
+
.then(() => companion.probeCompanion({
|
|
2411
|
+
config: companionConfig,
|
|
2412
|
+
markerPath: bridge.CONSTELLATION_BRIDGE_MARKER_PATH,
|
|
2413
|
+
}))
|
|
2414
|
+
.catch(() => null)
|
|
2415
|
+
.then((companionResult) => {
|
|
2416
|
+
for (const line of companion.formatCompanionLines(companionResult)) console.log(line);
|
|
2417
|
+
_bridgeDashboardHealthProbe();
|
|
2418
|
+
})
|
|
2419
|
+
.catch((err) => {
|
|
2420
|
+
// Preserves the pre-existing contract: any failure reaching the top of
|
|
2421
|
+
// `bridge health` is an error + exit 1, never a silent rejection.
|
|
2422
|
+
console.error(`error: ${(err && err.message) || err}`);
|
|
2423
|
+
process.exit(1);
|
|
2424
|
+
});
|
|
2335
2425
|
}
|
|
2336
2426
|
|
|
2337
2427
|
console.error(`Unknown bridge subcommand: ${subcmd}`);
|
|
@@ -2340,6 +2430,72 @@ const commands = {
|
|
|
2340
2430
|
}
|
|
2341
2431
|
};
|
|
2342
2432
|
|
|
2433
|
+
/**
|
|
2434
|
+
* Probe the versioned Constellation snapshot endpoint and print its metadata
|
|
2435
|
+
* summary. Extracted from `bridge health` so the companion report can be
|
|
2436
|
+
* printed first without nesting the dashboard probe inside a promise body.
|
|
2437
|
+
* Unchanged behavior: exit 1 on any dashboard/protocol failure.
|
|
2438
|
+
*/
|
|
2439
|
+
function _bridgeDashboardHealthProbe() {
|
|
2440
|
+
const bridge = require('../api/bridge');
|
|
2441
|
+
const http = require('http');
|
|
2442
|
+
const req = http.get(
|
|
2443
|
+
{ hostname: '127.0.0.1', port: 7331, path: '/api/constellation/v1/snapshot', timeout: 5000 },
|
|
2444
|
+
(res) => {
|
|
2445
|
+
let body = '';
|
|
2446
|
+
res.setEncoding('utf8');
|
|
2447
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
2448
|
+
res.on('end', () => {
|
|
2449
|
+
// The snapshot endpoint is gated on engine.constellationBridge.enabled,
|
|
2450
|
+
// so a 404 carrying the `bridge-disabled` code is the operator's own
|
|
2451
|
+
// flag, not a broken dashboard. Name the flag and the fix instead of
|
|
2452
|
+
// surfacing a bare status code. Any other non-200 keeps the generic
|
|
2453
|
+
// transport-level message.
|
|
2454
|
+
if (res.statusCode === 404) {
|
|
2455
|
+
let gate;
|
|
2456
|
+
try { gate = JSON.parse(body); } catch { gate = null; }
|
|
2457
|
+
if (gate && gate.code === 'bridge-disabled') {
|
|
2458
|
+
console.error('error: Constellation bridge is disabled — enable it with `minions bridge enable`');
|
|
2459
|
+
console.error(' config: engine.constellationBridge.enabled = false');
|
|
2460
|
+
process.exit(1);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
if (res.statusCode !== 200) {
|
|
2464
|
+
console.error(`error: dashboard returned HTTP ${res.statusCode}`);
|
|
2465
|
+
process.exit(1);
|
|
2466
|
+
}
|
|
2467
|
+
let parsed;
|
|
2468
|
+
try { parsed = JSON.parse(body); }
|
|
2469
|
+
catch (e) {
|
|
2470
|
+
console.error(`error: dashboard response was not JSON: ${e.message}`);
|
|
2471
|
+
process.exit(1);
|
|
2472
|
+
}
|
|
2473
|
+
const projection = bridge.projectSnapshotHealthForBridge(parsed);
|
|
2474
|
+
if (!projection) {
|
|
2475
|
+
console.error('error: dashboard returned an incompatible Constellation snapshot');
|
|
2476
|
+
process.exit(1);
|
|
2477
|
+
}
|
|
2478
|
+
console.log('bridge: dashboard reachable on http://127.0.0.1:7331');
|
|
2479
|
+
console.log(' snapshot (same metadata the Constellation bridge mirrors):');
|
|
2480
|
+
for (const [k, v] of Object.entries(projection)) {
|
|
2481
|
+
console.log(` ${k}: ${v === null ? '(unknown)' : v}`);
|
|
2482
|
+
}
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
);
|
|
2486
|
+
req.on('timeout', () => {
|
|
2487
|
+
req.destroy(new Error('connect timeout'));
|
|
2488
|
+
});
|
|
2489
|
+
req.on('error', (err) => {
|
|
2490
|
+
if (err.code === 'ECONNREFUSED') {
|
|
2491
|
+
console.error('error: dashboard not running on :7331 — start it with `minions dash`');
|
|
2492
|
+
} else {
|
|
2493
|
+
console.error(`error: ${err.message}`);
|
|
2494
|
+
}
|
|
2495
|
+
process.exit(1);
|
|
2496
|
+
});
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2343
2499
|
module.exports = {
|
|
2344
2500
|
handleCommand,
|
|
2345
2501
|
// exported for downstream indexing (dashboard CC preamble)
|
|
@@ -2357,6 +2513,8 @@ module.exports = {
|
|
|
2357
2513
|
_summarizeActiveDispatchPids: summarizeActiveDispatchPids,
|
|
2358
2514
|
_dispatchSafeId: dispatchSafeId,
|
|
2359
2515
|
_readDispatchPid: readDispatchPid,
|
|
2516
|
+
_evaluateReattachEvidence: evaluateReattachEvidence,
|
|
2517
|
+
_shouldMarkConfirmedDeadAtRestart: shouldMarkConfirmedDeadAtRestart,
|
|
2360
2518
|
_normalizeSessionBranch: normalizeSessionBranch,
|
|
2361
2519
|
_dispatchSessionBranch: dispatchSessionBranch,
|
|
2362
2520
|
_resolveCommentExecutionModel: resolveCommentExecutionModel, // exported for testing
|