Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
68 changes: 61 additions & 7 deletions app/growth_chains.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,28 @@
QQ_BUDDY_ID = "cb_y5Dy46tPQGGWtueMxXbe"
QQ_BUDDY_NAME = "企鹅教师助手"
LIGHTHOUSE_EXPERT_ID = "ex_2cvvUZQhDyeJ"
NAMED_EXPERT_TASKS = {
"WorkBuddy_Valorant": {
"expert_id": "ex_xQHHD1u5Jkua", "expert_type": "agent",
"display_name_zh": "无畏契约拼豆专家", "profession_zh": "无畏契约拼豆专家",
"version": "1.2.7", "categories": ["03-GameSpatial", "06-ContentCreative"]},
"expert_use_bowangwang": {
"expert_id": "ex_WZY136wV5aMt", "expert_type": "agent",
"display_name_zh": "播旺旺", "profession_zh": "直播电商教练",
"version": "1.0.2", "categories": ["05-MarketingGrowth", "18-Double11"]},
"expert_use_douyinyin": {
"expert_id": "ex_FbY5nlWTqDod", "expert_type": "agent",
"display_name_zh": "斗音音", "profession_zh": "抖音策略师",
"version": "1.0.2", "categories": ["06-ContentCreative", "18-Double11"]},
"expert_use_maidehao": {
"expert_id": "ex_GDl31WahyIH0", "expert_type": "agent",
"display_name_zh": "卖得好", "profession_zh": "中国电商运营专家",
"version": "1.0.1", "categories": ["05-MarketingGrowth", "18-Double11"]},
"expert_use_shangjia_xuanchuan": {
"expert_id": "ex_Weo3x2EZuSDm", "expert_type": "agent",
"display_name_zh": "商家宣传设计师", "profession_zh": "商家宣传页与海报设计师",
"version": "1.6.0", "categories": ["01-ProductDesign", "05-MarketingGrowth", "18-Double11"]},
}
SKILL_NAME = "润泽小馆·日报撰写"
SKILL_ID = "skill_2097350077599879168"
THEME_KEY = "theme-tkmw7j"
Expand Down Expand Up @@ -279,18 +301,16 @@ def run_expert_team_3(ctx, task):
return _run_expert_batch(ctx, "team", 3)


def run_expert_lighthouse(ctx, task):
expert = {"expert_id": LIGHTHOUSE_EXPERT_ID, "expert_type": "agent",
"display_name_zh": "腾讯轻量云专家", "profession_zh": "腾讯轻量云专家",
"version": "1.0.2", "categories": []}
def _run_named_expert(ctx, expert, *, local=False):
expert = dict(expert)
try:
for candidate in growth_events.market_expert_list(ctx.base, ctx.token, ctx.uid, "agent",
domain=ctx.domain):
if candidate.get("expert_id") == LIGHTHOUSE_EXPERT_ID:
if candidate.get("expert_id") == expert["expert_id"]:
expert = candidate
break
except growth_events.Failure:
pass # 市场不可用时用内置专家信息(id 为平台真实 id)
pass # Fall back to the verified task-specific id when the market is unavailable.
_report_desktop(ctx, growth_events.expert_summon_sequence(expert))
conversation_id, request_id = _real_chat(ctx, expert_id=expert["expert_id"])
events = growth_events.chat_sequence(conversation_id, request_id,
Expand All @@ -299,11 +319,40 @@ def run_expert_lighthouse(ctx, task):
if event["eventCode"] == "agent_task_created":
event.update(has_expert=True, expert_id=expert["expert_id"],
expert_name=expert.get("display_name_zh") or "", expert_industry_id="")
events.append(growth_events.expert_actual_use(expert, conversation_id, request_id, mode="LOCAL"))
events.append(growth_events.expert_actual_use(
expert, conversation_id, request_id, mode="LOCAL" if local else "craft"))
_report_desktop(ctx, events)
return f"已上报「{expert.get('display_name_zh') or expert['expert_id']}」召唤+使用链(真实对话 requestId)"


def run_expert_lighthouse(ctx, task):
_run_named_expert(ctx, {
"expert_id": LIGHTHOUSE_EXPERT_ID, "expert_type": "agent",
"display_name_zh": "腾讯轻量云专家", "profession_zh": "腾讯轻量云专家",
"version": "1.0.2", "categories": []}, local=True)
return "已上报轻量云专家召唤+使用链(真实对话 requestId)"


def run_workbuddy_valorant(ctx, task):
return _run_named_expert(ctx, NAMED_EXPERT_TASKS["WorkBuddy_Valorant"])


def run_expert_bowangwang(ctx, task):
return _run_named_expert(ctx, NAMED_EXPERT_TASKS["expert_use_bowangwang"])


def run_expert_douyinyin(ctx, task):
return _run_named_expert(ctx, NAMED_EXPERT_TASKS["expert_use_douyinyin"])


def run_expert_maidehao(ctx, task):
return _run_named_expert(ctx, NAMED_EXPERT_TASKS["expert_use_maidehao"])


def run_expert_shangjia_xuanchuan(ctx, task):
return _run_named_expert(ctx, NAMED_EXPERT_TASKS["expert_use_shangjia_xuanchuan"])


def run_black_cat(ctx, task):
if not in_night_window():
return "当前不在 23:00–08:00 计数窗口,行为不计分;窗口内自动补足"
Expand Down Expand Up @@ -522,6 +571,11 @@ def events():
"Hp_Appearance": run_hp_appearance,
"Model_chat_GLM5.2": run_model_chat_glm, "skill_1": run_skill_1, "expert_5": run_expert_5,
"Expert_team_use_3": run_expert_team_3, "Expert_lighthouse": run_expert_lighthouse,
"WorkBuddy_Valorant": run_workbuddy_valorant,
"expert_use_bowangwang": run_expert_bowangwang,
"expert_use_douyinyin": run_expert_douyinyin,
"expert_use_maidehao": run_expert_maidehao,
"expert_use_shangjia_xuanchuan": run_expert_shangjia_xuanchuan,
"black_cat": run_black_cat,
"school_season": run_school_season, "Sequential_Tasks_1": run_sequential_1,
"Sequential_Tasks_2": run_sequential_2, "Sequential_Tasks_3": run_sequential_3,
Expand Down
3 changes: 3 additions & 0 deletions app/growth_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
"Hp_Appearance": "behavior",
"Model_chat_GLM5.2": "chat", "skill_1": "chat", "expert_5": "chat",
"Expert_team_use_3": "chat", "Expert_lighthouse": "chat", "black_cat": "chat",
"WorkBuddy_Valorant": "chat", "expert_use_bowangwang": "chat",
"expert_use_douyinyin": "chat", "expert_use_maidehao": "chat",
"expert_use_shangjia_xuanchuan": "chat",
"school_season": "miniprogram", "Sequential_Tasks_1": "miniprogram",
"Sequential_Tasks_2": "miniprogram", "Sequential_Tasks_3": "miniprogram",
"Sequential_Tasks_4": "miniprogram", "Sequential_Tasks_5": "miniprogram",
Expand Down
2 changes: 1 addition & 1 deletion docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Upstream client identities follow verified official packages: domestic CLI 2.165
## Automation and rewards

Automatic check-in and Buddy travel are per-account switches (WebUI credentials page): on by default for domestic personal accounts, off internationally, applied live without restart. The growth center is personal-only, so enterprise accounts never travel and no longer surface the upstream 403.
The growth-center sweep (`auto_growth`, on by default for domestic personal accounts) rides the hourly maintenance pass as pure accept/claim: it claims finished tasks for credit and energy and never starts a behavior chain by itself. Per-task `growth_auto` switches (off by default) opt specific tasks into automation, so the sweep also drives the matching chain — real conversations, app opens or expert summons that may spend credits. The credential drawer lists live tasks with one-click manual runs for a single task or all enabled ones, and task switches can be copied to selected accounts (`POST /admin/credentials/growth-batch`); `GET /admin/credentials/{id}/growth-tasks`, `.../growth-tasks/auto` and `.../auto-all` back the panel. Manual runs are server-side jobs, one per account: the request returns the job at once, closing the drawer or the page does not stop it, starting again while it runs returns the running job, and `GET /admin/growth-jobs` lists every account's current or last job with the task in progress for the page banner. Manual runs and sweep claims are audited; enterprise or international accounts never see the panel.
The growth-center sweep (`auto_growth`, on by default for domestic personal accounts) rides the hourly maintenance pass as pure accept/claim: it claims finished tasks for credit and energy and never starts a behavior chain by itself. Per-task `growth_auto` switches (off by default) opt specific tasks into automation, so the sweep also drives the matching chain — real conversations, app opens or expert summons that may spend credits. The credential drawer lists live tasks with one-click manual runs for a single task or all enabled ones, and task switches can be copied to selected accounts (`POST /admin/credentials/growth-batch`); `GET /admin/credentials/{id}/growth-tasks`, `.../growth-tasks/auto` and `.../auto-all` back the panel. Manual runs are server-side jobs, one per account: the request returns the job at once, closing the drawer or the page does not stop it, starting again while it runs returns the running job, and `GET /admin/growth-jobs` lists every account's current or last job with the task in progress for the page banner. Named expert tasks use verified task-specific expert IDs through the same opt-in real-conversation chain. Manual runs and sweep claims are audited; enterprise or international accounts never see the panel.
Check-in, travel, growth and daily-chat results or exceptions carrying 11140 use the same persistent two-hour restricted state as inference. The current sweep stops immediately, including remaining growth claims, and later sweeps skip the account until the rest expires or an operator releases it; other codes retain their existing logging and retry behavior.

`CODEBUDDY2API_AUTO_ACCEPT_BUDDY` is startup-only and defaults to `false`. It preauthorizes enabled domestic accounts for first-Buddy onboarding, agreement and travel; automatic travel still respects its account switch. `first_buddy` needs no acceptance API: pending states, including `not_accepted`, allow one real domestic WorkBuddy conversation on that account. Prefer an eligible zero-rate model, otherwise the lowest known rate; request at most 32 output tokens with possible credit usage. Other reward tasks, paid boxes, pet switching and international trials are excluded.
Expand Down
2 changes: 1 addition & 1 deletion docs/advanced.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ scoped 模式可选传入 `X-Codebuddy-Session-ID`、`metadata.conversation_id`

自动签到与 Buddy 旅行是按账号设置的开关(WebUI 凭证页):国内个人账号默认开启,国际默认关闭,保存即生效,无需重启。成长中心仅面向个人账号,企业账号不参与旅行(也不再显示上游 403)。

成长中心补领(`auto_growth`,国内个人账号默认开启)随每小时维护扫描执行,只做接取与领取:领取已完成任务的积分与能量,自身不启动任何行为链。任务级 `growth_auto` 开关(默认全部关闭)把指定任务纳入自动化,扫描随后驱动对应行为链——可能消耗积分的真实对话、应用体验或专家召唤。凭证页展示实时任务列表,支持单任务与已开开关任务的一键手动执行,任务开关可复制到已选账号(`POST /admin/credentials/growth-batch`);面板由 `GET /admin/credentials/{id}/growth-tasks`、`.../growth-tasks/auto` 与 `.../auto-all` 支撑。手动执行是服务端后台任务(每个账号同时只有一个):请求立即返回任务,关闭抽屉或页面不影响执行,运行中再次发起会返回当前任务;`GET /admin/growth-jobs` 列出各账号当前或最近一次任务及正在执行的任务,供页面横幅显示。手动执行与扫描领取均入审计;企业与国际账号不显示该面板。
成长中心补领(`auto_growth`,国内个人账号默认开启)随每小时维护扫描执行,只做接取与领取:领取已完成任务的积分与能量,自身不启动任何行为链。任务级 `growth_auto` 开关(默认全部关闭)把指定任务纳入自动化,扫描随后驱动对应行为链——可能消耗积分的真实对话、应用体验或专家召唤。凭证页展示实时任务列表,支持单任务与已开开关任务的一键手动执行,任务开关可复制到已选账号(`POST /admin/credentials/growth-batch`);面板由 `GET /admin/credentials/{id}/growth-tasks`、`.../growth-tasks/auto` 与 `.../auto-all` 支撑。手动执行是服务端后台任务(每个账号同时只有一个):请求立即返回任务,关闭抽屉或页面不影响执行,运行中再次发起会返回当前任务;`GET /admin/growth-jobs` 列出各账号当前或最近一次任务及正在执行的任务,供页面横幅显示。指定专家任务使用已核实的任务专属专家 ID,沿用需主动开启的真实对话链。手动执行与扫描领取均入审计;企业与国际账号不显示该面板。
签到、旅行、成长和活跃打卡的结果或异常携带 11140 时,与推理共用持久两小时官方限制状态。当前扫描立即停止,包括剩余成长领取;限制到期或人工解除前,后续扫描跳过该账号;其他业务码保持原有日志和重试行为。

`CODEBUDDY2API_AUTO_ACCEPT_BUDDY` 仅启动读取,默认 `false`;预授权已启用国内账号完成首次领猫任务、协议及旅行,自动旅行仍受账号开关控制。`first_buddy` 无需单独接取,包含 `not_accepted` 在内的待完成状态可直接发起一次本账号的真实国内 WorkBuddy 对话;优先可用零倍率模型,否则取最低已知倍率,最多请求 32 个输出 token,可能消耗少量积分。不执行其他奖励任务、不付费开盒、不切猫、不领取国际试用积分。
Expand Down
48 changes: 48 additions & 0 deletions tests/test_growth_chains.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,54 @@ def test_real_chat_request_id_shape(self):
self.assertTrue(growth_events.SERVER_ID_RE.match("a" * 32))


class NamedExpertTests(ChainHarness):
def exercise(self, code, expert):
reports, seen = [], {}

def real_chat(_ctx, expert_id="", model=growth_chains.DEFAULT_CHAT_MODEL):
seen["expert_id"] = expert_id
return "wb2api-conv-x", "cmb-" + "a" * 32

# Same-name market entries must never replace the task-specific expert id.
wrong = {"expert_id": "ex_wrong", "display_name_zh": expert["display_name_zh"]}
ctx = growth_chains.Context(base="https://www.workbuddy.cn", token="tok", uid="u1")
with patch.object(growth_events, "market_expert_list", return_value=[wrong]), \
patch.object(growth_chains, "_real_chat", side_effect=real_chat), \
patch.object(growth_chains, "_report_desktop",
side_effect=lambda _ctx, events: reports.append(events)):
message = growth_chains.RUNNERS[code](ctx, raw_task(code))
return message, reports, seen

def test_cloud_expert_tasks_keep_their_verified_ids(self):
for code, expert in growth_chains.NAMED_EXPERT_TASKS.items():
with self.subTest(code=code):
message, reports, seen = self.exercise(code, expert)
self.assertEqual(seen["expert_id"], expert["expert_id"])
self.assertEqual(growth_tasks.GROUPS[code], "chat")
self.assertIn(expert["display_name_zh"], message)
summoned = next(event for report in reports for event in report
if event["eventCode"] == "expert_summoned")
actual = next(event for report in reports for event in report
if event["eventCode"] == "expert_actual_use")
self.assertEqual(summoned["id"], expert["expert_id"])
self.assertEqual(actual["id"], expert["expert_id"])
self.assertEqual(actual["name"], expert["display_name_zh"])
self.assertEqual(actual["version"], expert["version"])
self.assertEqual(actual["type"], expert["categories"][0])
self.assertEqual(actual["mode"], "craft")

def test_lighthouse_keeps_its_local_event_shape(self):
expert = {"expert_id": growth_chains.LIGHTHOUSE_EXPERT_ID,
"display_name_zh": "腾讯轻量云专家"}
_message, reports, seen = self.exercise("Expert_lighthouse", expert)
self.assertEqual(seen["expert_id"], growth_chains.LIGHTHOUSE_EXPERT_ID)
actual = next(event for report in reports for event in report
if event["eventCode"] == "expert_actual_use")
self.assertEqual(actual["mode"], "LOCAL")
self.assertEqual(actual["type"], "")
self.assertEqual(actual["cost"], 0)


class NightWindowTests(ChainHarness):
def test_window_boundaries(self):
for hour, expected in ((22, False), (23, True), (0, True), (7, True), (8, False)):
Expand Down
Loading