I've solved this with the following AppleScript. Explanations are included in the source code.
To use this code, you need to save it as an Application with the option "Stay open" in Script Editor or Script Debugger.
(If you have syntactical improvements, please edit the code directly. For functional changes, please comment or post your own.)
-- This script makes sure that a particular program keeps on running,
-- even if it has frozen, in which case it'll be force-quit and relaunched.
--
-- Written 31 May 2021 by Thomas Tempelmann (apps.tempel.org).
-- I claim no copyright. Use it as you like.
-----------
-- Setup
-----------
-- This is the time after which an unresponsive app is considered
-- frozen. I suggest using 30 (for 30 seconds).
property timeoutInSeconds : 30
-- Set the app's bundle ID directly if you know it,
-- such as "com.yourdomain.appname"
property bundleID : ""
-- Otherwise, specify the path to the app:
property theAppPath : "/Applications/The App Name.app"
-----------
-- The following code should need no alterations. It'll force-quit the app if it
-- is frozen, and launch the app if it was not runnnig or if it was force-quit.
-----------
global theAppFile
on run
if bundleID is "" then
set theAppFile to POSIX file theAppPath
set bundleID to bundle identifier of (info for (theAppFile))
else
set theAppFile to null
end if
end run
on idle
-- This code gets invoked once a minute
-- It will then check on the app and restart it if necessary:
my checkTheApp()
return 60 -- idleInterval in seconds
end idle
on checkTheApp()
-- Locate all running programs that use the given 'bundleID'
tell application id "com.apple.systemevents" -- System Events.app
set matchingProcesses to (every process whose bundle identifier is bundleID)
set isRunning to matchingProcesses is not {}
if isRunning then
tell first item of matchingProcesses
set theID to its unix id
set theAppFile to (POSIX path of its application file) as POSIX file
end tell
end if
end tell
set isFrozen to false
if isRunning then
-- The program is running. Now let's check whether it's frozen.
with timeout of timeoutInSeconds seconds
try
tell application id bundleID
get documents
end tell
on error errMsg number errNum
if errNum is -1712 then
-- This error means that the request timed out (i.e. no response).
set isFrozen to true
else
-- Some other error, usually -1728 for apps that do not process
-- this particular AppleEvent request.
-- As it's not a timeout, we assume that the app is not frozen.
end if
end try
end timeout
end if
-- If the app is frozen, we force-quit it.
-- Unsaved changes will be lost!
if isFrozen then
try
do shell script "/bin/kill -9 " & theID
set isRunning to false
delay 1
end try
end if
-- Restart the app if it's not runnning
if not isRunning then
if (theAppFile is not null) and (theAppFile exists) then
-- We prefer to restart exactly the very same app that
-- we had force-quit above
set itsAlias to theAppFile as alias
activate application (itsAlias as string)
else
-- If don't know the app's path we launch it by its bundleID
activate application id bundleID
end if
end if
end checkTheApp