Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
d35bdfe
fix: 以 v1.13.0 为基线重放 37 项修复(不含上游 9 个后续提交)
Oct 20, 2026
399104c
feat(server): 6021 queue.full 的 503 响应透传上游 Retry-After(issue #184)
Oct 20, 2026
10e476d
go下载源改为国内源
pengyouak Oct 19, 2026
8eae0d4
feat(upstream): 出站 DNS 可指定(dns_servers / WB2A_DNS)
yuanxing109 Oct 9, 2026
7058593
feat(panel): 运行日志下新增「积分历史」,逐账号记录余额变动
Tinkler-i Oct 8, 2026
e6eae11
feat(panel): 手机端适配(抽屉侧栏 + 宽表卡片化 + 窄屏时序图)
yuanxing109 Oct 9, 2026
941a0db
feat: 增加后台任务并修正计费与任务结果
Saito-Sakuya Oct 3, 2026
38ec2c4
fix(panel+server): 合并 #104 与分支语义——global 任务只读 + 计费口径分列判定
Oct 20, 2026
a889dea
feat(upstream): 新增 WorkBuddy 联网搜索客户端
zcbxxx1 Oct 17, 2026
16ed6d7
feat(server): 新增搜索端点 POST /v1/search/messages
zcbxxx1 Oct 17, 2026
8c6ed88
feat(server): 搜索请求接入请求日志记账
zcbxxx1 Oct 17, 2026
7bd5881
docs: README 补充搜索端点说明
zcbxxx1 Oct 17, 2026
6455128
fix(server): 搜索端点 review 修复(4 项)
zcbxxx1 Oct 17, 2026
84a78fb
fix(server): v1.13.0 基线上补 conversation_id → sessKey 回退 + gofmt 修正
Oct 20, 2026
e6b6733
fix(upstream): 企业额度 used 改为向上取整 + data:null 显式判空(issue #222 建议 1/3)
Oct 20, 2026
2380854
test: 同步企业额度取整方向变更的期望值(1188 → 1187)
Oct 20, 2026
56590bb
feat(server): 零 tool_calls 停摆检测与换号重试(issue #149)
Oct 20, 2026
16028ed
fix(upstream): 余额判空按「字段存在性」判定,并补齐个人口径与面板可观测性(issue #222 建议 3)
Oct 20, 2026
bed3c8b
fix(panel): 静态 API Key 账号也要进积分历史(probe 缺 uid 被账本丢弃)
Oct 20, 2026
75c013b
fix(upstream): 企业额度字段按归一化键读取,消除「判空」与「取值」口径不一致
Oct 20, 2026
6c9091f
fix(upstream): 剔除 function.name 为空的 tool_call,修线上 11133
zh2847125776-wq Oct 10, 2026
1a353cc
fix(panel): 积分到期卡片改为按需查询,避免打开账号池就打上游(issue #145/#137)
Oct 20, 2026
54b10da
fix(server): 零 tool_calls 停摆检测的换号重试必须真换号,且不得把模型答案变成 503
Oct 20, 2026
567aab3
style: 修正 sse.go 的 gofmt(新增 WithStreamEndObserver 后的空行)
Oct 20, 2026
bc4b8b6
feat(panel): 停摆率可见——请求表挂「停摆」角标 + 统计条计数(issue #149)
Oct 20, 2026
71028bc
docs(config): 补齐新配置项的示例与说明(burn guard / 停摆重试)
Oct 20, 2026
84b7acc
docs(readme): 补上本 fork 修复版的本地构建路径
Oct 20, 2026
e754885
chore(release): 版本号 1.13.0-panel → 1.13.1-panel(CI tag 断言要求 tag == 源码版本)
Oct 20, 2026
4db668d
feat(server): 接入 Codex Responses / Anthropic Messages 原生协议与官方检索透传
Oct 20, 2026
a48ac6c
fix(server): 搜索路径的在途租约用 defer 成对归还(错误/超时/ctx 取消/panic 全覆盖)
Oct 20, 2026
9ac9041
fix(auth): Windows 上 rename 失败必须降级原地覆盖,否则刷新后的 token 不落盘
Oct 20, 2026
1765068
fix(server): 流式停摆跳也必须排除在积分燃尽熔断之外(与非流式对称)
Oct 20, 2026
94e9c31
ci: 增加 Windows 专项测试 job,让 Windows 专属分支真正被执行
Oct 20, 2026
4618ffc
test(atomicfile): 把依赖 POSIX 目录权限的用例在 Windows 上显式跳过
Oct 20, 2026
4171620
fix(atomicfile): 并发写同一目标不得共用暂存文件名(Windows CI 首跑抓出)
Oct 20, 2026
8c294c3
fix(auth): 保存账号文件不得共用固定暂存名,否则并发刷新会静默丢内容
Oct 20, 2026
47075e7
fix(server): 12153 与 scheduler 对齐为「连续 3 次才禁用」
Oct 20, 2026
112be51
fix(panel): 企业账号「跳过签到」不是错误,别再渲染成红色报错
Oct 20, 2026
7a84aab
fix(pool): 402 硬冷却的到期时间跟上游时区走,别跟部署机器的本地时区
Oct 20, 2026
f24d8e2
fix(upstream): 停摆判定自带「含原生工具调用标记 ⇒ 不是停摆」自防御;逐帧观察者可共存
Oct 20, 2026
411a5ee
fix(upstream): 合并冲突解析时误删了帧规范化步骤,已还原
Oct 20, 2026
69aa664
fix(upstream): 企业额度解析的取值必须与遍历顺序无关,类型不匹配不能静默当 0
Oct 20, 2026
af4ba2b
test(pool): 402 冷却到期时刻的断言按上游时区看,别看部署机器的本地小时
Oct 20, 2026
339bc67
fix(atomicfile): rename 撞上「目标被另一个写者占用」时要重试,不能直接判失败
Oct 20, 2026
50f6f01
fix(upstream): 工具调用判读收成一套,避免聚合形态被当成「没调工具」
Oct 20, 2026
bbd1885
fix(upstream): 流式 SSE error 帧的分类必须带上 error.code
Oct 20, 2026
063c26d
fix(upstream): 11-128 与 11140 的判据按「主文案 + 业务码」分流,惩罚强度按状态码分级
Oct 20, 2026
722fd26
fix(pool): 积分燃尽熔断要在面板口径里体现,并可被「解冻」清除
Oct 20, 2026
e4cc713
fix(pool): 「额度未知」不等于「额度为 0」,否则新账号会被积分保底静默挡掉(审计 Q6.1)
Oct 20, 2026
0559756
fix(upstream): 逐帧观察者必须拿到**深拷贝**,否则「只读不改对象」的承诺只对同步读取成立(审计 Q5.3)
Oct 20, 2026
d129fbb
fix(config): 配置写坏不许拖垮启动——留 .bak 备份 + 解析失败自动回退(审计 Q4.5)
Oct 20, 2026
d88ed5e
fix(upstream): error 帧也必须深拷,逐帧观察者才真正拿到「上游原样」
Oct 20, 2026
88b5519
feat(upstream): 加 WB2A_DUMP_BODY 调试开关,打印入站/出站请求体
Oct 20, 2026
ee098ab
feat(upstream): dump 开关同时打印出站请求头
Oct 20, 2026
166d493
feat(logging): 运行日志可落盘,Windows 上没有控制台也能查
Oct 21, 2026
b920596
fix(upstream): 调试 body dump 截断 + 出站头 dump 补测试
Oct 21, 2026
b0f2d70
fix(panel): 后台任务状态与任务日志归档必须走 atomicfile(Windows 上 rename 必失败)
Oct 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/docker-ghcr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,35 @@ env:
IMAGE_NAME: ${{ github.repository }}

jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- run: go test ./...
- run: go vet ./...

# Windows 专项测试:Windows 与 POSIX 的错误码语义不同(ERROR_ACCESS_DENIED /
# ERROR_NOT_SAME_DEVICE vs EXDEV / EBUSY),而仓库**同时发布 Windows 二进制**
# → 只在 ubuntu 上跑测试会让 Windows 专属分支永远不被执行到。
test-windows:
runs-on: windows-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- run: go test ./...
- run: go vet ./...

build-and-publish:
needs: [test, test-windows]
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down
17 changes: 16 additions & 1 deletion .github/workflows/go-binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,23 @@ jobs:
# ubuntu runner 自带 node → panel 包的 JS 运行时冒烟测试(TestAppJSTopLevelSmoke)会真实执行
- run: go test ./...

# Windows 专项测试:Windows 与 POSIX 的错误码语义不同(ERROR_ACCESS_DENIED /
# ERROR_NOT_SAME_DEVICE vs EXDEV / EBUSY),而仓库发布**包含 Windows 二进制**
# → 只在 ubuntu 上跑测试会让 Windows 专属分支永远不被执行到。
# 实测教训:internal/auth 的 rename 降级判据在 Windows 上恒为 false
# → 刷新后的 accessToken 不落盘(issue #226 修复的 Windows 侧缺口)。
test-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- run: go vet ./...
- run: go test ./...

build:
needs: test
needs: [test, test-windows]
runs-on: ubuntu-latest
strategy:
fail-fast: false
Expand Down
7 changes: 5 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
FROM golang:1.23-alpine AS build
WORKDIR /src
COPY go.mod ./
RUN go mod download
# 防止因为直接使用官方源导致下载失败
RUN go env -w GOPROXY=https://goproxy.cn,direct && \
go env GOPROXY && \
go mod download
COPY . .
# 一次编译全部二进制(工具进镜像,容器内可直接跑脚本)。全部 -trimpath -s -w。
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/wb2api ./cmd/server \
Expand Down Expand Up @@ -32,4 +35,4 @@ USER app
EXPOSE 7863
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s \
CMD wget -qO- http://127.0.0.1:7863/healthz || exit 1
ENTRYPOINT ["/app/wb2api", "-config", "/app/config.json"]
ENTRYPOINT ["/app/wb2api", "-config", "/app/config.json"]
258 changes: 188 additions & 70 deletions README.md

Large diffs are not rendered by default.

60 changes: 60 additions & 0 deletions cmd/server/cn_chat_base_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package main

import (
"slices"
"testing"

"github.com/linguo2625469/workbuddy2api-panel/internal/upstream"
)

// 老配置(无 cn 段)必须保留内置 CN base,零回归。
func TestCNChatBaseDefaultKeepsBuiltin(t *testing.T) {
cfg, err := ParseConfig([]byte(`{}`))
if err != nil {
t.Fatalf("parse: %v", err)
}
up := upstream.New()
want := up.ChatBaseCN
applyCNChatBase(up, cfg)
if up.ChatBaseCN != want {
t.Fatalf("ChatBaseCN=%q want builtin %q", up.ChatBaseCN, want)
}
}

func TestCNChatBaseOverride(t *testing.T) {
cfg, err := ParseConfig([]byte(`{"cn":{"chat_base":"https://www.workbuddy.cn"}}`))
if err != nil {
t.Fatalf("parse: %v", err)
}
up := upstream.New()
applyCNChatBase(up, cfg)
if up.ChatBaseCN != "https://www.workbuddy.cn" {
t.Fatalf("ChatBaseCN=%q want override", up.ChatBaseCN)
}
// global base 不受 cn 覆盖影响。
if up.ChatBaseGlobal != "" {
t.Fatalf("ChatBaseGlobal=%q want untouched", up.ChatBaseGlobal)
}
}

// cn.chat_base 只在装配期生效,面板保存后若被改动须提示重启。
//
// 语义随 #104 改为「按与运行中配置的实际差异列出」:cn.chat_base 没变就不必
// 提示重启,变了就必须提示。
func TestCNChatBaseRequiresRestart(t *testing.T) {
current, err := ParseConfig([]byte(`{}`))
if err != nil {
t.Fatalf("parse current: %v", err)
}
next, err := ParseConfig([]byte(`{"cn":{"chat_base":"https://www.workbuddy.cn"}}`))
if err != nil {
t.Fatalf("parse next: %v", err)
}
if !slices.Contains(restartRequiredFields(current, next), "cn.chat_base") {
t.Fatal("cn.chat_base missing from restart-required fields")
}
// 没改动时不应提示(装配期字段的提示只在真的变了时出现)。
if slices.Contains(restartRequiredFields(current, current), "cn.chat_base") {
t.Fatal("cn.chat_base reported although unchanged")
}
}
Loading
Loading