@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,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* engine/timeout.js — Runtime timeout, stale-orphan cleanup, steering, and idle checks.
|
|
2
|
+
* engine/orchestration/timeout.js — Runtime timeout, stale-orphan cleanup, steering, and idle checks.
|
|
3
3
|
*
|
|
4
4
|
* Live-checkout dispatches (project.checkoutMode === 'live') are killed by PID
|
|
5
5
|
* exactly like isolated-mode dispatches — no special handling, no in-place
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
|
-
const shared = require('
|
|
13
|
-
const queries = require('
|
|
14
|
-
const steering = require('
|
|
12
|
+
const shared = require('../core/shared');
|
|
13
|
+
const queries = require('../core/queries');
|
|
14
|
+
const steering = require('../agents/steering');
|
|
15
15
|
|
|
16
16
|
const { safeRead, safeWrite, safeJson, mutateWorkItems, getProjects, log, ts,
|
|
17
17
|
ENGINE_DEFAULTS, ENGINE_DIR, WI_STATUS, WORK_TYPE, DISPATCH_RESULT, AGENT_STATUS, FAILURE_CLASS } = shared;
|
|
@@ -22,7 +22,7 @@ const MINIONS_DIR = shared.MINIONS_DIR;
|
|
|
22
22
|
// Lazy require to break circular dependency with engine.js
|
|
23
23
|
// Only needed for engine().activeProcesses and engine().engineRestartGraceUntil — log/ts come from shared.js
|
|
24
24
|
let _engine = null;
|
|
25
|
-
function engine() { if (!_engine) _engine = require('
|
|
25
|
+
function engine() { if (!_engine) _engine = require('../../engine'); return _engine; }
|
|
26
26
|
|
|
27
27
|
function resolveDispatchBranchPath(item, config) {
|
|
28
28
|
if (item?.worktreePath) return item.worktreePath;
|
|
@@ -57,7 +57,7 @@ function resolveDispatchProject(item, config) {
|
|
|
57
57
|
|
|
58
58
|
function getWorktreeProcessAnchorReason(item, managedSpawnSpawnedCount = 0) {
|
|
59
59
|
if (!item?.worktreePath) return null;
|
|
60
|
-
const decision = require('
|
|
60
|
+
const decision = require('../worktrees/gc').shouldGcDispatchWorktree({
|
|
61
61
|
worktreePath: item.worktreePath,
|
|
62
62
|
agentId: item.agent,
|
|
63
63
|
managedSpawnSpawnedCount,
|
|
@@ -74,7 +74,7 @@ function gcRecoveredDispatchWorktree(item, project, config) {
|
|
|
74
74
|
project.localPath,
|
|
75
75
|
config.engine?.worktreeRoot || ENGINE_DEFAULTS.worktreeRoot,
|
|
76
76
|
);
|
|
77
|
-
require('
|
|
77
|
+
require('../worktrees/gc').gcDispatchWorktreeIfOrphan({
|
|
78
78
|
worktreePath: item.worktreePath,
|
|
79
79
|
gitRoot: project.localPath,
|
|
80
80
|
worktreeRoot,
|
|
@@ -87,15 +87,28 @@ function gcRecoveredDispatchWorktree(item, project, config) {
|
|
|
87
87
|
async function _finalizeDeadDispatch(item, reason, failureClass, config, deps = {}) {
|
|
88
88
|
const preserveBranch = deps.preserveBranch || preserveDispatchBranch;
|
|
89
89
|
const restoreLiveCheckout = deps.restoreLiveCheckout ||
|
|
90
|
-
require('
|
|
90
|
+
require('../worktrees/live-checkout').maybeRestoreLiveCheckoutFromRecord;
|
|
91
91
|
const complete = deps.complete || dispatch().completeDispatch;
|
|
92
|
+
// W-ms9tcs3o01y03383 — optional observability label describing HOW this
|
|
93
|
+
// dispatch reached its terminal state (shared.DISPATCH_COMPLETION_PATH).
|
|
94
|
+
const completionPath = deps.completionPath || null;
|
|
92
95
|
const deferredTerminal = item?._worktreeProtectionDeferred?.terminal || {};
|
|
93
96
|
const terminalReason = deferredTerminal.reason || reason;
|
|
94
97
|
const terminalResultSummary = deferredTerminal.resultSummary || '';
|
|
95
|
-
const
|
|
98
|
+
const terminalBaseCompleteOpts = deferredTerminal.completeOpts
|
|
96
99
|
&& typeof deferredTerminal.completeOpts === 'object'
|
|
97
100
|
? deferredTerminal.completeOpts
|
|
98
|
-
: (failureClass
|
|
101
|
+
: (failureClass
|
|
102
|
+
? {
|
|
103
|
+
failureClass,
|
|
104
|
+
...(failureClass === FAILURE_CLASS.ENGINE_RESTART
|
|
105
|
+
? { retryBudget: 'infrastructure', countAgentRetryFailure: false }
|
|
106
|
+
: {}),
|
|
107
|
+
}
|
|
108
|
+
: {});
|
|
109
|
+
const terminalCompleteOpts = completionPath
|
|
110
|
+
? { ...terminalBaseCompleteOpts, completionPath }
|
|
111
|
+
: terminalBaseCompleteOpts;
|
|
99
112
|
|
|
100
113
|
if (resolveDispatchBranchPath(item, config) && item.meta?.branch) {
|
|
101
114
|
try {
|
|
@@ -203,7 +216,7 @@ const STEERING_KILL_INTERVALS_MS = [30000, 60000, 120000];
|
|
|
203
216
|
// have already terminated (delivered/dropped/etc).
|
|
204
217
|
const STEERING_ACTIVE_STATUSES = new Set(['queued', 'live_kill', 'deferred', 're_spawning']);
|
|
205
218
|
|
|
206
|
-
// W-mq066js7000fff1f-c (Gap G): lazy require for engine/steering-store.js.
|
|
219
|
+
// W-mq066js7000fff1f-c (Gap G): lazy require for engine/persistence/steering-store.js.
|
|
207
220
|
// Sibling branch -d ships the store; my branch ships independently and no-ops
|
|
208
221
|
// when the module is absent. Only swallows MODULE_NOT_FOUND for the exact
|
|
209
222
|
// resolution — real syntax/runtime errors propagate so a broken store fails
|
|
@@ -214,11 +227,11 @@ function getSteeringStore() {
|
|
|
214
227
|
if (_steeringStoreLoaded) return _steeringStore;
|
|
215
228
|
_steeringStoreLoaded = true;
|
|
216
229
|
try {
|
|
217
|
-
_steeringStore = require('
|
|
230
|
+
_steeringStore = require('../persistence/steering-store');
|
|
218
231
|
} catch (err) {
|
|
219
232
|
const isMissing = err && err.code === 'MODULE_NOT_FOUND' && /steering-store/.test(String(err.message || ''));
|
|
220
233
|
if (!isMissing) {
|
|
221
|
-
try { log('warn', `Steering: failed to load engine/steering-store.js: ${err.message}`); } catch {}
|
|
234
|
+
try { log('warn', `Steering: failed to load engine/persistence/steering-store.js: ${err.message}`); } catch {}
|
|
222
235
|
}
|
|
223
236
|
_steeringStore = null;
|
|
224
237
|
}
|
|
@@ -281,7 +294,7 @@ function runtimeSupportsMidRunSessionId(info) {
|
|
|
281
294
|
if (typeof info?.runtime?.capabilities?.midRunSessionId === 'boolean') return info.runtime.capabilities.midRunSessionId;
|
|
282
295
|
if (info?.runtimeName) {
|
|
283
296
|
try {
|
|
284
|
-
const { resolveRuntime } = require('
|
|
297
|
+
const { resolveRuntime } = require('../runtimes');
|
|
285
298
|
const runtime = resolveRuntime(info.runtimeName);
|
|
286
299
|
if (typeof runtime.capabilities?.midRunSessionId === 'boolean') return runtime.capabilities.midRunSessionId;
|
|
287
300
|
} catch {
|
|
@@ -316,7 +329,7 @@ function deferSteeringUntilCheckpoint(id, info, steerEntry) {
|
|
|
316
329
|
// once the resumed turn produces output evidence.
|
|
317
330
|
if (steerEntry?.steerId) {
|
|
318
331
|
try {
|
|
319
|
-
const store = require('
|
|
332
|
+
const store = require('../persistence/steering-store');
|
|
320
333
|
store.updateStatus(steerEntry.steerId, 'deferred', { dispatchId: id, runtime: info?.runtimeName || null });
|
|
321
334
|
} catch { /* best-effort */ }
|
|
322
335
|
}
|
|
@@ -369,29 +382,46 @@ function checkDeferredStranded(activeProcesses, config) {
|
|
|
369
382
|
}
|
|
370
383
|
}
|
|
371
384
|
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
// not a per-dispatch child process. Every OS-level kill helper in this file
|
|
377
|
-
// (`shared.killGracefully`, `shared.killImmediate`, `_escalatePlatformKill`)
|
|
378
|
-
// ultimately runs `taskkill /F /T /PID <pid>` or SIGKILLs that pid's whole
|
|
379
|
-
// process tree. Doing that to a pooled dispatch would kill the shared worker
|
|
380
|
-
// out from under every OTHER dispatch waiting to reuse it from the pool —
|
|
381
|
-
// exactly the collateral-damage risk `PooledAgentProcess#kill()` (P-1d8f0b93)
|
|
382
|
-
// was built to avoid: it cancels the in-flight ACP turn and releases the
|
|
383
|
-
// worker back to the pool without touching the OS process. Route every kill
|
|
384
|
-
// request here through this helper so no call site can accidentally bypass
|
|
385
|
-
// that primitive for a pooled record.
|
|
386
|
-
function killTrackedProcess(info, { graceMs } = {}) {
|
|
387
|
-
if (!info || !info.proc) return;
|
|
385
|
+
// A pooled dispatch PID belongs to its shared ACP worker. Its registered
|
|
386
|
+
// gateway site cancels the facade; cold sites verify the tracked PID identity.
|
|
387
|
+
function killTrackedProcess(info, { graceMs, processes } = {}) {
|
|
388
|
+
if (!info || !info.proc) return false;
|
|
388
389
|
if (info._pooled && typeof info.proc.kill === 'function') {
|
|
389
|
-
try { info.proc
|
|
390
|
+
try { return shared.terminateProcess('engine.timeout.tracked-pooled', info.proc); }
|
|
390
391
|
catch (err) { try { log('warn', `Pooled dispatch cancel failed for ${info.agentId || '?'}: ${err.message}`); } catch {} }
|
|
391
|
-
return;
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
const ownership = {
|
|
395
|
+
recordedAtMs: info.runtimePidObservedAt,
|
|
396
|
+
tracked: true,
|
|
397
|
+
...(Array.isArray(processes) ? { processes } : {}),
|
|
398
|
+
};
|
|
399
|
+
if (graceMs == null) {
|
|
400
|
+
return shared.terminateProcess('engine.timeout.tracked-cold', info.proc, ownership);
|
|
392
401
|
}
|
|
393
|
-
|
|
394
|
-
|
|
402
|
+
return shared.terminateProcess('engine.timeout.tracked-cold-graceful', info.proc, {
|
|
403
|
+
...ownership,
|
|
404
|
+
graceMs,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function cancelTrackedProcessesWithoutDispatch(activeProcesses, activeDispatchItems, opts = {}) {
|
|
409
|
+
const activeIds = new Set((activeDispatchItems || []).map(item => item?.id).filter(Boolean));
|
|
410
|
+
let cancelled = 0;
|
|
411
|
+
let processSnapshot = Array.isArray(opts.processes) ? opts.processes : null;
|
|
412
|
+
for (const [id, info] of activeProcesses.entries()) {
|
|
413
|
+
if (activeIds.has(id)) continue;
|
|
414
|
+
if (!info?._pooled && !processSnapshot) processSnapshot = shared.listAllProcesses();
|
|
415
|
+
const stopped = killTrackedProcess(info, { processes: processSnapshot });
|
|
416
|
+
if (stopped) {
|
|
417
|
+
log('warn', `Cancelling tracked process ${id} after its dispatch record was removed`);
|
|
418
|
+
cancelled++;
|
|
419
|
+
} else if (!info?._pooled) {
|
|
420
|
+
log('warn', `Dropping unowned tracked process ${id} without killing its PID`);
|
|
421
|
+
}
|
|
422
|
+
activeProcesses.delete(id);
|
|
423
|
+
}
|
|
424
|
+
return cancelled;
|
|
395
425
|
}
|
|
396
426
|
|
|
397
427
|
// W-mq066js7000fff1f-c (Gap C): runs the kill-retry escalation ladder for an
|
|
@@ -451,49 +481,16 @@ function _escalatePlatformKill(info) {
|
|
|
451
481
|
// ACP turn and release the worker instead of taskkill/SIGKILLing the
|
|
452
482
|
// shared worker process. See killTrackedProcess's comment above.
|
|
453
483
|
if (info?._pooled && info.proc && typeof info.proc.kill === 'function') {
|
|
454
|
-
try { info.proc
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
const pid = info?.proc?.pid;
|
|
458
|
-
if (!pid) return;
|
|
459
|
-
if (process.platform === 'win32') {
|
|
460
|
-
try { shared.exec(`taskkill /F /T /PID ${pid}`, { timeout: 3000 }); }
|
|
461
|
-
catch { /* may already be dead */ }
|
|
484
|
+
try { shared.terminateProcess('engine.timeout.tracked-pooled', info.proc); } catch { /* best-effort */ }
|
|
462
485
|
return;
|
|
463
486
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
catch { /* children may already be dead */ }
|
|
472
|
-
try { process.kill(pid, 'SIGKILL'); } catch { /* parent may already be dead */ }
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
function _collectDescendantPids(rootPid) {
|
|
476
|
-
// Returns descendant PIDs ordered deepest-first so leaves die before parents
|
|
477
|
-
// (otherwise grandchildren get re-parented to init and disappear from pgrep -P
|
|
478
|
-
// <pid>). BFS the tree, then reverse.
|
|
479
|
-
const visited = new Set();
|
|
480
|
-
const queue = [rootPid];
|
|
481
|
-
const order = [];
|
|
482
|
-
while (queue.length) {
|
|
483
|
-
const current = queue.shift();
|
|
484
|
-
if (visited.has(current)) continue;
|
|
485
|
-
visited.add(current);
|
|
486
|
-
let out = '';
|
|
487
|
-
try { out = String(shared.exec(`pgrep -P ${current}`, { timeout: 2000 }) || ''); }
|
|
488
|
-
catch { /* no children or pgrep missing */ continue; }
|
|
489
|
-
const children = out.split(/\s+/).map(s => Number(s)).filter(n => Number.isFinite(n) && n > 0);
|
|
490
|
-
for (const child of children) {
|
|
491
|
-
if (visited.has(child)) continue;
|
|
492
|
-
order.push(child);
|
|
493
|
-
queue.push(child);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
return order.reverse();
|
|
487
|
+
if (!info?.proc?.pid) return;
|
|
488
|
+
try {
|
|
489
|
+
shared.terminateProcess('engine.timeout.tracked-cold-escalation', info.proc, {
|
|
490
|
+
recordedAtMs: info.runtimePidObservedAt,
|
|
491
|
+
tracked: true,
|
|
492
|
+
});
|
|
493
|
+
} catch { /* may already be dead */ }
|
|
497
494
|
}
|
|
498
495
|
|
|
499
496
|
function checkSteering(config) {
|
|
@@ -604,7 +601,7 @@ function checkSteering(config) {
|
|
|
604
601
|
// process to deliver this message via session resume. Best-effort.
|
|
605
602
|
if (steerEntry?.steerId) {
|
|
606
603
|
try {
|
|
607
|
-
const store = require('
|
|
604
|
+
const store = require('../persistence/steering-store');
|
|
608
605
|
store.updateStatus(steerEntry.steerId, 'live_kill', { dispatchId: id, runtime: info?.runtimeName || null });
|
|
609
606
|
} catch { /* best-effort */ }
|
|
610
607
|
}
|
|
@@ -657,7 +654,7 @@ function trackedProcessPid(procInfo) {
|
|
|
657
654
|
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
658
655
|
}
|
|
659
656
|
|
|
660
|
-
function isTrackedProcessAlive(procInfo) {
|
|
657
|
+
function isTrackedProcessAlive(procInfo, opts = {}) {
|
|
661
658
|
if (!procInfo) return false;
|
|
662
659
|
const proc = procInfo.proc;
|
|
663
660
|
if (proc && Object.prototype.hasOwnProperty.call(proc, 'exitCode') && proc.exitCode !== null) {
|
|
@@ -666,12 +663,9 @@ function isTrackedProcessAlive(procInfo) {
|
|
|
666
663
|
|
|
667
664
|
const pid = trackedProcessPid(procInfo);
|
|
668
665
|
if (!pid) return !!proc && proc.killed !== true;
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
} catch {
|
|
673
|
-
return false;
|
|
674
|
-
}
|
|
666
|
+
// W-ms6dmv99005o26f4 — EPERM means the kernel FOUND the process and refused the
|
|
667
|
+
// signal; only ESRCH proves absence. See process-utils#inspectPidLiveness.
|
|
668
|
+
return shared.inspectPidLiveness(pid, opts).alive;
|
|
675
669
|
}
|
|
676
670
|
|
|
677
671
|
// Read the on-disk PID for a dispatch ID, or null if missing/invalid. Shared by
|
|
@@ -689,16 +683,43 @@ function _readDispatchPid(itemId) {
|
|
|
689
683
|
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
690
684
|
}
|
|
691
685
|
|
|
686
|
+
// W-ms6dmv99005o26f4 — decide whether an ALIVE OS PID may be demoted to "dead"
|
|
687
|
+
// because its command line does not look like an agent runtime.
|
|
688
|
+
//
|
|
689
|
+
// The recycled-PID cross-check this backs is real and must stay (Windows reuses
|
|
690
|
+
// PIDs aggressively, and a coincidentally-alive unrelated process would otherwise
|
|
691
|
+
// park the dispatch as `active` forever). But `isProcessCommandLineMatchingAgent`
|
|
692
|
+
// returns a bare `false` for BOTH "I read the command line and it isn't an agent"
|
|
693
|
+
// and "I could not read the command line at all". Collapsing those made a probe
|
|
694
|
+
// outage — PowerShell Get-CimInstance timing out under fleet load, a `/proc` read
|
|
695
|
+
// racing teardown, `ps` failing to spawn — read as proof of death for every silent
|
|
696
|
+
// dispatch simultaneously. That is the second link of the 2026-07-29T17:44Z
|
|
697
|
+
// false-orphan storm, and unlike the restart link it needs no engine restart to
|
|
698
|
+
// fire.
|
|
699
|
+
//
|
|
700
|
+
// Only a CONCLUSIVE mismatch demotes the PID. An unobservable one is kept, exactly
|
|
701
|
+
// as if the cross-check had not run — the dispatch stays on its normal bounded
|
|
702
|
+
// orphan path and is re-evaluated next tick.
|
|
703
|
+
function evaluateLivePidCommandLine(livePid, opts = {}) {
|
|
704
|
+
if (!livePid) return { treatAsDead: false, inconclusive: true, reason: 'no-pid-file' };
|
|
705
|
+
const inspect = opts.inspect || shared.inspectProcessCommandLineForAgent;
|
|
706
|
+
const evidence = inspect(livePid, opts.inspectOpts || {}) || {};
|
|
707
|
+
if (!evidence.conclusive) {
|
|
708
|
+
return { treatAsDead: false, inconclusive: true, reason: evidence.reason || 'unobservable' };
|
|
709
|
+
}
|
|
710
|
+
return { treatAsDead: !evidence.match, inconclusive: false, reason: evidence.reason || 'conclusive' };
|
|
711
|
+
}
|
|
712
|
+
|
|
692
713
|
// Last-resort liveness check via the on-disk PID file (engine/tmp/pid-<safeId>.pid).
|
|
693
714
|
// Used by orphan detection to avoid false-positive kills when the engine has lost the
|
|
694
715
|
// tracked process handle (engine restart, never-tracked spawn, etc.) but the OS-level
|
|
695
716
|
// child process is still alive and healthy. The safeId here mirrors engine.js spawn
|
|
696
|
-
// (id.replace(/[:\\/*?"<>|]/g, '-')) — same pattern engine/cli.js uses to re-attach.
|
|
697
|
-
function isOsPidAliveForDispatch(itemId) {
|
|
717
|
+
// (id.replace(/[:\\/*?"<>|]/g, '-')) — same pattern engine/operations/cli.js uses to re-attach.
|
|
718
|
+
function isOsPidAliveForDispatch(itemId, opts = {}) {
|
|
698
719
|
const pid = _readDispatchPid(itemId);
|
|
699
720
|
if (!pid) return false;
|
|
700
|
-
|
|
701
|
-
|
|
721
|
+
// W-ms6dmv99005o26f4 — EPERM is proof of existence, not of death.
|
|
722
|
+
return shared.inspectPidLiveness(pid, opts).alive;
|
|
702
723
|
}
|
|
703
724
|
|
|
704
725
|
function readFileTail(filePath, maxBytes) {
|
|
@@ -733,10 +754,10 @@ function readAcceptedCompletionReport(item, procInfo) {
|
|
|
733
754
|
const expectedNonce = procInfo?._completionNonce || null;
|
|
734
755
|
if (!expectedNonce) return null;
|
|
735
756
|
try {
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
757
|
+
return shared.acceptCompletionReportForNonce(
|
|
758
|
+
require('./lifecycle').parseCompletionReportFile(item),
|
|
759
|
+
expectedNonce,
|
|
760
|
+
);
|
|
740
761
|
} catch (e) {
|
|
741
762
|
log('warn', `resume-stall completion-report probe failed for ${item?.id}: ${e.message}`);
|
|
742
763
|
return null;
|
|
@@ -745,12 +766,14 @@ function readAcceptedCompletionReport(item, procInfo) {
|
|
|
745
766
|
|
|
746
767
|
function parseProcessExitCode(logText) {
|
|
747
768
|
if (!logText) return null;
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
769
|
+
// A sentinel is terminal evidence only while it remains the final non-empty
|
|
770
|
+
// line. Agent live-output files survive engine restarts and can therefore
|
|
771
|
+
// contain an old sentinel followed by a newer attempt's active output.
|
|
772
|
+
const match = String(logText).match(
|
|
773
|
+
/(?:^|\n)\[process-exit\]\s+(?:code=)?(-?\d+|spawn-failed)[^\S\r\n]*(?:\r?\n)?\s*$/
|
|
774
|
+
);
|
|
775
|
+
if (!match) return null;
|
|
776
|
+
return match[1] === 'spawn-failed' ? -1 : parseInt(match[1], 10);
|
|
754
777
|
}
|
|
755
778
|
|
|
756
779
|
function terminalResultIndicatesError(obj) {
|
|
@@ -817,11 +840,12 @@ function checkTimeouts(config) {
|
|
|
817
840
|
// The exception is a resumed runtime that has not produced its first stdout/stderr
|
|
818
841
|
// heartbeat after spawn; that is the "alive but stuck in --resume" failure mode.
|
|
819
842
|
const dispatchData = getDispatch();
|
|
843
|
+
cancelTrackedProcessesWithoutDispatch(activeProcesses, dispatchData.active);
|
|
820
844
|
const deadItems = [];
|
|
821
845
|
const legacyAnnotationClears = new Set();
|
|
822
846
|
const finalizationPromises = [];
|
|
823
847
|
|
|
824
|
-
function completeFromOutput(item, liveLogPath, processExitCode, detectedLogText, hasProcess, expectedProcInfo = activeProcesses.get(item.id)) {
|
|
848
|
+
function completeFromOutput(item, liveLogPath, processExitCode, detectedLogText, hasProcess, expectedProcInfo = activeProcesses.get(item.id), completionPath = shared.DISPATCH_COMPLETION_PATH.OUTPUT_DETECTION) {
|
|
825
849
|
if (hasProcess && (
|
|
826
850
|
expectedProcInfo?._steeringAt
|
|
827
851
|
|| expectedProcInfo?._steeringMessage
|
|
@@ -951,6 +975,7 @@ function checkTimeouts(config) {
|
|
|
951
975
|
}
|
|
952
976
|
|
|
953
977
|
const completionOpts = {
|
|
978
|
+
completionPath,
|
|
954
979
|
...(structuredCompletion?._path ? { completionReportPath: structuredCompletion._path } : {}),
|
|
955
980
|
...(structuredCompletion ? { structuredCompletion } : {}),
|
|
956
981
|
};
|
|
@@ -1040,7 +1065,7 @@ function checkTimeouts(config) {
|
|
|
1040
1065
|
|
|
1041
1066
|
if (shared.isLiveCheckoutDispatchRecord(item, config)) {
|
|
1042
1067
|
try {
|
|
1043
|
-
await require('
|
|
1068
|
+
await require('../worktrees/live-checkout').maybeRestoreLiveCheckoutFromRecord({
|
|
1044
1069
|
item,
|
|
1045
1070
|
config,
|
|
1046
1071
|
isTerminalFailure: effectiveResult === DISPATCH_RESULT.ERROR,
|
|
@@ -1210,7 +1235,8 @@ function checkTimeouts(config) {
|
|
|
1210
1235
|
// race against our own cleanup. procInfo is still passed as the
|
|
1211
1236
|
// expected proc info so the per-spawn nonce is available to the
|
|
1212
1237
|
// lifecycle hooks.
|
|
1213
|
-
completeFromOutput(item, liveLogPath, 0, liveLogTailForItem(liveLogPath), false, procInfo
|
|
1238
|
+
completeFromOutput(item, liveLogPath, 0, liveLogTailForItem(liveLogPath), false, procInfo,
|
|
1239
|
+
shared.DISPATCH_COMPLETION_PATH.RESUME_STALL_REPORT);
|
|
1214
1240
|
activeProcesses.delete(item.id);
|
|
1215
1241
|
killTrackedProcess(procInfo, { graceMs: 5000 });
|
|
1216
1242
|
continue;
|
|
@@ -1223,7 +1249,12 @@ function checkTimeouts(config) {
|
|
|
1223
1249
|
try { shared.safeUnlink(path.join(AGENTS_DIR, item.agent, 'session.json')); } catch {}
|
|
1224
1250
|
activeProcesses.delete(item.id);
|
|
1225
1251
|
killTrackedProcess(procInfo, { graceMs: 5000 });
|
|
1226
|
-
deadItems.push({
|
|
1252
|
+
deadItems.push({
|
|
1253
|
+
item,
|
|
1254
|
+
reason,
|
|
1255
|
+
failureClass: FAILURE_CLASS.TIMEOUT,
|
|
1256
|
+
completionPath: shared.DISPATCH_COMPLETION_PATH.RESUME_STALL_TIMEOUT,
|
|
1257
|
+
});
|
|
1227
1258
|
}
|
|
1228
1259
|
}
|
|
1229
1260
|
continue;
|
|
@@ -1276,7 +1307,7 @@ function checkTimeouts(config) {
|
|
|
1276
1307
|
// Last-resort PID check: lost tracked handle but OS process may still be alive.
|
|
1277
1308
|
// P-3f5b7d26 — SKIPPED for pooled dispatches (item.pooled, persisted by
|
|
1278
1309
|
// engine.js#spawnAgent). The PID recorded for a pooled dispatch is the
|
|
1279
|
-
// shared ACP worker's real OS pid (engine/acp-transport.js#spawnAcp),
|
|
1310
|
+
// shared ACP worker's real OS pid (engine/agents/acp-transport.js#spawnAcp),
|
|
1280
1311
|
// whose command line is literally `copilot --acp ...` — it would always
|
|
1281
1312
|
// pass isProcessCommandLineMatchingAgent's `cmdline.includes('copilot')`
|
|
1282
1313
|
// check below even though that worker has no relationship to THIS
|
|
@@ -1290,11 +1321,23 @@ function checkTimeouts(config) {
|
|
|
1290
1321
|
// under an unrelated process (Windows reuses PIDs aggressively). Without
|
|
1291
1322
|
// this cmdline gate, the dispatch is parked indefinitely as 'active'
|
|
1292
1323
|
// pinned to a process that has nothing to do with the agent.
|
|
1324
|
+
//
|
|
1325
|
+
// W-ms6dmv99005o26f4 — this gate must fire on OBSERVED mismatch only. The
|
|
1326
|
+
// boolean helper also returns false when the per-PID command-line probe
|
|
1327
|
+
// itself fails (PowerShell Get-CimInstance timing out under fleet load,
|
|
1328
|
+
// /proc read racing a namespace, `ps` spawn EAGAIN), which turned a probe
|
|
1329
|
+
// outage into "treating as dead" for every silent dispatch at once — the
|
|
1330
|
+
// second link of the 2026-07-29T17:44Z false-orphan storm. An alive OS PID
|
|
1331
|
+
// we merely failed to inspect is kept, exactly as if the probe had not run.
|
|
1293
1332
|
const livePid = _readDispatchPid(item.id);
|
|
1294
|
-
|
|
1333
|
+
const cmdlineEvidence = evaluateLivePidCommandLine(livePid);
|
|
1334
|
+
if (cmdlineEvidence.treatAsDead) {
|
|
1295
1335
|
log('warn', `Orphan check: ${item.agent} (${item.id}) — PID ${livePid} alive but command line doesn't match agent, treating as dead`);
|
|
1296
1336
|
processAlive = false;
|
|
1297
1337
|
} else {
|
|
1338
|
+
if (cmdlineEvidence.inconclusive) {
|
|
1339
|
+
log('warn', `Orphan check: ${item.agent} (${item.id}) — PID ${livePid} alive but command line could not be observed (${cmdlineEvidence.reason}); keeping the dispatch rather than inferring death`);
|
|
1340
|
+
}
|
|
1298
1341
|
log('info', `Orphan check: ${item.agent} (${item.id}) silent ${silentSec}s but OS PID is alive — keeping [${_logState}]`);
|
|
1299
1342
|
continue;
|
|
1300
1343
|
}
|
|
@@ -1324,16 +1367,20 @@ function checkTimeouts(config) {
|
|
|
1324
1367
|
dispatch().updateAgentStatus(item.id, AGENT_STATUS.TIMED_OUT, `Orphaned — no process, silent for ${silentSec}s`);
|
|
1325
1368
|
// Clear session so retry starts fresh
|
|
1326
1369
|
try { shared.safeUnlink(path.join(AGENTS_DIR, item.agent, 'session.json')); } catch {}
|
|
1327
|
-
deadItems.push({
|
|
1370
|
+
deadItems.push({
|
|
1371
|
+
item,
|
|
1372
|
+
reason: `Orphaned — no process, silent for ${silentSec}s`,
|
|
1373
|
+
failureClass: confirmedDeadAtRestart ? FAILURE_CLASS.ENGINE_RESTART : FAILURE_CLASS.TIMEOUT,
|
|
1374
|
+
});
|
|
1328
1375
|
activeProcesses.delete(item.id);
|
|
1329
1376
|
}
|
|
1330
1377
|
}
|
|
1331
1378
|
|
|
1332
1379
|
// Clean up dead items
|
|
1333
|
-
for (const { item, reason, failureClass } of deadItems) {
|
|
1380
|
+
for (const { item, reason, failureClass, completionPath } of deadItems) {
|
|
1334
1381
|
const finalizationOwner = 'timeout-orphan';
|
|
1335
1382
|
if (!shared.claimDispatchFinalization(item.id, finalizationOwner)) continue;
|
|
1336
|
-
const finalizationPromise = _finalizeDeadDispatch(item, reason, failureClass, config, { complete: completeDispatch })
|
|
1383
|
+
const finalizationPromise = _finalizeDeadDispatch(item, reason, failureClass, config, { complete: completeDispatch, completionPath })
|
|
1337
1384
|
.catch(err => log('warn', `Orphan cleanup failed for ${item.id}: ${err.message}`))
|
|
1338
1385
|
.finally(() => shared.releaseDispatchFinalization(item.id, finalizationOwner));
|
|
1339
1386
|
finalizationPromises.push(finalizationPromise);
|
|
@@ -1413,14 +1460,16 @@ module.exports = {
|
|
|
1413
1460
|
checkIdleThreshold,
|
|
1414
1461
|
dropSteeringForPurgedSession,
|
|
1415
1462
|
isOsPidAliveForDispatch,
|
|
1463
|
+
evaluateLivePidCommandLine, // exported for testing
|
|
1464
|
+
_isTrackedProcessAlive: isTrackedProcessAlive, // exported for testing
|
|
1416
1465
|
parseProcessExitCode, terminalResultIndicatesError, parseTerminalResultFallbackExitCode, // exported for testing
|
|
1417
1466
|
readFileTail, runtimeSupportsMidRunSessionId, // exported for testing
|
|
1418
1467
|
readAcceptedCompletionReport, liveLogTailForItem, // exported for testing (W-ms0vk9nv000z985b)
|
|
1419
1468
|
// exported for testing
|
|
1420
|
-
rememberDeferredSteering, checkDeferredStranded, _runSteeringKillLadder,
|
|
1469
|
+
rememberDeferredSteering, checkDeferredStranded, _runSteeringKillLadder,
|
|
1421
1470
|
_resetSteeringStoreCacheForTest, _setSteeringStoreForTest,
|
|
1422
1471
|
// exported for testing — steering clamp helpers and deferred checkpoint
|
|
1423
1472
|
_clampSteeringDeferredMaxMs, _clampSteeringMaxKillRetries, _appendLiveOutputLine, deferSteeringUntilCheckpoint,
|
|
1424
|
-
killTrackedProcess, _escalatePlatformKill, // exported for testing — P-6e2a4c15 cancel-vs-kill
|
|
1473
|
+
killTrackedProcess, cancelTrackedProcessesWithoutDispatch, _escalatePlatformKill, // exported for testing — P-6e2a4c15 cancel-vs-kill
|
|
1425
1474
|
_finalizeDeadDispatch, // exported for testing
|
|
1426
1475
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// engine/db-events.js — `emitStateEvent(topic, payload?)` helper.
|
|
1
|
+
// engine/persistence/db-events.js — `emitStateEvent(topic, payload?)` helper.
|
|
2
2
|
//
|
|
3
3
|
// Append a row to the `events` table so the dashboard's status cache (and
|
|
4
4
|
// any future topic-scoped subscriber) sees the change on its next poll.
|
|
@@ -16,7 +16,7 @@ let _logged = false;
|
|
|
16
16
|
|
|
17
17
|
function emitStateEvent(topic, payload) {
|
|
18
18
|
try {
|
|
19
|
-
const { getDb } = require('
|
|
19
|
+
const { getDb } = require('../db');
|
|
20
20
|
const db = getDb();
|
|
21
21
|
if (payload !== undefined && payload !== null) {
|
|
22
22
|
db.prepare('INSERT INTO events (topic, payload) VALUES (?, ?)')
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// engine/dispatch-store.js — SQL-backed section-shaped dispatch state:
|
|
1
|
+
// engine/persistence/dispatch-store.js — SQL-backed section-shaped dispatch state:
|
|
2
2
|
//
|
|
3
3
|
// readDispatchSectioned() -> { pending, active, completed, review }
|
|
4
4
|
// applyDispatchMutation(fn) -> runs fn against the section object,
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// (event emission, cache invalidation)
|
|
10
10
|
// when nothing actually changed.
|
|
11
11
|
//
|
|
12
|
-
// All callers go through engine/dispatch.js#mutateDispatch and
|
|
13
|
-
// engine/queries.js#getDispatch as before — those are thin wrappers
|
|
12
|
+
// All callers go through engine/orchestration/dispatch.js#mutateDispatch and
|
|
13
|
+
// engine/core/queries.js#getDispatch as before — those are thin wrappers
|
|
14
14
|
// over the helpers here. Zero changes at any call site.
|
|
15
15
|
|
|
16
16
|
const SECTIONS = ['pending', 'active', 'completed', 'review'];
|
|
@@ -33,7 +33,7 @@ function _parseRow(row) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
function readDispatchSectioned() {
|
|
36
|
-
const { getDb } = require('
|
|
36
|
+
const { getDb } = require('../db');
|
|
37
37
|
const db = getDb();
|
|
38
38
|
|
|
39
39
|
// Return the complete dispatch state regardless of how old
|
|
@@ -59,7 +59,7 @@ function readDispatchSectioned() {
|
|
|
59
59
|
// not just the recent window. Mutators that prune old completed
|
|
60
60
|
// dispatches MUST see those rows or the prune is a no-op.
|
|
61
61
|
function _readDispatchSectionedFull() {
|
|
62
|
-
const { getDb } = require('
|
|
62
|
+
const { getDb } = require('../db');
|
|
63
63
|
let db;
|
|
64
64
|
try { db = getDb(); }
|
|
65
65
|
catch { return _emptySectioned(); }
|
|
@@ -169,11 +169,11 @@ function _applyDispatchDiff(db, diff) {
|
|
|
169
169
|
// mutator mutated in place + returned undefined). Same
|
|
170
170
|
// contract as the legacy mutateJsonFileLocked-based path.
|
|
171
171
|
function applyDispatchMutation(mutator) {
|
|
172
|
-
const { getDb, withTransaction } = require('
|
|
172
|
+
const { getDb, withTransaction } = require('../db');
|
|
173
173
|
let db;
|
|
174
174
|
try { db = getDb(); }
|
|
175
175
|
catch (e) {
|
|
176
|
-
throw new Error(`engine/dispatch-store: SQLite unavailable (${e.message}); cannot mutate dispatch state`);
|
|
176
|
+
throw new Error(`engine/persistence/dispatch-store: SQLite unavailable (${e.message}); cannot mutate dispatch state`);
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
// withTransaction is nestable: shared.mutateWorkItems → mutator → cli
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// engine/inbox-store.js — SQL-backed inbox_entries table handlers.
|
|
1
|
+
// engine/persistence/inbox-store.js — SQL-backed inbox_entries table handlers.
|
|
2
2
|
//
|
|
3
3
|
// Provides read/write helpers for the operator and agent inbox message queue.
|
|
4
4
|
// Inboxes are decoupled from the runtime work-item/dispatch lifecycle and
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// Schema: docs/design-inbox-entries-schema.md
|
|
8
8
|
|
|
9
9
|
const crypto = require('node:crypto');
|
|
10
|
-
const { getDb, withTransaction } = require('
|
|
10
|
+
const { getDb, withTransaction } = require('../db');
|
|
11
11
|
|
|
12
12
|
// Per CLAUDE.md's "State storage" mutator contract, every mutator wrapper
|
|
13
13
|
// must call emitStateEvent() after a successful write so the dashboard's
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// engine/note-link-backfill.js
|
|
1
|
+
// engine/persistence/note-link-backfill.js
|
|
2
2
|
//
|
|
3
3
|
// One-time repair for work-item note links that broke BEFORE the live
|
|
4
4
|
// rewrite landed (W-mq1j85cj00055a8f extended to _artifacts.notes / artifacts).
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// notes.md) are left untouched. Idempotent: a second pass finds nothing because
|
|
19
19
|
// the repaired links no longer reference `notes/inbox/`.
|
|
20
20
|
//
|
|
21
|
-
// Wired as an idempotent engine-boot sweep (engine/cli.js, alongside the other
|
|
21
|
+
// Wired as an idempotent engine-boot sweep (engine/operations/cli.js, alongside the other
|
|
22
22
|
// one-shot boot migrations). It runs through shared.mutateWorkItems — the same
|
|
23
23
|
// write path the live rewriter uses — so the canonical SQL store and its JSON
|
|
24
24
|
// mirror stay consistent, and it's a no-op once every link is healed.
|
|
@@ -53,7 +53,7 @@ function buildNoteResolver(dirs) {
|
|
|
53
53
|
const kbCategories = Array.isArray(dirs.kbCategories) ? dirs.kbCategories : [];
|
|
54
54
|
// Reuse the live rewriter's encoder so the backfill and the live path emit
|
|
55
55
|
// byte-identical pill tokens (one source for the archive:/kb: grammar).
|
|
56
|
-
const tokenFor = require('
|
|
56
|
+
const tokenFor = require('../core/shared')._artifactNoteFileToken;
|
|
57
57
|
|
|
58
58
|
const archiveByBase = new Map();
|
|
59
59
|
let archiveFiles = [];
|
|
@@ -150,7 +150,7 @@ function repointItemNoteLinks(item, resolve) {
|
|
|
150
150
|
|
|
151
151
|
// Repair broken note links across every project scope plus central.
|
|
152
152
|
function runNoteLinkBackfill(opts = {}) {
|
|
153
|
-
const shared = require('
|
|
153
|
+
const shared = require('../core/shared');
|
|
154
154
|
const minionsDir = opts.minionsDir || shared.MINIONS_DIR;
|
|
155
155
|
const resolve = buildNoteResolver({
|
|
156
156
|
inboxDir: path.join(minionsDir, 'notes', 'inbox'),
|