- 工作目录探测新增 pyproject.toml / requirements*.txt 扫描、uv 可用性与 can_create - 新增 POST /api/v1/python/venv/create:已有虚拟环境优先复用;pyproject 走 uv sync,requirements 走 uv venv + uv pip install,并使用默认 PyPI 镜像 - 编辑器在缺少虚拟环境且具备依赖文件时显示「用 uv 创建环境」;没有 uv 或没有依赖文件时明确报错 - 解释器字段改为可选,留空由服务端默认 python.exe 并使用虚拟环境;Python 任务缺少虚拟环境/依赖文件时运行直接失败,不再回退系统解释器 - 补后端探测/创建测试、前端入口测试与 README
28 KiB
TaskPilot
TaskPilot 是一个面向 Windows 单机 的本地定时任务管理平台:React 提供任务管理与观测界面,Rust 负责 Cron 调度、进程治理、运行日志与本地 API。
- 单用户、单实例,只监听
127.0.0.1 - 三类任务:Python/Shell 脚本、HTTP 请求、二进制进程
- SQLite 持久化,重启后任务与历史仍在
- 运行日志实时推送(SSE),按窗口合并事件;断线自动回退轮询,长日志用虚拟滚动展示
- Python 包镜像管理:内置常用 PyPI 源,默认镜像在任务运行时注入
PIP_INDEX_URL/UV_INDEX_URL - 前端零外部网络依赖:样式与组件来自同级的
stash-ui设计系统(file:依赖,构建后随包自带);界面字体可选,未生成时自动回退系统字体
根目录的
task_pilot.html只是最初的单文件视觉原型,正式入口是 React 应用。
目录
- 快速开始
- 功能总览
- 任务模型
- 调度与执行语义
- 日志与编码
- Python 与虚拟环境
- Web 界面
- 安全模型
- 配置项
- API 参考
- 输入校验
- 数据与文件布局
- 架构与模块
- 开发与测试
- 故障排查
- 已知限制
- 架构决策记录
快速开始
方式一:打包版(推荐)
.\scripts\build-release.ps1 # 构建前端 + Release 可执行文件 + 可分发目录
.\release\TaskPilot\start.ps1 # 启动并访问 http://127.0.0.1:3000
start.ps1 -OpenBrowser 会自动打开浏览器。产物目录结构:
release/TaskPilot/
├── task-pilot-backend.exe # 单可执行文件(内嵌 Web 资源托管)
├── web/ # 前端静态资源(字体可选)
├── data/task_pilot.db # SQLite 数据库(首次启动创建)
├── logs/{task_id}/{run_id}.log
├── start.ps1
└── README.md # 即 docs/RELEASE.md
方式二:本地开发
# 前端依赖 @stash/ui(file:../../stash-ui),首次使用先在组件库构建 dist
npm --prefix ..\stash-ui install
npm --prefix ..\stash-ui run build
pnpm --dir frontend install
cargo run --manifest-path backend/Cargo.toml # 终端一:API,127.0.0.1:3000
pnpm --dir frontend dev # 终端二:http://127.0.0.1:5173
Vite 把 /api 代理到后端。改后端端口时(TASK_PILOT_PORT)需同步修改
frontend/vite.config.ts 的代理目标;打包版同源,不需要改前端。
@stash/ui 走 file: 依赖(pnpm 会复制一份构建产物):组件库重新 npm run build
之后,需要再执行一次 pnpm --dir frontend install 才会同步新的 dist。
方式三:用 uv 跑官方示例与自检
仓库自带一个 uv 管理的演示任务和一个端到端自检脚本:
# 1) 在示例目录创建虚拟环境(Python 3.11)
cd samples\python-task
uv venv
cd ..\..
# 2) 启动一个临时后端(任意终端)
cargo run --manifest-path backend/Cargo.toml
# 3) 运行 14 项端到端自检
uv run --no-project tools/verify.py --base-url http://127.0.0.1:3000
自检会真实创建任务、立即运行、读回日志,校验:健康检查、令牌、目录列举、
虚拟环境探测、创建/调度注册、运行成功、中文日志、VIRTUAL_ENV 注入、
无替换字符、统计口径、删除任务。
[OK ] venv probe links the demo environment status=linked path=.venv\Scripts\python.exe
[OK ] task is registered in the schedule next_run_at=2026-10-25T19:00:00Z
[OK ] run succeeded status=succeeded exit=0
[OK ] log contains Chinese output
14/14 checks passed
--keep 保留任务,--working-dir 可换成任意含 .venv 的目录。
功能总览
| 分组 | 能力 |
|---|---|
| 任务管理 | 增删改查、启用/停用、按 ID/名称搜索、按启用状态与 Action 类型筛选、分页 |
| 调度 | 5/6 位 Cron(支持秒级、别名、月份/星期名)、IANA 时区、启动恢复调度、下次触发时间 |
| 执行 | 手动立即运行;同一任务重叠触发记为 skipped;启动时对账遗留运行 |
| 进程治理 | Windows Job Object 管理进程树;超时或取消时终止整棵树 |
| 观测 | 运行历史(状态/触发方式筛选)、退出码、耗时、错误信息、日志下载 |
| 日志 | SSE 实时推送 + 增量轮询 + 断线重连;编码自动识别;敏感值脱敏;容量与保留期清理 |
| Python | 自动关联 .venv/venv/env、解释器可选、用 uv 从 pyproject.toml / requirements*.txt 创建环境、注入 PATH/VIRTUAL_ENV/PYTHONIOENCODING、默认 PyPI 镜像注入 |
| 界面 | 左侧导航(任务调度 / Python 镜像)、三类动态表单、Cron 预览、虚拟环境状态条、页面内目录浏览器、深色日志弹窗 |
| 安全 | 仅回环监听、Origin 白名单、写操作令牌、常量时间比较、请求体上限 |
| 交付 | 单进程静态托管、SPA 回退、Release 打包、OpenAPI 契约、可选离线字体 |
任务模型
字段
| 字段 | 说明 |
|---|---|
id |
稳定标识,[A-Za-z0-9_-],1–64 字符;同时决定日志目录名 |
name |
展示名,1–120 字符 |
cron |
5 或 6 位表达式,例:0 0 2 * * *、*/30 * * * * *、@daily |
timezone |
IANA 时区,例:Asia/Shanghai |
enabled |
是否注册调度 |
timeout_secs |
1–86400;超时终止整个进程树 |
concurrency_policy |
目前仅 skip(同任务重叠触发被跳过) |
retry_policy |
max_attempts ≤ 10、delay_secs ≤ 3600;当前只持久化,不自动重试 |
action |
见下 |
三类 Action
// 脚本
{ "type": "script", "working_dir": "D:\\Projects\\sync", "venv": null,
"interpreter": "python.exe", "script": "main.py",
"args": ["--mode", "full"], "env": { "PYTHONUNBUFFERED": "1" } }
// HTTP
{ "type": "http", "url": "https://api.example.com/health", "method": "GET",
"headers": { "X-Token": "..." }, "body": null, "expected_status": [200, 204] }
// 进程
{ "type": "process", "executable": "C:\\Tools\\agent.exe",
"working_dir": "C:\\Tools", "args": ["--serve"], "env": {} }
运行状态机
queued ──► running ──► succeeded / failed / timed_out / cancelled
└────────────────► skipped (同一任务已有运行实例)
终态不可再取消;进程被强杀后,下次启动会把遗留的 queued/running
统一标记为 cancelled,并写入「TaskPilot 进程重启,运行被中断」。
调度与执行语义
- 三类任务都参与调度。启用即注册,停用即取消;后端重启后从数据库恢复注册。
- 每个任务一个独立 Tokio 循环:计算下次触发 → 等待 → 调用执行器。
- 重叠触发:同一任务已有运行实例时,新触发记为
skipped,不会并发。 - 没有全局并发上限:10 个任务同时到点会各自启动一个进程。
- 取消:通过
CancellationToken+TerminateJobObject,5 秒内未退出则kill。 - 超时:与取消同一路径,状态记为
timed_out。 - 取消后立即重跑:允许;旧进程终止与新进程启动可能短暂重叠。
- 错过的触发不补跑:下次触发始终从"现在"开始计算。
- 下次触发时间:调度器保存在内存中,
GET /api/v1/schedule暴露给前端。 - 重试:字段已持久化但执行器尚未实现自动重试(见「已知限制」)。
日志与编码
文件布局:logs/{task_id}/{run_id}.log(UTF-8 写入)
- 每次运行 5 MiB 上限,超出后丢弃后续输出并写一条
[system]说明。 - 单行 256 KiB 上限:超出部分丢弃、继续读到行尾,避免内存无界增长。
- 保留期(默认 30 天)与总容量(默认 512 MiB)清理;跳过正在写入的日志。
- 读取按 64 KiB 分块且对齐 UTF-8 边界;
complete只在"运行结束且读到文件末尾"时为真。 - 合并推送:子进程输出按行写入文件后,先在内存里按 100 ms / 16 KiB 窗口合并,
每个窗口只发一条 SSE
log事件(偏移仍严格连续);低输出时由后台任务按时推, 运行结束时在LogSink析构里兜底 flush,不会丢尾部。 - 虚拟滚动:日志抽屉只渲染视口附近的行(固定行高 20 px),几万行时 DOM 节点数恒定; 增量先并进行缓冲、一帧最多重渲染一次。往上翻时停止跟随并显示「回到最新」。长行横向滚动、不折行。
- 输出编码:默认
auto= 逐行先按 UTF-8 解码,失败回退到系统 ANSI 代码页(简体中文为 GBK)。 可用TASK_PILOT_LOG_ENCODING=gbk|utf-8|windows-1251强制。 - 脱敏:环境变量/Header 名包含
SECRET、TOKEN、PASSWORD、PASS、API_KEY、PRIVATE_KEY、CREDENTIAL、AUTHORIZATION、AUTH时,其值在日志中被替换为[REDACTED]。
Python 与虚拟环境
编辑器和执行器共用同一份解析逻辑:
- 手动指定的
action.venv - 从解释器路径推断(
...\.venv\Scripts\python.exe) - 从工作目录向上查找
.venv、venv、env
虚拟环境必须同时存在 pyvenv.cfg 与 Scripts\python.exe,否则显式配置在保存时被拒绝。
没有虚拟环境时:
- 工作目录根部有
pyproject.toml或requirements*.txt,且 PATH 中能找到uv: 编辑器会显示「用 uv 创建环境」按钮,点击后pyproject.toml走uv sync,仅 requirements 走uv venv .venv+uv pip install -r。 已有.venv/venv/env会优先复用,不会重复创建。创建时会使用当前默认 PyPI 镜像 (PIP_INDEX_URL/UV_DEFAULT_INDEX),不会绕过镜像设置。 - 没装 uv:必须手动准备虚拟环境;Python 任务缺少
.venv时运行失败并给出提示。 - 既没有虚拟环境、也没有 pyproject/requirements:编辑器显示错误,Python 任务运行失败。 Python 任务不再静默回退到系统解释器。
- 解释器字段是可选的:留空表示 Python,运行时会自动使用虚拟环境里的
python.exe; Node / Shell 等非 Python 脚本再填写具体可执行程序。
命中虚拟环境后注入:
| 变量 | 行为 |
|---|---|
PATH |
虚拟环境 Scripts 目录置前,保留用户 PATH,按大小写不敏感去重 |
VIRTUAL_ENV / VIRTUAL_ENV_PROMPT |
指向虚拟环境根目录 |
PYTHONUNBUFFERED |
强制 1(实时日志依赖) |
PYTHONIOENCODING |
默认 utf-8,用户显式设置优先;避免 GBK 控制台下打印 emoji/生僻字符直接崩溃 |
PYTHONHOME / PYTHONPATH |
虚拟环境模式下清理继承的污染值 |
编辑器会实时显示 ● 已关联虚拟环境: .venv\Scripts\python.exe,并同步更新调用预览。
Web 界面
- 左侧导航:任务调度 / Python 镜像两个入口;底部显示 Rust 引擎在线状态与版本。
- 指标卡:已托管任务、运行中任务、可用脚本、引擎在线状态。
- 任务表:名称/ID + 类型标签、时区、调度周期(Cron + 下次触发时间)、状态、超时、操作。
- 编辑器:三类动态表单、Cron 预览(实时计算未来 3/5/8/10 次)、时区下拉、虚拟环境状态条、调用预览; 探测到虚拟环境会自动回填「虚拟环境目录」,仍可手动修改;缺少虚拟环境但项目有依赖文件时可直接用 uv 创建。 解释器字段可选,留空自动使用虚拟环境 Python。
- 目录选择:页面内目录浏览器(盘符 → 逐级进入 → 使用此目录),同时保留手动输入。
- 日志弹窗:运行历史筛选、SSE 实时日志、取消运行、下载日志、Escape/焦点陷阱。
- Python 镜像:内置清华 TUNA / 阿里云 / 中科大 / PyPI 官方;可新增、编辑、启用停用、设为默认。
默认镜像在 Python 脚本任务启动时注入
PIP_INDEX_URL/UV_INDEX_URL(任务自身配置的环境变量优先),不会自动安装依赖。 - 设计系统:按钮、输入框、表格、标签、分页、弹窗、Toast、排版全部来自
@stash/ui(同级目录../stash-ui,file:依赖);本仓库只保留页面级布局与 业务块(侧栏、任务表单元格、日志控制台、表单栅格)。 - 视觉:对齐 Alright 主题(
#3858f6主色、灰阶体系、0.3s 缓动);正文使用LXGW WenKai、标题与数字使用Outfit-Medium。字体二进制不入库,用tools/build-fonts.py生成,缺失时回退系统字体。
安全模型
| 边界 | 实现 |
|---|---|
| 监听地址 | 固定 127.0.0.1(TASK_PILOT_PORT 只改端口) |
| Origin | 带 Origin 的请求必须是 http://127.0.0.1:* 或 http://localhost:*,否则 403 |
| 管理令牌 | 非 GET/HEAD/OPTIONS 必须带 X-TaskPilot-Token;进程启动时随机生成,重启轮换 |
| 比较方式 | 常量时间比较,避免时序侧信道 |
| 请求体 | 上限 2 MiB |
| 命令执行 | 参数以 argv 传递,不经过 Shell 拼接 |
| 读取接口 | 只读接口(任务列表、日志、目录列举、统计)免令牌 |
| 前端令牌 | 客户端缓存令牌;写请求 403 时丢弃缓存并重试一次 |
这是本地单用户工具的威胁模型:本机其他进程仍可调用只读接口。不要把端口暴露到局域网。
配置项
| 变量 | 默认值 | 说明 |
|---|---|---|
TASK_PILOT_PORT |
3000 |
监听端口(地址固定回环) |
TASK_PILOT_DATABASE_URL |
sqlite://data/task_pilot.db |
SQLite 路径;sqlite::memory: 用于测试 |
TASK_PILOT_LOG_DIR |
logs |
运行日志根目录 |
TASK_PILOT_STATIC_DIR |
frontend/dist |
生产静态资源目录(打包版由 start.ps1 设为 web) |
TASK_PILOT_LOG_RETENTION_DAYS |
30 |
日志保留天数 |
TASK_PILOT_LOG_MAX_TOTAL_MB |
512 |
日志总容量上限 |
TASK_PILOT_LOG_ENCODING |
auto |
子进程输出编码;auto 为 UTF-8→系统代码页回退 |
RUST_LOG |
task_pilot_backend=debug,tower_http=debug |
日志级别 |
数据库连接:SQLite 开启 foreign_keys、WAL、busy_timeout=5s、synchronous=NORMAL,最多 5 个连接。
API 参考
系统
| 方法 | 路径 | 说明 |
|---|---|---|
GET |
/api/v1/health |
健康检查 |
GET |
/api/v1/session |
获取当前进程的管理令牌 |
GET |
/api/v1/openapi.json |
运行时 OpenAPI 文档 |
POST |
/api/v1/cron/preview |
计算未来若干次触发时间 |
POST |
/api/v1/python/venv |
探测工作目录会关联的虚拟环境、uv 可用性与依赖文件 |
POST |
/api/v1/python/venv/create |
用 uv 创建/同步项目 .venv(优先复用已有环境) |
GET |
/api/v1/system/directories?path= |
列盘符或子文件夹(目录浏览器) |
GET |
/api/v1/stats |
仪表盘计数(总数/启用/运行中/三类数量) |
GET |
/api/v1/schedule |
已启用任务的下次触发时间 |
Python 镜像
| 方法 | 路径 | 说明 |
|---|---|---|
GET |
/api/v1/python/mirrors |
镜像列表(默认项优先) |
POST |
/api/v1/python/mirrors |
新增镜像 |
GET |
/api/v1/python/mirrors/{id} |
镜像详情 |
PUT |
/api/v1/python/mirrors/{id} |
更新名称 / 地址 / 信任主机 / 启用状态 |
DELETE |
/api/v1/python/mirrors/{id} |
删除镜像 |
POST |
/api/v1/python/mirrors/{id}/default |
设为默认(同一时间仅一个默认,并自动启用) |
任务与运行
| 方法 | 路径 | 说明 |
|---|---|---|
GET |
/api/v1/tasks |
任务列表(q、enabled、action_type、page、page_size) |
POST |
/api/v1/tasks |
创建任务 |
GET |
/api/v1/tasks/{id} |
任务详情 |
PUT |
/api/v1/tasks/{id} |
完整更新 |
DELETE |
/api/v1/tasks/{id} |
删除(级联删除运行记录) |
POST |
/api/v1/tasks/{id}/enable |
启用并注册调度 |
POST |
/api/v1/tasks/{id}/disable |
停用并取消调度 |
POST |
/api/v1/tasks/{id}/run |
手动立即执行(202) |
GET |
/api/v1/tasks/{id}/runs |
运行历史(status、trigger、分页) |
GET |
/api/v1/runs/{run_id} |
单次运行详情 |
GET |
/api/v1/runs/{run_id}/logs?offset=0 |
增量日志(content、next_offset、complete) |
GET |
/api/v1/runs/{run_id}/logs/download |
下载完整日志 |
POST |
/api/v1/runs/{run_id}/cancel |
取消运行 |
GET |
/api/v1/events |
SSE:run / log / lagged / ready |
示例
创建任务:
POST /api/v1/tasks
X-TaskPilot-Token: <session token>
Content-Type: application/json
{
"id": "sync_data",
"name": "数据同步",
"cron": "0 0 2 * * *",
"timezone": "Asia/Shanghai",
"enabled": true,
"timeout_secs": 300,
"action": {
"type": "script",
"working_dir": "D:\\Projects\\sync",
"interpreter": "python.exe",
"script": "main.py",
"args": ["--mode", "full"],
"env": {}
}
}
统计数据:
{ "total": 12, "enabled": 9, "running": 1, "script": 7, "http": 3, "process": 2 }
调度信息:
[ { "task_id": "sync_data", "next_run_at": "2026-10-26T18:00:00Z" } ]
统一错误结构:
{ "code": "VALIDATION_ERROR", "message": "Cron 表达式无效", "field": "cron", "details": {} }
常见错误码:VALIDATION_ERROR、INVALID_JSON、INVALID_QUERY、TASK_NOT_FOUND、
RUN_NOT_FOUND、RUN_LOG_NOT_FOUND、RUN_NOT_CANCELLABLE、TASK_ID_CONFLICT、
DIRECTORY_PATH_INVALID、DIRECTORY_NOT_FOUND、DIRECTORY_UNREADABLE、FORBIDDEN、INTERNAL_ERROR。
输入校验
保存任务时逐项强制(失败返回 400 VALIDATION_ERROR 并带 field):
| 字段 | 规则 |
|---|---|
id |
1–64,[A-Za-z0-9_-];重复返回 409 TASK_ID_CONFLICT |
name |
去空格后 1–120 字符 |
cron |
可解析且存在未来触发点 |
timezone |
有效 IANA 名称 |
timeout_secs |
1–86400 |
retry_policy |
max_attempts ≤ 10,delay_secs ≤ 3600 |
action.working_dir / script |
非空 |
action.interpreter |
可空;留空时服务端默认成 python.exe,Python 任务会用虚拟环境解释器 |
action.venv |
若填写,必须存在 pyvenv.cfg 与 Scripts\python.exe |
action.args |
≤ 256 个,单个 ≤ 2048 字符 |
action.env |
≤ 128 条;key ≤ 128、不含 =/\0;value ≤ 8192、不含 \0 |
action.url |
可解析、限 http/https、必须有主机名 |
action.headers |
名称/值符合 HTTP 规范 |
action.body |
≤ 1 MiB |
action.expected_status |
每项 100–599 |
| 请求体 | 未知字段直接拒绝(deny_unknown_fields) |
查询与读取:page ≥ 1、page_size 夹到 1–100、日志 offset 不得超过文件长度。
不在保存时校验:工作目录是否存在、脚本文件是否存在、解释器是否存在(虚拟环境除外)。 这些在执行时才暴露,属于日志里的运行错误。
数据与文件布局
data/task_pilot.db # SQLite(WAL)
logs/{task_id}/{run_id}.log # UTF-8 运行日志
.tmp/ # check.ps1 的测试临时目录(已 gitignore)
tasks 表:主键 id,索引 updated_at、enabled、action_type。
task_runs 表:task_id 外键 ON DELETE CASCADE,status/trigger_type 带 CHECK 约束,
索引 (task_id, created_at DESC) 与 status。
架构与模块
React (Vite) ──fetch──► Axum router ──security::api_guard(Origin + token)
│
├── tasks: routes → service(校验) → repository(SQL)
├── runs : routes → repository
├── Scheduler ──trigger──► TaskExecutor ──► Job Object
└── broadcast(512) ──► SSE /api/v1/events
| 模块 | 职责 |
|---|---|
api.rs |
路由、OpenAPI 聚合、系统级接口(health/session/cron/stats/schedule) |
tasks/ |
任务模型、校验、SQL、CRUD 路由 |
runs/ |
运行记录、执行器、日志落盘、日志读取与 SSE |
scheduler.rs |
每任务一个调度循环、下次触发时间、注册与取消 |
executor.rs |
三类 Action 执行、并发闸门、超时/取消、日志脱敏与截断 |
process_job.rs |
Windows Job Object(KILL_ON_JOB_CLOSE) |
python_env.rs |
虚拟环境解析与探测、PATH/编码注入 |
python_mirrors/ |
PyPI 镜像的模型、校验、CRUD 与默认项事务 |
directory_browser.rs |
盘符与目录列举(页面内目录选择器) |
security.rs |
Origin 白名单、令牌校验、常量时间比较 |
database.rs |
连接池与迁移(WAL/busy_timeout) |
前端:
| 模块 | 职责 |
|---|---|
@stash/ui(同级 ../stash-ui) |
设计令牌与通用组件:按钮、输入、表格、标签、弹窗、分页、Toast、排版 |
components/AppSidebar.tsx |
左侧导航、品牌、引擎状态 |
components/PageHeader.tsx |
页面标题区(标题 / 说明 / 操作) |
pages/TaskDashboard.tsx |
列表、筛选、分页、指标卡、下次触发时间 |
pages/PythonMirrorsPage.tsx |
Python 镜像列表、默认项、新增 / 编辑 / 删除 |
components/PythonMirrorModal.tsx |
镜像表单(名称 / 索引地址 / 信任主机 / 启用) |
components/TaskEditorModal.tsx |
三类动态表单、Cron 预览、虚拟环境状态条 |
components/DirectoryPickerDialog.tsx |
页面内目录浏览器 |
components/RunLogDrawer.tsx |
运行历史、日志、取消、下载、SSE |
components/LogViewer.tsx |
日志虚拟滚动、跟随底部/回到最新 |
lib/logLines.ts |
增量日志的行缓冲(残行拼接、CRLF 归一) |
lib/logWindow.ts |
可见行窗口计算 |
api/client.ts |
类型化客户端、令牌缓存、403 重试 |
lib/taskForm.ts |
Zod 校验与表单 ↔ API 载荷映射 |
开发与测试
环境
- Windows 10/11
- Rust 1.95(
rust-toolchain.toml固定,MSVC) - Node.js 24 / pnpm 11(
frontend/package.json的volta固定) - Visual Studio C++ Build Tools(Rust MSVC 需要)
- 可选:
uv(跑示例任务与自检脚本)
完整质量门
.\scripts\check.ps1
依次执行:前端 lint → 类型检查 → Vitest → 生产构建 → cargo fmt --check →
clippy -D warnings → cargo test → Playwright(真实后端 + Vite + Chrome)。
脚本会把 TMP/TEMP 指向仓库内 .tmp/,让测试不依赖系统临时目录的权限策略,
结束时自动清理。
单独命令
pnpm --dir frontend lint | typecheck | test | build
cargo fmt --manifest-path backend/Cargo.toml -- --check
cargo clippy --manifest-path backend/Cargo.toml --all-targets -- -D warnings
cargo test --manifest-path backend/Cargo.toml
pnpm --dir frontend test:e2e
契约与类型
.\scripts\generate-api.ps1 # 更新 contracts/openapi.json 与 frontend/src/api/generated.ts
生成文件不要手工修改。
界面字体(可选)
字体二进制体积约 6.7 MB,不纳入仓库。首次克隆后如需与设计一致的中文界面:
uv run --with fonttools --with brotli tools/build-fonts.py
脚本会从 lxgw-wenkai-webfont 的 CDN 下载分包、合并并裁剪到 GB2312 + 中英文标点,
输出到 frontend/public/fonts/(已被 .gitignore 忽略)。不生成也能正常运行,
只是正文回退到系统字体;Outfit-Medium.ttf(55 KB)仍随仓库提供。
测试构成
| 层 | 覆盖 |
|---|---|
| Rust 单元(59) | Cron、虚拟环境解析与探测、项目依赖文件扫描、uv 可用性、执行器(编码/截断/清理/句柄归属/日志合并推送)、安全、目录列举 |
| Rust 集成(38) | 任务 CRUD 与校验、运行生命周期、取消与超时、日志分块、调度恢复、统计与调度接口、重启对账、镜像 CRUD、venv 探测/创建 |
| 前端 Vitest(30) | 仪表盘渲染、侧栏导航到镜像页、表单载荷、虚拟环境状态、uv 创建入口、目录浏览器、下次触发时间、日志行切分/窗口计算/虚拟滚动 |
| Playwright(6) | 真实任务执行与日志、编辑/停用/删除、窄屏、无外部网络渲染、1500 行日志的虚拟滚动、镜像增删与设默认 |
| Python 自检(14 项) | tools/verify.py 覆盖 API 全链路 |
故障排查
| 现象 | 处理 |
|---|---|
start.ps1 无法运行 |
powershell -ExecutionPolicy Bypass -File .\release\TaskPilot\start.ps1 |
| 端口被占用 | $env:TASK_PILOT_PORT = "3100" 后重启 |
| 任务不执行 | 确认已启用、Cron 与下次触发时间正确;查看运行历史里的错误信息 |
| PowerShell 脚本任务报「不是数字签名」 | 执行策略拦未签名脚本:在参数里加 -ExecutionPolicy Bypass,或对脚本目录 Unblock-File |
日志出现 ���� |
程序未按 UTF-8 输出:设置 TASK_PILOT_LOG_ENCODING=gbk(或让程序输出 UTF-8) |
| 日志尾部缺失 | 单次运行超过 5 MiB 或单行超过 256 KiB,会截断并写 [system] 说明 |
| 运行一直显示"运行中" | 已由启动对账修复;若仍出现,检查进程是否被系统挂起 |
| 虚拟环境未生效 | 目录下需同时有 pyvenv.cfg 与 Scripts\python.exe;也可在表单里显式指定 |
cargo test 报临时目录拒绝访问 |
用 .\scripts\check.ps1(会把 TEMP 指向 .tmp/)或手动设置 TEMP 到可写目录 |
已知限制
- 自动重试尚未实现:
retry_policy只校验与存储。 - 并发策略只有
skip;没有全局并发上限与队列。 - HTTP Action 只校验状态码,不支持响应体断言、认证方案或 Header 断言。
- 敏感值脱敏基于变量名关键词与值替换,不是凭据保险箱。
- Conda 环境不会自动激活,需要显式填写解释器路径。
- 用 uv 创建环境是编辑器里的显式操作;运行中的任务不会在后台自动安装依赖。首次创建需要网络与可写的 uv 缓存目录。
- 本地单用户工具:读取接口免令牌,不支持远程部署与多用户。
- 未安装为 Windows 服务;关闭启动脚本后调度停止。
- 错过的调度不会补跑。
- 界面字体不入库(体积原因),需用
tools/build-fonts.py生成;缺失时回退系统字体。
架构决策记录
- ADR-0001:Cron 解析与任务调度
- ADR-0002:前后端开发与生产托管
- ADR-0003:Python 虚拟环境启动环境
- ADR-0004:运行日志的输出编码
- ADR-0005:由后端提供系统文件夹选择
- ADR-0006:视觉风格对齐主题并自托管字体
- ADR-0007:运行期可靠性优化
- ADR-0008:日志合并推送与虚拟滚动
开发计划
完整架构、数据模型、阶段任务与验收标准见 PLAN.md。六个阶段均已完成,当前处于维护与迭代阶段。