# 实施计划 — 脚本库全局执行进度 + Telegram 通知

**日期**: 2026-06-08  
**设计**: [2026-06-08-script-exec-global-progress-telegram-design.md](../specs/2026-06-08-script-exec-global-progress-telegram-design.md)

## 分期概览

| 阶段 | 内容 | 估时 | 依赖 |
|------|------|------|------|
| **P0** | 后端异步 exec + terminal hook | 1d | — |
| **P1** | WS `script_progress` / `script_complete` | 0.5d | P0 |
| **P2** | Telegram `send_telegram_script_complete` | 0.5d | P0 |
| **P3** | 前端全局 SnackbarQueue + Progress + Alert + 声音 | 1d | P1 |
| **P4** | ScriptsPage 接入 / 移除页内面板 | 0.5d | P3 |
| **P5** | 测试、changelog、audit、部署 | 0.5d | P0–P4 |

**合计**：约 3–4 人日（单 Agent 连续实施）。

---

## P0 — 后端：异步执行（关键路径）

### 涉及文件

| 文件 | 变更 |
|------|------|
| [server/application/services/script_service.py](../../server/application/services/script_service.py) | `execute_command` 拆为 `start_execution` + `_run_execution_batches` |
| [server/api/scripts.py](../../server/api/scripts.py) | `/exec`、`/retry` 立即返回 running |
| [server/main.py](../../server/main.py) | 确保 task 异常日志 + 可选 task 注册防 shutdown 丢失 |

### 步骤

1. `execute_command` 创建 DB/Redis 记录后 `asyncio.create_task(_run_execution_batches(...))`。
2. 将现有 batch 循环移入 `_run_execution_batches`，逻辑不变。
3. Task 顶层 `try/except`：异常时 `apply_update(status=failed, terminal=True)`。
4. API 返回 `get_execution_detail(id)`（status=running, progress=0/N）。
5. **注意**：同一 worker 内 task 即可；多 worker 时 Redis live 已是 SSOT（已满足）。

### 测试

- `tests/test_script_execution_async.py`：mock dispatch，断言 POST /exec 立即返回 & 后台终态。

---

## P1 — WebSocket 进度广播

### 涉及文件

| 文件 | 变更 |
|------|------|
| [server/api/websocket.py](../../server/api/websocket.py) | `broadcast_script_progress` / `broadcast_script_complete` |
| [server/infrastructure/redis/script_execution_store.py](../../server/infrastructure/redis/script_execution_store.py) | `apply_update` 中调用 broadcast（或 script_service 批次回调） |
| [server/application/services/script_job_callback.py](../../server/application/services/script_job_callback.py) | long_task 单台 callback 时也 broadcast |

### 步骤

1. 定义消息 schema（见 design doc）。
2. 每批 `batch_progress` 或每台完成后推送（366 台建议**每完成 1 台或每批**推送，避免 WS 风暴可 batch 每 5 台）。
3. 前端 [useWebSocket.ts](../../frontend/src/composables/useWebSocket.ts) 解析 `script_progress` / `script_complete`。

---

## P2 — Telegram 脚本执行结果

### 涉及文件

| 文件 | 变更 |
|------|------|
| [server/infrastructure/telegram/__init__.py](../../server/infrastructure/telegram/__init__.py) | `send_telegram_script_complete` |
| [server/config.py](../../server/config.py) | `NOTIFY_SCRIPT_COMPLETE` |
| [server/api/settings.py](../../server/api/settings.py) | 设置项读写（若 NOTIFY_* 走 settings 表） |
| terminal hook | 组装 failed 列表（server name + exit_code + stderr 首行） |

### 步骤

1. 复用 `send_telegram_sync_complete` 结构与 `_notify_enabled`。
2. 失败明细：查 server repo 取 name；summary 用 `sanitize_external_message`。
3. 超长分片或截断 + 指向 execution #id。
4. 单测：mock httpx，断言 HTML 含失败台名。

---

## P3 — 前端全局 UI（Vuetify 4）

### 涉及文件

| 文件 | 变更 |
|------|------|
| [frontend/src/App.vue](../../frontend/src/App.vue) | `VSnackbarQueue` 替换单条 `v-snackbar`；挂载 `ScriptExecProgressHost` |
| **新建** `frontend/src/components/script/ScriptExecProgressHost.vue` | Queue item 模板：ProgressLinear chunks + 文案 |
| **新建** `frontend/src/composables/useScriptExecutionQueue.ts` | track / onProgress / onComplete / playSound |
| **新建** `frontend/src/components/script/ScriptExecCompleteAlert.vue` | VAlert closable + 1s leave transition |
| `frontend/public/notify.mp3` 或 assets | 提示音（短、免版权） |

### UI 行为

1. **进行中**：SnackbarQueue 顶部，`location="top"`，`timeout="-1"`（手动关闭或完成关闭）。
2. **进度条**：`v-progress-linear` `rounded` `height="6"` `:model-value="done/total*100"` `:buffer-value="..."` 制造流动感（见 Vuetify chunks 示例）。
3. **完成**：
   - Snackbar queue 项 resolve（Promise API）。
   - 显示 `VAlert`（type=success/warning/error），`closable`，点击关闭触发 `v-fade-transition` 1000ms。
   - `new Audio(...).play().catch(() => {})`（静默处理自动播放策略拦截）。
4. Alert 可点「查看详情」→ `router.push('#/scripts')` + `openExecDetail(id)`（通过 event bus 或 query）。

### 与现有 `$snackbar` 兼容

- 保留 `window.$snackbar` 给普通 toast；脚本进度走独立 queue，避免互相覆盖。

---

## P4 — ScriptsPage 接入

### 涉及文件

| 文件 | 变更 |
|------|------|
| [frontend/src/pages/ScriptsPage.vue](../../frontend/src/pages/ScriptsPage.vue) | `submitExec` → `queue.track()`；删除/隐藏页内「批量执行状态」卡片 |
| [frontend/src/pages/SchedulesPage.vue](../../frontend/src/pages/SchedulesPage.vue) | 若 schedule 触发 exec，同样 track（可选 P4+） |

### 步骤

1. `doRun` / `doQuickExec`：POST 成功后 `showRun=false`，调用 `queue.track(id, label, total)`。
2. 移除 `trackedExecs` / `pollTimer` / `activeTrackedExecs`（逻辑迁至 composable）。
3. WS 断线时 composable 降级 3s 轮询直至终态。

---

## P5 — 验证与交付

```bash
pytest tests/test_script_execution_async.py tests/test_telegram_script.py -q
cd frontend && npx vite build
bash scripts/local_verify.sh
```

### 手动验收

1. 生产 10 台脚本：顶部进度 + 完成 Alert + 声音。
2. 366 台（或 staging）：Telegram 汇总与失败明细。
3. 关闭 Telegram 通知设置后无推送。

### 文档

- `docs/changelog/2026-06-08-script-exec-global-progress-telegram.md`
- `docs/audit/2026-06-08-script-exec-global-progress-telegram.md`
- 更新 [AI-HANDOFF-2026-06-08.md](../../project/AI-HANDOFF-2026-06-08.md)

### 回滚

- 后端：revert commit；进行中的 execution 靠 Redis live 继续，无 schema 迁移。
- 前端：revert + vite build deploy。

---

## 风险与对策

| 风险 | 对策 |
|------|------|
| 366 台 WS 消息过多 | 节流：每批或每 5 台推送一次 |
| 浏览器拦截自动播放 | 首次用户手势后解锁；Alert 仍可见 |
| 异步 exec 重复提交 | 可选：同脚本 running 时 confirm（P4+） |
| Telegram 过长 | 截断 + 链接 execution id |
| 多 Tab 重复声音 | `localStorage` leader election 或仅 focused tab 播放 |

---

## 建议实施顺序（给 Agent）

```
P0 异步 API → P1 WS → P2 Telegram → P3 全局 UI → P4 ScriptsPage 接入 → P5 验证部署
```

**请先确认设计再写代码**（perfect-implementation 纪律）。
