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 多渠道账号聚合工具,演进历程:
- 上游 API 仓库:3 个独立仓库 (
wild-work-buddy-api,wild-work-traework-api,wild-work-qoder-api) → 提供各渠道基础 API 封装 - v0.1.x (workbuddy-wild):Wails GUI 包装器,仅支持 WorkBuddy 单渠道
- v0.2.x:增加 TraeWork 集成,仍用 Wails
- v2.0.x (当前 master):大改版弃用 Wails,改用系统托盘 daemon + 浏览器 Web UI;新增 Qoder 渠道
- v2.1.x:新增 WorkBuddy 国际版(
www.workbuddy.ai)渠道,与国内版workbuddy完全独立
⚠️ v0.2.x 代码已迁移至
legacy-wails分支,不再维护。
0. 项目一句话
去掉 wails/WebView2,改为 系统托盘 daemon + 系统浏览器 Web UI 的多平台(Win/mac)多渠道账号聚合工具。
1. 已定决议项(不要推翻,除非有强理由并更新本节)
| # | 决议 | 说明 |
|---|---|---|
| R1 | 托盘菜单固定,不做动态内容、不做定时/事件刷新 | 用户在自己客户端操作无法捕捉,动态展示无意义 |
| R2 | 托盘菜单精简为:打开主界面 / 查看日志 / 退出 | |
| R3 | 托盘固定菜单项:打开主界面 / 查看日志 / 退出 | 双击托盘 = 打开主界面;不再弹"已启动"提示框 |
| R4 | 不设管理 API 鉴权 | 单机个人工具;监听 0.0.0.0 的风险由用户承担,UI/文档给一句风险提示 |
| R5 | Web UI 用纯静态 HTML/CSS/JS(无前端编译链) | go:embed 打进单文件;实用 + 大众审美即可 |
| R6 | 托盘库:保留 energye/systray(已跨平台 Win/mac/Linux) | 各菜单项使用不同颜色纯 Go 生成图标,无需外部图标文件 |
| R7 | 移除 wails / WebView2 全部依赖 | 省内存与运行时;平台能力封装进 internal/platform(build tag 拆分) |
| R8 | daemon 单进程:一个 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) 五渠道 |
| R11 | Windows 产物在 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 监听不可作目标、鉴权双份、启动竞态) |
| R14 | Stream/Aggregate 的 model 由调用方显式传入,渠道不得用实例字段记忆「上次请求的模型名」 | 旧实现 qoder 用全局 lastModel,多账号并发会串号;traework 恒为空串。详见 docs/三接口兼容改造备忘.md §3 |
| R15 | Responses 的 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 + 千问办公)
- 新建
internal/<channel>/包,实现provider.Upstream接口 internal/auth增加对应Load<Channel>Dir()(文件名前缀<channel>-*.json)- 装配处注册
server.Runtime{Kind, Pool, Upstream, StaticModels}+app.Runtime{..., Scheduler} - 前端渠道选择器加一项;
internal/login_<channel>实现登录编排(如需)
provider.Kind 即模型名前缀;server 按
channel/<model>前缀路由,无需改接口。 Qoder 渠道无签到活动:DailyCheckin返回错误,调度器只做 token keepalive。 WorkBuddyAI 国际版:DailyCheckin实现为「免费模型对话保活 + 签到探测」(对用户透明,无前端界面); token 有效期 365 天,故 KeepaliveHours 设为 nil。详见docs/workbuddy国际版渠道接入备忘.md。
6. 关键不变量(改动前必读)
- 每次代码变更后必须本地重新构建
dist/wild-work.exe(见 §8)。dist/在.gitignore中,CI 只产出带平台后缀的wild-work-<os>-<arch>, 不会生成dist/wild-work.exe——该文件只能手动构建。 不重建会导致:本地运行的二进制与源码不一致(例如改了版本号但仍显示旧版本)。 PrepareBody三改写勿动:强制stream=true、tool_choice归一化、developer→system- 日志/面板/消息框零 token:不得输出 access/refresh token(调试用假 token)
- auth 文件嵌套格式
{auth:{...},account:{...}},internal/auth.Parse与 login.SaveAuth 必须一致 config.listen兼容新对象格式 + 旧字符串格式":7863"data/state-*.json只增不减字段,向后兼容;旧 state.json 自动迁移- 托盘回调必须 goroutine 化
config.example.json与config.Default()同步- 上游 HTTP ≥400 错误直接透传原始响应,不包装
- 定价缓存持久化到
data/pricing-cache.json,启动加载,超 1h 自动刷新 - 无桌面 Linux 必须
--no-tray,不带参数 panic 直接 exit 提示 - 粘性路由:
pickWithSticky优先复用上次账号,直至连续成功请求达 50 次或遭遇错误冷却。成功时stickySuccess递增计数,错误时stickyClear清除粘性记录。不使用 credits 阈值(pool 中余额是 stale 数据)。 internal/server主链路不得被绕过:POST /v1/chat/completions与GET /v1/models由内层直接服务,internal/gateway只接管/v1/responses、/v1/messages、/v1/messages/count_tokens。- 兼容层调用内层只能经
Gateway.call()(io.Pipe),调用方读完必须res.Close(),否则内层 goroutine 可能阻塞在 Write 上泄漏。 pipeRW.Flush()为空操作是刻意的:io.Pipe无缓冲,Write 即送达;不要改成缓冲 + 定时 flush。- 错误分类 429 必须优先于 hardMarkers:限流 body 高频带
quota exceeded,先判 hardRule 会把限流误归余额耗尽 → 12h 硬冷却。三渠道Classify均已修复此顺序。 - 脱敏层仅做文本替换不做语义变更:
internal/sanitize只改模板句、不改用户内容语义;预检不命中时零分配原样通过。将来配置features.sanitize_fingerprints可一键关闭(逃生门)。 - 积分「可用/不可用」拆分统计:
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()排序。
- TraeWork 判据是
- 到期时间字段因渠道而异,缺失则不显示:WorkBuddy 系是
CycleEndTime(上游从不下发PackageEndTime,旧判据恒 miss), TraeWork 是expire_time(Unix 秒),Qoder 无此字段。均按 UTC+8 墙钟解析(softRateResetLoc), 用time.Local会在非 UTC+8 机器上算错一天。上游未下发时ResourceItem.ExpireAt必须为空串, 前端据此隐藏整列——不得用零值时间冒充「永不过期」。 - 401 必须自愈,不能只信本地
expiresAt:上游刷新会作废旧 access token(refresh token 同步轮换)。若新 token 未落盘、 或同一账号在别处被刷新,本地文件里的 tokenexpiresAt仍在未来,但上游已拒绝 →NeedsRefresh恒为假、永不刷新、 积分恒 0、明细恒空。因此积分/明细/费率路径遇ErrSessionDead必须「refresh + 落盘 + 重试一次」 (app.refreshIfSessionDead,scheduler 的 checkin 路径同理)。 - 凡是调
Upstream.RefreshToken的地方必须紧跟SaveAtomic:refresh token 会轮换,不落盘 = 下次启动用旧 refresh token, 重回上一条的死锁(RefreshPricing曾漏,已补)。
7. 平台能力差异表(internal/platform)
| 能力 | Windows | macOS | Linux |
|---|---|---|---|
| 打开浏览器 | rundll32 url.dll | open <url> | xdg-open |
| 系统消息框 | MessageBoxW | osascript display dialog | stderr |
| 开机自启 | 注册表 Run | LaunchAgent plist | 未实现 |
| 打开日志文件 | notepad | open -a TextEdit | xdg-open |
| 确认框 | MessageBoxW YESNO | osascript 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. 文档索引
- README.md — 用户文档
- DEVELOPMENT.md — 开发者文档(面向 AI Agent)
- HANDOFF.md — 交接文档(历史记录)
- docs/workbuddy国际版逆向分析备忘.md — 国际版接口逆向(含抓包证据、模型倍率全表、凭据通用性验证)
- docs/workbuddy国际版渠道接入备忘.md — 国际版渠道接入方案(接口规格、代码映射、调度设计、实施 Checklist)
- docs/upstream-reverse-engineering.md — 各上游渠道 API 逆向记录
- docs/三接口兼容改造备忘.md — 三接口(Chat/Responses/Anthropic)兼容层架构决策、实施记录、验证清单、已知限制