Wild-Work 项目提示词(AGENTS.md)

September 20, 2026 · View on GitHub

本文件面向 AI Agent 与开发者,记录工具设计、架构选型、决议项与开发约定。 项目渊源:原始上游为 3 个分离的 xxx2api 仓库 → workbuddy-wild v0.1.x(wails GUI 包装器)→ v0.2.x(增加 traework 集成)→ v2.0.x(大改版弃用 wails,改用系统托盘 daemon + Web UI,增加 qoder 渠道)。 当前 master 分支为 v2.0.x 版本;v0.2.x 已迁移至 legacy-wails 分支。


0. 项目渊源

Wild-Work 是 WorkBuddy(国内版+国际版)/TraeWork/Qoder 多渠道账号聚合工具,演进历程:

  1. 上游 API 仓库:3 个独立仓库 (wild-work-buddy-api, wild-work-traework-api, wild-work-qoder-api) → 提供各渠道基础 API 封装
  2. v0.1.x (workbuddy-wild):Wails GUI 包装器,仅支持 WorkBuddy 单渠道
  3. v0.2.x:增加 TraeWork 集成,仍用 Wails
  4. v2.0.x (当前 master):大改版弃用 Wails,改用系统托盘 daemon + 浏览器 Web UI;新增 Qoder 渠道
  5. v2.1.x:新增 WorkBuddy 国际版www.workbuddy.ai)渠道,与国内版 workbuddy 完全独立

⚠️ v0.2.x 代码已迁移至 legacy-wails 分支,不再维护。

0. 项目一句话

去掉 wails/WebView2,改为 系统托盘 daemon + 系统浏览器 Web UI 的多平台(Win/mac)多渠道账号聚合工具。

1. 已定决议项(不要推翻,除非有强理由并更新本节)

#决议说明
R1托盘菜单固定,不做动态内容、不做定时/事件刷新用户在自己客户端操作无法捕捉,动态展示无意义
R2托盘提供「刷新积分」菜单 已移除。刷新积分改为 Web UI 面板操作托盘菜单精简为:打开主界面 / 查看日志 / 退出
R3托盘固定菜单项:打开主界面 / 查看日志 / 退出双击托盘 = 打开主界面;不再弹"已启动"提示框
R4不设管理 API 鉴权单机个人工具;监听 0.0.0.0 的风险由用户承担,UI/文档给一句风险提示
R5Web UI 用纯静态 HTML/CSS/JS(无前端编译链)go:embed 打进单文件;实用 + 大众审美即可
R6托盘库:保留 energye/systray(已跨平台 Win/mac/Linux)各菜单项使用不同颜色纯 Go 生成图标,无需外部图标文件
R7移除 wails / WebView2 全部依赖省内存与运行时;平台能力封装进 internal/platform(build tag 拆分)
R8daemon 单进程:一个 http.Server 同时服务 OpenAI 端点 + 管理 API + 静态 UI沿用 server 现有 ServeMux 扩展
R9核心业务(pool/scheduler/upstream/traework/server/login/config/auth/provider)整体复用,格式零迁移config.json / auths/ / data/state.json 兼容旧版;旧 state.json 自动迁移到 state-workbuddy.json
R10新增渠道扩展方式:实现 provider.Upstream 接口 + auth 加载器 + 注册 Runtime模型前缀 channel/<model> 路由;已实现 WorkBuddy(国内) + WorkBuddyAI(国际) + TraeWork + Qoder + 千问办公(qwenwork) 五渠道
R11Windows 产物在 WSL 交叉编译(GOOS=windows CGO_ENABLED=0,已验证可行);macOS 产物走 GitHub Actions macos-latest(cgo 必需)WSL 无法编 darwin cgo;CI 增加 darwin job
R12无桌面 Linux 使用 --no-tray 参数无参启动在无 DBus 环境托盘 panic 直接 exit 并提示;--no-tray 跳过托盘打印信息阻塞等待 Ctrl+C
R13三接口兼容采用两层结构:内层 handler 不动,新增 internal/gateway 边缘层,经 in-process 调用io.Pipe + ResponseWriter 形状)复用内层代码量比内联重构多 20%,但改动面小一个数量级(主链路仅 2 处调用点 + 1 个访问器),回归风险低、可脱离 pool 单测。不得用 HTTP 自环0.0.0.0 监听不可作目标、鉴权双份、启动竞态)
R14Stream/Aggregate 的 model 由调用方显式传入,渠道不得用实例字段记忆「上次请求的模型名」旧实现 qoder 用全局 lastModel,多账号并发会串号;traework 恒为空串。详见 docs/三接口兼容改造备忘.md §3
R15Responses 的 function_call 必须是独立 output item(带 call_id),Anthropic 的 tool_use 参数必须走 input_json_delta参考实现 tokligence-gateway 两处写法不合规范(塞进 message.content、start 里一次性给完整 input),Codex/Claude Code 会解析失败

2. 架构选型(依据)

主题选型理由
GUI 壳(删除 wails)WebView2 内存开销大 + Windows 绑定;托盘 + 浏览器足够
托盘energye/systray v1.0.3(现有)已跨平台;菜单固定方案规避其不可删菜单项限制
管理后端现有 http.Server 扩展 /api/*单端口、复用鉴权中间件(无鉴权)、零新服务
Web UI纯静态 embed + fetch无 Node 构建链,单 exe 双击即用
登录复用 internal/login + login_trae纯 HTTP + 本地回调端口,跨平台
平台能力internal/platform + build tag(windows/darwin/other)浏览器无痕/开机自启/消息框/日志/工作区,接口同名
构建WSL 交叉编译 win;CI macos-latest 编 darwin见 R11

3. 托盘菜单设计(当前形态)

wild-work
──────────
打开主界面          → 系统浏览器打开 http://<listen>/
查看日志            → 系统默认编辑器打开 data/app.log
──────────
退出                → 退出 daemon(确认框)
  • 单击/双击/右击:右击弹菜单;单击与双击 = 打开主界面
  • 各菜单项使用不同颜色纯 Go 生成图标(蓝色=打开、灰色=日志、红色=退出)
  • 刷新积分功能已移至 Web UI 面板操作

4. Web UI 页面规划(纯静态,一个 index.html + app.js + style.css)

页面/区块内容
顶部栏品牌名/版本号、API 地址(点击弹窗配置)、API-Key(点击弹窗修改)、帮助/关于
账号管理双列卡片网格,账号名/UID/积分/签到状态,图标按钮操作(签到/刷新/停用/删除)
自动签到签到时间(HH:MM 多组)+ 开机自启开关(左右布局)
渠道费率五渠道模型定价表(按渠道分组,合并单元格),刷新按钮

管理 API(REST,均挂 /api/*):

GET  /api/state                    # 全量状态(账号/积分/签到/配置)
POST /api/login/start              # {channel} → {auth_url}
POST /api/login/cancel
POST /api/account/checkin          # {uid}
POST /api/account/checkin_all
POST /api/account/refresh          # {uid}
POST /api/account/refresh_all
POST /api/account/remove           # {uid}
POST /api/account/disable          # {uid,disabled} 停用/启用
POST /api/account/resource_detail  # {uid} → 积分明细
POST /api/config/checkin_times     # {times:["09:00","21:30"]}
POST /api/config/listen            # {host,port}
POST /api/config/api_key           # {key}
POST /api/config/autostart         # {on:bool}
GET  /api/fees                     # 渠道费率(本地缓存 + 按需刷新)
POST /api/fees/refresh             # 异步刷新费率
GET  /api/logs                     # 最近 300 行日志
POST /api/quit                     # 退出程序

5. 渠道(已实现 WorkBuddy 国内版 + WorkBuddyAI 国际版 + TraeWork + Qoder + 千问办公)

  1. 新建 internal/<channel>/ 包,实现 provider.Upstream 接口
  2. internal/auth 增加对应 Load<Channel>Dir()(文件名前缀 <channel>-*.json
  3. 装配处注册 server.Runtime{Kind, Pool, Upstream, StaticModels} + app.Runtime{..., Scheduler}
  4. 前端渠道选择器加一项;internal/login_<channel> 实现登录编排(如需)

provider.Kind 即模型名前缀;server 按 channel/<model> 前缀路由,无需改接口。 Qoder 渠道无签到活动:DailyCheckin 返回错误,调度器只做 token keepalive。 WorkBuddyAI 国际版:DailyCheckin 实现为「免费模型对话保活 + 签到探测」(对用户透明,无前端界面); token 有效期 365 天,故 KeepaliveHours 设为 nil。详见 docs/workbuddy国际版渠道接入备忘.md

6. 关键不变量(改动前必读)

  1. 每次代码变更后必须本地重新构建 dist/wild-work.exe(见 §8)。 dist/.gitignore 中,CI 只产出带平台后缀的 wild-work-<os>-<arch>不会生成 dist/wild-work.exe——该文件只能手动构建。 不重建会导致:本地运行的二进制与源码不一致(例如改了版本号但仍显示旧版本)。
  2. PrepareBody 三改写勿动:强制 stream=truetool_choice 归一化、developer→system
  3. 日志/面板/消息框零 token:不得输出 access/refresh token(调试用假 token)
  4. auth 文件嵌套格式 {auth:{...},account:{...}}internal/auth.Parse 与 login.SaveAuth 必须一致
  5. config.listen 兼容新对象格式 + 旧字符串格式 ":7863"
  6. data/state-*.json 只增不减字段,向后兼容;旧 state.json 自动迁移
  7. 托盘回调必须 goroutine 化
  8. config.example.jsonconfig.Default() 同步
  9. 上游 HTTP ≥400 错误直接透传原始响应,不包装
  10. 定价缓存持久化到 data/pricing-cache.json,启动加载,超 1h 自动刷新
  11. 无桌面 Linux 必须 --no-tray,不带参数 panic 直接 exit 提示
  12. 粘性路由pickWithSticky 优先复用上次账号,直至连续成功请求达 50 次或遭遇错误冷却。成功时 stickySuccess 递增计数,错误时 stickyClear 清除粘性记录。不使用 credits 阈值(pool 中余额是 stale 数据)。
  13. internal/server 主链路不得被绕过POST /v1/chat/completionsGET /v1/models 由内层直接服务,internal/gateway 只接管 /v1/responses/v1/messages/v1/messages/count_tokens
  14. 兼容层调用内层只能经 Gateway.call()io.Pipe),调用方读完必须 res.Close(),否则内层 goroutine 可能阻塞在 Write 上泄漏。
  15. pipeRW.Flush() 为空操作是刻意的io.Pipe 无缓冲,Write 即送达;不要改成缓冲 + 定时 flush。
  16. 错误分类 429 必须优先于 hardMarkers:限流 body 高频带 quota exceeded,先判 hardRule 会把限流误归余额耗尽 → 12h 硬冷却。三渠道 Classify 均已修复此顺序。
  17. 脱敏层仅做文本替换不做语义变更internal/sanitize 只改模板句、不改用户内容语义;预检不命中时零分配原样通过。将来配置 features.sanitize_fingerprints 可一键关闭(逃生门)。
  18. 积分「可用/不可用」拆分统计provider.ResourceItem.Usable 标记条目是否属于本工具可消耗的额度池,provider.Summarize() 汇总小计。
    • TraeWork 判据是 available_endpoint == 0(ep=1 是官方客户端专用池,本工具扣不到); 不得用 group_type 判定——同名「每日签到」「用户福利」会同时存在 ep=0 与 ep=1 两份。 实测证据见 docs/upstream-reverse-engineering.md §2.3。
    • UserResource / UserResourceDetail 返回的 remain 只能是可消耗余额(ep=0), 否则 pool 会按虚高余额选号。含专用池的总量(usage_summary.total_amount)不能作路由依据。
    • 不可消耗额度仅用于面板展示(pool.Status.UnusableCredits),不参与 Pick() 排序。
  19. 到期时间字段因渠道而异,缺失则不显示:WorkBuddy 系是 CycleEndTime上游从不下发 PackageEndTime,旧判据恒 miss), TraeWork 是 expire_time(Unix 秒),Qoder 无此字段。均按 UTC+8 墙钟解析(softRateResetLoc), 用 time.Local 会在非 UTC+8 机器上算错一天。上游未下发时 ResourceItem.ExpireAt 必须为空串, 前端据此隐藏整列——不得用零值时间冒充「永不过期」
  20. 401 必须自愈,不能只信本地 expiresAt:上游刷新会作废旧 access token(refresh token 同步轮换)。若新 token 未落盘、 或同一账号在别处被刷新,本地文件里的 token expiresAt 仍在未来,但上游已拒绝 → NeedsRefresh 恒为假、永不刷新、 积分恒 0、明细恒空。因此积分/明细/费率路径遇 ErrSessionDead 必须「refresh + 落盘 + 重试一次」 (app.refreshIfSessionDead,scheduler 的 checkin 路径同理)。
  21. 凡是调 Upstream.RefreshToken 的地方必须紧跟 SaveAtomic:refresh token 会轮换,不落盘 = 下次启动用旧 refresh token, 重回上一条的死锁(RefreshPricing 曾漏,已补)。

7. 平台能力差异表(internal/platform)

能力WindowsmacOSLinux
打开浏览器rundll32 url.dllopen <url>xdg-open
系统消息框MessageBoxWosascript display dialogstderr
开机自启注册表 RunLaunchAgent plist未实现
打开日志文件notepadopen -a TextEditxdg-open
确认框MessageBoxW YESNOosascript buttons默认否
无头模式--no-tray--no-tray--no-tray(推荐)

8. 构建

⚠️ 本地构建是日常约束:任何代码变更后都要重新构建 dist/wild-work.exe (见 §6 第 0 条)。CI 不生成该文件,且 dist/ 不入版本控制。 标准流程:go build ./... && go vet ./... && go test ./... 全绿后再构建。

# Windows(本机直接构建,或 WSL 交叉编译)
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-H windowsgui" -o dist/wild-work.exe ./cmd/wild-work

# macOS(需 macOS 真机或 CI,cgo 必需)
GOOS=darwin GOARCH=arm64 go build -o dist/wild-work-darwin ./cmd/wild-work

# Linux 无头
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o dist/wild-work-linux ./cmd/wild-work

构建后核对版本号已进二进制(防止拿到旧文件):

# Windows bash 下用 python 字节计数(strings 对 Go 二进制的长串不可靠)
python -c "b=open('dist/wild-work.exe','rb').read(); print('new:',b.count(b'2.1.0'),'old:',b.count(b'2.0.1'))"
# 期望:new >= 1 且 old == 0。若旧版本号仍在,说明构建未生效。

发版(tag 触发)

push v* tag → GitHub Actions 构建五平台产物并创建正式 release。 release note 用仓库根目录的 RELEASE-<tag>.md(手写摘要,面向用户), 而不是 --generate-notes(那只给 commit 链接列表);文件缺失时回退自动生成,不阻塞发版。

# 发版前确认:版本常量已 bump(internal/app/app.go const Version)、
# RELEASE-vX.Y.Z.md 已写好且与 tag 名一致、dist/wild-work.exe 已本地重建验证
git tag vX.Y.Z && git push origin vX.Y.Z

9. 文档索引