v0.5.2: shell_bg / remote_shell_bg — background launch tools
server.lua gains a shell_bg tool that launches a detached command via setsid + nohup + stdio-redirect + &, returns immediately with PID and log path. Linux-only for MVP (Windows Start-Process equivalent TBD). hub.lua gains remote_shell_bg, forwarding to backend shell_bg. lmcp-only, no ssh fallback — fallback for fire-and-forget is semantically murky. Addresses the 'how do I launch a daemon over lmcp without the sentinel- file wrapper blocking forever' question. Existing remote_shell keeps its current synchronous-with-timeout behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+43
@@ -170,6 +170,49 @@ server:tool("shell", "Execute a shell command.", {
|
||||
return run(cmd, a.timeout or 120)
|
||||
end)
|
||||
|
||||
server:tool("shell_bg",
|
||||
"Fire-and-forget shell command (Linux-only). Fully detaches via setsid+nohup+stdio-redirect and returns immediately with PID and log path. Use for daemons that must outlive the lmcp request.",
|
||||
{
|
||||
type = "object",
|
||||
properties = {
|
||||
command = { type = "string", description = "Shell command to launch" },
|
||||
cwd = { type = "string", description = "Working directory" },
|
||||
log = { type = "string", description = "Log file (stdout+stderr). Default: /tmp/lmcp-bg-<ts>-<rand>.log" },
|
||||
},
|
||||
required = { "command" },
|
||||
},
|
||||
function(a)
|
||||
if WINDOWS then
|
||||
return "Error: shell_bg is Linux-only (Windows Start-Process equivalent TBD)"
|
||||
end
|
||||
if type(a.command) ~= "string" or a.command == "" then
|
||||
return "Error: command required"
|
||||
end
|
||||
local log = a.log
|
||||
if not log or log == "" then
|
||||
log = string.format("/tmp/lmcp-bg-%d-%d.log", os.time(), math.random(1000, 9999))
|
||||
end
|
||||
local pid_file = log .. ".pid"
|
||||
local inner = a.command
|
||||
if a.cwd and a.cwd ~= "" then
|
||||
inner = "cd '" .. a.cwd:gsub("'", "'\\''") .. "' && " .. inner
|
||||
end
|
||||
local sq = function(s) return "'" .. s:gsub("'", "'\\''") .. "'" end
|
||||
local full = string.format(
|
||||
"setsid nohup sh -c %s </dev/null >%s 2>&1 & echo $! > %s",
|
||||
sq(inner), sq(log), sq(pid_file)
|
||||
)
|
||||
os.execute(full)
|
||||
local f = io.open(pid_file, 'r')
|
||||
local pid = "?"
|
||||
if f then
|
||||
pid = (f:read('*a') or ""):match("(%d+)") or "?"
|
||||
f:close()
|
||||
os.remove(pid_file)
|
||||
end
|
||||
return string.format("launched pid=%s log=%s", pid, log)
|
||||
end)
|
||||
|
||||
server:tool("read_file", "Read a file.", {
|
||||
type = "object",
|
||||
properties = { path = { type = "string" } },
|
||||
|
||||
Reference in New Issue
Block a user