项目文件夹

文件
wehub-resource-sync 2114b14ee0
Sync main into demo / sync (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:26 +08:00

845 行
31 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
"""
微信读书(wechat_readingbench 任务定义。
各 Task 类 docstring 遵循 bench_env/docs/task/TASK_CODE_SPEC.md §12:第一段概括验证目标;第二段「判定」说明
ground truth 来源、check 结构与匹配方式;第三段「注意」仅在边界非显然时补充。
实现仍须与 docstring 一致;代码是最终依据。
"""
# -- Task Index (auto-generated, do not edit) --
# 22 tasks | L1×4 L2×8 L3×9 L4×1
#
# [L1] CheckCoinBalance 微信读书里书币还有多少
# [L3] CheckHotSearchRank 微信读书热搜榜第{rank}名是什么书
# [L1] CheckBookRating 帮我看看微信读书里《{book_title}》推荐值多少
# [L2] AddBookToShelf 把《{book_title}》加到微信读书书架
# [L1] ManageShelf 把微信读书书架里《{book_title}》移出去
# [L2] SearchBookAuthor 微信读书里《{book_title}》是谁写的
# [L3] TogglePrivateReading 把书架里的《{book_title}》设成私密阅读
# [L2] EditProfileName 把微信读书的昵称改成{new_name}
# [L3] SetDarkMode 把微信读书深色模式改成{dark_mode}
# [L2] FindAudiobookPlays 微信读书里《{book_title}》有声版播放量多少
# [L3] AnalyzeReadingHabit 最近一周在微信读书上哪天读的时间最长
# [L3] CheckCalendarMonthReading 微信读书{year}年{month}月总共读了多少天
# [L2] CompareBookLengths 对比微信读书里《{book1}》和《{book2}》的字数,告诉我字数多的那本,然后加到书架
# [L3] FindHighestRatedBookInCategory 微信读书{category}分类里评分最高的书是哪本
# [L2] ConfigureReaderSettings 把微信读书的阅读器字体大小调成{font_size},翻页方式改成{style}
# [L1] UnfollowUser 在微信读书取消关注{user_name}
# [L2] SetProfileVisibility 把微信读书主页可见范围改成{visibility}
# [L3] ReadBookProgress 把微信读书里《{book_title}》翻到{percentage}%的位置
# [L4] OrganizeShelfByRecommendation 整理微信读书书架,把推荐值不高于{recommendation}%的书都删掉
# [L2] AddBookAndReadTo 帮我在微信读书找到《{book_title}》加到书架,调整读书进度到{percentage}%
# [L3] FindLowestProgressAndRead 微信读书书架里哪本书我读的进度最低,帮我翻到{percentage}%的位置
# [L3] PrivacyAndThemeBundle 把微信读书的阅读颜色换成{theme_color},开启"{privacy_label}",再把翻页方式改成{style}
# -- End Task Index --
from __future__ import annotations
import datetime
import re
from typing import Any
from bench_env.task.base import BaseTask
from bench_env.task.common_tasks import AnswerTask, CriteriaTask, build_answer_checks, match_duration
from bench_env.task.judge import JudgeInput
from bench_env.task.utils import sim_today
from bench_env.task.wechat_reading.app import (
WECHAT_READING_FONT_SIZE_VALUES,
WECHAT_READING_PAGE_TURN_STYLE_VALUES,
WECHAT_READING_PROFILE_NAME_VALUES,
WECHAT_READING_PROFILE_VISIBILITY_VALUES,
WECHAT_READING_THEME_COLOR_PARAM,
WechatReading,
)
# =============================================================================
# L1 — 基础覆盖
# =============================================================================
class CheckCoinBalance(AnswerTask):
"""验证 Agent 能否正确读出当前用户书币余额。
判定:宣告式 answer=.user.coinBalance;ground truth 来自 App state;
AnswerTask 默认 match_value 比对回答中的数值。
注意:objective=query,仅答案判定,无状态变更要求。
"""
templates = ["微信读书里书币还有多少"]
apps = ["wechat_reading"]
scope = "S1"
objective = "query"
composition = "atomic"
difficulty = "L1"
capabilities = ["extract"]
answer = ".user.coinBalance"
answer_fields = [{"type": "number", "label": "书币余额"}]
class CheckHotSearchRank(AnswerTask):
"""验证 Agent 能否读出热搜榜指定名次对应的书名。
判定:answer=.hotSearch[rank={rank}].title;单一路径、单值匹配。
"""
templates = ["微信读书热搜榜第{rank}名是什么书"]
apps = ["wechat_reading"]
scope = "S1"
objective = "query"
composition = "atomic"
difficulty = "L3"
max_steps = 15
capabilities = ["extract"]
parameters = {
"rank": {
"type": "integer",
"default": 1,
"description": "热搜排名",
},
}
answer = ".hotSearch[rank={rank}].title"
answer_fields = [{"type": "text", "label": "书名", "hint": "如:围城"}]
class CheckBookRating(AnswerTask):
"""验证 Agent 能否读出书城内某书的推荐值。
判定:answer=.store[title={book_title}].recommendedValue;按 AnswerTask
默认数值匹配(match_value 自动提取回答中的数字,容忍 % 后缀)。
"""
templates = ["帮我看看微信读书里《{book_title}》推荐值多少"]
apps = ["wechat_reading"]
scope = "S1"
objective = "query"
composition = "sequential"
difficulty = "L1"
capabilities = ["extract"]
parameters = {
"book_title": {
"type": "string",
"source": "apps.wechat_reading.store[title]",
"default": "活着",
"description": "书籍标题",
},
}
answer = ".store[title={book_title}].recommendedValue"
answer_fields = [{"type": "number", "label": "推荐值(%"}]
# =============================================================================
# L2 — 核心原子操作
# =============================================================================
class AddBookToShelf(BaseTask):
"""验证是否将初始不在架的书加入书架。
判定:一项 check — WechatReading.check_on_shelf(book_title),expected=True。
注意:operate;expected_changes 含 shelf、bookProgress、readingBookIds、allProgressBookIds。
"""
templates = [
"把《{book_title}》加到微信读书书架",
"Add 《{book_title}》 to my WeChat Read bookshelf",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L2"
capabilities = ["search", "create"]
parameters = {
"book_title": {
"type": "string",
"default": "三体",
"sampler": WechatReading.sample_book_title_not_on_shelf,
"description": "书籍标题(必须初始不在书架中)",
},
}
expected_changes = ["shelf", "bookProgress", "readingBookIds", "allProgressBookIds"]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
wr = WechatReading(input.apps["wechat_reading"])
return [wr.check_on_shelf(self.p.book_title)]
class ManageShelf(BaseTask):
"""验证是否将指定书从书架移出。
判定:check_on_shelf(book_title, expected=False)。
"""
templates = [
"把微信读书书架里《{book_title}》移出去",
"Remove 《{book_title}》 from my WeChat Read bookshelf",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L1"
max_steps = 30
capabilities = ["delete"]
parameters = {
"book_title": {
"type": "string",
"default": "红楼梦",
"sampler": WechatReading.sample_shelf_title,
"description": "书籍标题(必须初始在书架中)",
},
}
expected_changes = ["shelf"]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
wr = WechatReading(input.apps["wechat_reading"])
return [wr.check_on_shelf(self.p.book_title, expected=False)]
class SearchBookAuthor(AnswerTask):
"""验证 Agent 能否答出某书的作者。
判定:answer=.store[title={book_title}].author。
"""
templates = ["微信读书里《{book_title}》是谁写的"]
apps = ["wechat_reading"]
scope = "S1"
objective = "query"
composition = "sequential"
difficulty = "L2"
capabilities = ["search", "extract"]
parameters = {
"book_title": {
"type": "string",
"source": "apps.wechat_reading.store[title]",
"default": "活着",
"description": "书籍标题",
},
}
answer = ".store[title={book_title}].author"
answer_fields = [{"type": "text", "label": "作者", "hint": "如:鲁迅"}]
class TogglePrivateReading(BaseTask):
"""验证是否将书架上的书设为私密阅读。
判定:check_private_reading(book_title),校验 shelf 项 isPrivate。
"""
templates = [
"把书架里的《{book_title}》设成私密阅读",
"Set 《{book_title}》 on my bookshelf to private reading",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L3"
max_steps = 30
capabilities = ["settings", "edit"]
parameters = {
"book_title": {
"type": "string",
"default": "苏菲的世界",
"sampler": WechatReading.sample_public_shelf_title,
"description": "书籍标题(必须初始在书架里且不是私密阅读)",
},
}
expected_changes = ["shelf"]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
wr = WechatReading(input.apps["wechat_reading"])
return [wr.check_private_reading(self.p.book_title)]
class EditProfileName(CriteriaTask):
"""验证用户昵称是否被改为目标值。
判定:criteria user.name == 参数 new_name;_post_sample 调用 _invert_criteria。
"""
templates = [
"把微信读书的昵称改成{new_name}",
"Change my WeChat Read nickname to {new_name}",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L2"
capabilities = ["edit"]
parameters = {
"new_name": {
"type": "enum",
"values": WECHAT_READING_PROFILE_NAME_VALUES,
"default": "阿青",
},
}
criteria = {"user.name": "{new_name}"}
async def _post_sample(self, env):
await self._invert_criteria(env)
class SetDarkMode(CriteriaTask):
"""验证深色模式是否为浅色 / 深色 / 跟随系统之一。
判定:criteria settings.darkMode == 参数内部值(模板展示为「深色模式」等,
由 enum 映射到 store 值);_post_sample_invert_criteria。
"""
templates = ["把微信读书深色模式改成{dark_mode}"]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L3"
max_steps = 30
capabilities = ["settings"]
parameters = {
"dark_mode": {
"type": "enum",
"values": {
"深色模式": "深色",
"浅色模式": "浅色",
"跟随系统": "跟随系统",
},
"default": "深色",
},
}
criteria = {"settings.darkMode": "{dark_mode}"}
async def _post_sample(self, env):
await self._invert_criteria(env)
class FindAudiobookPlays(AnswerTask):
"""验证 Agent 能否读出某有声书的播放量文案。
判定:answer=.audiobooks[title={book_title}].plays(如「25万」)。
"""
templates = ["微信读书里《{book_title}》有声版播放量多少"]
apps = ["wechat_reading"]
scope = "S1"
objective = "query"
composition = "sequential"
difficulty = "L2"
capabilities = ["search", "extract"]
parameters = {
"book_title": {
"type": "string",
"source": "apps.wechat_reading.audiobooks[title]",
"default": "红楼梦",
"description": "有声书标题",
},
}
answer = ".audiobooks[title={book_title}].plays"
answer_fields = [{"type": "text", "label": "播放量", "hint": "如:8万"}]
expected_changes = ["recommendedAudiobooks"]
# =============================================================================
# L3 — 多步推理与跨功能
# =============================================================================
class AnalyzeReadingHabit(BaseTask):
"""验证能否指出最近一周(7 天窗口)阅读时长最长的那一天。
判定:best_reading_dates_in_last_week 返回所有并列最高日期;
回答须命中其中任一日期的 date_labels(多日并列时答任一天均可)。
注意:与 AnswerTask 不同,需自定义日期回答匹配(§4.6)。
并列时 ground truth 为多个日期,无法用单一 answer 路径表达。
"""
templates = [
"最近一周在微信读书上哪天读的时间最长",
"看看微信读书这一周的阅读记录里哪天读得最久",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "query"
composition = "deep_dive"
difficulty = "L3"
capabilities = ["extract", "reasoning"]
answer_fields = [{"type": "choice", "label": "阅读时间最长的一天",
"options": ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"]}]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
wr = WechatReading(input.apps_init["wechat_reading"])
best_dates = wr.best_reading_dates_in_last_week(input.os_init)
answer_text = re.sub(r"\s+", "", str(input.answer or ""))
all_labels: list[str] = []
for d in best_dates:
all_labels.extend(WechatReading.date_labels(d, input.os_init))
return [{
"field": "answer",
"expected": all_labels,
"actual": input.answer,
"passed": any(label in answer_text for label in all_labels),
}]
class CheckCalendarMonthReading(AnswerTask):
"""验证指定自然月内有阅读记录的天数。
判定:get_answer 为 month_reading_day_count(year, month);按 readingRecords
去重日期;返回 int。
"""
templates = [
"微信读书{year}{month}月总共读了多少天",
"查下{year}{month}月我在微信读书上有几天阅读记录",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "query"
composition = "deep_dive"
difficulty = "L3"
capabilities = ["extract", "reasoning"]
parameters = {
"year": {
"type": "integer",
"default": 2026,
"description": "年份",
},
"month": {
"type": "integer",
"default": 1,
"description": "月份",
},
"_record_month": {
"sampler": WechatReading.sample_year_month_with_records,
"fields": {"year": "year", "month": "month"},
},
}
answer_fields = [{"type": "number", "label": "阅读天数"}]
def get_answer(self, input: JudgeInput) -> Any:
wr = WechatReading(input.apps_init["wechat_reading"])
return wr.month_reading_day_count(self.p.year, self.p.month)
class CompareBookLengths(BaseTask):
"""对比两书字数,将字数更多的一本上架,并在回答中体现该书书名。
判定:① check_on_shelf(字数多者);② build_answer_checks(书名)。
totalWords 来自 store;采样保证两书字数不等。
注意:objective=hybrid;状态与回答均需通过。
"""
templates = [
"对比微信读书里《{book1}》和《{book2}》的字数,告诉我字数多的那本,然后加到书架",
"告诉我《{book1}》和《{book2}》在微信读书里哪本更厚,帮我把厚的那本收到书架",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "hybrid"
composition = "deep_dive"
difficulty = "L2"
max_steps = 45
capabilities = ["extract", "reasoning", "create"]
parameters = {
"book1": {
"type": "string",
"default": "三体",
"description": "书籍1",
},
"book2": {
"type": "string",
"default": "活着",
"description": "书籍2",
},
"_book_pair": {
"sampler": WechatReading.sample_two_books_unequal_word_counts,
"fields": {"book1": "book1", "book2": "book2"},
},
}
expected_changes = ["shelf", "bookProgress", "readingBookIds", "allProgressBookIds"]
answer_fields = [{"type": "choice", "label": "字数更多的书名", "options": ["{book1}", "{book2}"]}]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
wr = WechatReading(input.apps["wechat_reading"])
b1 = wr.require_book_by_title(self.p.book1)
b2 = wr.require_book_by_title(self.p.book2)
w1, w2 = int(b1["totalWords"]), int(b2["totalWords"])
thicker = self.p.book1 if w1 > w2 else self.p.book2
checks = [wr.check_on_shelf(thicker, field="shelf_thicker_book")]
checks.extend(build_answer_checks(thicker, input.answer))
return checks
class FindHighestRatedBookInCategory(AnswerTask):
"""验证某分类下评分最高的书名(同分并列时多答案均可)。
判定:get_answer 用 highest_rated_books_in_category;唯一最高返回 str,
并列返回 re.Pattern 匹配任一书名(§4.3)。
"""
templates = [
"微信读书{category}分类里评分最高的书是哪本",
"看看微信读书{category}类的书哪本评分最高",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "query"
composition = "deep_dive"
difficulty = "L3"
capabilities = ["extract", "reasoning"]
parameters = {
"category": {
"type": "string",
"default": "文学",
"sampler": WechatReading.sample_category_with_multiple_books,
"description": "书籍分类",
},
}
answer_fields = [{"type": "text", "label": "评分最高的书名", "hint": "如:围城"}]
def get_answer(self, input: JudgeInput) -> Any:
wr = WechatReading(input.apps_init["wechat_reading"])
best_books = wr.highest_rated_books_in_category(self.p.category)
if len(best_books) == 1:
return str(best_books[0]["title"])
return re.compile("|".join(re.escape(str(book["title"])) for book in best_books))
def get_expected_response(self, input):
wr = WechatReading(input.apps_init["wechat_reading"])
best_books = wr.highest_rated_books_in_category(self.p.category)
# In grounded mode, return the first tied book's title
return [str(best_books[0]["title"])]
class ConfigureReaderSettings(CriteriaTask):
"""同时设置阅读器字号与系统级翻页方式。
判定:criteria 同时检查 readerPrefs.fontSize 与 settings.pageTurnStyle;
_post_sample_invert_criteria。
"""
templates = [
"把微信读书的阅读器字体大小调成{font_size},翻页方式改成{style}",
"在微信读书里把阅读器字体大小改成{font_size},顺便把翻页方式换成{style}",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L2"
capabilities = ["settings"]
parameters = {
"font_size": {
"type": "enum",
"values": WECHAT_READING_FONT_SIZE_VALUES,
"default": 22,
},
"style": {
"type": "enum",
"values": WECHAT_READING_PAGE_TURN_STYLE_VALUES,
"default": "仿真翻页",
},
}
criteria = {
"readerPrefs.fontSize": "{font_size}",
"settings.pageTurnStyle": "{style}",
}
async def _post_sample(self, env):
await self._invert_criteria(env)
class UnfollowUser(BaseTask):
"""验证是否取消关注指定用户(初始为已关注)。
判定:check_following(user_id, expected=False)。
注意:user_id / user_name 由 sample_following_user 协同采样。
"""
templates = [
"在微信读书取消关注{user_name}",
"帮我取关微信读书上的{user_name}",
"Unfollow {user_name} on WeChat Read",
"Stop following {user_name} on WeChat Read",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L1"
capabilities = ["edit"]
parameters = {
"user_id": {
"type": "string",
"default": "user_508",
},
"user_name": {
"type": "string",
"default": "508",
},
"_following_user": {
"sampler": WechatReading.sample_following_user,
"fields": {"user_id": "user_id", "user_name": "user_name"},
},
}
expected_changes = ["user.following"]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
wr = WechatReading(input.apps["wechat_reading"])
return [wr.check_following(self.p.user_id, expected=False)]
class SetProfileVisibility(CriteriaTask):
"""验证主页可见范围是否为目标枚举(仅自己 / 互关 / 所有人)。
判定:criteria settings.privacy.profile.visibility;_post_sample_invert_criteria。
"""
templates = [
"把微信读书主页可见范围改成{visibility}",
"微信读书的主页可见性帮我调成{visibility}",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L2"
capabilities = ["settings"]
parameters = {
"visibility": {
"type": "enum",
"values": WECHAT_READING_PROFILE_VISIBILITY_VALUES,
"default": "仅自己可见",
},
}
criteria = {"settings.privacy.profile.visibility": "{visibility}"}
async def _post_sample(self, env):
await self._invert_criteria(env)
class ReadBookProgress(BaseTask):
"""验证是否将指定书阅读进度推进到不低于某百分比。
判定:check_progress(book_title, min_pct);进度由 bookProgress.charOffset
与 store.totalWords 推导。
"""
templates = [
"把微信读书里《{book_title}》翻到{percentage}%的位置",
"打开微信读书里的《{book_title}》读到{percentage}%左右",
"Turn to the {percentage}% position of 《{book_title}》 in WeChat Read",
"Read 《{book_title}》 on WeChat Read to about {percentage}%",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L3"
capabilities = ["edit"]
parameters = {
"book_title": {
"type": "string",
"default": "红楼梦",
"description": "书籍标题(必须在书架中)",
},
"percentage": {
"type": "integer",
"default": 20,
"description": "阅读进度百分比",
},
"_progress_target": {
"sampler": WechatReading.sample_progress_target,
"fields": {"book_title": "book_title", "percentage": "percentage"},
},
}
expected_changes = ["bookProgress", "readingBookIds", "allProgressBookIds"]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
wr = WechatReading(input.apps["wechat_reading"])
return [wr.check_progress(self.p.book_title, self.p.percentage)]
# =============================================================================
# L4 — 复杂组合与条件分支
# =============================================================================
class OrganizeShelfByRecommendation(BaseTask):
"""按推荐值阈值清理书架:删除推荐值不高于阈值的书,保留更高推荐的书。
判定:一项自定义 check — 当前书架 bookId 集合应等于初始架中
recommendedValue > threshold 的子集(与 check_goals 实现一致)。
注意:依赖 init 与 curr 快照对比,非单一 criteria。
"""
templates = [
"整理微信读书书架,把推荐值不高于{recommendation}%的书都删掉",
"微信读书书架里推荐值不超过{recommendation}%的书帮我清理掉",
"Clean up my WeChat Read bookshelf by removing all books with a recommendation score of {recommendation}% or below",
"Organize my WeChat Read bookshelf and delete books with a recommendation value no higher than {recommendation}%",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "deep_dive"
difficulty = "L4"
capabilities = ["delete", "reasoning"]
parameters = {
"recommendation": {
"type": "float",
"default": 95.0,
"description": "推荐值阈值(百分比)",
},
}
expected_changes = ["shelf"]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
init_wr = WechatReading(input.apps_init["wechat_reading"])
curr_wr = WechatReading(input.apps["wechat_reading"])
expected_ids = sorted(
str(item["bookId"])
for item in init_wr.shelf
if float(init_wr.require_store_book(str(item["bookId"]))["recommendedValue"]) > self.p.recommendation
)
actual_ids = sorted(str(item["bookId"]) for item in curr_wr.shelf)
return [{
"field": "shelf",
"expected": expected_ids,
"actual": actual_ids,
"passed": actual_ids == expected_ids,
}]
class AddBookAndReadTo(BaseTask):
"""加书到架并将该书读到不低于目标进度。
判定:① check_on_shelf;② check_progress;两项均须通过。
注意:objective=hybrid;初始书不在架由采样保证。
"""
templates = [
"帮我在微信读书找到《{book_title}》加到书架,调整读书进度到{percentage}%",
"Find 《{book_title}》 on WeChat Read, add it to my bookshelf, and set the reading progress to {percentage}%",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "hybrid"
composition = "sequential"
difficulty = "L2"
capabilities = ["search", "create", "edit"]
parameters = {
"book_title": {
"type": "string",
"default": "三体",
"description": "书籍标题(初始不在书架)",
},
"percentage": {
"type": "integer",
"default": 20,
"description": "阅读进度百分比",
},
"_add_read": {
"sampler": WechatReading.sample_add_book_and_read,
"fields": {"book_title": "book_title", "percentage": "percentage"},
},
}
expected_changes = ["shelf", "bookProgress", "readingBookIds", "allProgressBookIds"]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
wr = WechatReading(input.apps["wechat_reading"])
return [
wr.check_on_shelf(self.p.book_title, field="shelf_after_add"),
wr.check_progress(self.p.book_title, float(self.p.percentage), field="read_progress"),
]
class FindLowestProgressAndRead(BaseTask):
"""将书架中进度最低的书读到目标进度。
判定:先在 **初始 state** 上用 WechatReading.shelf_book_title_with_lowest_progress()
锁定「起始进度最低」的书名,然后在最终 state 上只检查这本书的进度
>= percentage。并列最低时与 App 内 shelf 遍历顺序一致。
注意:percentage 由 sample_percentage_for_lowest_progress_read 单独采样,
保证相对采样时刻的最低进度书存在可达目标;判定时书名与初始 state 绑定,
避免用户先把最低那本读高后判定目标“跳到下一本”。objective=operate。
"""
templates = [
"微信读书书架里哪本书我读的进度最低,帮我翻到{percentage}%的位置",
"看看微信读书书架上我进度最落后的是哪本,翻到{percentage}%的位置",
"Which book on my WeChat Read bookshelf has the lowest reading progress? Jump to the {percentage}% position for me",
"Find the book with the lowest progress on my WeChat Read bookshelf and jump to the {percentage}% position",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "deep_dive"
difficulty = "L3"
max_steps = 60
capabilities = ["reasoning", "edit"]
parameters = {
"percentage": {
"type": "integer",
"default": 50,
"description": "当前最低进度书需达到的目标百分比",
},
"_lowest_pct": {
"sampler": WechatReading.sample_percentage_for_lowest_progress_read,
"fields": {"percentage": "percentage"},
},
}
expected_changes = [
"bookProgress",
"readingBookIds",
"allProgressBookIds",
"finishedBookIds",
"homeFinishedBookIds",
]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
init_wr = WechatReading(input.apps_init["wechat_reading"])
curr_wr = WechatReading(input.apps["wechat_reading"])
title = init_wr.shelf_book_title_with_lowest_progress()
return [curr_wr.check_progress(title, float(self.p.percentage))]
class PrivacyAndThemeBundle(CriteriaTask):
"""同时设置阅读器颜色(themeColor)、开启一项隐私开关、设置翻页方式。
判定:criteria 三条 — readerPrefs.themeColor、settings.privacy.{setting_key}=True、
settings.pageTurnStyle;隐私项由 sample_privacy_setting_bundle 采样。
_post_sample_invert_criteria。
注意:与 App 阅读器「主题」面板首行「颜色」一致(readerPrefs.themeColor
white/yellow/green/dark),不是第二行「背景」(themeBg)。展示名白色/米黄/
绿色/深色的内部值见 WECHAT_READING_THEME_COLOR_PARAM。
"""
templates = [
"把微信读书的阅读颜色换成{theme_color},开启\"{privacy_label}\",再把翻页方式改成{style}",
"在微信读书里把阅读颜色调成{theme_color}、开启\"{privacy_label}\"、翻页方式设成{style}",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L3"
capabilities = ["settings"]
parameters = {
"theme_color": {
"type": "enum",
"values": WECHAT_READING_THEME_COLOR_PARAM,
"default": "yellow",
},
"privacy_label": {
"type": "string",
"default": "关注你须获得你的同意",
},
"setting_key": {
"type": "string",
"default": "requireFollowRequest",
},
"style": {
"type": "enum",
"values": WECHAT_READING_PAGE_TURN_STYLE_VALUES,
"default": "仿真翻页",
},
"_privacy_setting": {
"sampler": WechatReading.sample_privacy_setting_bundle,
"fields": {"privacy_label": "privacy_label", "setting_key": "setting_key"},
},
}
criteria = {
"readerPrefs.themeColor": "{theme_color}",
"settings.privacy.{setting_key}": True,
"settings.pageTurnStyle": "{style}",
}
async def _post_sample(self, env):
await self._invert_criteria(env)