Skip to content

Commit a2a17d3

Browse files
committed
feat(codebuddy): 活跃上报——可选、默认关闭,按实测实现(B1.7)
用测试账号直连实测后实现(端到端点亮连登 1→2): 协议(src/provider/codebuddy/activity.py): - POST {endpoint}/v2/report,body 为事件数组 [chatRequestSendEvent] (eventCode=chat_request_send,全字段 36 键) - userId 必填:缺失时上游 HTTP 200 code:0 但静默丢弃(连登不变,实测复现) - OAuth 凭证 user_id/account_uid 实测为空 → 回落 bearer JWT 的 sub; 取不到则跳过,绝不编造 任务层(src/tasks/activity.py,独立循环): - ACTIVITY_REPORT_HOUR(默认 10,北京时间)窗口内执行,每 10 分钟醒一次 - 按「endpoint + userId」隔离,当日成功封账(内存态),失败下轮重试 - 结果复用 growth_events 记一行,不新增表 - ACTIVITY_REPORT_ENABLED 默认 false(官方条款禁止脚本篡改活动数据, 处罚为取消资格并追回礼品) 按实测收窄(推翻计划原拟项): - 不做「三套客户端指纹 / X-Device-Token 派生」:现有 CLI 指纹实测即被接受 - 不做「每轮事件条数上限」:每号每天 1 条即可 其他:CodeBuddyProvider.activity/activity_scope;管理台 POST /api/credentials/{id}/activity + 行内「活跃上报」菜单 + 功能说明; docker-compose/README/README.en/TECHNICAL §3.6/§6.1/PROPOSAL/NOTICE 同步; NOTICE 补 Sliverkiss/workbuddy2api 署名。 测试:tests/test_activity.py 37 例;1127 passed,行/分支 100%;前端三连全绿。
1 parent 4e72cc4 commit a2a17d3

17 files changed

Lines changed: 1112 additions & 3 deletions

‎NOTICE‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ Copyright (c) 2026 Hongsheng Luo (Robbs)
1616
(提供 CodeBuddy 成长中心与签到接口的逆向结论与运行经验:端点路径、
1717
client_token 规则、/redeem 传天数、4xx 业务规则不算失败、时间预算与退避重试的教训)
1818

19+
- workbuddy2api - https://github.com/Sliverkiss/workbuddy2api
20+
Copyright (c) 2026 Sliverkiss - MIT License
21+
(提供 CodeBuddy 活跃上报 `/v2/report` 的协议形状与实测结论:事件全字段
22+
形状、userId 必填、缺失时 200 但静默丢弃、每号每天一次的口径)
23+
1924
其上游:
2025
- xueyue33/codebuddy2api - https://github.com/xueyue33/codebuddy2api
2126
- Sliverkiss/traework2api - https://github.com/Sliverkiss/traework2api

‎PROPOSAL.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@
7272
写入 `buddy/travel/claim`、`buddy/travel/depart`、`tasks/accept`、`makeup-cards/use`、`redeem`、`lottery/draw`、`buddy/open`
7373
- 鉴权头与聊天一致(`Authorization` + `X-User-Id` + `X-Domain`);成长中心实测可用项目内的 OAuth bearer 凭证直连,无需桌面端凭据文件
7474
- 成长中心任务契约(2026-09):`accept_status` 五态;接单 `POST /tasks/accept` 收 `{"task_codes": [...]}`(单数一律 400);领奖 `POST /tasks/{code}/claim`;`/redeem` 的 `tier` 是档位标识 `"7d"/"14d"/"28d"`,实发字段是 `*_granted`
75-
- **活跃度的驱动来源(实测,勿凭单次实验下结论)**:活动类操作(领取成长中心奖励等)**计入**——两天无桌面端使用、仅差别在有无领取动作,`score` 就从 0 变 5;而纯 `/v2/chat/completions` 对话**不计入**(3 次完整对话后 `today.score` 与 `updated_at` 均不动)。连登天数含 1 天容忍窗口(H5 规则原文:按「连续登录且使用的天数」计,每月清零)。热力墙分档 = score 0 / 1-10 / 11-30 / 31-60 / >60,每日 02:00 批算。**与积分无关,不参与调度决策;不实现伪造客户端行为**(H5 条款明禁模拟器/脚本篡改数据,处罚为取消资格并追回已发礼品)
75+
- **活跃度的驱动来源(实测,勿凭单次实验下结论)**:活动类操作(领取成长中心奖励等)**计入**——两天无桌面端使用、仅差别在有无领取动作,`score` 就从 0 变 5;而纯 `/v2/chat/completions` 对话**不计入**(3 次完整对话后 `today.score` 与 `updated_at` 均不动)。连登天数含 1 天容忍窗口(H5 规则原文:按「连续登录且使用的天数」计,每月清零)。热力墙分档 = score 0 / 1-10 / 11-30 / 31-60 / >60,每日 02:00 批算。**与积分无关,不参与调度决策**
76+
- **活跃上报(B1.7,默认关闭)**:`POST /v2/report`(与聊天同基址),body 为 `[chatRequestSendEvent]`(`eventCode=chat_request_send`,全字段),`userId` 必填——缺失时上游 HTTP 200 `code:0` 但静默丢弃(连登不变)。OAuth 凭证 `account_uid`/`user_id` 实测为空,回落 bearer JWT 的 `sub`。实测一条即点亮连登(1→2)。**默认关闭**:H5 条款明禁模拟器/脚本篡改数据,处罚为取消资格并追回已发礼品;事件形状依赖上游实现、改版即失效,不作为可靠性功能
7677
- **凭证身份可能为空**:OAuth 登录路径下上游账号接口未回填 `account_uid`/`user_id`(实测发生),签到/成长中心的同账号隔离必须回落到 `credential_id`,否则第二个账号会被静默跳过
7778
- 请求头需 `X-Domain`、`X-User-Id`、`X-Enterprise-Id`、`X-Department-Info`(部门名须 UTF-8 百分号编码)
7879
- **reasoning 字段当前直接透传,不做注入也不剥离**(实测 71 份真实请求 dump):客户端自己会带 `reasoning_effort`(69/71,只有 `low`/`medium` 两档,非推理模型如 `hy3` 不带),也会在历史 assistant 消息里回传 `reasoning_content`(51/71,含带 `tool_calls` 的消息),上游原样接受(`deepseek-v4.1-flash` 4260 次请求 99.6% 成功)。因此既不需要「effort 档位映射」,也不存在「客户端丢弃 reasoning_content」这一前提;`enable_thinking` 只在客户端未给时补 `true`
@@ -235,7 +236,7 @@ v1 只接 OpenAI 出口,但上游 SSE 解析到「中立事件」这一步独
235236
- **API Key 存摘要**:SHA-256,明文仅创建时返回一次
236237
- **凭证加密列**:`data_enc` 走 Fernet,调度状态(`health` / `cooling_until` / `err_count` / `pinned` / `quota_expiry_ladder`)落库,进程重启不丢冷却状态与到期阶梯
237238
- **用量脱敏**:`usage_events`(明细 90 天)+ `usage_hourly`(小时汇总永久),`credit`/`cached_tokens` 可空仅辅助展示
238-
- **成长中心**:`growth_events` 只存汇总行(一轮一行人话汇报 + 积分/能量/连签 + trigger),不存活动内部数据结构;`credentials.growth_last_run_at/growth_last_result` 供列表直接显示
239+
- **成长中心**:`growth_events` 只存汇总行(一轮一行人话汇报 + 积分/能量/连签 + trigger),不存活动内部数据结构;`credentials.growth_last_run_at/growth_last_result` 供列表直接显示;活跃上报(B1.7)复用该表记一行,不新增表
239240
- 签到去重与模型列表缓存均进程内实现,不进库
240241

241242
DDL 以 [src/db/schema.sql](../src/db/schema.sql) 为准,补充实现细节见 [TECHNICAL.md §7](TECHNICAL.md)。

‎README.en.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ upstream channels, with a shared credential pool, unified scheduling, and per-us
1414
- **Two upstreams, one model namespace**: flat model names auto-route — credits expiring within the window first, then health; `model@provider` pins an upstream
1515
- **DeepSeek-compatible balance query**: `GET /v1/user/balance` aggregates the pool's probed quotas, so clients like Cherry Studio / cc-switch can display remaining credits
1616
- **CodeBuddy growth-center automation** (CodeBuddy only): claims Buddy travel gifts, departs Buddy, accepts/claims tasks, streak redemption, lottery and blind boxes; irreversible actions can be switched off via `GROWTH_IRREVERSIBLE_ACTIONS=false`
17+
- **Activity reporting** (CodeBuddy only, **off by default**): `ACTIVITY_REPORT_ENABLED=true` posts one chat-activity event per account per day to keep the growth-center streak alive. The upstream requires a `userId` and silently drops reports without one (HTTP 200 `{"code":0}`, no streak change); when the OAuth credential has no `user_id`, the gateway falls back to the bearer JWT `sub`. Risk: the activity terms forbid scripted data tampering (penalty: disqualification and clawback), and the event shape is upstream-internal and may break without notice — not a reliability feature. Admins can also trigger one report manually from the credential menu.
1718
- **Expiry-aware scheduling**: sums the credits expiring within `QUOTA_EXPIRY_WINDOW_SECONDS` (default 36h) per credential and burns the largest first, so near-expiry quota is not wasted
1819
- **Conversation stickiness**: multi-turn conversations keep the same credential (matched by message-prefix fingerprint) and only rotate on errors, avoiding upstream risk control and lost prompt cache; pinned credentials always win over stickiness
1920
- **Cached-token accounting**: TRAE's `cache_read_input_tokens` / `cache_creation_input_tokens` are mapped to per-request `cached_tokens` and surfaced in stats

‎README.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,23 @@ curl http://127.0.0.1:8000/v1/user/balance -H "Authorization: Bearer sk-你的ke
108108
`GROWTH_IRREVERSIBLE_ACTIONS=false` 可全部跳过(仍会领旅行礼物与任务奖励)。
109109
管理台凭证列表的「成长中心」列显示每个账号最近一轮的结果,行内菜单可手动执行一次。
110110

111+
### 活跃上报(可选,默认关闭)
112+
113+
CodeBuddy 成长中心的「连登天数 / 活跃地图」按日统计客户端对话事件。若账号长期
114+
只被本网关自动调用(没有真实客户端对话),连登会断。`ACTIVITY_REPORT_ENABLED=true`
115+
时,后台任务每天在 `ACTIVITY_REPORT_HOUR`(默认 10 点)所在的整点窗口内,为每个
116+
账号补发**一条** `chat_request_send` 事件,续上连登——与积分、调度无关,上报失败
117+
不影响任何聊天请求。管理台凭证行内菜单也可手动补报一次(不受该开关影响)。
118+
119+
> ⚠️ **风险与限制**:官方活动条款禁止使用模拟器/脚本篡改活动数据,处罚为**取消
120+
> 资格并追回已发礼品**。本功能默认关闭,开启前请自行评估账号风险。上报的事件名与
121+
> 请求形状依赖上游实现,**上游改版即失效**,不承诺任何收益。因此它**不作为可靠性
122+
> 功能**,也不接收依赖「产品内真实功能使用」的任务。
123+
>
124+
> 实测(2026-09-21):上游对缺少 `userId` 的上报返回 HTTP 200 `{"code":0}` 但
125+
> **静默丢弃**(连登不变)。本网关的 OAuth 凭证 `user_id`/`account_uid` 可能为空,
126+
> 此时从 bearer JWT 的 `sub` 取 userId,缺失则跳过(绝不编造)。
127+
111128
## 配置
112129

113130
常用项如下;完整的可配置项见 `src/config.py`(权威),且**每一个都已透传到 `docker-compose.yml`**——`.env` 里写这些变量即可生效(compose 的 `.env` 只做插值,未透传的变量不会进容器)。
@@ -125,6 +142,8 @@ curl http://127.0.0.1:8000/v1/user/balance -H "Authorization: Bearer sk-你的ke
125142
| `QUOTA_PROBE_MINUTES` | `60` | 额度探测周期 |
126143
| `GROWTH_INTERVAL_MINUTES` | `60` | 成长中心(仅 CodeBuddy)一轮领取的周期;下限 5 分钟 |
127144
| `GROWTH_IRREVERSIBLE_ACTIONS` | `true` | 是否允许成长中心的不可逆动作:抽奖、连登兑换、开 Buddy 盲盒、消耗补登卡。`false` 时仍会领取旅行礼物与任务奖励 |
145+
| `ACTIVITY_REPORT_ENABLED` | `false` | 活跃上报(仅 CodeBuddy):每天为账号补发一条对话事件续连登。**默认关闭**——官方条款禁止脚本篡改活动数据(处罚为取消资格并追回礼品),上游改版即失效,不作为可靠性功能(见上文「活跃上报」) |
146+
| `ACTIVITY_REPORT_HOUR` | `10` | 活跃上报的本地(北京)时间整点窗口;仅在 `ACTIVITY_REPORT_ENABLED=true` 时生效 |
128147
| `QUOTA_EXPIRY_WINDOW_SECONDS` | `129600` | 到期排序窗口:把距到期 ≤ 该秒数的积分加总,多的账号先用(避免积分过期浪费);`≤0` 关闭,退回纯健康度排序 |
129148
| `CONVERSATION_STICKY_SECONDS` | `3600` | 会话粘性 TTL:优先按请求体显式会话标识(`conversation_id`/`conversationId`/`prompt_cache_key`,metadata 或顶层),无则回落消息前缀指纹,多轮请求固定用同一凭证(手动 pin 的凭证优先,粘性让位);带 `user_id` 时不派生前缀兜底键(避免并行对话误钉同一号);凭证出错仍会轮换,成功后重新粘定;`≤0` 关闭 |
130149
| `MODEL_BLOCKLIST` | `custom_model_*,*sub*agent*,summary,browser_use_*,file_search_agent,default,hunyuan-image-*` | 模型列表黑名单(fnmatch,仅影响列表展示,直连指定不受影响);默认值按两边上游实测清单补入内部/不可用模型(`default` 零内容、`hunyuan-image-*` 400 11103),刻意不含 `*-volc` 与 `aquila`/`sagitta`/`seed-code-pro-0430`(实测可正常 chat)(见 TECHNICAL.md §3.5) |

‎TECHNICAL.md‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,31 @@ model/messages/tools/stream/stream_options/store/reasoning_effort)**未观测
291291
`*-lkeap`(两边清单零命中,无从核实)、`aquila` / `sagitta` /
292292
`seed-code-pro-0430`(TRAE 实测均正常 chat,虽名字可疑但可用)。
293293

294+
### 3.6 活跃上报(B1.7,默认关闭)
295+
296+
CodeBuddy 成长中心的「连登天数 / 活跃地图」按日统计客户端对话事件;账号只被网关
297+
自动调用时会断连登。`ACTIVITY_REPORT_ENABLED=true` 时 `src/tasks/activity.py` 每天在
298+
`ACTIVITY_REPORT_HOUR`(默认 10 点,北京时间)整点窗口内,为每个上游账号补发**一条**
299+
`chat_request_send`;按「endpoint + userId」隔离,当日成功即封账(内存态),失败下轮
300+
重试。协议在 `src/provider/codebuddy/activity.py`。
301+
302+
实测(2026-09-21,直连 CN 上游,端到端点亮连登 1→2):
303+
304+
| 项 | 实测结论 |
305+
|---|---|
306+
| 端点 | `POST {endpoint}/v2/report`(与聊天同基址 `copilot.tencent.com`),HTTP 200 `{"code":0,"msg":"OK"}` |
307+
| 请求体 | **事件数组** `[chatRequestEvent]`(非单对象),`eventCode=chat_request_send`,全字段形状(36 键) |
308+
| `userId` | **必填**。缺失时上游 HTTP 200 `code:0` 但**静默丢弃**(连登不变,实测复现) |
309+
| userId 来源 | OAuth 凭证的 `user_id`/`account_uid` 实测**为空**(上游账号接口未回填),回落 bearer JWT 的 `sub`(36 位 UUID,实测有效);取不到则跳过,绝不编造 |
310+
| `X-User-Id` | 与 body `userId` 同时带上(body 是必要项;头补一份更稳) |
311+
| 频率 | 每号每天 1 条即可,不做多时点高频上报 |
312+
313+
**刻意不做**:计划原拟的「三套客户端指纹」(CLI / 桌面 / Web+小程序)。实测现有
314+
CLI 指纹(`build_headers`)即可被接受,无需切换身份;多套指纹只增加失败面。
315+
316+
**风险声明**:官方活动条款禁止模拟器/脚本篡改活动数据(处罚为取消资格并追回礼品)。
317+
默认关闭;事件名与形状依赖上游实现,改版即失效,**不作为可靠性功能**。
318+
294319
---
295320

296321
## 4. Provider 协议(Q16=A 细接口)
@@ -419,6 +444,7 @@ class Scheduler:
419444
| token 预刷新(refresh.py) | 每 60 分钟 | 到期前 `REFRESH_SKEW_HOURS`(默认 24h)窗口内轮换 refresh token |
420445
| 每日签到(checkin.py) | 每 10 分钟(全天) | 成功即封账该凭证当日(`日期:scope`,进程内内存态,重启重建);失败凭证持续重试,同账号多凭证共享一次 |
421446
| 成长中心(growth.py) | 每 `GROWTH_INTERVAL_MINUTES`(默认 60,下限 5 分钟) | 仅 CodeBuddy:领旅行礼物 / 派 Buddy / 领取新任务 / 领任务奖 / 断登补登 / 连登兑换 / 开盲盒 / Buddy 盲盒;结果落 `growth_events` + 回写 `credentials.growth_last_result` |
447+
| 活跃上报(activity.py,默认关闭) | 每 10 分钟醒一次,仅 `ACTIVITY_REPORT_HOUR`(默认 10 点,北京时间)窗口内执行 | 仅 CodeBuddy:补发一条 `chat_request_send` 续连登;按「endpoint + userId」隔离,当日封账;成功落一行 `growth_events` |
422448
| 明细清理(retention.py) | 每 5 分钟 | `usage_events` 全量重算小时汇总(幂等 upsert,与 record 的增量双写对账)+ 90 天前明细清理 |
423449

424450
**签到 / 成长中心的「同账号」隔离键**:`checkin_scope(data) or f"credential|{credential_id}"`。

‎docker-compose.yml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ services:
2020
# (抽奖/连登兑换/开 Buddy 盲盒/消耗补登卡)
2121
GROWTH_INTERVAL_MINUTES: ${GROWTH_INTERVAL_MINUTES:-60}
2222
GROWTH_IRREVERSIBLE_ACTIONS: ${GROWTH_IRREVERSIBLE_ACTIONS:-true}
23+
# 活跃上报(B1.7,仅 CodeBuddy,默认关闭):补发一条对话事件续连登;
24+
# 官方条款禁止脚本篡改活动数据,开启前请自行评估账号风险
25+
ACTIVITY_REPORT_ENABLED: ${ACTIVITY_REPORT_ENABLED:-false}
26+
ACTIVITY_REPORT_HOUR: ${ACTIVITY_REPORT_HOUR:-10}
2327
# 到期排序窗口(秒):把距到期 ≤ 该值的积分加总,多的账号先用;≤0 关闭
2428
QUOTA_EXPIRY_WINDOW_SECONDS: ${QUOTA_EXPIRY_WINDOW_SECONDS:-129600}
2529
# 会话粘性 TTL(秒):同一对话多轮请求固定用同一凭证;≤0 关闭

‎src/api/admin_credentials.py‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from ..auth.rbac import require_admin
1313
from ..compat.openai.request import InvalidRequest
14+
from ..provider.base import GrowthResult, GrowthStep, StepStatus
1415
from .deps import Services, csrf_protected, principal_from_request
1516

1617
logger = logging.getLogger(__name__)
@@ -211,6 +212,34 @@ async def run_growth(credential_id: str,
211212
"steps": [{"name": step.name, "status": step.status, "detail": step.detail,
212213
"credit": step.credit} for step in result.steps]}
213214

215+
@router.post("/api/credentials/{credential_id}/activity")
216+
async def run_activity(credential_id: str,
217+
_csrf: None = Depends(csrf_protected),
218+
principal=Depends(principal_from_request)):
219+
"""手动补发一条活跃上报(B1.7):与定时任务同一条路径。
220+
221+
独立于 activity_report_enabled 开关:定时任务默认关闭不影响管理员在
222+
管理台手动补报一次(用途就是部署后验证闭环)。结果记一行 growth_events。
223+
"""
224+
require_admin(principal)
225+
provider_id = credentials.provider_of(credential_id)
226+
provider = registry.get(provider_id or "")
227+
data = credentials.credential_data(credential_id)
228+
activity = getattr(provider, "activity", None)
229+
if provider is None or data is None or activity is None:
230+
raise InvalidRequest("credential does not support activity report")
231+
result = await activity(data)
232+
if result.ok:
233+
detail = result.message or "已上报一条对话事件"
234+
services.growth_events.record(
235+
credential_id=credential_id, trigger="manual",
236+
result=GrowthResult(
237+
report=f"活跃上报:{detail}",
238+
steps=[GrowthStep("活跃上报", StepStatus.DONE, detail)]))
239+
logger.info("管理员 %s 手动活跃上报 %s(ok=%s)", principal.username,
240+
credential_id, result.ok)
241+
return {"ok": result.ok, "message": result.message}
242+
214243
@router.get("/api/credentials/{credential_id}/accounts")
215244
async def list_credential_accounts(credential_id: str,
216245
principal=Depends(principal_from_request)):

‎src/config.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ class Settings(BaseSettings):
5353
# 需要保守部署时可关闭:关闭后仍会领取旅行礼物与任务奖励。
5454
growth_interval_minutes: int = 60
5555
growth_irreversible_actions: bool = True
56+
# 活跃上报(B1.7,仅 CodeBuddy):默认关闭。给账号补发一条对话事件,续上
57+
# 成长中心连登天数/活跃地图,与积分、调度无关。官方条款禁止脚本篡改活动
58+
# 数据(处罚为取消资格并追回礼品),开启前请评估账号风险;上游改版即失效。
59+
activity_report_enabled: bool = False
60+
activity_report_hour: int = 10 # 本地(北京)时间整点窗口内执行一次
5661
pacer_min_seconds: float = 5
5762
pacer_max_seconds: float = 20
5863
# CodeBuddy 聊天最小间隔:与 TRAE 共享的最小请求间隔,避开两渠道各自的

0 commit comments

Comments
 (0)