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:
2026-06-24 21:45:25 +08:00
parent 6c2db2dfa3
commit c10cacd5c6
59 changed files with 3989 additions and 800 deletions

91
lib/external_runner.py Normal file
View 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