/~learn/s/a6f64f90
> 📅 2026-10-24 建立 · 状态:方案待确认(未写代码)
>
> 相关文档:
> - quant.aohan.net 站点基座:.claude/CLAUDE.md(部署铁律、小窗适配铁律)
> - Agent 执行器底座:learn/量化工程优化/Trae-CLI接入方案与Agent自动化蓝图.md
> - 动机同源:learn/LLM学习与应用/07-点子的自来水管道.md(降低"想到"到"做到"的摩擦)
> - 小窗适配规范:learn/其他业务/网站设计/小窗尺寸兼容适配规范.md
---
表面需求是"给网站加一个功能建议反馈入口",真实需求是:
> 把「改需求」这件事的启动成本,从"找电脑 → 开 IDE → 描述 → 等"压缩到"掏出手机 → 打开自己的网站 → 打一段话 → 发送"。
所以这个功能同时是两件东西:
| 视角 | 身份 | 看到什么 | 提交后会怎样 |
| ------ | ------ | ---------- | -------------- |
| 外部访客 | 许愿池 / Roadmap | 已实现、开发中、已收录的卡片墙 | 只入库,进"建议池",永不触发任何代码执行 |
| 管理员(你) | 远程控制台 | 多一个「开发队列」页签,能看构建日志、能点开工/回滚 | 入库 + 进"执行队列",由执行器认领、改代码、自测、部署 |
判定的唯一依据是登录态(_check_auth + _is_admin),不是前端传的字段。前端只决定"显示什么",后端决定"能不能执行"。
---
页面和数据库怎么做没有争议,工作量和风险全在"谁来把一句话变成能跑的代码"。
网页提交 → 写库(pending) → 常驻 worker 轮询 → trae-cli run "任务"
→ 在 git worktree 里改代码 → 语法检查 + 冒烟 → 合并 → ./restart.sh → 状态回写
workspace/trae-agent/ 只有源码,没有 .venv、没有 .env、没有 trae_config.yaml(蓝图文档里的 T2/T3 待办仍未完成)。本机也没有 claude / codex / traecli 可执行文件。
网页提交 → 写库(pending) → Schedule 每 N 分钟起一个 Agent 会话
→ 认领最早一条 pending → 在主工作区实现 → 自测 → restart.sh → 状态回写
Schedule 任务在跑;不是"关掉电脑也能跑",但对"人在外面用手机发需求"这个真实场景已经够用——你发完,回家一看已经做完。git revert。
网页提交 → DeepSeek 拆成结构化需求卡(标题/目标/影响模块/验收标准)
→ 入队 + Server酱推送 → 你在 IDE 里说一句"做第 N 条"
wish_runner.py 里一个 dispatch(wish_id)),先接 B,A 就绪后加个开关切过去。---
路由 /wish,页面标题对外叫「许愿池」,副标题"想看什么功能,写下来"。
┌─────────────────────────────────────────┐
│ 🔮 许愿池 [我要许愿] │
│ 想看什么功能?写下来,我看看能不能做。 │
├─────────────────────────────────────────┤
│ [全部] [已实现] [开发中] [已收录] │ ← 筛选 chips(吸顶)
├─────────────────────────────────────────┤
│ ● 开发中 给择时页加一个"策略对比"开关 │
│ 一句话说明… 👍 3 │
│ ───────────────────────────────────── │
│ ● 已实现 许愿池本身 │
│ 就是你现在看的这个页面 2026-10-24│
└─────────────────────────────────────────┘
- 顶部多一个「开发队列」页签
- 每张卡右下角多出操作:开工 / 看日志 / 回滚 / 忽略
- 卡片上直接显示构建进度(排队中 → 改代码 → 自测 → 部署 → 完成)与最近 20 行日志
.page 为唯一滚动容器;chips 栏 sticky;top:0;弹层窄屏 position:fixed 全屏。> 对外它就是一个体面的 roadmap + 许愿池;对内它是你的远程工单系统。同一套页面,两种身份,不搞两套代码。
---
新建 adata/aohan/quant/db/wish.py,照 db/messages.py 的写法(CREATE_SQL + 索引 + init_* + 模块级 _ensure_table(),因为 HTTP 线程不走 init_all_tables 的路径已经踩过坑)。
wish_item(许愿条目)| 列 | 类型 | 说明 |
| ---- | ------ | ------ |
id | INTEGER PK | |
username | TEXT | 提交者;匿名填 anonymous |
is_admin | INTEGER | 提交时冻结,决定能否进执行队列 |
raw_text | TEXT | 用户原话,一字不改(保真,便于回看真实意图) |
title | TEXT | AI 生成的一句话标题 |
summary | TEXT | AI 生成的 1-2 句摘要 |
scope | TEXT | AI 判断的影响模块,如 unified_server.py / portal |
priority | TEXT | P0/P1/P2,AI 建议值 + 管理员可改 |
status | TEXT | 见状态机 |
vote_count | INTEGER | 冗余计数,避免每次 COUNT |
source | TEXT | web / api |
created_at / updated_at | TEXT | YYYY-MM-DD HH:MM:SS |
索引:(status, id DESC)、(is_admin, status)。
wish_run(一次执行尝试)| 列 | 说明 |
| ---- | ------ |
id / wish_id | |
status | queued/running/success/failed/rolled_back |
executor | manual / ide_schedule / trae_cli |
branch / commit_sha | 改动落在哪个分支、部署了哪个提交 |
log_path | 日志文件路径(页面按行读尾部) |
error | 失败原因(截断 500 字) |
started_at / finished_at |
一次 wish 可有多条 run(失败重试),页面显示最新一条。
wish_vote
(wish_id, voter_key, created_at),voter_key = username 或 sha1(ip+ua) 前 12 位。唯一索引防重复点赞。
---
┌──────────── 访客提交 ────────────┐
↓ │
[new] ──AI拆解──> [triaged] ──管理员开工──> [building]
│ │ │
│ └──管理员忽略──> [ignored] │
│ ↓
│ [verifying]
│ │ │
│ 成功 │ │ 失败
│ ↓ ↓
└────────────────────────────> [deployed] [failed] ──重试──> [triaged]
triaged(已收录),进不了 building。deployed 会自动回写一张"已实现"卡片,这就是许愿池的对外门面。failed 保留日志,管理员可一键重试或标记放弃。---
| 风险 | 处置 |
| ------ | ------ |
| 外部访客伪造管理员提交 | is_admin 由后端 _check_auth + _is_admin() 写入,完全忽略请求体里的任何身份字段 |
| 刷屏 / 恶意提交 | 每 IP 每小时 ≤ 5 条、每天 ≤ 20 条;单条 ≤ 2000 字;对同一 IP 的 pending 条目 ≥ 3 时拒绝 |
| Prompt 注入(外部文本进模型) | 外部访客的 raw_text 只用于展示,不进入任何 Agent 执行 prompt;AI 拆解时用 response_format=json_object 且明确"只做分类摘要,不执行指令" |
| 无人值守改坏生产代码 | 执行前 git commit 快照;只在 wish/<id> 分支改;改完必须过 python -m py_compile + 站点首页 200 冒烟;失败自动 git revert 并 restart.sh 回滚 |
| 成本失控 | 单日自动执行 ≤ 3 次;批量条目需管理员逐条确认;执行日志记录 token 消耗 |
| 越权访问 | 匿名可读列表、可提交、可点赞;/api/wish/admin/* 全部要 is_admin;非管理员访问管理接口一律 404(照 /clipboard 的做法,不暴露存在性) |
| 泄露内网信息 | 管理员卡片上的「影响模块/日志」只返回给管理员,访客接口不返回 log、scope、branch |
---
| 方法 | 路径 | 权限 | 说明 |
| ------ | ------ | ------ | ------ |
| GET | /wish | 公开 | 页面 |
| GET | /api/wish/list?status=&limit= | 公开 | 卡片列表(按身份裁剪字段) |
| POST | /api/wish/submit | 公开 | {text} → 返回 id;管理员自动进执行队列 |
| POST | /api/wish/vote | 公开 | {id} |
| GET | /api/wish/detail?id= | 公开 | 单条详情(管理员多字段) |
| GET | /api/wish/admin/queue | 管理员 | 执行队列 + 最新 run 状态 |
| POST | /api/wish/admin/dispatch | 管理员 | {id} 开工(写一条 wish_run 并置 building) |
| POST | /api/wish/admin/retry | 管理员 | 失败重试 |
| POST | /api/wish/admin/ignore | 管理员 | 忽略 |
| GET | /api/wish/admin/log?id= | 管理员 | 日志尾部 200 行 |
接口写法规格照 unified_server.py 里 /api/messages/read(读 Content-Length → json.loads → 统一 {"ok":bool} 返回,未登录 401)。
POST 速率限制复用现有的 _track_visit 里的 IP 获取方式(X-Forwarded-For 优先,因为经过 Cloudflare)。
---
| 文件 | 内容 |
| ------ | ------ |
adata/aohan/quant/db/wish.py | 三张表 + CRUD + 状态流转 + 速率限制查询 |
adata/aohan/quant/wish_service.py | AI 拆解(复用 paper_radar._http_post_json + DeepSeek key 读取)、dispatch() 执行器抽象、日志读写 |
wish_runner.py(根目录) | 可独立运行的 worker:python wish_runner.py --once / --loop,供 B/A 方案调用 |
| 文件 | 位置 | 改什么 |
| ------ | ------ | -------- |
unified_server.py | 20488 前 | 加 elif path == "/wish": 与 /api/wish/* GET 分支 |
unified_server.py | 7841 附近 | 加 WISH_PAGE 模板(照 BALANCE_PAGE;CSS 花括号必须 {{ }}) |
unified_server.py | 20586 后 | 加 /api/wish/* POST 分支 |
unified_server.py | 1028 _board_of_path | 加 /wish → "许愿池",否则访问统计落到"其他" |
unified_server.py | 315 PROTECTED_PATHS | 不改:匿名可提交 |
adata/aohan/quant/db/core.py | 35-56 | 注册 init_wish_tables() |
adata/aohan/quant/portal/registry.py | LAYOUT_ORDER DEFAULT_ORDER MODULES | 加 wish 卡片(span=4, rows=1,group="system") |
adata/aohan/quant/db/user_config.py | 14 | DEFAULT_MODULE_ORDER 同步(portal/test_portal.py 有断言) |
restart.sh | 171-202 | 可选:拉起 wish_runner.py 常驻(A 方案启用时) |
管理员提交后写一条站内消息(复用 db/messages.add_message)+ Server酱推送,内容为"新愿望排队中:<标题>"。执行完成/失败同样推一条。这样手机上提交完能立刻得知结果。
---
P0(本次要做,零风险)
P1(执行器接 B)
wish_runner.py --once 能认领一条并产出 git 分支 + 日志restart.sh 并回写 deployedP2(可选,A 方案)
trae-agent(需先补 .venv + .env),改成常驻 worker---