feat: 多语言任务、WebUI 增强与 Agent MCP 集成
重构 lib 为扁平模块并支持 Windows schtasks;新增 JS/Bash/PowerShell 模板、WebUI 调度编辑,以及 Cursor Skill 与 MCP 工具供 Agent 管理定时任务。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
91
lib/external_runner.py
Normal file
91
lib/external_runner.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""非 Python 任务:由 Python 编排 disable / 锁 / 日志,subprocess 执行业务脚本。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
from runner import (
|
||||
TaskContext,
|
||||
acquire_cron_lock,
|
||||
append_raw_to_log,
|
||||
task_is_disabled,
|
||||
task_logging,
|
||||
)
|
||||
from task_manifest import TaskManifest
|
||||
|
||||
|
||||
def _resolve_interpreter(runtime: str) -> list[str] | None:
|
||||
if runtime == "javascript":
|
||||
node = shutil.which("node")
|
||||
return [node] if node else None
|
||||
if runtime == "bash":
|
||||
bash = shutil.which("bash")
|
||||
return [bash] if bash else None
|
||||
if runtime == "powershell":
|
||||
pwsh = shutil.which("pwsh")
|
||||
if pwsh:
|
||||
return [pwsh, "-File"]
|
||||
ps = shutil.which("powershell")
|
||||
return [ps, "-File"] if ps else None
|
||||
return None
|
||||
|
||||
|
||||
def build_command(manifest: TaskManifest, ctx: TaskContext) -> list[str]:
|
||||
interpreter = _resolve_interpreter(manifest.runtime)
|
||||
if interpreter is None:
|
||||
raise FileNotFoundError(f"未找到 {manifest.runtime} 解释器,请确认已安装并在 PATH 中")
|
||||
entry = ctx.task_dir / manifest.entry
|
||||
if manifest.runtime == "powershell":
|
||||
return [*interpreter, str(entry)]
|
||||
if manifest.runtime == "bash":
|
||||
return [*interpreter, str(entry)]
|
||||
return [*interpreter, str(entry)]
|
||||
|
||||
|
||||
def _task_env(ctx: TaskContext) -> dict[str, str]:
|
||||
env = os.environ.copy()
|
||||
env["CRON_TASK_ID"] = ctx.task_id
|
||||
env["CRON_TASK_DIR"] = str(ctx.task_dir)
|
||||
env["CRON_ROOT"] = str(ctx.cron_root)
|
||||
return env
|
||||
|
||||
|
||||
def run_external_task(ctx: TaskContext, manifest: TaskManifest) -> int:
|
||||
if task_is_disabled(ctx):
|
||||
return 0
|
||||
|
||||
with task_logging(ctx) as log:
|
||||
with acquire_cron_lock(ctx.lock_file, log=log) as locked:
|
||||
if not locked:
|
||||
return 0
|
||||
|
||||
log("start")
|
||||
log(f"runtime={manifest.runtime} entry={manifest.entry}")
|
||||
|
||||
try:
|
||||
cmd = build_command(manifest, ctx)
|
||||
except FileNotFoundError as exc:
|
||||
log(str(exc))
|
||||
log("end")
|
||||
return 1
|
||||
|
||||
proc = subprocess.run(
|
||||
cmd,
|
||||
cwd=str(ctx.task_dir),
|
||||
env=_task_env(ctx),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
)
|
||||
output = ((proc.stdout or "") + (proc.stderr or "")).strip()
|
||||
if output:
|
||||
append_raw_to_log(ctx.log_file, output)
|
||||
|
||||
if proc.returncode != 0:
|
||||
log(f"exit={proc.returncode}")
|
||||
|
||||
log("end")
|
||||
return proc.returncode
|
||||
Reference in New Issue
Block a user