# Changelog — 长任务回调与 Redis 执行态

**日期**: 2026-05-22  
**类型**: 功能  
**上级索引**: [2026-05-22-script-execution-platform.md](./2026-05-22-script-execution-platform.md)  
**SSOT**: [docs/project/script-execution.md](../project/script-execution.md)

## 变更摘要

1. 长任务：`nohup` + 子机结束 `curl` → `POST /api/agent/script-callback`
2. 执行热数据 Redis（TTL 2h），MySQL 仅 60s 分批刷盘
3. 短任务 stop、执行历史页、自动卡住检测（详见平台 changelog）

## 长任务流程

勾选「长任务」执行脚本时：

1. 主站为每台子机生成 `job_id` + 一次性 `secret`，写入 Redis（TTL 7 天）
2. 主站用 `nohup` 包装脚本，脚本结束后在子机执行 `curl` → `POST /api/agent/script-callback`
3. 主站更新 `script_executions.results`，并通过 WebSocket 推送 `script_job_done`

## 配置要求

- `.env` 中必须配置 **`NEXUS_API_BASE_URL`**（子机可访问的主站 HTTPS 地址），否则长任务接口返回 400

## API

| 端点 | 认证 | 说明 |
|------|------|------|
| `POST /api/scripts/exec` | JWT | 请求体增加 `long_task: true` |
| `POST /api/agent/script-callback` | `job_id` + `secret`（一次性） | 子机 curl，无需 JWT |

回调 JSON 示例：

```json
{
  "job_id": "…",
  "server_id": 12,
  "secret": "…",
  "exit_code": 0,
  "message": "done"
}
```

## Redis → MySQL

| 常量 | 值 | 文件 |
|------|-----|------|
| `REDIS_TTL_SECONDS` | 7200 | `script_execution_store.py` |
| `FLUSH_INTERVAL_SECONDS` | 60 | 同上 |
| `FLUSH_BATCH_LIMIT` | 50 | 同上 |
| `STUCK_NO_PROGRESS_SECONDS` | 3600 | 同上 |

- 全部状态变更只写 Redis；读接口优先 Redis
- 后台 `script_execution_flush`：刷盘 + `detect_stuck_executions`
- `results._meta.events` 时间线；`audit_logs` 即时写入

## 审计 action

`execute_started`、`execute_command`、`stop_execution`、`retry_execution`、`mark_stuck`、`script_job_callback`、`auto_stuck`、`server_auto_stuck`

## 子机 Agent（短任务停止）

| 接口 | 说明 |
|------|------|
| `POST /exec` + `wait: false` | 返回 `pid` |
| `POST /exec/wait` | 等待结束 |
| `POST /exec/kill` | 终止进程 |

未升级 Agent 时无法短任务中途停止。

## 涉及文件

- `server/application/services/script_jobs.py`
- `server/application/services/script_job_callback.py`
- `server/infrastructure/redis/script_execution_store.py`
- `server/background/script_execution_flush.py`
- `server/api/agent.py`
- `web/app/scripts.html`、`web/app/script-executions.html`
- `web/agent/agent.py`
