claude-code-session-manager 0.87.0 → 0.88.1

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 (291) hide show
  1. package/README.md +26 -53
  2. package/dist/assets/{AgentLibrary-DyLWzZDf.js → AgentLibrary-kQF8_wAw.js} +1 -1
  3. package/dist/assets/{DataModel--mISIJ6h.js → DataModel-CY_mMnsr.js} +1 -1
  4. package/dist/assets/{History-C2ahUXTg.js → History-Sji2brb0.js} +1 -1
  5. package/dist/assets/{Hooks-BiC6oyR2.js → Hooks-CtqpO-p5.js} +1 -1
  6. package/dist/assets/{HostBilko-BPleEOld.js → HostBilko-DN0ydOCr.js} +1 -1
  7. package/dist/assets/{Library-Dc8Qst1R.js → Library-CoiLZTKh.js} +1 -1
  8. package/dist/assets/{ListDetail-DIXh-OLX.js → ListDetail-CmqiQ_io.js} +1 -1
  9. package/dist/assets/{MarkdownEditor-C90bkLXK.js → MarkdownEditor-HPoNeKhT.js} +1 -1
  10. package/dist/assets/{McpServers-DqcbLOLZ.js → McpServers-Bj1X3_NT.js} +1 -1
  11. package/dist/assets/{Memory-CW62MXlh.js → Memory-1PKjsM3b.js} +1 -1
  12. package/dist/assets/{Panel-Bw1FhRuF.js → Panel-I1s6ghPo.js} +1 -1
  13. package/dist/assets/{Permissions-BcUC-5y8.js → Permissions-BhiiZjHQ.js} +1 -1
  14. package/dist/assets/{Plugins-BnKx9flD.js → Plugins-gv3_fkPX.js} +2 -2
  15. package/dist/assets/{ProvenanceBadge-Bw5vNVPT.js → ProvenanceBadge-BGcgBd8V.js} +1 -1
  16. package/dist/assets/{SaveBar-CWr0O_w-.js → SaveBar-BGr9eZ4g.js} +1 -1
  17. package/dist/assets/{Scheduler-DYdLuUqq.js → Scheduler-Bdo_awPq.js} +7 -7
  18. package/dist/assets/{ScopeSwitcher-CrBLbg8s.js → ScopeSwitcher-ukl0qWaA.js} +1 -1
  19. package/dist/assets/{Settings-DluB-vN1.js → Settings-DJkFj22G.js} +1 -1
  20. package/dist/assets/{SkillReferenceGraph-CHLSseay.js → SkillReferenceGraph-0t1Qblp6.js} +1 -1
  21. package/dist/assets/{Skills-gNdo_HNK.js → Skills-CeVdBGE-.js} +1 -1
  22. package/dist/assets/{SystemPrompt-Cru05-Ia.js → SystemPrompt-CfKvNHi3.js} +1 -1
  23. package/dist/assets/{TagLibrary-DNHY0xou.js → TagLibrary-DAzNaUPd.js} +1 -1
  24. package/dist/assets/{TiptapBody-I4lmbCgP.js → TiptapBody--S_rGXoA.js} +1 -1
  25. package/dist/assets/{Toggle-bWMHjmRh.js → Toggle-Dszm0xnp.js} +1 -1
  26. package/dist/assets/{index-DV3PorRY.css → index-BHTX4OTc.css} +1 -1
  27. package/dist/assets/{index-fc_JjdxL.js → index-Dp4Rc--q.js} +356 -356
  28. package/dist/assets/{settingsSchema-BfhtZnGD.js → settingsSchema-BLbssMYo.js} +1 -1
  29. package/dist/assets/{whisperWorker-Dbia1OpC.js → whisperWorker-C7ZGQwKg.js} +7 -7
  30. package/dist/index.html +2 -2
  31. package/dist/vad/ort-wasm-simd-threaded.asyncify.mjs +106 -110
  32. package/dist/vad/ort-wasm-simd-threaded.asyncify.wasm +0 -0
  33. package/dist/vad/ort-wasm-simd-threaded.jsep.mjs +98 -98
  34. package/dist/vad/ort-wasm-simd-threaded.jsep.wasm +0 -0
  35. package/dist/vad/ort-wasm-simd-threaded.jspi.mjs +99 -102
  36. package/dist/vad/ort-wasm-simd-threaded.jspi.wasm +0 -0
  37. package/dist/vad/ort-wasm-simd-threaded.mjs +46 -46
  38. package/dist/vad/ort-wasm-simd-threaded.wasm +0 -0
  39. package/package.json +10 -13
  40. package/scripts/README.md +59 -0
  41. package/scripts/audit-ops-hygiene.cjs +350 -0
  42. package/scripts/hooks/guard-destructive-git.cjs +10 -34
  43. package/scripts/hooks/guard-inline-implementation.cjs +14 -8
  44. package/scripts/hooks/guard-prd-writes.cjs +7 -36
  45. package/scripts/hooks/guard-self-schedule.cjs +175 -0
  46. package/scripts/ops-sweep.cjs +355 -0
  47. package/scripts/scheduler-mcp-server.cjs +28 -71
  48. package/src/main/__tests__/bilkoHost-integration.test.cjs +3 -3
  49. package/src/main/__tests__/chat-cancel-terminal.test.cjs +6 -9
  50. package/src/main/__tests__/chat-exit-close-race.test.cjs +3 -3
  51. package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +4 -5
  52. package/src/main/__tests__/chat-queue.test.cjs +2 -2
  53. package/src/main/__tests__/chat-stop-signal.test.cjs +2 -2
  54. package/src/main/__tests__/dep-orphan-archive-health.test.cjs +77 -0
  55. package/src/main/__tests__/dod-batchkey.test.cjs +2 -2
  56. package/src/main/__tests__/dod-drain-hook.test.cjs +2 -2
  57. package/src/main/__tests__/dod-report.test.cjs +2 -2
  58. package/src/main/__tests__/dod-reverify.test.cjs +2 -2
  59. package/src/main/__tests__/epicMint.test.cjs +2 -2
  60. package/src/main/__tests__/exchanges.test.cjs +2 -2
  61. package/src/main/__tests__/extractJson.test.cjs +2 -2
  62. package/src/main/__tests__/files-reject-credentials.test.cjs +1 -1
  63. package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +556 -0
  64. package/src/main/__tests__/health-build-freshness.test.cjs +39 -0
  65. package/src/main/__tests__/health-delegation-chain.test.cjs +15 -1
  66. package/src/main/__tests__/health-queue-dispatch.test.cjs +58 -7
  67. package/src/main/__tests__/health-tick-liveness.test.cjs +27 -3
  68. package/src/main/__tests__/health-usage-poller.test.cjs +70 -23
  69. package/src/main/__tests__/historyRollup.test.cjs +2 -2
  70. package/src/main/__tests__/kg-augment.test.cjs +2 -2
  71. package/src/main/__tests__/mcpStatus.test.cjs +2 -2
  72. package/src/main/__tests__/memoryAggregate.test.cjs +1 -1
  73. package/src/main/__tests__/memoryStale.test.cjs +1 -1
  74. package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +25 -1
  75. package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +33 -3
  76. package/src/main/__tests__/prd-group-allocator.test.cjs +2 -2
  77. package/src/main/__tests__/prdAdminRouteParity.test.cjs +2 -0
  78. package/src/main/__tests__/prdAdminRoutes.test.cjs +14 -2
  79. package/src/main/__tests__/prdAuthoringSeed.test.cjs +39 -0
  80. package/src/main/__tests__/prdLocationsArchived.test.cjs +20 -13
  81. package/src/main/__tests__/proc-role-env.test.cjs +125 -0
  82. package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +304 -0
  83. package/src/main/__tests__/procname-sm-processes.test.cjs +127 -0
  84. package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +81 -401
  85. package/src/main/__tests__/projectPages.test.cjs +63 -149
  86. package/src/main/__tests__/queue-health-verdict.test.cjs +9 -9
  87. package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +117 -20
  88. package/src/main/__tests__/queueHistory.test.cjs +2 -2
  89. package/src/main/__tests__/rateLimitPollerStreak.test.cjs +38 -3
  90. package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +181 -0
  91. package/src/main/__tests__/runVerify.test.cjs +5 -5
  92. package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +3 -3
  93. package/src/main/__tests__/scheduleJobTransitions.test.cjs +2 -2
  94. package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +143 -0
  95. package/src/main/__tests__/scheduler-autofix-select.test.cjs +2 -2
  96. package/src/main/__tests__/scheduler-autopromote.test.cjs +2 -2
  97. package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +3 -5
  98. package/src/main/__tests__/scheduler-boot-orphans.test.cjs +78 -97
  99. package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +161 -0
  100. package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +58 -0
  101. package/src/main/__tests__/scheduler-epic-digest.test.cjs +3 -5
  102. package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +2 -2
  103. package/src/main/__tests__/scheduler-gate-shadow.test.cjs +119 -0
  104. package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +46 -0
  105. package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +80 -0
  106. package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +25 -18
  107. package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +4 -4
  108. package/src/main/__tests__/scheduler-launch-failure.test.cjs +3 -5
  109. package/src/main/__tests__/scheduler-looks-done.test.cjs +26 -7
  110. package/src/main/__tests__/scheduler-manual-pause.test.cjs +118 -0
  111. package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +26 -3
  112. package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +17 -2
  113. package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +3 -5
  114. package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +41 -6
  115. package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +62 -6
  116. package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +3 -5
  117. package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +23 -4
  118. package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +100 -0
  119. package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +3 -3
  120. package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +4 -4
  121. package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +110 -0
  122. package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +81 -1
  123. package/src/main/__tests__/scheduler-stall-per-project.test.cjs +14 -0
  124. package/src/main/__tests__/scheduler-starve-escalation.test.cjs +4 -4
  125. package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +245 -0
  126. package/src/main/__tests__/scheduler-supervisor-record.test.cjs +81 -0
  127. package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +2 -2
  128. package/src/main/__tests__/scheduler-tick-wedge.test.cjs +172 -0
  129. package/src/main/__tests__/scheduler-transient-failure.test.cjs +2 -2
  130. package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +37 -11
  131. package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +3 -5
  132. package/src/main/__tests__/usageSingleFlight.test.cjs +157 -0
  133. package/src/main/__tests__/workTypeLibrary.test.cjs +1 -1
  134. package/src/main/bilkoHost.cjs +16 -7
  135. package/src/main/build-info.json +8 -0
  136. package/src/main/chatRunner.cjs +4 -2
  137. package/src/main/config.cjs +2 -3
  138. package/src/main/docEdit.cjs +4 -2
  139. package/src/main/health.cjs +276 -52
  140. package/src/main/heapSnapshot.cjs +2 -2
  141. package/src/main/historyAggregator.cjs +1 -1
  142. package/src/main/index.cjs +61 -10
  143. package/src/main/ipcSchemas.cjs +6 -17
  144. package/src/main/lib/__tests__/auditLog.test.cjs +38 -0
  145. package/src/main/lib/__tests__/buildIdentity.test.cjs +121 -0
  146. package/src/main/lib/__tests__/cwdClassify.test.cjs +111 -0
  147. package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +95 -0
  148. package/src/main/lib/__tests__/delegationReadiness.test.cjs +1 -1
  149. package/src/main/lib/__tests__/dispatchLoop.test.cjs +63 -0
  150. package/src/main/lib/__tests__/gateFixtures.json +20 -0
  151. package/src/main/lib/__tests__/gitWorktree.test.cjs +8 -1
  152. package/src/main/lib/__tests__/instanceLock.test.cjs +93 -7
  153. package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +78 -0
  154. package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +11 -0
  155. package/src/main/lib/__tests__/localAdminHttp.test.cjs +1 -1
  156. package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +6 -1
  157. package/src/main/lib/__tests__/procIdentity.test.cjs +119 -0
  158. package/src/main/lib/__tests__/procName.test.cjs +92 -0
  159. package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +67 -0
  160. package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +6 -7
  161. package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +37 -204
  162. package/src/main/lib/__tests__/schedulerPaths.test.cjs +226 -0
  163. package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +94 -0
  164. package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +56 -0
  165. package/src/main/lib/__tests__/sessionSlots.test.cjs +45 -0
  166. package/src/main/lib/__tests__/telemetryBacklog.test.cjs +1 -1
  167. package/src/main/lib/__tests__/upgradeDrain.test.cjs +130 -0
  168. package/src/main/lib/__tests__/usageCircuit.test.cjs +61 -0
  169. package/src/main/lib/__tests__/watchdog-helpers.test.cjs +63 -0
  170. package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +73 -0
  171. package/src/main/lib/activeIndexRebuild.cjs +1 -3
  172. package/src/main/lib/activeSessions.cjs +20 -88
  173. package/src/main/lib/adoptedRunSupervisor.cjs +136 -0
  174. package/src/main/lib/agentModelResolve.cjs +33 -1
  175. package/src/main/lib/agentPersonaSchema.cjs +2 -2
  176. package/src/main/lib/auditLog.cjs +30 -5
  177. package/src/main/lib/buildIdentity.cjs +113 -0
  178. package/src/main/lib/classifyPromptTicket.cjs +3 -2
  179. package/src/main/lib/claudeBin.cjs +37 -2
  180. package/src/main/lib/cleanEnv.cjs +27 -1
  181. package/src/main/lib/credentials.cjs +4 -2
  182. package/src/main/lib/cwdClassify.cjs +185 -0
  183. package/src/main/lib/definitionOfDone.cjs +296 -52
  184. package/src/main/lib/dispatchLoop.cjs +40 -0
  185. package/src/main/lib/effectiveModelInfo.cjs +9 -10
  186. package/src/main/lib/ephemeralCwd.cjs +7 -28
  187. package/src/main/lib/gitWorktree.cjs +95 -8
  188. package/src/main/lib/guardShims.cjs +3 -3
  189. package/src/main/lib/historyRollup.cjs +6 -7
  190. package/src/main/lib/instanceLock.cjs +32 -5
  191. package/src/main/lib/jobSupervisorRecord.cjs +147 -0
  192. package/src/main/lib/jobWorktreeBootLive.cjs +9 -5
  193. package/src/main/lib/localAdminHttp.cjs +9 -27
  194. package/src/main/lib/mcpToolCatalog.cjs +29 -77
  195. package/src/main/lib/opsOwnership.cjs +27 -19
  196. package/src/main/lib/prdAuthoringSeed.cjs +36 -0
  197. package/src/main/lib/prdLocations.cjs +48 -1
  198. package/src/main/lib/procIdentity.cjs +126 -0
  199. package/src/main/lib/procName.cjs +98 -0
  200. package/src/main/lib/projectHomeAdminRoutes.cjs +56 -327
  201. package/src/main/lib/queueHistory.cjs +8 -7
  202. package/src/main/lib/queueStore.cjs +57 -37
  203. package/src/main/lib/quietMachineLease.cjs +19 -2
  204. package/src/main/lib/reservationExpiry.cjs +30 -0
  205. package/src/main/lib/runClaudeP.cjs +4 -2
  206. package/src/main/lib/runLogRetention.cjs +3 -2
  207. package/src/main/lib/scheduleJobSchema.cjs +2 -2
  208. package/src/main/lib/scheduleJobTransitions.cjs +29 -2
  209. package/src/main/lib/schedulerBatch.cjs +27 -2
  210. package/src/main/lib/schedulerPaths.cjs +175 -0
  211. package/src/main/lib/schedulerRuntimeState.cjs +59 -0
  212. package/src/main/lib/sessionSlots.cjs +47 -9
  213. package/src/main/lib/smProcNames.cjs +51 -0
  214. package/src/main/lib/upgradeDrain.cjs +188 -0
  215. package/src/main/lib/usageCircuit.cjs +53 -6
  216. package/src/main/lib/watchdogHelpers.cjs +70 -37
  217. package/src/main/lib/withTimeout.cjs +33 -0
  218. package/src/main/mcpStatus.cjs +4 -2
  219. package/src/main/pluginInstall.cjs +6 -2
  220. package/src/main/projectPages.cjs +41 -145
  221. package/src/main/pty.cjs +8 -1
  222. package/src/main/queueOps.cjs +10 -10
  223. package/src/main/runVerify.cjs +69 -3
  224. package/src/main/scheduler.cjs +1607 -383
  225. package/src/main/seedAgentPersonas.cjs +1 -1
  226. package/src/main/seedDevPlugin.cjs +1 -1
  227. package/src/main/seedSchedulerMcp.cjs +7 -6
  228. package/src/main/seedStatus.cjs +1 -1
  229. package/src/main/supervisor.cjs +5 -3
  230. package/src/main/usage.cjs +126 -62
  231. package/src/preload/api.d.ts +18 -64
  232. package/src/preload/index.cjs +2 -4
  233. package/src/seed/agents/project-home-builder.md +31 -48
  234. package/screenshots/.gitkeep +0 -0
  235. package/screenshots/README-screenshots.md +0 -13
  236. package/src/main/lib/projectPageSummarySchema.cjs +0 -181
  237. package/src/main/teams.cjs +0 -95
  238. package/src/main/templates/project-pages-catalog.json +0 -741
  239. package/src/main/templates/project-pages-default-home.html +0 -123
  240. package/src/main/templates/project-pages-pipeline.md +0 -417
  241. package/src/seed/prompts/code-review/ac-coverage-check.md +0 -8
  242. package/src/seed/prompts/code-review/correctness-only.md +0 -8
  243. package/src/seed/prompts/code-review/full-spectrum-high.md +0 -8
  244. package/src/seed/prompts/code-review/hallucination-check.md +0 -8
  245. package/src/seed/prompts/code-review/public-api-compat.md +0 -8
  246. package/src/seed/prompts/code-review/readability-naming.md +0 -8
  247. package/src/seed/prompts/debugging/bug-as-failing-test.md +0 -8
  248. package/src/seed/prompts/debugging/git-bisect-regression.md +0 -8
  249. package/src/seed/prompts/debugging/instrument-intermittent-bug.md +0 -8
  250. package/src/seed/prompts/debugging/localize-pipeline-failure.md +0 -8
  251. package/src/seed/prompts/debugging/reproduce-then-diagnose.md +0 -8
  252. package/src/seed/prompts/documentation/adr-from-change.md +0 -8
  253. package/src/seed/prompts/documentation/module-readme.md +0 -8
  254. package/src/seed/prompts/documentation/onboarding-plan.md +0 -8
  255. package/src/seed/prompts/documentation/refresh-claude-md.md +0 -8
  256. package/src/seed/prompts/documentation/tsdoc-public-exports.md +0 -8
  257. package/src/seed/prompts/git-pr/conventional-commit.md +0 -8
  258. package/src/seed/prompts/git-pr/draft-pr-title-body.md +0 -8
  259. package/src/seed/prompts/git-pr/pre-commit-safety-sweep.md +0 -8
  260. package/src/seed/prompts/git-pr/release-notes-block.md +0 -8
  261. package/src/seed/prompts/git-pr/split-large-pr.md +0 -8
  262. package/src/seed/prompts/performance/bundle-startup-audit.md +0 -8
  263. package/src/seed/prompts/performance/complexity-audit.md +0 -8
  264. package/src/seed/prompts/performance/cpu-profile-hot-path.md +0 -8
  265. package/src/seed/prompts/performance/db-query-plan-review.md +0 -8
  266. package/src/seed/prompts/performance/memory-leak-hunt.md +0 -8
  267. package/src/seed/prompts/qa/api-contract-tests.md +0 -8
  268. package/src/seed/prompts/qa/e2e-critical-path.md +0 -8
  269. package/src/seed/prompts/qa/failing-test-for-bug.md +0 -8
  270. package/src/seed/prompts/qa/find-missing-test-coverage.md +0 -8
  271. package/src/seed/prompts/qa/stabilize-flaky-test.md +0 -8
  272. package/src/seed/prompts/qa/tdd-red-first.md +0 -8
  273. package/src/seed/prompts/qa/visual-regression-review.md +0 -8
  274. package/src/seed/prompts/qa/wcag-axe-scan.md +0 -8
  275. package/src/seed/prompts/refactoring/dead-code-sweep.md +0 -8
  276. package/src/seed/prompts/refactoring/extract-duplicated-pattern.md +0 -8
  277. package/src/seed/prompts/refactoring/modernize-legacy-file.md +0 -8
  278. package/src/seed/prompts/refactoring/reduce-cyclomatic-complexity.md +0 -8
  279. package/src/seed/prompts/refactoring/tighten-module-boundaries.md +0 -8
  280. package/src/seed/prompts/security/authz-audit.md +0 -8
  281. package/src/seed/prompts/security/crypto-correctness.md +0 -8
  282. package/src/seed/prompts/security/cwe-top-25-hunt.md +0 -8
  283. package/src/seed/prompts/security/dependency-audit.md +0 -8
  284. package/src/seed/prompts/security/ipc-boundary-hardening.md +0 -8
  285. package/src/seed/prompts/security/owasp-top-10-staged-diff.md +0 -8
  286. package/src/seed/prompts/security/secret-credential-scan.md +0 -8
  287. package/web/README.md +0 -41
  288. package/web/project-pages/logic/dist/logic.cjs +0 -4709
  289. package/web/project-pages/render.cjs +0 -70
  290. package/web/project-pages/renderer/dist/renderer.cjs +0 -18900
  291. package/web/project-pages/validate-summary.cjs +0 -62
@@ -6,6 +6,17 @@
6
6
  * circuit with jittered backoff; degradedBudget() carries forward the last
7
7
  * known-good BINDING window's utilization (never 0), pinning to 100 on a
8
8
  * fresh executor-observed 429 and capping concurrency at 2. Pure Node.
9
+ *
10
+ * Kill switches (read live from process.env, so a test/operator can flip
11
+ * them without restarting anything that already holds a circuit instance):
12
+ * SM_USAGE_CIRCUIT=0 — bypasses the breaker entirely: state() always
13
+ * reports 'closed' and recordFailure() never
14
+ * opens it. Escape hatch if the breaker itself
15
+ * is ever suspected of wedging dispatch.
16
+ * SM_USAGE_DEGRADED_CAP=n — overrides degradedBudget()'s concurrencyCap
17
+ * outright (bypasses the normal min(cap, 2)),
18
+ * for an operator who needs a different ceiling
19
+ * while the meter is degraded.
9
20
  */
10
21
 
11
22
  const FLOOR_MS = 30_000;
@@ -63,12 +74,25 @@ function bindingWindow(payload) {
63
74
  };
64
75
  }
65
76
 
77
+ /**
78
+ * SM_USAGE_DEGRADED_CAP=<n> overrides the normal min(configuredCap, 2)
79
+ * ceiling outright — an operator escape hatch, not a default path. Falls
80
+ * through to the normal min(cap, 2) rule when unset/non-positive.
81
+ */
82
+ function degradedConcurrencyCap(configuredCap) {
83
+ const override = Number(process.env.SM_USAGE_DEGRADED_CAP);
84
+ if (Number.isFinite(override) && override > 0) return override;
85
+ const cap = Number.isFinite(configuredCap) ? configuredCap : 2;
86
+ return Math.min(cap, 2);
87
+ }
88
+
66
89
  /**
67
90
  * Conservative budget to run on while the meter is down. utilization is
68
91
  * carried forward from the last known-good BINDING window — never 0, which
69
92
  * would read as "plenty of headroom" instead of "we don't know." A fresh
70
93
  * executor-observed 429 (its own window not yet passed) pins utilization to
71
- * 100 regardless of the stale cached value. concurrencyCap never exceeds 2.
94
+ * 100 regardless of the stale cached value. concurrencyCap never exceeds 2
95
+ * (or SM_USAGE_DEGRADED_CAP, if set).
72
96
  */
73
97
  function degradedBudget(lastGoodPayload, executorEvidence = {}) {
74
98
  const window = bindingWindow(lastGoodPayload);
@@ -76,10 +100,7 @@ function degradedBudget(lastGoodPayload, executorEvidence = {}) {
76
100
  ? window.utilization
77
101
  : 100;
78
102
 
79
- const configuredCap = Number.isFinite(executorEvidence.configuredCap)
80
- ? executorEvidence.configuredCap
81
- : 2;
82
- const concurrencyCap = Math.min(configuredCap, 2);
103
+ const concurrencyCap = degradedConcurrencyCap(executorEvidence.configuredCap);
83
104
 
84
105
  if (executorEvidence.observed429 && isResetFresh(executorEvidence.resetsAt, executorEvidence.now)) {
85
106
  utilization = 100;
@@ -98,6 +119,12 @@ function singleFlight(fn) {
98
119
  };
99
120
  }
100
121
 
122
+ /** SM_USAGE_CIRCUIT=0 (or 'false') bypasses the breaker entirely. */
123
+ function breakerEnabled() {
124
+ const v = process.env.SM_USAGE_CIRCUIT;
125
+ return v == null || v === '' || (v !== '0' && String(v).toLowerCase() !== 'false');
126
+ }
127
+
101
128
  /**
102
129
  * Breaker state machine over the meter: closed -> open after 3 consecutive
103
130
  * failures; open allows exactly one probe per backoff interval (half_open);
@@ -110,6 +137,12 @@ function createUsageCircuit({ now = () => Date.now() } = {}) {
110
137
  let backoffBaseMs = 0;
111
138
  let nextProbeAtMs = 0;
112
139
  let probeInFlight = false;
140
+ // ms timestamp the circuit most recently transitioned closed -> open; kept
141
+ // through half_open <-> open cycles of the SAME streak (a failed probe
142
+ // re-arms the timer but the meter has been down continuously since this
143
+ // timestamp), cleared only by recordSuccess. Feeds health.cjs's
144
+ // GREEN/YELLOW/RED ladder (open duration), not just a failure count.
145
+ let openedAtMs = null;
113
146
 
114
147
  function scheduleNextProbe() {
115
148
  const intervalMs = backoffWithJitter(backoffBaseMs);
@@ -118,6 +151,7 @@ function createUsageCircuit({ now = () => Date.now() } = {}) {
118
151
  }
119
152
 
120
153
  function state() {
154
+ if (!breakerEnabled()) return 'closed';
121
155
  if (currentState === 'open' && !probeInFlight && now() >= nextProbeAtMs) {
122
156
  currentState = 'half_open';
123
157
  probeInFlight = true;
@@ -127,6 +161,7 @@ function createUsageCircuit({ now = () => Date.now() } = {}) {
127
161
 
128
162
  function recordFailure(kind) { // eslint-disable-line no-unused-vars
129
163
  consecutiveFailures += 1;
164
+ if (!breakerEnabled()) return;
130
165
  if (currentState === 'half_open') {
131
166
  probeInFlight = false;
132
167
  currentState = 'open';
@@ -135,6 +170,7 @@ function createUsageCircuit({ now = () => Date.now() } = {}) {
135
170
  }
136
171
  if (currentState === 'closed' && consecutiveFailures >= 3) {
137
172
  currentState = 'open';
173
+ openedAtMs = now();
138
174
  scheduleNextProbe();
139
175
  }
140
176
  }
@@ -144,9 +180,19 @@ function createUsageCircuit({ now = () => Date.now() } = {}) {
144
180
  probeInFlight = false;
145
181
  backoffBaseMs = 0;
146
182
  currentState = 'closed';
183
+ openedAtMs = null;
184
+ }
185
+
186
+ /** ms timestamp this streak opened, or null while closed. */
187
+ function openedAt() {
188
+ return currentState === 'open' || currentState === 'half_open' ? openedAtMs : null;
189
+ }
190
+
191
+ function getConsecutiveFailures() {
192
+ return consecutiveFailures;
147
193
  }
148
194
 
149
- return { recordSuccess, recordFailure, state };
195
+ return { recordSuccess, recordFailure, state, openedAt, getConsecutiveFailures };
150
196
  }
151
197
 
152
198
  module.exports = {
@@ -155,5 +201,6 @@ module.exports = {
155
201
  isResetFresh,
156
202
  bindingWindow,
157
203
  degradedBudget,
204
+ degradedConcurrencyCap,
158
205
  singleFlight,
159
206
  };
@@ -4,14 +4,11 @@
4
4
  // Testable without spawning the watchdog entry script.
5
5
 
6
6
  const fs = require('node:fs');
7
- const os = require('node:os');
8
7
  const path = require('node:path');
8
+ const schedulerPaths = require('./schedulerPaths.cjs');
9
+ const { isRestartingMarkerActive } = require('./upgradeDrain.cjs');
9
10
 
10
11
 
11
- // Mirrors scheduler.cjs:215 (single source of truth there; kept in sync here).
12
- const DEFAULT_HEARTBEAT_PATH = path.join(
13
- os.homedir(), '.claude', 'session-manager', 'scheduler-heartbeat.log',
14
- );
15
12
 
16
13
  // The in-app heartbeat ticks every 60 s; 3 missed ticks = stale.
17
14
  const DEFAULT_MAX_AGE_MS = 180_000;
@@ -40,7 +37,7 @@ const TAIL_BYTES = 4096;
40
37
  * Single source of truth for the file-read + reverse-scan logic; used by
41
38
  * readLastHeartbeatTs() and heartbeatFresh().
42
39
  */
43
- function readLastHeartbeat(heartbeatPath = DEFAULT_HEARTBEAT_PATH) {
40
+ function readLastHeartbeat(heartbeatPath = schedulerPaths.heartbeatPath()) {
44
41
  let buf;
45
42
  try {
46
43
  const stat = fs.statSync(heartbeatPath);
@@ -79,7 +76,7 @@ function readLastHeartbeat(heartbeatPath = DEFAULT_HEARTBEAT_PATH) {
79
76
  * Returns the `ts` field (epoch ms) from the last heartbeat entry.
80
77
  * Returns null on missing / empty / unparseable file or missing ts field.
81
78
  */
82
- function readLastHeartbeatTs(heartbeatPath = DEFAULT_HEARTBEAT_PATH) {
79
+ function readLastHeartbeatTs(heartbeatPath = schedulerPaths.heartbeatPath()) {
83
80
  const entry = readLastHeartbeat(heartbeatPath);
84
81
  return (entry !== null && typeof entry.ts === 'number') ? entry.ts : null;
85
82
  }
@@ -88,12 +85,43 @@ function readLastHeartbeatTs(heartbeatPath = DEFAULT_HEARTBEAT_PATH) {
88
85
  * heartbeatFresh(heartbeatPath?, maxAgeMs?) → boolean
89
86
  *
90
87
  * Returns true iff the last heartbeat ts is within maxAgeMs of `now`.
91
- * Missing / empty / unparseable file → false.
88
+ * Missing / empty / unparseable file → false. A `degraded: true` line (a
89
+ * heartbeat subsystem threw — see scheduler.cjs heartbeatTick) is NOT fresh:
90
+ * the loop ran but could not read state, so it must not disarm the watchdog.
92
91
  */
93
- function heartbeatFresh(heartbeatPath = DEFAULT_HEARTBEAT_PATH, maxAgeMs = DEFAULT_MAX_AGE_MS) {
94
- const ts = readLastHeartbeatTs(heartbeatPath);
95
- if (ts === null) return false;
96
- return (Date.now() - ts) < maxAgeMs;
92
+ function heartbeatFresh(heartbeatPath = schedulerPaths.heartbeatPath(), maxAgeMs = DEFAULT_MAX_AGE_MS) {
93
+ const entry = readLastHeartbeat(heartbeatPath);
94
+ if (entry === null || entry.degraded === true || typeof entry.ts !== 'number') return false;
95
+ return (Date.now() - entry.ts) < maxAgeMs;
96
+ }
97
+
98
+ const DEFAULT_DISPATCH_DEAD_MS = 30 * 60_000;
99
+
100
+ /**
101
+ * evaluateDispatchLiveness(entry, now, { deadMs }?) → { dead, reason }
102
+ *
103
+ * Pure. Observability only — a logically-dead scheduler (wedged tick chain,
104
+ * leaked slots) still writes fresh heartbeats, so freshness alone can't see
105
+ * it. `dead` only when ALL hold: heartbeat fresh (not degraded, within
106
+ * DEFAULT_MAX_AGE_MS), dispatch.lastRunAt older than deadMs (absent/unparseable
107
+ * counts as older), pendingDispatchable > 0, runningCount === 0, not paused.
108
+ * Callers must NOT relaunch on this: the process is alive, and a relaunch
109
+ * would spawn a second app on top of it.
110
+ */
111
+ function evaluateDispatchLiveness(entry, now = Date.now(), { deadMs = DEFAULT_DISPATCH_DEAD_MS } = {}) {
112
+ if (!entry || entry.degraded === true || typeof entry.ts !== 'number') {
113
+ return { dead: false, reason: 'no-fresh-heartbeat' };
114
+ }
115
+ if (now - entry.ts >= DEFAULT_MAX_AGE_MS) return { dead: false, reason: 'heartbeat-stale' };
116
+ const d = entry.dispatch;
117
+ if (!d || typeof d !== 'object') return { dead: false, reason: 'no-dispatch-field' };
118
+ if (d.paused) return { dead: false, reason: 'paused' };
119
+ if (d.drain) return { dead: false, reason: 'draining' };
120
+ if (!(d.pendingDispatchable > 0)) return { dead: false, reason: 'nothing-dispatchable' };
121
+ if (d.runningCount !== 0) return { dead: false, reason: 'jobs-running' };
122
+ const lastRunMs = Date.parse(d.lastRunAt ?? '');
123
+ if (Number.isFinite(lastRunMs) && now - lastRunMs <= deadMs) return { dead: false, reason: 'recent-dispatch' };
124
+ return { dead: true, reason: 'dispatch-dead' };
97
125
  }
98
126
 
99
127
  /**
@@ -132,12 +160,6 @@ function isPidAlive(pid) {
132
160
  // 60 s by scheduler.cjs's heartbeatInterval regardless of pause state) as the
133
161
  // liveness signal — no second heartbeat file.
134
162
 
135
- const DEFAULT_RELAUNCH_STATE_PATH = path.join(
136
- os.homedir(), '.claude', 'session-manager', 'watchdog-relaunch-state.json',
137
- );
138
- const DEFAULT_RELAUNCH_LOG_PATH = path.join(
139
- os.homedir(), '.claude', 'logs', 'scheduler-watchdog-relaunch.log',
140
- );
141
163
  const DEFAULT_RELAUNCH_DEBOUNCE_MS = 90_000;
142
164
  const DEFAULT_MAX_RELAUNCH_ATTEMPTS = 3;
143
165
 
@@ -150,7 +172,7 @@ const DEFAULT_MAX_RELAUNCH_ATTEMPTS = 3;
150
172
  * rather than relaunching on top of a live process.
151
173
  */
152
174
  function checkAppLiveness({
153
- heartbeatPath = DEFAULT_HEARTBEAT_PATH,
175
+ heartbeatPath = schedulerPaths.heartbeatPath(),
154
176
  maxAgeMs = DEFAULT_MAX_AGE_MS,
155
177
  } = {}) {
156
178
  if (heartbeatFresh(heartbeatPath, maxAgeMs)) {
@@ -164,7 +186,7 @@ function checkAppLiveness({
164
186
  }
165
187
 
166
188
  /** Default { lastAttemptTs: null, attemptCount: 0 } on missing/unparseable state file. */
167
- function readRelaunchState(statePath = DEFAULT_RELAUNCH_STATE_PATH) {
189
+ function readRelaunchState(statePath = schedulerPaths.watchdogRelaunchStatePath()) {
168
190
  try {
169
191
  const raw = JSON.parse(fs.readFileSync(statePath, 'utf8'));
170
192
  return {
@@ -203,7 +225,7 @@ function logRelaunchLine(logPath, message) {
203
225
  * (short-lived) process doesn't block on the launched app and the app
204
226
  * survives the watchdog exiting.
205
227
  */
206
- function defaultSpawnRelaunch({ logPath = DEFAULT_RELAUNCH_LOG_PATH } = {}) {
228
+ function defaultSpawnRelaunch({ logPath = schedulerPaths.watchdogRelaunchLogPath() } = {}) {
207
229
  const { spawn } = require('node:child_process');
208
230
  fs.mkdirSync(path.dirname(logPath), { recursive: true });
209
231
  const fd = fs.openSync(logPath, 'a');
@@ -236,14 +258,15 @@ function defaultSpawnRelaunch({ logPath = DEFAULT_RELAUNCH_LOG_PATH } = {}) {
236
258
  * intervenes or the app comes up some other way.
237
259
  */
238
260
  function maybeRelaunchApp({
239
- heartbeatPath = DEFAULT_HEARTBEAT_PATH,
261
+ heartbeatPath = schedulerPaths.heartbeatPath(),
240
262
  maxAgeMs = DEFAULT_MAX_AGE_MS,
241
- statePath = DEFAULT_RELAUNCH_STATE_PATH,
242
- logPath = DEFAULT_RELAUNCH_LOG_PATH,
263
+ statePath = schedulerPaths.watchdogRelaunchStatePath(),
264
+ logPath = schedulerPaths.watchdogRelaunchLogPath(),
243
265
  debounceMs = DEFAULT_RELAUNCH_DEBOUNCE_MS,
244
266
  maxAttempts = DEFAULT_MAX_RELAUNCH_ATTEMPTS,
245
267
  now = Date.now(),
246
268
  spawnFn = defaultSpawnRelaunch,
269
+ restartingMarkerPath = schedulerPaths.restartingMarkerPath(),
247
270
  } = {}) {
248
271
  const liveness = checkAppLiveness({ heartbeatPath, maxAgeMs });
249
272
  const state = readRelaunchState(statePath);
@@ -255,6 +278,14 @@ function maybeRelaunchApp({
255
278
  return { relaunched: false, reason: 'alive', attemptCount: 0 };
256
279
  }
257
280
 
281
+ // A deliberate drain-restart is mid-exit: the old process is going away ON
282
+ // PURPOSE and its successor is booting. Relaunching now would race the
283
+ // single-instance lock. The marker's lifetime is bounded, so a restart that
284
+ // never comes up is picked up again once it expires.
285
+ if (isRestartingMarkerActive({ file: restartingMarkerPath, now })) {
286
+ return { relaunched: false, reason: 'restarting', attemptCount: state.attemptCount };
287
+ }
288
+
258
289
  if (state.attemptCount >= maxAttempts) {
259
290
  logRelaunchLine(
260
291
  logPath,
@@ -286,12 +317,6 @@ function maybeRelaunchApp({
286
317
  // walk/persist logic — this is just the once-per-day scheduling + lock
287
318
  // wrapper around it, tailored to being invoked repeatedly by a systemd timer.
288
319
 
289
- const DEFAULT_STAMP_PATH = path.join(
290
- os.homedir(), '.claude', 'session-manager', 'history-rollup.stamp',
291
- );
292
- const DEFAULT_LOCK_PATH = path.join(
293
- os.homedir(), '.claude', 'session-manager', 'history-rollup.lock',
294
- );
295
320
  const DEFAULT_LOCK_STALE_MS = 10 * 60 * 1000; // 10 min
296
321
  const DEFAULT_FINALIZE_BUDGET_MS = 60_000;
297
322
 
@@ -384,8 +409,8 @@ function defaultFinalizeClosedDays(opts) {
384
409
  * finalizeFn — injectable finalizeClosedDays for testing
385
410
  */
386
411
  async function maybeFinalizeHistory({
387
- stampPath = DEFAULT_STAMP_PATH,
388
- lockPath = DEFAULT_LOCK_PATH,
412
+ stampPath = schedulerPaths.historyRollupStampPath(),
413
+ lockPath = schedulerPaths.historyRollupLockPath(),
389
414
  staleLockMs = DEFAULT_LOCK_STALE_MS,
390
415
  budgetMs = DEFAULT_FINALIZE_BUDGET_MS,
391
416
  dryRun = process.env.SM_WATCHDOG_DRYRUN === '1',
@@ -423,6 +448,7 @@ module.exports = {
423
448
  readLastHeartbeat,
424
449
  readLastHeartbeatTs,
425
450
  heartbeatFresh,
451
+ evaluateDispatchLiveness,
426
452
  isPidAlive,
427
453
  localDateStr,
428
454
  maybeFinalizeHistory,
@@ -433,14 +459,21 @@ module.exports = {
433
459
  writeRelaunchState,
434
460
  defaultSpawnRelaunch,
435
461
  maybeRelaunchApp,
436
- DEFAULT_HEARTBEAT_PATH,
437
462
  DEFAULT_MAX_AGE_MS,
438
- DEFAULT_STAMP_PATH,
439
- DEFAULT_LOCK_PATH,
463
+ DEFAULT_DISPATCH_DEAD_MS,
440
464
  DEFAULT_LOCK_STALE_MS,
441
465
  DEFAULT_FINALIZE_BUDGET_MS,
442
- DEFAULT_RELAUNCH_STATE_PATH,
443
- DEFAULT_RELAUNCH_LOG_PATH,
444
466
  DEFAULT_RELAUNCH_DEBOUNCE_MS,
445
467
  DEFAULT_MAX_RELAUNCH_ATTEMPTS,
446
468
  };
469
+
470
+ // Lazy default-path getters (SM_SCHEDULER_HOME resolved at read, never at require).
471
+ for (const [name, resolve] of [
472
+ ['DEFAULT_HEARTBEAT_PATH', schedulerPaths.heartbeatPath],
473
+ ['DEFAULT_STAMP_PATH', schedulerPaths.historyRollupStampPath],
474
+ ['DEFAULT_LOCK_PATH', schedulerPaths.historyRollupLockPath],
475
+ ['DEFAULT_RELAUNCH_STATE_PATH', schedulerPaths.watchdogRelaunchStatePath],
476
+ ['DEFAULT_RELAUNCH_LOG_PATH', schedulerPaths.watchdogRelaunchLogPath],
477
+ ]) {
478
+ Object.defineProperty(module.exports, name, { get: resolve, enumerable: true });
479
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * withTimeout.cjs — bound an async body with a wall-clock budget.
3
+ *
4
+ * `withTimeout(promiseFactory, ms, onTimeout)` starts `promiseFactory()` and
5
+ * resolves with its result. If it has not settled after `ms`, `onTimeout()`
6
+ * is called and ITS return value becomes the resolved value; the original
7
+ * promise is abandoned (never cancelled — callers that must neutralise a
8
+ * late-resuming body do so with their own generation fence). The timer is
9
+ * always cleared on settle, and unref'd so it can never hold the process open.
10
+ * `ms <= 0` (or non-finite) disables the bound entirely.
11
+ *
12
+ * Plain Node module (no Electron deps).
13
+ */
14
+ 'use strict';
15
+
16
+ function withTimeout(promiseFactory, ms, onTimeout) {
17
+ if (!Number.isFinite(ms) || ms <= 0) return Promise.resolve().then(promiseFactory);
18
+ return new Promise((resolve, reject) => {
19
+ let done = false;
20
+ const timer = setTimeout(() => {
21
+ if (done) return;
22
+ done = true;
23
+ try { resolve(onTimeout()); } catch (e) { reject(e); }
24
+ }, ms);
25
+ if (typeof timer.unref === 'function') timer.unref();
26
+ Promise.resolve().then(promiseFactory).then(
27
+ (v) => { if (done) return; done = true; clearTimeout(timer); resolve(v); },
28
+ (e) => { if (done) return; done = true; clearTimeout(timer); reject(e); },
29
+ );
30
+ });
31
+ }
32
+
33
+ module.exports = { withTimeout };
@@ -10,7 +10,8 @@
10
10
  */
11
11
 
12
12
  const { spawn } = require('node:child_process');
13
- const { resolveClaudeBin } = require('./lib/claudeBin.cjs');
13
+ const { resolveClaudeBin, claudeSpawnTarget } = require('./lib/claudeBin.cjs');
14
+ const { withProcRole } = require('./lib/cleanEnv.cjs');
14
15
 
15
16
  const PROBE_TIMEOUT_MS = 30_000;
16
17
 
@@ -56,7 +57,8 @@ function runProbe() {
56
57
  }
57
58
  let child;
58
59
  try {
59
- child = spawn(bin, ['mcp', 'list'], { env: process.env, stdio: ['ignore', 'pipe', 'pipe'] });
60
+ const target = claudeSpawnTarget('aux', 'mcp-list', bin);
61
+ child = spawn(target.command, ['mcp', 'list'], { env: withProcRole(process.env, 'aux'), stdio: ['ignore', 'pipe', 'pipe'], ...(target.argv0 ? { argv0: target.argv0 } : {}) });
60
62
  } catch (e) {
61
63
  resolve({ ok: false, servers: [], error: e?.message || 'spawn error', checkedAt: Date.now() });
62
64
  return;
@@ -31,7 +31,7 @@ const path = require('node:path');
31
31
  const os = require('node:os');
32
32
  const fs = require('node:fs');
33
33
  const { cleanChildEnv, pathWithUserBins } = require('./lib/cleanEnv.cjs');
34
- const { resolveClaudeBin } = require('./lib/claudeBin.cjs');
34
+ const { resolveClaudeBin, claudeSpawnTarget } = require('./lib/claudeBin.cjs');
35
35
  const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
36
36
  const { schemas } = require('./ipcSchemas.cjs');
37
37
 
@@ -88,9 +88,13 @@ function runStep({ slug, args, register, unregister }) {
88
88
  return;
89
89
  }
90
90
  const claudeBin = resolveClaudeBin();
91
+ // node-pty has NO `argv0` option, so the alias symlink is the only naming
92
+ // mechanism here — the comm is set, argv0 stays the alias path (which still
93
+ // contains the word `claude`). Do not try to pass argv0.
94
+ const { command } = claudeSpawnTarget('aux', 'plugin', claudeBin);
91
95
  let proc;
92
96
  try {
93
- proc = pty.spawn(claudeBin, args, {
97
+ proc = pty.spawn(command, args, {
94
98
  name: 'xterm-256color',
95
99
  cols: 120,
96
100
  rows: 30,