@yemi33/minions 0.1.2448 → 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.
Files changed (315) hide show
  1. package/bin/cli-api-client.js +1 -1
  2. package/bin/install-internal-minions.js +1382 -44
  3. package/bin/install-layout.js +150 -0
  4. package/bin/minions.js +460 -167
  5. package/dashboard/docs/typography.md +65 -12
  6. package/dashboard/js/command-center.js +58 -4
  7. package/dashboard/js/detail-panel.js +36 -0
  8. package/dashboard/js/memory-panel.js +59 -12
  9. package/dashboard/js/qa.js +179 -20
  10. package/dashboard/js/refresh.js +148 -12
  11. package/dashboard/js/render-dispatch.js +3 -4
  12. package/dashboard/js/render-inbox.js +2 -2
  13. package/dashboard/js/render-other.js +3 -3
  14. package/dashboard/js/render-pipelines.js +14 -0
  15. package/dashboard/js/render-plans.js +57 -9
  16. package/dashboard/js/render-prd.js +132 -23
  17. package/dashboard/js/render-prs.js +195 -166
  18. package/dashboard/js/render-schedules.js +63 -3
  19. package/dashboard/js/render-utils.js +3 -3
  20. package/dashboard/js/render-watches.js +19 -3
  21. package/dashboard/js/render-work-items.js +238 -30
  22. package/dashboard/js/settings.js +205 -54
  23. package/dashboard/js/utils.js +51 -1
  24. package/dashboard/pages/home.html +1 -1
  25. package/dashboard/pages/qa.html +1 -16
  26. package/dashboard/pages/work.html +40 -0
  27. package/dashboard/shared/cc-limits.js +79 -0
  28. package/dashboard/shared/pr-filters.js +21 -38
  29. package/dashboard/shared/project-git-summary.js +1 -1
  30. package/dashboard/shared/record-filters.js +169 -0
  31. package/dashboard/shared/watches-source.js +1 -1
  32. package/dashboard/shared/welcome-popup.js +1 -1
  33. package/dashboard/shared/wi-filters.js +302 -0
  34. package/dashboard/slim/body.html +1 -0
  35. package/dashboard/slim/js/command-send.js +26 -0
  36. package/dashboard/slim/js/modals-tiles.js +380 -39
  37. package/dashboard/slim/js/status.js +13 -21
  38. package/dashboard/slim/layout.html +1 -0
  39. package/dashboard/slim/panel-bootstrap.js +6 -2
  40. package/dashboard/slim/styles.css +38 -0
  41. package/dashboard/styles.css +159 -55
  42. package/dashboard-build.js +13 -2
  43. package/dashboard.js +956 -423
  44. package/docs/README.md +11 -6
  45. package/docs/api-errors.md +2 -2
  46. package/docs/architecture-review-2026-07-09.md +1 -1
  47. package/docs/architecture.excalidraw +2 -2
  48. package/docs/auto-discovery.md +18 -9
  49. package/docs/branch-derivation.md +4 -4
  50. package/docs/capture-demos.js +39 -2
  51. package/docs/ci-runner-canary.md +123 -0
  52. package/docs/claude-md-propagation.md +2 -2
  53. package/docs/cloud-agent-dispatch.md +204 -0
  54. package/docs/command-center.md +7 -7
  55. package/docs/completion-reports.md +43 -20
  56. package/docs/constants.md +10 -3
  57. package/docs/constellation-bridge.md +134 -6
  58. package/docs/constellation-style-telemetry.md +4 -4
  59. package/docs/contracts/capability-protocol.v1.json +165 -0
  60. package/docs/cooldown-merge-semantics.md +12 -12
  61. package/docs/copilot-cli-schema.md +7 -7
  62. package/docs/cross-repo-plans.md +10 -10
  63. package/docs/dead-code-audit-retractions.md +5 -5
  64. package/docs/default-branch-ci.md +173 -0
  65. package/docs/deprecated.json +31 -31
  66. package/docs/design-inbox-entries-schema.md +3 -3
  67. package/docs/design-language.md +1051 -0
  68. package/docs/design-state-storage.md +11 -11
  69. package/docs/diagnostics-crash-reports.md +9 -9
  70. package/docs/diagnostics-memory.md +5 -5
  71. package/docs/documentation-audit-2026-07-09.md +7 -7
  72. package/docs/engine-restart.md +90 -5
  73. package/docs/harness-mode.md +1 -1
  74. package/docs/internal-install.md +338 -39
  75. package/docs/kb-dedup-duplicate-pair-investigation.md +5 -5
  76. package/docs/kb-pr3223-cascade-archiving.md +1 -1
  77. package/docs/kb-pr696-merge-conflict-docs.md +6 -6
  78. package/docs/kb-sweep.md +35 -35
  79. package/docs/keep-processes.md +1 -1
  80. package/docs/live-checkout-mode.md +30 -30
  81. package/docs/managed-spawn.md +18 -14
  82. package/docs/named-agents.md +7 -7
  83. package/docs/plan-lifecycle.md +69 -2
  84. package/docs/pr-author-identity.md +114 -0
  85. package/docs/pr-auto-fix-dispatch.md +19 -4
  86. package/docs/pr-comment-followup.md +6 -6
  87. package/docs/pr-review-fix-loop.md +59 -10
  88. package/docs/process-termination.md +40 -0
  89. package/docs/proposals/repo-pool-for-live-checkout.md +13 -13
  90. package/docs/qa-runbook-lifecycle.md +367 -17
  91. package/docs/qa-runbooks.md +3 -3
  92. package/docs/rfc-completion-json.md +18 -18
  93. package/docs/runtime-adapters.md +26 -21
  94. package/docs/security.md +6 -6
  95. package/docs/self-improvement.md +4 -4
  96. package/docs/shared-lifecycle-module-map.md +473 -472
  97. package/docs/skills.md +52 -3
  98. package/docs/slim-ux/concepts.md +121 -116
  99. package/docs/specs/agent-configurability.md +18 -18
  100. package/docs/specs/agent-rename.md +18 -18
  101. package/docs/team-memory.md +38 -21
  102. package/docs/timeouts-and-liveness.md +118 -10
  103. package/docs/tutorials/01-install-and-connect.md +1 -1
  104. package/docs/watches.md +40 -39
  105. package/docs/workspace-manifests.md +4 -4
  106. package/docs/worktree-lifecycle.md +293 -14
  107. package/engine/README.md +46 -0
  108. package/engine/{ado-comment.js → ado/comment.js} +8 -8
  109. package/engine/{ado-git-auth.js → ado/git-auth.js} +4 -4
  110. package/engine/{ado.js → ado/index.js} +417 -63
  111. package/engine/{ado-status.js → ado/status.js} +6 -8
  112. package/engine/{ado-token.js → ado/token.js} +1 -1
  113. package/engine/{acp-transport.js → agents/acp-transport.js} +62 -22
  114. package/engine/{agent-worker-pool.js → agents/agent-worker-pool.js} +17 -8
  115. package/engine/{cc-worker-pool.js → agents/cc-worker-pool.js} +16 -6
  116. package/engine/{claude-md-context.js → agents/claude-md-context.js} +5 -5
  117. package/engine/{harness-context.js → agents/harness-context.js} +5 -5
  118. package/engine/{harness.js → agents/harness.js} +3 -3
  119. package/engine/{llm.js → agents/llm.js} +18 -14
  120. package/engine/{model-discovery.js → agents/model-discovery.js} +2 -2
  121. package/engine/{playbook.js → agents/playbook.js} +155 -22
  122. package/engine/{pooled-agent-process.js → agents/pooled-agent-process.js} +14 -12
  123. package/engine/{preflight.js → agents/preflight.js} +29 -10
  124. package/engine/{spawn-agent.js → agents/spawn-agent.js} +25 -14
  125. package/engine/{spawn-phase-watchdog.js → agents/spawn-phase-watchdog.js} +16 -7
  126. package/engine/{steering.js → agents/steering.js} +5 -5
  127. package/engine/{tools-inventory.js → agents/tools-inventory.js} +2 -2
  128. package/engine/{agent-api-validation.js → api/agent-api-validation.js} +2 -2
  129. package/engine/{api-validation.js → api/api-validation.js} +1 -1
  130. package/engine/api/bridge.js +787 -0
  131. package/engine/{cc-api-validation.js → api/cc-api-validation.js} +1 -1
  132. package/engine/api/companion.js +560 -0
  133. package/engine/{content-api-validation.js → api/content-api-validation.js} +2 -2
  134. package/engine/{pr-issue-validation.js → api/pr-issue-validation.js} +33 -6
  135. package/engine/{settings-validation.js → api/settings-validation.js} +32 -4
  136. package/engine/api-contracts/agent-content.js +4 -4
  137. package/engine/api-contracts/capability-manifest.js +236 -0
  138. package/engine/api-contracts/capability-protocol.js +333 -0
  139. package/engine/api-contracts/cc-ops.js +1 -1
  140. package/engine/api-contracts/config-runtime.js +5 -0
  141. package/engine/api-contracts/core.js +28 -1
  142. package/engine/api-contracts/index.js +100 -0
  143. package/engine/api-contracts/orchestration.js +18 -5
  144. package/engine/api-contracts/pull-requests.js +37 -6
  145. package/engine/api-contracts/qa-process.js +29 -6
  146. package/engine/api-contracts/work-plan-prd.js +21 -1
  147. package/engine/cloud/contract.js +212 -0
  148. package/engine/cloud/index.js +159 -0
  149. package/engine/{execution-model.js → core/execution-model.js} +1 -1
  150. package/engine/{features.js → core/features.js} +4 -4
  151. package/engine/{operator-identity.js → core/operator-identity.js} +1 -1
  152. package/engine/{queries.js → core/queries.js} +201 -36
  153. package/engine/{safe-expr.js → core/safe-expr.js} +1 -1
  154. package/engine/{shared.js → core/shared.js} +1637 -175
  155. package/engine/{stdio-timestamps.js → core/stdio-timestamps.js} +1 -1
  156. package/engine/{untrusted-fence.js → core/untrusted-fence.js} +3 -3
  157. package/engine/db/index.js +11 -2
  158. package/engine/db/migrations/002-dispatches.js +3 -3
  159. package/engine/db/migrations/003-work-items.js +1 -1
  160. package/engine/db/migrations/004-pull-requests.js +1 -1
  161. package/engine/db/migrations/006-metrics.js +1 -1
  162. package/engine/db/migrations/007-watches.js +2 -2
  163. package/engine/db/migrations/008-small-state.js +1 -1
  164. package/engine/db/migrations/009-qa.js +1 -1
  165. package/engine/db/migrations/010-pr-links.js +1 -1
  166. package/engine/db/migrations/011-remaining-state.js +1 -1
  167. package/engine/db/migrations/012-steering-deliveries.js +2 -2
  168. package/engine/db/migrations/013-backfill-broken-note-links.js +1 -1
  169. package/engine/db/migrations/014-pr-fix-target-prefs.js +2 -2
  170. package/engine/db/migrations/015-plans-prds.js +0 -0
  171. package/engine/db/migrations/018-sql-only-cutover.js +2 -2
  172. package/engine/db/migrations/021-archived-work-items.js +1 -1
  173. package/engine/db/migrations/022-global-cc-session.js +1 -1
  174. package/engine/db/migrations/023-engine-state.js +1 -1
  175. package/engine/db/migrations/025-malformed-work-item-phantoms.js +1 -1
  176. package/engine/db/migrations/027-review-learning-lifecycle.js +1 -1
  177. package/engine/db/migrations/029-repair-reused-versions.js +20 -0
  178. package/engine/db/migrations/031-pr-author-identity.js +137 -0
  179. package/engine/{consolidation.js → memory/consolidation.js} +6 -6
  180. package/engine/{kb-sweep-runner.js → memory/kb-sweep-runner.js} +2 -2
  181. package/engine/{kb-sweep.js → memory/kb-sweep.js} +9 -7
  182. package/engine/{memory-retrieval.js → memory/memory-retrieval.js} +46 -4
  183. package/engine/{memory-store.js → memory/memory-store.js} +3 -3
  184. package/engine/{promotion.js → memory/promotion.js} +3 -3
  185. package/engine/{review-learning-backfill.js → memory/review-learning-backfill.js} +6 -6
  186. package/engine/{review-learning.js → memory/review-learning.js} +10 -5
  187. package/engine/{diagnostics-memory.js → observability/diagnostics-memory.js} +1 -1
  188. package/engine/{logs-store.js → observability/logs-store.js} +5 -5
  189. package/engine/{metrics-store.js → observability/metrics-store.js} +4 -4
  190. package/engine/{check-status.js → operations/check-status.js} +3 -3
  191. package/engine/{cli.js → operations/cli.js} +271 -113
  192. package/engine/{distribution.js → operations/distribution.js} +5 -6
  193. package/engine/{cleanup.js → orchestration/cleanup.js} +72 -45
  194. package/engine/{cooldown.js → orchestration/cooldown.js} +5 -5
  195. package/engine/{dispatch-events.js → orchestration/dispatch-events.js} +2 -2
  196. package/engine/{dispatch.js → orchestration/dispatch.js} +129 -36
  197. package/engine/orchestration/failed-scheduled-cleanup.js +274 -0
  198. package/engine/{lifecycle.js → orchestration/lifecycle.js} +198 -90
  199. package/engine/{meeting.js → orchestration/meeting.js} +6 -16
  200. package/engine/{pipeline.js → orchestration/pipeline.js} +12 -12
  201. package/engine/{pre-dispatch-eval.js → orchestration/pre-dispatch-eval.js} +10 -9
  202. package/engine/{routing.js → orchestration/routing.js} +3 -3
  203. package/engine/{schedule-bootstrap.js → orchestration/schedule-bootstrap.js} +4 -4
  204. package/engine/{scheduler.js → orchestration/scheduler.js} +38 -8
  205. package/engine/{timeout.js → orchestration/timeout.js} +158 -109
  206. package/engine/{db-events.js → persistence/db-events.js} +2 -2
  207. package/engine/{dispatch-store.js → persistence/dispatch-store.js} +7 -7
  208. package/engine/{inbox-store.js → persistence/inbox-store.js} +2 -2
  209. package/engine/{note-link-backfill.js → persistence/note-link-backfill.js} +4 -4
  210. package/engine/{pr-fix-target-store.js → persistence/pr-fix-target-store.js} +8 -8
  211. package/engine/{pull-requests-store.js → persistence/pull-requests-store.js} +21 -7
  212. package/engine/{small-state-store.js → persistence/small-state-store.js} +31 -31
  213. package/engine/persistence/state-operations.js +350 -0
  214. package/engine/{steering-store.js → persistence/steering-store.js} +6 -6
  215. package/engine/{issues.js → planning/issues.js} +2 -2
  216. package/engine/{plan-prd-validation.js → planning/plan-prd-validation.js} +8 -2
  217. package/engine/planning/prd-result-sidecar.js +190 -0
  218. package/engine/{prd-store.js → planning/prd-store.js} +17 -17
  219. package/engine/{project-discovery.js → planning/project-discovery.js} +5 -5
  220. package/engine/{projects.js → planning/projects.js} +10 -10
  221. package/engine/{resolve-area.js → planning/resolve-area.js} +1 -1
  222. package/engine/{work-item-validation.js → planning/work-item-validation.js} +39 -3
  223. package/engine/{work-items-store.js → planning/work-items-store.js} +29 -21
  224. package/engine/{keep-process-sweep.js → processes/keep-process-sweep.js} +57 -17
  225. package/engine/{managed-spawn-launcher.js → processes/managed-spawn-launcher.js} +3 -3
  226. package/engine/{managed-spawn.js → processes/managed-spawn.js} +97 -46
  227. package/engine/{process-utils.js → processes/process-utils.js} +599 -55
  228. package/engine/{abandoned-pr-reconciliation.js → providers/abandoned-pr-reconciliation.js} +17 -7
  229. package/engine/{comment-classifier.js → providers/comment-classifier.js} +85 -17
  230. package/engine/{comment-format.js → providers/comment-format.js} +5 -5
  231. package/engine/{gh-comment.js → providers/gh-comment.js} +15 -15
  232. package/engine/{gh-token.js → providers/gh-token.js} +4 -4
  233. package/engine/{github.js → providers/github.js} +131 -54
  234. package/engine/{pr-action.js → providers/pr-action.js} +13 -12
  235. package/engine/{pr-clone-keep.js → providers/pr-clone-keep.js} +7 -7
  236. package/engine/{pr-devbox.js → providers/pr-devbox.js} +6 -6
  237. package/engine/{pr-fix-target.js → providers/pr-fix-target.js} +13 -13
  238. package/engine/{pr-remote-patch.js → providers/pr-remote-patch.js} +4 -4
  239. package/engine/{pr-resolve.js → providers/pr-resolve.js} +7 -7
  240. package/engine/{pr-temp-clone.js → providers/pr-temp-clone.js} +5 -5
  241. package/engine/{pr-track.js → providers/pr-track.js} +11 -13
  242. package/engine/{shared-branch-pr-reconcile.js → providers/shared-branch-pr-reconcile.js} +4 -4
  243. package/engine/qa/auto-prd-qa.js +313 -0
  244. package/engine/{qa-from-prd.js → qa/from-prd.js} +42 -12
  245. package/engine/qa/prd-session.js +240 -0
  246. package/engine/{qa-process-validation.js → qa/process-validation.js} +14 -9
  247. package/engine/{qa-runbooks.js → qa/runbooks.js} +1 -1
  248. package/engine/{qa-runs.js → qa/runs.js} +286 -15
  249. package/engine/{qa-sessions.js → qa/sessions.js} +595 -49
  250. package/engine/qa/visual-journey.js +654 -0
  251. package/engine/{qa-runners.js → qa-runners/index.js} +7 -7
  252. package/engine/qa-runners/maestro.js +3 -3
  253. package/engine/qa-runners/playwright.js +2 -2
  254. package/engine/{restart-health.js → recovery/restart-health.js} +48 -4
  255. package/engine/recovery/stop-stack.js +607 -0
  256. package/engine/{supervisor.js → recovery/supervisor.js} +105 -175
  257. package/engine/{watchdog.js → recovery/watchdog.js} +136 -13
  258. package/engine/runtimes/claude.js +14 -12
  259. package/engine/runtimes/codex.js +8 -6
  260. package/engine/runtimes/copilot.js +17 -16
  261. package/engine/{watch-actions.js → watches/actions.js} +13 -13
  262. package/engine/{watches.js → watches/index.js} +43 -32
  263. package/engine/{watches-store.js → watches/store.js} +4 -4
  264. package/engine/{create-pr-worktree.js → worktrees/create-pr.js} +1 -1
  265. package/engine/{worktree-gc.js → worktrees/gc.js} +70 -22
  266. package/engine/worktrees/inventory.js +671 -0
  267. package/engine/{live-checkout.js → worktrees/live-checkout.js} +4 -4
  268. package/engine/{worktree-pool.js → worktrees/pool.js} +2 -2
  269. package/engine/{worktree-preflight.js → worktrees/preflight.js} +1 -0
  270. package/engine/worktrees/quarantine-refs.js +173 -0
  271. package/engine.js +1137 -208
  272. package/minions.js +147 -77
  273. package/package.json +10 -6
  274. package/playbooks/_pr-description-audit.md +110 -78
  275. package/playbooks/build-fix-complex.md +2 -0
  276. package/playbooks/fix.md +16 -12
  277. package/playbooks/implement-shared.md +2 -0
  278. package/playbooks/implement.md +19 -20
  279. package/playbooks/plan-to-prd.md +18 -3
  280. package/playbooks/qa-session-draft.md +136 -1
  281. package/playbooks/qa-session-execute.md +80 -2
  282. package/playbooks/qa-session-setup.md +17 -1
  283. package/playbooks/qa-validate.md +1 -1
  284. package/playbooks/setup.md +2 -0
  285. package/playbooks/shared-rules.md +25 -32
  286. package/playbooks/templates/followup-dispatch.md +4 -3
  287. package/playbooks/verify.md +1 -1
  288. package/prompts/cc-system.md +19 -27
  289. package/watch-plugins/README.md +92 -0
  290. package/watch-plugins/ado-author-prs.js +336 -0
  291. package/watch-plugins/gh-author-prs.js +375 -0
  292. package/watch-plugins/http.js +474 -0
  293. package/watch-plugins/teams-channel.js +869 -0
  294. package/docs/dev-composite-workflow.md +0 -101
  295. package/docs/pr-screenshots/pr-886/after-single-header.png +0 -0
  296. package/docs/pr-screenshots/pr-886/before-duplicate-header.png +0 -0
  297. package/docs/pr-screenshots/pr-895/01-cancellation-reason-detail.png +0 -0
  298. package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
  299. package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
  300. package/docs/pr-screenshots/pr-901/projects-tab-default.png +0 -0
  301. package/docs/pr-screenshots/pr-901/projects-tab-fmf-selected.png +0 -0
  302. package/docs/pr-screenshots/pr-916/model-picker-AFTER-crop.png +0 -0
  303. package/docs/pr-screenshots/pr-916/model-picker-AFTER.png +0 -0
  304. package/docs/pr-screenshots/pr-916/model-picker-BEFORE-crop.png +0 -0
  305. package/docs/pr-screenshots/pr-916/model-picker-BEFORE.png +0 -0
  306. package/docs/pr-screenshots/pr-916/model-picker-dropdown-AFTER.png +0 -0
  307. package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
  308. package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
  309. package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
  310. package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
  311. package/docs/visual-evidence-ci.md +0 -103
  312. package/engine/bridge.js +0 -379
  313. package/engine/quarantine-refs.js +0 -103
  314. package/engine/state-operations.js +0 -178
  315. /package/engine/{steering-constraints.js → agents/steering-constraints.js} +0 -0
@@ -1,7 +1,7 @@
1
- // engine/watchdog.js — out-of-process recovery scheduled by the OS.
1
+ // engine/recovery/watchdog.js — out-of-process recovery scheduled by the OS.
2
2
  //
3
3
  // PROBLEM (cluster-kill):
4
- // The in-process supervisor (engine/supervisor.js) respawns engine/dashboard
4
+ // The in-process supervisor (engine/recovery/supervisor.js) respawns engine/dashboard
5
5
  // when one of them dies in isolation, but it can't respawn ITSELF. When
6
6
  // something kills all three at once — Windows job-object teardown when a
7
7
  // parent terminal closes (RDP disconnect, Windows Terminal auto-update,
@@ -41,6 +41,7 @@ const WIN_TASK_NAME = 'MinionsWatchdog';
41
41
  const MAC_PLIST_LABEL = 'io.minions.watchdog';
42
42
  const LINUX_UNIT_NAME = 'minions-watchdog';
43
43
  const TICK_SPAWN_TIMEOUT_MS = 120000;
44
+ const WIN_TASK_EXECUTION_LIMIT_MIN = 2;
44
45
  const WATCHDOG_LOG_NAME = 'watchdog-stdio.log';
45
46
  // Cap the watchdog log so a misbehaving scheduler can't fill the disk
46
47
  // over months. Truncates to half-size on overflow (oldest lines dropped).
@@ -53,9 +54,13 @@ const WATCHDOG_LOG_MAX_BYTES = 2 * 1024 * 1024;
53
54
  // "dead", and fire a SECOND `minions start` before the first finished binding
54
55
  // — re-introducing the documented duplicate-engine/dashboard storm class. The
55
56
  // in-process supervisor avoids this with an in-memory POST_SPAWN_GRACE_MS
56
- // (engine/supervisor.js:76); the watchdog can't keep state across ticks, so it
57
+ // (engine/recovery/supervisor.js:76); the watchdog can't keep state across ticks, so it
57
58
  // persists a tiny timestamp beacon to disk and stands down inside the window.
58
59
  const WATCHDOG_SPAWN_BEACON_NAME = 'watchdog-spawn.json';
60
+ const WATCHDOG_PARTIAL_BEACON_NAME = 'watchdog-partial.json';
61
+ const PARTIAL_RECOVERY_GRACE_MS = 90 * 1000;
62
+ const FRESH_ENGINE_HEARTBEAT_MS = Number(process.env.MINIONS_WATCHDOG_FRESH_HEARTBEAT_MS) || 60 * 1000;
63
+ const STALE_ENGINE_HEARTBEAT_MS = Number(process.env.MINIONS_WATCHDOG_STALE_HEARTBEAT_MS) || 180 * 1000;
59
64
  // Floor for the grace window. The effective window is max(2 × interval, floor)
60
65
  // so a fast cadence (e.g. --interval=1) still leaves a slow cold-start time to
61
66
  // finish, while a long cadence scales the window up with the interval.
@@ -65,6 +70,10 @@ function watchdogSpawnBeaconPath(minionsHome) {
65
70
  return path.join(minionsHome, 'engine', WATCHDOG_SPAWN_BEACON_NAME);
66
71
  }
67
72
 
73
+ function watchdogPartialBeaconPath(minionsHome) {
74
+ return path.join(minionsHome, 'engine', WATCHDOG_PARTIAL_BEACON_NAME);
75
+ }
76
+
68
77
  // Effective grace window: max(2 × interval, 90s). intervalMin defaults to the
69
78
  // scheduler's default cadence when the caller doesn't thread one through.
70
79
  function resolvePostSpawnGraceMs(intervalMin) {
@@ -94,6 +103,28 @@ function writeSpawnBeacon(minionsHome, nowMs, action) {
94
103
  } catch { /* fail-open — recovery still proceeds */ }
95
104
  }
96
105
 
106
+ function readPartialBeacon(minionsHome) {
107
+ try {
108
+ const obj = JSON.parse(fs.readFileSync(watchdogPartialBeaconPath(minionsHome), 'utf8'));
109
+ const ts = Number(obj?.ts);
110
+ const state = typeof obj?.state === 'string' ? obj.state : '';
111
+ return Number.isFinite(ts) && state ? { ts, state } : null;
112
+ } catch { return null; }
113
+ }
114
+
115
+ function writePartialBeacon(minionsHome, nowMs, state) {
116
+ try {
117
+ const dir = path.join(minionsHome, 'engine');
118
+ fs.mkdirSync(dir, { recursive: true });
119
+ fs.writeFileSync(watchdogPartialBeaconPath(minionsHome), JSON.stringify({ ts: nowMs, state }));
120
+ return true;
121
+ } catch { return false; }
122
+ }
123
+
124
+ function clearPartialBeacon(minionsHome) {
125
+ try { fs.unlinkSync(watchdogPartialBeaconPath(minionsHome)); } catch { /* absent/unremovable: best effort */ }
126
+ }
127
+
97
128
  function logLine(minionsHome, msg) {
98
129
  try {
99
130
  const dir = path.join(minionsHome, 'engine');
@@ -118,6 +149,16 @@ function isPidAlive(pid) {
118
149
  try { process.kill(pid, 0); return true; } catch { return false; }
119
150
  }
120
151
 
152
+ function readEngineHeartbeat(minionsHome) {
153
+ try {
154
+ const control = JSON.parse(fs.readFileSync(path.join(minionsHome, 'engine', 'control.json'), 'utf8'));
155
+ const heartbeat = Number(control?.heartbeat);
156
+ return Number.isFinite(heartbeat) && heartbeat > 0 ? heartbeat : null;
157
+ } catch {
158
+ return null;
159
+ }
160
+ }
161
+
121
162
  /**
122
163
  * Probe engine + dashboard and recover if needed. Always resolves (never
123
164
  * throws). Returns { healthy, action, exitCode? } describing what was done.
@@ -127,9 +168,8 @@ function isPidAlive(pid) {
127
168
  * respawning would undo `minions stop`)
128
169
  * - both alive → no-op
129
170
  * - both dead → `minions start` (idempotent fast path)
130
- * - one alive one dead → `minions restart` (only restart can
131
- * reconcile partial state — start refuses
132
- * by design)
171
+ * - engine dead, dashboard up → start only engine after recovery grace
172
+ * - engine up, dashboard dead → start only dashboard after recovery grace
133
173
  */
134
174
  async function tick(opts) {
135
175
  opts = opts || {};
@@ -149,6 +189,7 @@ async function tick(opts) {
149
189
  // loop is briefly blocked, so it stays accurate exactly when netstat fails.
150
190
  const confirmPortUp = opts.confirmPortUp;
151
191
  const isStopIntentSet = opts.isStopIntentSet;
192
+ const readHeartbeat = opts.readEngineHeartbeat || readEngineHeartbeat;
152
193
  const spawner = opts.spawner || spawn;
153
194
  const now = opts.now || (() => new Date());
154
195
 
@@ -173,13 +214,34 @@ async function tick(opts) {
173
214
  try { stopWanted = !!isStopIntentSet(); } catch { stopWanted = false; }
174
215
  }
175
216
  if (stopWanted) {
217
+ clearPartialBeacon(minionsHome);
176
218
  logLine(minionsHome, `stop-intent set → standing down (no probe, no recovery)`);
177
219
  return { healthy: false, action: 'stand-down' };
178
220
  }
179
221
 
180
222
  let enginePid = null;
181
223
  try { enginePid = readEnginePid(minionsHome); } catch { enginePid = null; }
182
- const engineAlive = isPidAlive(enginePid);
224
+ let engineAlive = isPidAlive(enginePid);
225
+ if (enginePid) {
226
+ let heartbeat = null;
227
+ try { heartbeat = readHeartbeat(minionsHome); } catch { heartbeat = null; }
228
+ const heartbeatAgeMs = heartbeat ? now().getTime() - heartbeat : null;
229
+ if (!engineAlive && heartbeatAgeMs !== null && heartbeatAgeMs < FRESH_ENGINE_HEARTBEAT_MS) {
230
+ logLine(
231
+ minionsHome,
232
+ `engine=${enginePid} reported dead but heartbeat is ${Math.round(heartbeatAgeMs / 1000)}s old ` +
233
+ '— treating as alive (false-negative suppressed)',
234
+ );
235
+ engineAlive = true;
236
+ } else if (engineAlive && heartbeatAgeMs !== null && heartbeatAgeMs >= STALE_ENGINE_HEARTBEAT_MS) {
237
+ logLine(
238
+ minionsHome,
239
+ `engine=${enginePid} PID exists but heartbeat is ${Math.round(heartbeatAgeMs / 1000)}s old ` +
240
+ '— treating as dead (stale identity)',
241
+ );
242
+ engineAlive = false;
243
+ }
244
+ }
183
245
  let dashUp = false;
184
246
  try { dashUp = !!isPortListening(dashPort); } catch { dashUp = false; }
185
247
 
@@ -197,12 +259,38 @@ async function tick(opts) {
197
259
  }
198
260
 
199
261
  if (engineAlive && dashUp) {
262
+ clearPartialBeacon(minionsHome);
200
263
  logLine(minionsHome, `ok engine=${enginePid} port=${dashPort} ts=${now().toISOString()}`);
201
264
  return { healthy: true, action: 'none' };
202
265
  }
203
266
 
204
267
  const partial = engineAlive || dashUp;
205
- const action = partial ? 'restart' : 'start';
268
+ const action = !partial ? 'start' : (engineAlive ? 'dashboard' : 'engine');
269
+ const nowMs = now().getTime();
270
+
271
+ // The dashboard watchdog and supervisor both repair partial state. Give
272
+ // those in-process mechanisms time to converge before the external watchdog
273
+ // launches a destructive full restart. The first observation is persisted
274
+ // because every OS-scheduled tick is a fresh process.
275
+ if (partial) {
276
+ const partialState = `${engineAlive ? 'engine-up' : 'engine-down'}:${dashUp ? 'dashboard-up' : 'dashboard-down'}`;
277
+ const observed = readPartialBeacon(minionsHome);
278
+ if (!observed || observed.state !== partialState) {
279
+ if (writePartialBeacon(minionsHome, nowMs, partialState)) {
280
+ logLine(minionsHome, `partial state ${partialState} first observed → standing down for ${PARTIAL_RECOVERY_GRACE_MS}ms in-process recovery grace`);
281
+ return { healthy: false, action: 'skip', reason: 'partial-recovery-grace' };
282
+ }
283
+ } else {
284
+ const ageMs = nowMs - observed.ts;
285
+ if (ageMs >= 0 && ageMs < PARTIAL_RECOVERY_GRACE_MS) {
286
+ logLine(minionsHome, `partial state ${partialState} persists ${ageMs}ms (< ${PARTIAL_RECOVERY_GRACE_MS}ms) → standing down for in-process recovery`);
287
+ return { healthy: false, action: 'skip', reason: 'partial-recovery-grace' };
288
+ }
289
+ }
290
+ clearPartialBeacon(minionsHome);
291
+ } else {
292
+ clearPartialBeacon(minionsHome);
293
+ }
206
294
 
207
295
  // Post-spawn grace: if a previous tick already issued a spawn within the
208
296
  // grace window, stand down instead of double-firing. Each tick is a fresh
@@ -210,7 +298,6 @@ async function tick(opts) {
210
298
  // the slow-cold-start race where control.json's late PID write lets the next
211
299
  // scheduled tick spawn a second daemon stack.
212
300
  const graceMs = resolvePostSpawnGraceMs(opts.intervalMin);
213
- const nowMs = now().getTime();
214
301
  const lastSpawnTs = readSpawnBeaconTs(minionsHome);
215
302
  if (lastSpawnTs != null && nowMs - lastSpawnTs < graceMs) {
216
303
  logLine(
@@ -224,7 +311,7 @@ async function tick(opts) {
224
311
  logLine(
225
312
  minionsHome,
226
313
  `unhealthy engine=${enginePid || '-'}(${engineAlive ? 'alive' : 'dead'}) ` +
227
- `port=${dashPort}(${dashUp ? 'up' : 'down'}) → invoking minions ${action}`
314
+ `port=${dashPort}(${dashUp ? 'up' : 'down'}) → invoking ${action === 'start' ? 'minions start' : `targeted ${action} recovery`}`
228
315
  );
229
316
 
230
317
  try {
@@ -236,7 +323,15 @@ async function tick(opts) {
236
323
  // Detach the child so its lifetime is independent of this tick process.
237
324
  // The OS scheduler closes our stdio shortly after exit; we don't want
238
325
  // that closure to cascade into the new daemon stack.
239
- const child = spawner(process.execPath, [minionsBin, action], {
326
+ let spawnArgs;
327
+ if (action === 'engine') {
328
+ spawnArgs = [path.join(minionsHome, 'engine.js'), 'start'];
329
+ } else if (action === 'dashboard') {
330
+ spawnArgs = [path.join(minionsHome, 'dashboard.js'), String(dashPort)];
331
+ } else {
332
+ spawnArgs = [minionsBin, 'start'];
333
+ }
334
+ const child = spawner(process.execPath, spawnArgs, {
240
335
  detached: true,
241
336
  stdio: 'ignore',
242
337
  windowsHide: true,
@@ -245,10 +340,14 @@ async function tick(opts) {
245
340
  // user action), so the post-restart hook MUST NOT pop a browser tab.
246
341
  // The CLI's spawnFullStackAndVerify reads this env var as a hard
247
342
  // kill-switch via MINIONS_NO_AUTO_OPEN=1.
248
- env: { ...process.env, MINIONS_NO_AUTO_OPEN: '1' },
343
+ env: {
344
+ ...process.env,
345
+ MINIONS_NO_AUTO_OPEN: '1',
346
+ ...(action === 'dashboard' ? { MINIONS_REQUIRE_DASHBOARD_PORT: '1' } : {}),
347
+ },
249
348
  });
250
349
  if (child && typeof child.unref === 'function') child.unref();
251
- logLine(minionsHome, `spawned minions ${action} pid=${child && child.pid} (detached)`);
350
+ logLine(minionsHome, `spawned ${action === 'start' ? 'minions start' : `targeted ${action}`} pid=${child && child.pid} (detached)`);
252
351
  // Crash-loop counter (W-mr2c46590003e3ee) — best-effort, injected so this
253
352
  // module stays dependency-free of shared.js (matches isStopIntentSet /
254
353
  // confirmPortUp above). Caller (bin/minions.js) wires opts.recordRespawn
@@ -324,6 +423,7 @@ REM Minions watchdog launcher — auto-generated by 'minions watchdog install'.\
324
423
  REM Re-run 'minions watchdog install' to regenerate after moving MINIONS_HOME.\r
325
424
  set "MINIONS_HOME=${opts.minionsHome}"\r
326
425
  "${opts.nodeBin}" "${opts.minionsBin}" watchdog tick\r
426
+ exit /b %ERRORLEVEL%\r
327
427
  `;
328
428
  }
329
429
 
@@ -350,6 +450,24 @@ function installWindows(opts) {
350
450
  if (r.status !== 0) {
351
451
  throw new Error(`schtasks /Create failed (exit ${r.status}): ${(r.stderr || r.stdout || '').trim()}`);
352
452
  }
453
+ // schtasks defaults one-shot tasks to IgnoreNew + a 72-hour execution
454
+ // limit. A stranded cmd.exe then blocks every later watchdog tick. Allow a
455
+ // later tick to run in parallel and bound every instance to two minutes.
456
+ const settingsScript = [
457
+ '$settings = New-ScheduledTaskSettingsSet -MultipleInstances Parallel ' +
458
+ `-ExecutionTimeLimit (New-TimeSpan -Minutes ${WIN_TASK_EXECUTION_LIMIT_MIN}) ` +
459
+ '-StartWhenAvailable',
460
+ `Set-ScheduledTask -TaskName '${WIN_TASK_NAME}' -Settings $settings | Out-Null`,
461
+ ].join('; ');
462
+ const settingsResult = sp('powershell.exe', [
463
+ '-NoProfile', '-NonInteractive', '-Command', settingsScript,
464
+ ], { encoding: 'utf8', windowsHide: true });
465
+ if (settingsResult.status !== 0) {
466
+ throw new Error(
467
+ `Set-ScheduledTask watchdog settings failed (exit ${settingsResult.status}): ` +
468
+ `${(settingsResult.stderr || settingsResult.stdout || '').trim()}`
469
+ );
470
+ }
353
471
  return { ok: true, scheduler: 'Task Scheduler', taskName: WIN_TASK_NAME, intervalMin: opts.intervalMin, launcherPath };
354
472
  }
355
473
 
@@ -626,8 +744,13 @@ module.exports = {
626
744
  LINUX_UNIT_NAME,
627
745
  WATCHDOG_LOG_NAME,
628
746
  WATCHDOG_SPAWN_BEACON_NAME,
747
+ WATCHDOG_PARTIAL_BEACON_NAME,
748
+ PARTIAL_RECOVERY_GRACE_MS,
749
+ FRESH_ENGINE_HEARTBEAT_MS,
750
+ STALE_ENGINE_HEARTBEAT_MS,
629
751
  POST_SPAWN_GRACE_FLOOR_MS,
630
752
  watchdogSpawnBeaconPath,
753
+ watchdogPartialBeaconPath,
631
754
  resolvePostSpawnGraceMs,
632
755
  buildMacPlist,
633
756
  buildLinuxService,
@@ -14,8 +14,8 @@
14
14
  const fs = require('fs');
15
15
  const os = require('os');
16
16
  const path = require('path');
17
- const { FAILURE_CLASS, safeWrite, ts, resolveEngineCacheDir, staleResumeSessionCwdReason } = require('../shared');
18
- const { wrapUntrusted, buildSource } = require('../untrusted-fence');
17
+ const { FAILURE_CLASS, safeWrite, ts, resolveEngineCacheDir, staleResumeSessionCwdReason } = require('../core/shared');
18
+ const { wrapUntrusted, buildSource } = require('../core/untrusted-fence');
19
19
 
20
20
  const ENGINE_DIR = __dirname.replace(/[\\/]runtimes$/, '');
21
21
  const MINIONS_DIR = path.resolve(ENGINE_DIR, '..');
@@ -26,7 +26,7 @@ const RUNTIME_NAME = 'claude';
26
26
  const SPAWN_STARTUP_SUBTYPES = new Set(['init', 'hook_started', 'hook_response']);
27
27
 
28
28
  // ── Binary Resolution ────────────────────────────────────────────────────────
29
- // Mirrors engine/spawn-agent.js:26-91. Cached at engine/claude-caps.json so the
29
+ // Mirrors engine/agents/spawn-agent.js:26-91. Cached at engine/claude-caps.json so the
30
30
  // repeated path-probe (PATH / npm-global / npm-root-g) only happens once per
31
31
  // install.
32
32
 
@@ -184,7 +184,7 @@ const MODELS_CACHE = path.join(_CACHE_DIR, 'claude-models.json');
184
184
  *
185
185
  * Mirrors the union of:
186
186
  * - engine.js:817-844 (agent dispatch)
187
- * - engine/llm.js:68-76 (CC / doc-chat direct spawn)
187
+ * - engine/agents/llm.js:68-76 (CC / doc-chat direct spawn)
188
188
  * - spawn-agent.js:118-128 (--system-prompt-file injection on first turn)
189
189
  *
190
190
  * Per the plan: emits `--dangerously-skip-permissions` (the modern Claude flag)
@@ -203,6 +203,7 @@ function buildArgs(opts = {}) {
203
203
  model,
204
204
  maxTurns,
205
205
  allowedTools,
206
+ disableTools,
206
207
  effort,
207
208
  sessionId,
208
209
  sysPromptFile,
@@ -231,6 +232,7 @@ function buildArgs(opts = {}) {
231
232
  }
232
233
  }
233
234
  if (allowedTools) args.push('--allowedTools', allowedTools);
235
+ if (disableTools === true) args.push('--tools', '');
234
236
  if (effort) args.push('--effort', String(effort));
235
237
  args.push('--dangerously-skip-permissions');
236
238
  if (maxBudget != null) args.push('--max-budget-usd', String(maxBudget));
@@ -535,7 +537,7 @@ function parseOutput(raw, { maxTextLength = 0 } = {}) {
535
537
  * Parse a single line from the stream-json stdout. Returns the parsed event
536
538
  * object, or null when the line is empty / non-JSON.
537
539
  *
538
- * Used by the streaming accumulator in engine/llm.js to react to assistant
540
+ * Used by the streaming accumulator in engine/agents/llm.js to react to assistant
539
541
  * text blocks, tool-use blocks, and the terminal `result` event without
540
542
  * waiting for the whole process to exit.
541
543
  */
@@ -672,7 +674,7 @@ function parseError(rawOutput) {
672
674
 
673
675
  // ── Stream Consumer ─────────────────────────────────────────────────────────
674
676
  //
675
- // Per-stream consumer factory invoked by engine/llm.js's accumulator. The
677
+ // Per-stream consumer factory invoked by engine/agents/llm.js's accumulator. The
676
678
  // accumulator owns global stream state (stdout/stderr/text dedup/tool dedup)
677
679
  // and exposes the `ctx` API below; the consumer owns Claude-specific per-stream
678
680
  // state (joined-text accumulator, content-block Map for tool/thinking
@@ -738,7 +740,7 @@ function createStreamConsumer(ctx) {
738
740
  // reset the hot-path accumulator so the NEXT text block starts fresh
739
741
  // instead of welding onto text from before this block. `ctx.pushText`
740
742
  // already opens a new segment when the pushed value doesn't extend
741
- // `lastTextSent` (see engine/llm.js), so this reset is sufficient to
743
+ // `lastTextSent` (see engine/agents/llm.js), so this reset is sufficient to
742
744
  // both stop the weld and trigger the segment boundary.
743
745
  if (block.type !== 'text') claudeJoinedText = '';
744
746
  // Out-of-order block landing: rebuild from the Map. Common case is
@@ -831,7 +833,7 @@ function createStreamConsumer(ctx) {
831
833
  // each block is {type:'tool_result', tool_use_id, is_error, content}.
832
834
  // Flip the previously-pushed chip to completed (success) or failed
833
835
  // (is_error). This is the Claude-direct analogue of Copilot's ACP
834
- // tool_call_update; engine/llm.js stays runtime-agnostic — the chip
836
+ // tool_call_update; engine/agents/llm.js stays runtime-agnostic — the chip
835
837
  // turns green/red only because this adapter calls ctx.updateToolUse.
836
838
  for (const block of obj.message.content) {
837
839
  if (block?.type === 'tool_result' && block.tool_use_id) {
@@ -884,7 +886,7 @@ const capabilities = {
884
886
  bareMode: true,
885
887
  // The Claude Code CLI reads repo-authored CLAUDE.md files itself at startup
886
888
  // (unless `--bare`). TRUE here tells the engine's CLAUDE.md-propagation layer
887
- // (engine/claude-md-context.js, injected in engine/playbook.js) to SKIP this
889
+ // (engine/agents/claude-md-context.js, injected in engine/agents/playbook.js) to SKIP this
888
890
  // runtime — re-injecting CLAUDE.md content into the prompt would double it.
889
891
  // Copilot/Codex set this false because they have no native CLAUDE.md auto-load.
890
892
  claudeMdNativeDiscovery: true,
@@ -908,7 +910,7 @@ const capabilities = {
908
910
  };
909
911
 
910
912
  // Install hint surfaced when `resolveBinary()` returns null. Consumed by
911
- // `engine/preflight.js` (per-runtime binary check) and `engine/spawn-agent.js`
913
+ // `engine/agents/preflight.js` (per-runtime binary check) and `engine/agents/spawn-agent.js`
912
914
  // (fatal error message). Multi-line so all platforms see actionable guidance.
913
915
  const INSTALL_HINT = 'install from https://claude.ai/download or: npm install -g @anthropic-ai/claude-code';
914
916
 
@@ -949,7 +951,7 @@ function getSkillWriteTargets({ homeDir = os.homedir(), project = null } = {}) {
949
951
  // Slash-command roots. Mirrors getSkillRoots' shape so consumers can iterate
950
952
  // runtimes generically. Plugin-installed command dirs are NOT enumerated here
951
953
  // — those are discovered dynamically from `~/.claude/plugins/installed_plugins.json`
952
- // in engine/queries.js#collectCommandFiles and stay there because they require
954
+ // in engine/core/queries.js#collectCommandFiles and stay there because they require
953
955
  // a registry read, not a fixed dir.
954
956
  function getCommandRoots({ homeDir = os.homedir(), project = null } = {}) {
955
957
  const roots = [
@@ -1076,7 +1078,7 @@ module.exports = {
1076
1078
  capsFile: CAPS_FILE,
1077
1079
  listModels,
1078
1080
  modelsCache: MODELS_CACHE,
1079
- spawnScript: path.join(ENGINE_DIR, 'spawn-agent.js'),
1081
+ spawnScript: path.join(ENGINE_DIR, 'agents', 'spawn-agent.js'),
1080
1082
  installHint: INSTALL_HINT,
1081
1083
  resolveInvocationOptions,
1082
1084
  buildSpawnFlags,
@@ -18,7 +18,7 @@ const {
18
18
  resolveEngineCacheDir,
19
19
  staleResumeSessionCwdReason,
20
20
  resolveCodexCcSandbox,
21
- } = require('../shared');
21
+ } = require('../core/shared');
22
22
 
23
23
  const ENGINE_DIR = __dirname.replace(/[\\/]runtimes$/, '');
24
24
  const MINIONS_DIR = path.resolve(ENGINE_DIR, '..');
@@ -345,6 +345,7 @@ function buildArgs(opts = {}) {
345
345
  sessionId,
346
346
  outputLastMessage,
347
347
  sandbox = 'workspace-write',
348
+ disableTools,
348
349
  skipGitRepoCheck = true,
349
350
  configOverrides,
350
351
  images,
@@ -356,7 +357,8 @@ function buildArgs(opts = {}) {
356
357
  // parser rejects flags such as --color and --sandbox after the subcommand.
357
358
  args.push('--json', '--color', 'never');
358
359
  if (model) args.push('--model', String(model));
359
- if (sandbox) args.push('--sandbox', String(sandbox));
360
+ const effectiveSandbox = disableTools === true ? 'read-only' : sandbox;
361
+ if (effectiveSandbox) args.push('--sandbox', String(effectiveSandbox));
360
362
  if (skipGitRepoCheck === true) args.push('--skip-git-repo-check');
361
363
  if (outputLastMessage) args.push('--output-last-message', String(outputLastMessage));
362
364
 
@@ -370,7 +372,7 @@ function buildArgs(opts = {}) {
370
372
  if (sessionId) args.push('resume', String(sessionId));
371
373
 
372
374
  // Codex accepts repeatable --image paths on fresh and resumed turns. The
373
- // call-scoped tmpDir is removed by engine/llm.js after the process exits.
375
+ // call-scoped tmpDir is removed by engine/agents/llm.js after the process exits.
374
376
  if (Array.isArray(images) && images.length && tmpDir) {
375
377
  for (let i = 0; i < images.length; i++) {
376
378
  const img = images[i];
@@ -469,7 +471,7 @@ function getResumeSessionId({ agentId, branchName, agentsDir, cwd = null, maxAge
469
471
  try { fs.unlinkSync(sessionPath); } catch {}
470
472
  return null;
471
473
  }
472
- // Stale-workdir invalidation (W-ms0vk9nv000z985b) — see engine/shared.js
474
+ // Stale-workdir invalidation (W-ms0vk9nv000z985b) — see engine/core/shared.js
473
475
  // staleResumeSessionCwdReason. A session anchored to a worktree the engine
474
476
  // has since removed/recycled cannot be resumed.
475
477
  const staleCwdReason = staleResumeSessionCwdReason(sessionFile.cwd, cwd);
@@ -1001,7 +1003,7 @@ const capabilities = {
1001
1003
  bareMode: false,
1002
1004
  fallbackModel: false,
1003
1005
  // Codex has no native CLAUDE.md auto-load — FALSE opts it into the engine's
1004
- // CLAUDE.md-propagation layer (engine/claude-md-context.js) so repo-authored
1006
+ // CLAUDE.md-propagation layer (engine/agents/claude-md-context.js) so repo-authored
1005
1007
  // CLAUDE.md guidance reaches the agent prompt.
1006
1008
  claudeMdNativeDiscovery: false,
1007
1009
  sessionPersistenceControl: false,
@@ -1022,7 +1024,7 @@ module.exports = {
1022
1024
  listModels,
1023
1025
  getModelCacheKey,
1024
1026
  modelsCache: MODELS_CACHE,
1025
- spawnScript: path.join(ENGINE_DIR, 'spawn-agent.js'),
1027
+ spawnScript: path.join(ENGINE_DIR, 'agents', 'spawn-agent.js'),
1026
1028
  installHint: INSTALL_HINT,
1027
1029
  buildSpawnFlags,
1028
1030
  resolveInvocationOptions,
@@ -32,7 +32,7 @@ const https = require('https');
32
32
  const os = require('os');
33
33
  const path = require('path');
34
34
  const { execSync } = require('child_process');
35
- const { ENGINE_DEFAULTS, FAILURE_CLASS, safeWrite, ts, resolveEngineCacheDir, staleResumeSessionCwdReason } = require('../shared');
35
+ const { ENGINE_DEFAULTS, FAILURE_CLASS, safeWrite, ts, resolveEngineCacheDir, staleResumeSessionCwdReason } = require('../core/shared');
36
36
 
37
37
  const ENGINE_DIR = __dirname.replace(/[\\/]runtimes$/, '');
38
38
  const _CACHE_DIR = resolveEngineCacheDir(ENGINE_DIR);
@@ -52,7 +52,7 @@ const SPAWN_STARTUP_TYPES = new Set([
52
52
  // 1. Standalone `copilot` (preferred) — WinGet, scoop, or manual install. PATH
53
53
  // probe finds it; we cache the resolved path with `leadingArgs: []`.
54
54
  // 2. `gh copilot` extension fallback — invoked as `gh copilot ...`. We return
55
- // `leadingArgs: ['copilot']` so engine/spawn-agent.js prepends "copilot"
55
+ // `leadingArgs: ['copilot']` so engine/agents/spawn-agent.js prepends "copilot"
56
56
  // to the gh binary invocation. NOTE: the older gh-copilot extension is
57
57
  // the explain/suggest UX, NOT the v1.0.36 agent CLI; flag support varies.
58
58
  // We surface it as best-effort and let preflight warn.
@@ -93,7 +93,7 @@ function _execSyncCapture(cmd, env, timeoutMs = 10000) {
93
93
  * C:\Users\<u>\AppData\Roaming\npm\copilot.cmd (Windows cmd shim)
94
94
  * C:\Users\<u>\AppData\Roaming\npm\copilot.ps1 (PowerShell shim)
95
95
  * The legacy resolver took `.split('\n')[0]` (the extensionless shim) and
96
- * marked it `native: false`, which made engine/llm.js spawn `node.exe <shim>`.
96
+ * marked it `native: false`, which made engine/agents/llm.js spawn `node.exe <shim>`.
97
97
  * Node parsed the bash script as JS and exit-1'd in <1s, silently breaking
98
98
  * every Copilot dispatch.
99
99
  *
@@ -120,7 +120,7 @@ function _pickStandaloneCopilotFromOutput(rawOutput, { existsSync = fs.existsSyn
120
120
  if (!_isWin) {
121
121
  // POSIX `which` returns one line; first existing entry wins. Mark native
122
122
  // when there's no extension or an executable extension — but since
123
- // engine/llm.js only checks `.exe` for native on Windows and otherwise
123
+ // engine/agents/llm.js only checks `.exe` for native on Windows and otherwise
124
124
  // routes through process.execPath on non-native, we keep native:true on
125
125
  // POSIX (matches legacy behavior — spawn(<bin>) works directly).
126
126
  return { bin: existing[0], native: true };
@@ -398,7 +398,7 @@ function resolveModel(input) {
398
398
  }
399
399
 
400
400
  // Heuristic: does `model` look like a Copilot-namespace identifier? Mirrors
401
- // engine/runtimes/claude.js#modelLooksFamiliar so engine/cli.js#_modelLooksIncompatible
401
+ // engine/runtimes/claude.js#modelLooksFamiliar so engine/operations/cli.js#_modelLooksIncompatible
402
402
  // can warn when a fleet/CC switch strands an incompatible model. Accepts the
403
403
  // three Minions short aliases (`sonnet`/`opus`/`haiku`) since the adapter
404
404
  // translates them to valid Copilot IDs at argv-build time. Returning false
@@ -440,7 +440,7 @@ function _mapEffort(level) {
440
440
  // Copilot has no --verbose flag — never emit it. The `bare` / `maxBudget` /
441
441
  // `fallbackModel` opts are silently ignored (their capability flags are false
442
442
  // so engine code shouldn't pass them, but we tolerate them gracefully).
443
- // Do not emit --log-level: Copilot CLI 1.0.76-1 exits silently with code 1
443
+ // Do not emit --log-level: Copilot CLI 1.0.76-1 and 1.0.76-2 exit with code 1
444
444
  // when given an explicit non-default level. Omitting it preserves the CLI's
445
445
  // compatible default while --output-format json keeps stdout machine-readable.
446
446
 
@@ -466,6 +466,7 @@ function buildArgs(opts = {}) {
466
466
  disableBuiltinMcps,
467
467
  reasoningSummaries,
468
468
  disabledMcpServers,
469
+ disableTools,
469
470
  images,
470
471
  tmpDir,
471
472
  } = opts;
@@ -475,10 +476,9 @@ function buildArgs(opts = {}) {
475
476
  '-s',
476
477
  '--no-color',
477
478
  '--plain-diff',
478
- '--autopilot',
479
- '--allow-all',
480
479
  '--no-ask-user',
481
480
  ];
481
+ if (disableTools !== true) args.push('--autopilot', '--allow-all');
482
482
 
483
483
  if (Array.isArray(addDirs)) {
484
484
  for (const d of addDirs) {
@@ -491,6 +491,7 @@ function buildArgs(opts = {}) {
491
491
  }
492
492
 
493
493
  if (model) args.push('--model', String(model));
494
+ if (disableTools === true) args.push('--available-tools=');
494
495
 
495
496
  const mappedEffort = _mapEffort(effort);
496
497
  if (mappedEffort) args.push('--effort', mappedEffort);
@@ -785,7 +786,7 @@ const KNOWN_EVENT_TYPES = new Set([
785
786
  /**
786
787
  * Parse the full JSONL output of a Copilot CLI invocation.
787
788
  * Returns { text, usage, sessionId, model } — same shape as the Claude adapter
788
- * so engine/lifecycle.js can consume both transparently.
789
+ * so engine/orchestration/lifecycle.js can consume both transparently.
789
790
  *
790
791
  * - text: the TRAILING text segment of the turn — answer
791
792
  * `assistant.message.data.content` values plus trailing deltas,
@@ -796,7 +797,7 @@ const KNOWN_EVENT_TYPES = new Set([
796
797
  * tool text never welds onto the post-tool answer. This mirrors
797
798
  * the stream consumer's `_resetTrailingSegment` and Claude's
798
799
  * result-carries-last-message shape, so the terminal
799
- * reconciliation in engine/llm.js#finalize agrees with the live
800
+ * reconciliation in engine/agents/llm.js#finalize agrees with the live
800
801
  * stream instead of re-welding split blocks (W-mrw87290000b2965).
801
802
  * - usage: mapped from the terminal `result` event. Copilot doesn't
802
803
  * report cost/tokens — those fields are NULL, not 0, so the
@@ -859,7 +860,7 @@ function parseOutput(raw, { maxTextLength = 0 } = {}) {
859
860
  // assistant.message or as raw deltas — does not weld onto the post-tool
860
861
  // answer. Without this, `text = messageContents.join('') + pendingDeltaContent`
861
862
  // re-welds `<pre-tool><post-tool>` in the terminal reconciliation
862
- // (engine/llm.js#finalize -> runtime.parseOutput), and the CC SSE `done`
863
+ // (engine/agents/llm.js#finalize -> runtime.parseOutput), and the CC SSE `done`
863
864
  // payload re-introduces the weld the stream consumer already split apart
864
865
  // (W-mrw87290000b2965). Mirrors the stream consumer's _resetTrailingSegment
865
866
  // and Claude, whose result event carries only the last assistant message.
@@ -1088,7 +1089,7 @@ function _resolveCachedModelIds() {
1088
1089
  // In-memory cache (seeded by `_warmModelCache()` or test `_resetModelCache`)
1089
1090
  // wins over disk so unit tests stay hermetic. Production code that hasn't
1090
1091
  // warmed the in-memory cache still gets the catalog via the disk file that
1091
- // `engine/model-discovery.js#getRuntimeModels` populates during preflight.
1092
+ // `engine/agents/model-discovery.js#getRuntimeModels` populates during preflight.
1092
1093
  if (Array.isArray(_modelDiscoveryResults) && _modelDiscoveryResults.length > 0) {
1093
1094
  return _modelDiscoveryResults.map(m => m && m.id ? String(m.id) : null).filter(Boolean);
1094
1095
  }
@@ -1230,7 +1231,7 @@ async function listModels({ env = process.env, timeoutMs = 10000 } = {}) {
1230
1231
 
1231
1232
  // ── Stream Consumer ─────────────────────────────────────────────────────────
1232
1233
  //
1233
- // Per-stream consumer factory invoked by engine/llm.js's accumulator. Owns
1234
+ // Per-stream consumer factory invoked by engine/agents/llm.js's accumulator. Owns
1234
1235
  // Copilot-specific per-stream state (delta-content buffer, task_complete
1235
1236
  // signal). Translates Copilot event shapes into ctx callbacks.
1236
1237
  //
@@ -1451,7 +1452,7 @@ const capabilities = {
1451
1452
  // Copilot has NO native CLAUDE.md auto-load — it only reads its own AGENTS.md
1452
1453
  // (native custom-instruction discovery owned by the CLI itself). FALSE
1453
1454
  // here opts this runtime INTO the engine's CLAUDE.md-propagation layer
1454
- // (engine/claude-md-context.js) so repo-authored CLAUDE.md guidance is
1455
+ // (engine/agents/claude-md-context.js) so repo-authored CLAUDE.md guidance is
1455
1456
  // surfaced in the prompt. This is orthogonal to AGENTS.md discovery —
1456
1457
  // CLAUDE.md carries no split-brain risk because Copilot never reads it itself.
1457
1458
  claudeMdNativeDiscovery: false,
@@ -1469,7 +1470,7 @@ const capabilities = {
1469
1470
  imageInput: true,
1470
1471
  // P-9b3d5f61: `copilot --acp` speaks the Agent Client Protocol, so per-agent
1471
1472
  // dispatches can route through a persistent ACP worker pool
1472
- // (engine/agent-worker-pool.js) instead of cold-spawning per dispatch.
1473
+ // (engine/agents/agent-worker-pool.js) instead of cold-spawning per dispatch.
1473
1474
  // Claude/Codex do not implement ACP — this flag is absent (falsy) there.
1474
1475
  acpWorkerPool: true,
1475
1476
  };
@@ -1553,7 +1554,7 @@ module.exports = {
1553
1554
  listModels,
1554
1555
  modelsCache: MODELS_CACHE,
1555
1556
  // Use the same wrapper as Claude — spawn-agent.js is runtime-agnostic per P-9c4f2d6a
1556
- spawnScript: path.join(ENGINE_DIR, 'spawn-agent.js'),
1557
+ spawnScript: path.join(ENGINE_DIR, 'agents', 'spawn-agent.js'),
1557
1558
  installHint: INSTALL_HINT,
1558
1559
  workerErrorHint: 'copilot --acp failed -- ensure copilot CLI >=1.0.46 and copilot login is complete',
1559
1560
  resolveInvocationOptions,