Guided Init Module
September 3, 2026 · View on GitHub
概述
引导初始化(guided init)让用户既能在命令行 openbiliclaw init、也能在浏览器插件「推荐」tab、桌面 Web(/web)未初始化空状态、或安装包首启 /setup/ 向导里点「开始初始化」完成首轮建模。所有图形入口共用同一套四阶段流水线,后端再叠加进度状态机、前置检查和写者门控,保证图形化初始化在一个活跃后端上安全运行。
四阶段(与 CLI 完全一致):
-
拉取数据 — 按本轮选择采集 B 站历史 / 收藏 / 关注,小红书、抖音、YouTube、知乎、Reddit、Linux.do、V2EX、微博扩展任务信号,X 服务端点赞 / 收藏,Bangumi 官方 API 收藏,以及 GitHub 官方 REST 的公开 starred repositories,再统一经
build_event→memory.propagate_events批量事务入库。GitHub 每个公开 Star 只映射为favorite,不从 forks / issues / watchers 合成其它行为;它没有扩展任务或后台账号增量,只在 init / 按需 fetch 重读。GitHub Star 分页若在至少一个完整页后超时,会保留此前事件并以partial_timeout结束;零完整页超时仍失败。GitHub 的身份、PAT 或采集失败按来源隔离:混合初始化继续使用其它有效来源并在 scope counts / 最终摘要保留稳定 GitHub reason;只有 GitHub 是唯一画像来源且没有有效事件时才硬失败。其它来源继续按各自 complete / empty / degraded 证据决定是否可建模和是否推进周期状态,任何 partial 都不冒充完整快照。 Linux.do bootstrap 的三个 scope 有部分失败时以degraded回传:已得到的书签/点赞/阅读事件仍进入本轮画像输入,但不种last_attempt_at、不视为完整采集,也不参与 6 小时近期任务复用;全部 scope 失败为failed,同样不复用。Linux.do 单任务默认有 32.5 分钟端到端总等待:pending 最多约 3 分钟等领取,进入in_progress后可按形状执行最多约 29 分钟,并留 30 秒结果余量。阶段 1 的基础总预算仍是 1800 秒;调用方使用默认值时,Linux.do 是唯一来源则至少给 32.5 分钟,Linux.do 与至少一个其它来源并选则给 30 + 32.5 = 62.5 分钟,避免串行的前序来源吃掉合法长任务窗口。显式传入collection_timeout_secondsoverride 时原样生效、不扩容。 -
分析偏好 —
soul_engine.analyze_events(...)分片并发;每个初始化 chunk 除了结构化偏好,也会产出少量临时awareness_candidates/insight_candidates,本地去重合并后只作为本次画像生成上下文。 -
生成并保存完整画像 —
soul_engine.build_initial_profile(...)消费合并后的 preference、history summary,以及第 2 段生成的临时觉察 / 洞察候选;这些候选不写入长期awareness/insight层。只有结构化画像通过校验并写入soul.json后,本阶段才完成。初始正向兴趣 / 避雷探针不再夹在这个提交屏障里,而由 init wrapper 结束后恢复的 runtime one-shot 调度。 -
生成首轮可用推荐 — 严格在阶段 3 完成后,使用刚落盘的完整画像执行内容发现、个性化评估、推荐文案生成,并校验 canonical pool 至少已有一条可直接浏览的内容。发现到 raw / evaluated candidate 但尚未生成
pool_expression、pool_topic_label、style_key、topic_group不算初始化成功;如果正式候选池为空,补池会先构造cold_start的PoolDistributionSnapshot,把画像中最高权重兴趣作为首批 query 的软避让方向,并优先覆盖次级兴趣 / 兴趣域,避免第一批 discovery 全部集中在同一个强 topic。
当前超时契约:阶段 1 的基础预算是全部来源共享 1800 秒墙钟,B 站和 X 分别有 600 / 480 秒单来源上限;默认预算下 Linux.do-only 至少为 32.5 分钟,Linux.do 与其它来源并选时为 62.5 分钟,显式 override 不扩。扩展 collector 支持协作取消,一个来源超时会继续尝试剩余来源,只在有效总预算耗尽且仍无任何信号时以 collection_timeout 硬失败。阶段 2 偏好分析采用“25 分钟无完整分片结果 + 45 分钟绝对上限”双重期限;只有真实 chunk 完成才刷新进展,心跳不算。25 分钟空闲期限高于 [llm].timeout=1200 的 20 分钟单请求上限,并为最多两次 65 秒临时 429 / cooldown 重试留出余量,避免外层看门狗提前取消仍在等待的 Provider 请求;402 / 余额不足仍立即失败。阶段 3 是没有分片进度的一次性画像综合,只设 1800 秒绝对上限。阶段 4 从发现到首条 canonical 推荐可用采用“15 分钟无进展 + 45 分钟绝对上限”,超时沿既有部分成功语义完成。硬失败 detail 会说明常见的 Base URL / 模型名 / 网络 / 代理 / 服务过慢原因和恢复动作;阶段 4 超时则说明完整画像已生成、首批可浏览推荐本次未完成、后台继续补池。
共享流水线 cli.run_guided_init
| 项 | 说明 |
|---|---|
| 位置 | src/openbiliclaw/cli.py |
| 签名 | async run_guided_init(*, client, memory, soul_engine, favorite_limit, follow_limit, history_limit=500, include_bili=True, include_xhs, include_dy, include_yt, include_x=False, include_zhihu=False, include_reddit=False, include_bangumi=False, include_github=False, include_linuxdo=False, include_v2ex=False, include_weibo=False, bangumi_username="", bangumi_token="", github_username="", github_token="", v2ex_username="", target_pool_count, discover_backfill, coordinator=None, run_id=None, profile_analysis_timeout_seconds=None, profile_build_timeout_seconds=1800, discovery_timeout_seconds=2700, collection_timeout_seconds=1800, ...) -> InitResult(include_bili=False 时 client 可为 None;GitHub 仅用公开 username 或可选 PAT 读公开 starred repositories,双方同时提供时用 numeric user id 防止混号;V2EX 仍由扩展浏览器任务采集四个只读 scope;timeout 传 <=0 可供受控调用方关闭对应上限) |
| 为什么是协程 | 四阶段原先内联在 init 命令里,被四处独立 asyncio.run 包着,后端无法复用(会嵌套事件循环)。合并为一个协程后,CLI 用单次 asyncio.run(run_guided_init(...)) 驱动、API 在服务 loop 里直接 await。 |
| bootstrap 采集器 | 仍是同步实现(有同步调用方 + 测试),但在流水线里走 await asyncio.to_thread(...),不冻结 API 事件循环;每个轮询 collector 接收 threading.Event,外层 timeout / 用户取消时置位并在下一次 0.5 秒轮询退出。由于取消 to_thread() 不会杀掉已经运行的原生线程,外层设置 stop flag 后还会等待最多 1 秒有界 drain(只轮询完成事件,不再占第二个 executor slot),避免线程池高负载时旧 collector 在 run 终态后迟到访问任务队列。外层统一按 stage 1 剩余预算裁剪等待时间,避免依次等待多个来源把总时长叠加到不可控。 |
discover_backfill 注入 | 唯一与运行路径相关的步骤。CLI 传 _run_init_discovery_backfill_async(一次性 discovery_engine + RecommendationEngine);API 传 controller.run_init_backfill(持 _refresh_lock,与连续 refresh 串行)。两条路径都在发现后同步 drain 待生成表达的候选,并以 count_pool_candidates()>0 作为完成条件。其余步骤完全共享。 |
| 进度上报 | 传入 coordinator / run_id 时,在每个 stage 边界回调 coordinator.stage_started/stage_done、并 register_enqueued_task 登记 bootstrap task id;run 生命周期(mark_running / complete / fail)留给调用方。阶段 1 在每个所选来源边界上报已完成来源数,并以非实质 tick 更新“已用 / 总剩余秒数”;阶段 2 同时上报真实分片完成数与等待时长;阶段 3/4 的单次长调用使用 mode="indeterminate" + elapsed_seconds/max_seconds,不伪造百分比。阶段 3 只有完整画像通过校验并落盘后才置 ok,然后阶段 4 才开始并依次报告「发现候选 → 生成推荐文案 → 验证可用性 → 首轮内容池就绪」。 |
| 失败语义 | 硬失败抛 GuidedInitError(reason)(collection_timeout(采集总预算耗尽且无信号)/ empty_history(只选 B 站且历史、收藏、关注均无信号,兼容旧 CLI 文案)/ empty_signals(多来源或非 B 站来源全部 0 信号)/ analyze_failed / profile_failed):CLI 转状态面板 + 退出码 1,API 转 coordinator.fail(reason, detail=message)。偏好 / 画像超时分别复用后两个 reason,detail 给出超时含义、常见配置 / 网络原因和模型设置测试 + 重试动作;阶段 4 失败、没有 canonical 可用行、15 分钟无进展或 45 分钟绝对超时是部分成功(完整画像已生成),InitResult 除 discovery_error / discover_exc 外还带 discovery_reason / discovery_detail,超时 reason 为 discovery_timeout。 |
InitResult 携带 CLI summary / API wrapper 需要的全部字段(含 Linux.do / Bangumi / GitHub 事件数、status scope counts、各来源事件数、profile、discovered_count、discovery_error / discover_exc、discovery_reason / discovery_detail)。GitHub 额外保留 repositories/pages_fetched/rows_seen/duplicates/rejected_private/rejected_malformed/scope_complete/terminal_evidence/error_code/identity_id,不持久 PAT;terminal_evidence=partial_timeout 可证明至少已有完整页,isolated_source_failure 则证明混合 init 已隔离 GitHub 失败而继续其它来源。API wrapper 用这些字段完成可诊断的部分成功终态,CLI warning 面板也显示同一稳定 reason。
首轮发现多样性由 discovery.pool_snapshot.build_cold_start_pool_snapshot() 提供:当 CLI _run_init_discovery_backfill_async 或 API ContinuousRefreshController.run_init_backfill() 看到 count_pool_candidates()==0 时,会基于阶段 3 返回的完整画像生成 cold_start=true 的 snapshot 并传给 ContentDiscoveryEngine.discover(..., pool_snapshot=...)。这份 snapshot 不代表真实池子已有饱和历史;它只把权重最高的 1-2 个兴趣当作 avoid_topics 软约束,把剩余兴趣名和一级兴趣域放入 prefer_axes,让搜索词 prompt 在保留少量强兴趣命中感的同时,把首批内容面铺开。池子已有内容后,API runtime 会改用真实 build_pool_distribution_snapshot();CLI 首轮 init 只做空池冷启动保护。初始化完成后的统一 keyword planner 如果遇到正式池仍为空,也会把同一套 cold-start hints 写进各平台的 merged keyword prompt,避免跨平台第一批关键词都押在同一个强兴趣上。
状态机 InitCoordinator
| 项 | 说明 |
|---|---|
| 位置 | runtime/init_coordinator.py;惰性挂在 RuntimeContext.init_coordinator(重建后仍读当前组件) |
| 持久化 | init_runs 表(storage/database.py):run_id / status / stage / stages_json / partial_success / error_reason / error_detail / sequence / progress_sequence / started_at / updated_at / progress_at / finished_at。updated_at 是 owner 心跳租约,progress_at/progress_sequence 只在阶段边界、真实批次或业务里程碑推进;旧库自动 ALTER TABLE 并以已有 updated_at 回填。error_detail 存失败细节;部分成功会保留对应降级原因(如 discovery_timeout / discovery_partial / douyin_degraded / linuxdo_degraded)并随 init_completed 下发。重新预约同 run id 时全部重置。 |
| 单飞启动 | try_start(run_id) → try_reserve_init_starting(BEGIN IMMEDIATE CAS);活跃 run 存在时返回 False。TOCTOU 收口在 DB。 |
| 单写者 | _write(...) 在 _write_lock 下串行化「读 stages → 改 → 写 → 发事件」,保证心跳、阶段进度、取消与终态写入的 sequence 严格递增、不丢更新;旧 run 的迟到 heartbeat / callback 在新 run 已预约后直接 no-op,不能拿新 row 的 stages 回写旧 run;同一 run 一旦首个 completed / failed / cancelled 写入,后续迟到 heartbeat / progress / stage_done / 第二终态也全部 no-op,终态快照不可被漏跑 sibling 污染。协调器还拒绝在阶段 3 未提交为 ok/warning 时启动阶段 4,严格顺序不只依赖调用方自觉。 |
| 事件 | init_progress(stage 起止)/ init_completed / init_failed,经 event_hub 推到 runtime-stream。 |
| 取消 | attach_task 同时绑定 run id 与任务句柄;cancel_current_run 调 task.cancel(),wrapper 捕获 CancelledError 后 shield 写入 cancelled 终态。三端运行时都提供显式取消按钮,并给请求本身设置有限 deadline。 |
| 租约 reconcile | reconcile_on_boot() 在进程启动时清理崩溃残留;任务 done callback、GET /api/init-status、开始与取消端点还会调用 reconcile_orphaned_run():已知 owner task 结束但终态写失败时立即标为 failed(interrupted),没有本进程 owner 且心跳超过 120 秒时回收租约,释放 logical running 锁。这样即使 SQLite 终态写或 wrapper 清理异常,也不会永久挡住重试。 |
| bootstrap 归属 | register_enqueued_task / is_owned_bootstrap_task 给写者门控判断某 task-result 是否属于本 init run。 |
| 阶段子进度 | stage_progress(run_id, stage, *, done, total, note=None, mode="determinate", elapsed_seconds=None, max_seconds=None, substantive=True):确定型进度 clamp 0≤done≤total;未知分母的长操作用 mode="indeterminate", done=total=0 并携带 elapsed / max。substantive=False 的时间 tick 只更新可见 note,不推进 progress_at/progress_sequence。stage_done / 终态失败会清掉陈旧 progress。 |
心跳 touch() | 只 bump sequence + updated_at,不更新 progress_sequence/progress_at、不发事件。last_heartbeat_at 新鲜代表后台 owner 仍在线;last_progress_at 长时间不变代表当前工作尚无业务里程碑,两者不能混为“没卡住”。 |
| 阶段 eta(v0.3.162+) | _initial_stages() 每个 stage 带 eta_seconds(_STAGE_ETAS = {1:90, 2:180, 3:70, 4:300};阶段 4 现在覆盖发现、评估、表达生成和 canonical 校验,provider / 来源换代需复核 calibration)。 |
| 双时钟状态 | get_status() 透出 last_heartbeat_at / last_progress_at / progress_sequence;last_activity 仅作为旧客户端兼容别名,等同 heartbeat。三端以 client-observed sequence/时间计算状态,避免服务器与浏览器时钟偏移造成误报。 |
前置探测 InitPrereqs(runtime/init_prereqs.py):chat_ready()(provider health,成功 TTL 300s / 失败 8s,30 秒超时判不就绪;覆盖 Ollama 7B 模型首次从磁盘加载;发请求前确认默认 / fallback 是 chat-capable,通用 health probe 强制 reasoning_effort="",不会探测 embedding-only Ollama 或把 DeepSeek hi 扩成 thinking 长请求)、bilibili_check()(validate_cookie,ok 60s / fail 10s TTL)、peek_chat() / peek_bilibili()(只读缓存值、不发探针)、enabled_platforms();v0.3.152+:GET /api/init-status 在 initialized && !running 时改用 peek 值,不再对已初始化实例发真实(计费)chat 探针或 B 站往返——此前开着 /setup/ 或桌面 Web 等首池页面会每 30s 烧一条 5-in/10-out 的 "hi" 补全;真实首启回归进一步确认,完成后恢复的 embedding 预热会占用 provider semaphore,若 status 仍排队做 live embedding probe,完成页会再等 15~30 秒,因此已初始化分支的 embedding readiness / diagnosis 也全部只读最近缓存。实时向量就绪状态由专用 /api/health 承担;POST /api/init(含 force 重建)仍做实时复验,桌面 Web 在 initialized 后也不再渲染前置 checklist。终态 run 的持久化 reason/detail 优先于随后发生的前置探测失败,避免明确的「偏好分析超过本轮动态上限」被泛化成「AI 服务不可用」。embedding readiness 复用 /api/health 的 _health_embedding_ready(),绕过缓存真实调用一次 EmbeddingService.probe();idle 且未初始化的 /api/init-status 与 POST /api/init 显式使用 strict 解释,同一缓存结果为 timed_out 时仍下发 embedding_ready=false / 返回 409,只有真实非空向量成功才放行,普通 health 对本地 Ollama 冷加载超时的容忍不会渗入初始化门禁。全部探测都 TTL 缓存 + 单飞,避免轮询打爆。v0.3.137+:/api/init-status.prerequisites.embedding_required 表示 [llm.embedding].provider 是否已配置;已配置时 can_start 会硬性等待 embedding_ready=true,POST /api/init 临界区也会复验,失败返回 409 embedding_not_ready 并把刚预约的 run 回滚为 idle。provider 为空代表用户明确关闭 embedding,仍允许降级初始化。v0.3.118+:B 站登录不再硬性拦截 GET /api/init-status 的 can_start(是否拦截取决于客户端勾选了哪些来源,只有 POST /api/init 知道)——bilibili_logged_in 仍在 prerequisites 里下发,前端在勾选了 B 站时自行拦截;POST /api/init 也只在所选来源包含 bilibili 时做登录 409 复验。显式 sources 为空或没有任何合法平台 key 时返回 409 no_sources_selected;其余合法勾选(包括 Reddit-only)会作为本轮显式 opt-in 生效,并 best-effort 写回 sources.<platform>.enabled=true。v0.3.153+:B 站探测恒直连——BilibiliAPIClient trust_env=False,不再继承环境变量 / 系统代理(代理出口 IP 常触发 B站 风控,已登录用户显示"未登录";开着 Clash 等代理无需任何操作即可通过检测),网络必须走代理时用 [bilibili].proxy 显式指定;探测失败时把失败原因下发到 prerequisites.bilibili_detail(POST /api/init 的 409 响应同样带 detail)——AuthStatus.network_error 区分传输层失败与 Cookie 真失效(-101 归 Cookie 类,不误导查代理),传输类 detail 按实际链路给排查提示(直连失败 → 查本机网络 / TUN 全局模式加直连规则;显式代理失败 → 检查该代理或清空改回直连);两处 Web checklist 的 B 站行 label 按探测真实结果措辞("登录检测未通过"),未通过时不再出现"已登录"字样,hint 展示 bilibili_detail。
Bangumi 在前置阶段不做网络或登录探测。显式 sources 只有 Bangumi,且本轮有效 username 为空时,会在预约 run 前返回 409 no_profile_signal_sources;混合来源没有 username 时仍可启动,202 响应带 warning,Bangumi 只参与后续 discovery。V2EX 的 source_options.v2ex.username 与兼容顶层 v2ex_username 只要显式出现(包括 "")就覆盖已保存 username,并在成功预约混合 init 时同步保存;缺少 username 时允许扩展在任务页从真实导航栏观察账号,无法观察时以分 scope partial 结果结束,不把匿名首页内容冒充用户主题。source_options 当前只接受 Bangumi 的 username/access_token、GitHub 的 username/access_token 与 V2EX 的 username;未知来源或未知字段返回 400,不污染配置或 init 状态。
V2EX task-result 完成后,guided init 会再次用后端身份阶梯解析 PAT verified、浏览器 observed、配置 / accepted username。证据不一致返回 identity_mismatch 并跳过 V2EX 的账号事件;没有可归属账号或没有任何有效行为事件返回 no_profile_signal_sources,该来源仅参加后续公开 discovery,不再把它模糊成 empty_signals。身份一致时 seen key、收藏快照和 Node Affinity 都以 resolved username 分区;账号切换不会复用另一账号的 V2EX bootstrap dedupe。
GitHub 在前置阶段不要求登录:匿名公开 repository discovery 始终可独立就绪,
但画像初始化必须有公开 username 或专用 PAT 能解出的 identity。只选 GitHub
且两者均缺失时,在 run admission 前以 no_profile_signal_sources 拒绝;混合来源则
保留 GitHub 公开 discovery,不伪造账号信号。PAT 与 username 同时存在时必须匹配
numeric user id;不一致、PAT 被拒绝、用户不存在、超时和其它失败分别使用
github_identity_mismatch、github_token_rejected、github_identity_not_found、
github_bootstrap_timeout 和 github_bootstrap_failed;只保留部分分页时为
github_partial。验收 ledger 尚未将有效 PAT 认证态记为 PASS,不得从此契约推断
真实账号 E2E 已完成。
运行中与完成后探针隔离补充:GET /api/init-status 在 run 活跃或完整画像已经存在时,对 B 站、chat、embedding readiness 和 embedding diagnosis 全部只读缓存 / 本地配置快照,不允许状态轮询把真实网络或 provider 探针插进初始化关键路径或完成页。即使阶段 3 已落盘导致 initialized=true、阶段 4 仍在执行导致 running=true,后端和三端 UI 都先按 running 处理;只有 idle 且未初始化时状态页才做真实前置探测,POST /api/init(含 force 重建)仍在占坑后实时复验。
API 端点
| 端点 | 方法 | 访问 | 说明 |
|---|---|---|---|
/api/init-status | GET | 远程可读 / 降级可读 | 权威进度 + 前置清单 + can_start(trusted-local && 硬前置 && 非 running && supported)/ can_manage(trusted-local)。前置清单包含 embedding_ready 与 embedding_required;v0.3.155+ 还包含 embedding_check / embedding_detail——向量模型未就绪的分类原因(disabled / misconfigured(provider 名无效,如被浏览器整页翻译写坏)/ not_running / model_missing / model_broken / model_path_encoding / disk_full / network / model_oom / provider_error,Ollama 路径经 llm/ollama_diagnostics.py 真实分类、失败 TTL 缓存),三端向量模型行 hint 直接展示 embedding_detail。model_path_encoding 表示 Windows 非 ASCII 用户名导致模型 blob 路径无法被 llama-server 加载,提示迁移模型目录或手动设置 OLLAMA_MODELS,不建议重复拉取到原路径;disk_full / network / model_oom 是手动处理型原因,分别提示清理磁盘、修网络 / 代理 / 镜像源、释放内存或换更小 embedding 模型。v0.3.155+ reason 梯子补上 not-trusted 分支:非本机(手机扫码 / 局域网)查看时 reason=local_only 而非 none,三端显示「只能在本机发起初始化」,不再出现全绿清单配「以下条件未满足」的矛盾文案。v0.3.156+:上次 run 失败 / 取消时顶层 detail 下发 init_runs.error_detail(异常摘要 / GuidedInitError message),三端失败文案渲染为「通用文案(具体原因)」,未映射的 typed reason(empty_history / empty_signals / profile_failed)直接显示其 message;interrupted / cancelled 补进三端 reason 映射。v0.3.162+ 过程可见性字段(全部 optional 向后兼容):stages[].progress({done,total,note},运行中 stage 的子进度,如阶段 2 分片批次)、stages[].eta_seconds(阶段典型耗时提示)、顶层 last_activity(最近一次状态写入的时间戳,含 30s 心跳——前端 >90s 无变化即显示停滞提示)。Issue #113 收口后,未初始化且没有活跃 run 时还会读取 AccountSyncService.last_account_sync_error:画像分析失败会进入顶层 detail;当前 chat 探针失败时 reason 保持 llm_not_ready,探针已恢复时 reason=analyze_failed 且 can_start 仍允许重试。已生成画像但 discovery 超时 / 失败时返回 partial_success=true、持久化的 discovery_timeout / discovery_partial 与人类可读 detail,不再被 already_initialized 覆盖;三端可据此说明部分完成。远程不 403、can_manage=false。 |
/api/init | POST | 仅本机 | 占坑前廉价拒绝(403 local_only / 409 unsupported_runtime / 409 already_initialized)→ try_start(409 already_running)→ 临界区复验前置(缺则复位 idle + 409,不留 stuck starting 行;包括已配置 embedding provider 时的 embedding_not_ready)→ 后台跑 wrapper → 202 + 初始 status。v0.3.162+:命中 embedding_not_ready 时会 best-effort 调用 _maybe_autostart_embedding_pull();仅 Ollama provider、model_missing / model_broken、loopback endpoint(含 127.0.0.1:11435)且磁盘守卫通过才复用现有修复锁与任务启动拉取,远程 / Docker 主机名及其他诊断不自动操作。409 始终带 detail:已拉取时为实时进度,未拉取时指向「修复向量模型」。可选 body sources(平台来源数组):传入时按合法平台 key 直接作为本轮显式 opt-in,并 best-effort 写回 sources.<platform>.enabled=true;不传则用全部已开启平台(CLI / 旧客户端行为)。Bangumi username 使用 canonical source_options: {"bangumi": {"username": "..."}},旧顶层 bangumi_username 仅作兼容输入;V2EX username 使用 source_options: {"v2ex": {"username": "..."}},旧顶层 v2ex_username 仅作兼容输入;GitHub 使用 source_options: {"github": {"username": "...", "access_token": "..."}},专用 PAT 的固定环境变量只是 OPENBILICLAW_GITHUB_TOKEN,不读 GITHUB_TOKEN / GH_TOKEN;微博不接受 Cookie / UID,选择微博时由扩展 heartbeat + task-result 提供已登录 uid。显式空字符串表示清空/本轮 discovery-only,字段缺失才回退配置;未知 source option 在预约前 400。Bangumi-only 无有效用户名、GitHub-only 无有效 username/PAT 或 Weibo-only 没有新鲜微博 heartbeat 时在预约前 409 no_profile_signal_sources,混合来源仍保留公开 discovery 并按 scope 结果返回部分状态。 |
/api/init/cancel | POST | 仅本机 | 协作取消在跑的 run;无运行中 → 409 not_running。 |
/api/embedding/repair | POST/GET | POST 仅本机 / GET 公开 | v0.3.155+ 一键修复向量模型:POST 先经 diagnose_ollama_embedding() 分类(已就绪 → 200 already_ok 并立即过期就绪缓存;not_running → 409 附排查提示;非 ollama provider → 409 unsupported_provider;修复已在跑 → 409 already_running),model_path_encoding 会先尝试重启托管 Ollama 到纯英文模型目录,成功后同样启动单飞后台任务;若无安全目录或检测到外部 Ollama,POST 返回 409 manual_fix_required / external_ollama 并附手动 OLLAMA_MODELS 指引。缺失 / 损坏则启动单飞后台任务经 Ollama /api/pull 拉取(202);GET 回报 {running, status, completed, total, done, ok, error} 供前端显示进度,成功后过期 _health_embedding_ready 缓存使横幅 / 清单在下一次轮询转绿。拉取期间 init-status 的 embedding_check="repairing"、embedding_detail 带实时百分比(绕过诊断 TTL 缓存),并下发 embedding_repair_running/completed/total 结构化进度;/setup/ 与桌面 Web 的向量模型行按 3s 轮询实时刷新进度,且在 model_missing / model_broken 时渲染「自动下载向量模型」按钮,model_path_encoding 时渲染「迁移模型目录并修复」按钮(data-embedding-repair)触发本端点。v0.3.162+:popup init checklist 复用 startEmbeddingRepair() 触发本端点,并轮询 /api/init-status 的既有 embedding_repair_* / ollama_phase / embedding_pull_status 字段渲染进度条与按诊断选择的修复按钮,不新增响应字段。 |
当前过程可见性契约在旧字段上做 additive 扩展:stages[].progress 现在是 {done,total,note,mode,elapsed_seconds,max_seconds};mode="indeterminate" 时客户端不得用 done/total 合成百分比。顶层新增 last_heartbeat_at / last_progress_at / progress_sequence,last_activity 仅保留为 heartbeat 兼容别名。三端对 init-status / start / cancel 请求分别设置有限 deadline;轮询失败保留最后一次已知进度并明确显示「暂时无法连接后台」,不会把网络错误渲染成静默冻结。
v0.3.157+:/api/init-status.prerequisites 还会下发 ollama_phase(starting / ready / down)和 embedding_pull_status。这两个字段来自进程全局 runtime.embedding_progress,因此桌面包首启后台自动拉取 bge-m3 与用户点击 /api/embedding/repair 共享同一套进度;只要任一路径正在拉取,embedding_check 会优先报 repairing,embedding_repair_running/completed/total 也会反映该进度。/api/embedding/repair 遇到 not_running 时若配置允许托管 Ollama 且 endpoint 是默认 loopback localhost:11434,会先尝试 _ollama_start_serve_background(),成功后重新诊断并继续既有 ok / pull / path-migration 梯子;远端、自定义端口或 manage_ollama=false 仍返回 409,不越权触碰外部 Ollama。
v0.3.157+:/api/embedding/repair 是有界的「诊断 → 修复 → 重新诊断」编排器,最多执行 3 次自动动作,避免「点重试 → 失败 → 再点」循环。not_running 只在托管默认 loopback Ollama 时尝试拉起并继续诊断;model_missing / model_broken 启动单飞后台 pull 前会先检查模型目录所在磁盘剩余空间;model_path_encoding 只在可安全迁移托管模型目录时重启并重拉。disk_full / network / model_oom 直接返回 409 和可执行排查步骤,不启动无意义 pull;泛 provider_error 只会对本进程托管的 Ollama 尝试一次重启,仍失败则提示升级 Ollama、检查 11434 端口和 NO_PROXY=127.0.0.1,localhost。
_init_wrapper(api/app.py)是某次 API run 的唯一状态 / 事件写者:mark_running → run_guided_init(coordinator=...) → complete(partial_success=discovery_partial or dy_degraded or linuxdo_degraded, reason=..., detail=...)。只有抖音降级时 reason 为 douyin_degraded,只有 Linux.do 降级时为 linuxdo_degraded;若发现阶段也降级则保留 discovery reason,并把账号来源的不完整明细合入 detail;抖音和 Linux.do 同时降级且发现正常时维持 douyin_degraded 主 reason、同时在 detail 保留两源事实。CancelledError → shield cancel,GuidedInitError → fail(reason, detail=exc.message),其它异常 → fail("internal_error", detail=_init_crash_detail(exc))(类名: 首行消息,截断 300 字——v0.3.156+ 失败原因可从 UI 报告,无需翻服务端日志)。v0.3.162+ 的自动拉取发生在启动端点把任务交给 wrapper 之前:自愈诊断、调度失败都会回落原 409 主路径,调度失败用 mark_pull_done(False, error) 回滚拉取态而不伪造 Ollama phase;因此 wrapper 的单写者契约不变。三个 path 都在 auth.py 公共集 + 降级白名单。v0.3.162+:wrapper 在 mark_running 后启动一个 30s 周期的 heartbeat task(_run_init_heartbeat → coordinator.touch(run_id),touch 失败吞掉 log WARNING、绝不杀 init),finally 里取消——长请求等待期间 last_activity 保持 ≤30s 新鲜(前端 90s 停滞阈值 = 心跳周期 × 3,改周期须同步改阈值);Issue #113 收口后心跳不再无限续命,阶段 2 按分片并发波次使用动态上限,阶段 3/4 分别在 360/600 秒进入失败或部分成功终态。
wrapper 的任务句柄另有 done callback 审计终态;若任务已经退出而 DB 仍是 starting/running,协调器会补写 interrupted 并发布失败事件。30 秒 heartbeat 只刷新 owner lease,阶段 1/2 的 elapsed tick 同样标记为非实质更新;它们都不会伪造 last_progress_at。
重新初始化(force 重建)
已初始化后的「重新初始化」复用同一条四阶段流水线,不删除任何既有数据(事件、收藏、对话历史、手动编辑覆盖保留),只重新拉取所选平台数据、重建完整画像并补足首轮发现池。入口收敛(gui-init §4):推荐 tab 的「开始初始化」CTA 只服务首跑;已初始化后唯一入口在设置页(桌面 Web 通用 tab「初始化与画像」区、扩展 popup 通用 tab),CLI 用 init --force。
- 后端:
POST /api/init的 bodyforce:true绕过409 already_initialized守卫(init-status的can_start=false与already_initializedreason 不受影响);其余前置复验、单飞预约、写者门控与四阶段流水线与首跑完全一致。 - 旧推荐池自动清空(force 专属):force 重初始化在 stage 4 开始前把
content_cache中所有活跃行(fresh / shown / suppressed)标记为pool_status='purged_by_reinit'(沿用purged_by_dislike先例:行保留用于审计 / 去重,但退出可用池门禁与 serve / recall)。否则旧画像打分的推荐会继续被推荐,且 backfill 目标只有 15 条(_INIT_POOL_TARGET_COUNT),旧池几乎总是 ≥15 → stage 4 直接跳过,重初始化对推荐完全无效。清空后 stage 4 按新画像重新发现、评估并生成首轮推荐。 - 认知层可选清空(
reset_cognition):换账号或大改兴趣时,建议同时清空长期 awareness / insight 层,避免旧账号的 LLM 观察笔记混入新画像构建上下文。CLI 用init --force --reset-cognition,API body 加reset_cognition:true,桌面 Web / 扩展设置页提供「同时清空旧认知观察与洞察」复选框;不清空时旧观察继续作为画像构建上下文(普通画像刷新更合适)。清理发生在 stage 2 之前,本轮分析新生成的草稿仍会落库成为新基线。 - 扩展平台 bootstrap 6 小时复用窗:xhs / dy / yt / zhihu / reddit / linuxdo 的 bootstrap 采集任务在 6 小时内复用近期结果(防重复入库),因此紧接上次 init 的 force 重初始化,这些平台可能复用近期任务结果而非重新采集;B 站 / X 是服务端直拉,不受复用窗影响。换账号场景下账号 identity key 变化通常能避免误复用,但期望"立刻重新采集全部平台"时应留意该窗口(
OPENBILICLAW_*_BOOTSTRAP_DEDUPE_HOURS=0可临时关闭复用)。 - 自动备份(force 专属):force 重初始化开始前自动创建快照到
data/backups/reinit-<时间戳>/——SQLite 冷备(含 WAL)+data/memory/全部 JSON 层(soul / awareness / insight / preference / overrides / speculative…,.lock跳过),这样重建覆盖的画像、以及reset_cognition删除的认知层都是可恢复的。CLI 用--no-backup跳过;API 路径默认备份(备份路径记录在 daemon 日志)。备份失败仅 WARNING、不阻断重初始化。 - CLI:
openbiliclaw init --force跳过已初始化二次确认,标题改为「重新初始化 OpenBiliClaw」;交互终端默认(无--force)在检测到画像已存在时先 y/N 确认(默认 No,选 No 直接退出不改数据);非交互终端保持原有直接重跑行为(不传--force时也不清池、不备份)。只想基于已有事件重跑画像可用rebuild-profile(不重新拉数据、不清池、不备份)。 - 桌面 Web / 扩展 popup:设置页「重新初始化 / 重建画像」按钮先
window.confirm二次确认(文案说明旧推荐池会清空重建),确认后调POST /api/init {force:true}(不传sources,使用全部已开启平台),成功后回到推荐 tab 复用既有进度面板展示四阶段;进行中禁用按钮。
init 期间写者门控
防止并发写污染在跑的 init(init_active() 为真时)。设计原则是 deny-by-default:不是枚举"要拦的写端"(总会漏),而是默认拦截一切变更、只放行 init 必需的少数路径。
- HTTP 写端(deny-by-default):
_init_active_write_guard中间件对所有POST/PUT/PATCH/DELETE返回409 init_running,除非命中放行清单:/api/init、/api/init/cancel、/api/bilibili/cookie、/api/auth/*、迁移暂存取消DELETE /api/migration/pending,以及精确 5 段匹配的/api/sources/<source>/{kick,task-result}(bootstrap 协议)。另有一个按 HTTP 方法看似写入、实际不持久化的精确例外:POST /api/config/probe-service只在配置内存副本上测试 LLM / 调用链 / embedding / 网络,init 期间保持可用;真正保存配置的PUT /api/config仍被拦截。 - 副作用 GET:写者门控只拦变更方法,所以两个会写状态的"读"另行处理:
GET /api/recommendations的空历史 bootstrapserve()(写推荐行 / 标记 shown)在 init 期跳过;GET /api/sources/*/next-task的 init ownership filter 只约束 legacy discovery/bootstrap queues(next_pending(only_ids=…)),避免陈旧任务饿死本轮采集器;durable native-save job 保持 native-first priority,不被 init-owned legacy ID 集过滤。 - 后台循环:
background_llm_work_allowed()(account_sync / startup one-shot)+ContinuousRefreshController._llm_work_allowed()(连续 refresh / soul pipeline / producer,经注入的init_active_check)在 init 期一律返回 False。POST /api/init先通过InitCoordinator.try_start()持久预定 run,再保存来源 opt-in / 热重载 runtime;旧 controller 在 handoff 前已能看见 init-active,新 controller 在run_post_reload_llm_work=false下整条暂停,早期前置复验失败时仅在确实做过 setup reload 后恢复正常 owner。扩展在线七来源周期回拉虽然不属于 LLM gate,也由自身init_active检查暂停整个 init 窗口。init 自身不受影响——它直调soul_engine/run_init_backfill,二者都不查该 gate。安装包/setup/第一页保存 LLM 配置会额外用PUT /api/config {suppress_background_llm_work:true}暂停配置热重载后的后台 LLM 循环和 post-reload 探针 / 预热,避免用户还没在第二页确认来源就生成画像或兴趣探针;init wrapper 终态后恢复后台循环。 - cookie 例外:
/api/bilibili/cookie在 init 期间:同值 200 no-op、异值 409(均不 validate / 不 rebuild,避免换掉正在用的客户端)。 - task-result 例外:
/api/sources/*/task-result放行,但 handler 在 init 期跳过所有发现池写;仅对 init-owned(is_owned_bootstrap_task)结果走 propagate(经既有 bootstrap-key 去重),并跳过增量画像管线(_ingest_profile_update_events)——新画像由 stage 2/3 从采集事件统一构建。 - 热重载豁免:
rebuild_from_config的cancel_all(exclude={"guided_init"})让 init 任务不被配置热重载取消。
八个扩展任务来源的 bootstrap final 统一采用 staged canonical result → durable event ingress → 原子有界 seen-key checkpoint → terminal flip。任一投影失败都保留非终态 staged row,租约到期重领后只从首份 canonical result 修复;init-owned 结果不带 generic owner,避免阶段 2/3 之外重复学习。V2EX 结果额外按 Topic 聚合回复、执行身份门禁并写入账号分区 Node affinity;首次 init / guided 的完整收藏 scope 会种账号快照基线,后续增量只有 scope 完整时才推进双确认 retraction / restore outbox。GitHub 没有扩展任务,不进入 staging。
图形 UI(extension / web)
推荐 tab 未初始化空状态给「开始初始化」面板:数据来源勾选(B 站默认勾选但可取消,小红书 / 抖音 / YouTube / X / 知乎 / Reddit / Linux.do / Bangumi / V2EX / 微博 / GitHub 一样可选,至少保留一个数据来源)+ 按钮(点击时拉 /api/init-status,所选来源作为本轮 opt-in 并自动开启;B 站登录、Bangumi 公开 username、V2EX username / 浏览器身份、微博 heartbeat + uid 与 GitHub username / 可选 PAT 均由后端权威复验。GitHub-only 无 username/PAT 或 Bangumi-only 无 username 时提示缺少画像身份;混合来源按来源隔离失败)+ 启动后进度条。GitHub 说明公开 discovery 无需登录、PAT 不改变 public-only 边界,并且只使用现有设置/初始化表面,不增加 host permission、content script 或 task poll。详见 extension 模块文档。
桌面 Web 对齐同一套交互:安装包首启 /setup/ 从「连接 AI → 连接 B站」后进入第 3 步「初始化画像和推荐池」。第一步把 provider、API Key、Base URL 和模型名作为普通字段保存,只热重载配置,不启动画像 / 探针 / 补池(已保存过 key 的 provider 允许留空沿用——PUT /api/config 只更新 payload 里出现的字段)。新装模板里的启用但无 Key 的 DeepSeek 项是为了让首屏有可编辑占位;它会让 active registry 进入可修复的 degraded 状态。用户改选 SenseNova 等其他 Provider 时,向导只停用 diagnostics 明确标为 blocking、且没有被自定义模块链引用的旧占位,并从默认链移除,避免新端点被无 Key fallback 连带校验失败;正常实例和用户自定义路由不自动改写。降级恢复时模型发现和真实草稿探测仍可调用,不会先被 503 guard 拦截;400 会展示结构化 blocking issue。
有效配置保存后,后端会复用 degraded context 保留的 stable 组件原地构建完整 runtime;成功响应为 reloaded=true / restart_required=false,同一进程立即解除 503 guard,向导直接进入第二步,不需要用户退出托盘或重启 daemon。核心构造失败时配置回滚、向导留在第一步展示可重试错误;前端仍保留 restart_required=true 的短期本地续接标记与 /api/ping 轮询,只兼容旧后端和无备份异常 bootstrap,绝不对仍然 degraded 的进程发起 init。
第二步展示同款来源勾选、前置清单、POST /api/init 启动和 runtime-stream/轮询进度,用户点击「开始初始化」后才真正进入四阶段流水线。向导 load 时会读一次 /api/init-status 恢复现场:running → 直接跳到第 2 步挂上实时进度(不再静默落回第 0 步),initialized → 直接按后端终态进入完成页;partial_success 会按 reason 区分解释:douyin_degraded 说明“抖音已采数据已用于画像,但分页未完整”,linuxdo_degraded 说明“Linux.do 已采个人信号已用于画像,但至少一个 scope 未完成”,discovery 类原因才说明首批推荐将在后台继续补齐;这些终态都允许进入应用。安装包入口(packaging/entry.py)开浏览器前先轮询 /api/health(≤30s,静态壳无法自救被拒绝的首个 GET),健康后再读 /api/init-status 决定落地页:新建 / 修复配置 → /setup/,已配置但从未完成初始化 → 也落 /setup/(读取失败时保守回落 /web/);「已有实例」分支同样按 init 状态选择落地页。后端只有在阶段 4 已验证 canonical pool 可用时才发普通 init_completed,所以 /setup/ 和 /web 不再额外轮询 /api/runtime-status、伪造 95% 的第二完成门槛;这避免后端已终态、前端却看似永远卡住。桌面 /web 仍会在 running=true → false 的终态边沿做一次非门控全量补水:阶段 3 已让 initialized=true 时也必须刷新 runtime snapshot,确保顶部连接徽标和侧栏库存不会停留在初始化前快照;这次读取只更新展示,不参与完成判定。用户跳过或后来直接打开 /web 时,推荐网格在没有插件同款“初始化后信号”(推荐数、候选池可用数、待整理数、最近发现 / 补货数)且任一权威来源报未初始化时渲染同款「开始初始化」面板:init-status.initialized=false(首选权威来源——runtime-status 拉取瞬时失败或被无 initialized 字段的 runtime 事件合并覆盖时依然生效)或 runtime-status.initialized=false;不再提示去命令行跑 init,也不展示示例推荐卡。/web 页面比后端先加载(冻结包启动竞速)时,runtime-stream 首次连上若发现 initStatus 与 runtimeStatus 都还是空,会触发一次补水重拉,保证引导卡在后端就绪后出现。
首启模型下载可见性:当桌面包在后台自动拉取 bge-m3(约 568MB)时,/setup/、/web 与 popup 会在首次加载时自动接管进程全局拉取状态,显示 .init-progress 进度条、embedding_pull_status 文案和「Ollama 启动中…」阶段提示,并持续轮询到终态;下载失败或中断后不再隐藏手动修复按钮,用户仍可点击「自动下载向量模型」重试。安装包入口在启动后台线程前先发布 embedding_progress 的 running 状态,避免 API 首次响应早于线程调度时出现一次 idle 快照,导致用户必须点击「开始初始化」才看到进度(Issue #142)。
运行中进度可见性:三个 GUI 面(popup / 桌面 /web / /setup/ 向导)统一区分确定型和不确定型工作。真实 done/total 才渲染百分比;画像生成、发现等无法量化的单次长调用渲染流动条、已用时和最大等待时间,绝不靠 ETA 曲线伪造 49%/95%。运行中阶段行不再预估耗时:stages[].eta_seconds 及其管线于 v0.3.181 整体删除(预估同时取决于平台数、历史量和 AI 服务快慢,任何固定数字都会误导,且每次预估落空都会让健康的长运行被读成卡死)。阶段行改为只显示后端已发布的客观事实“已用时 12 分钟 · 已完成 3/6”,无分批进度的阶段只显示已用时;进度条只由真实 done/total 驱动,无真实计数的阶段走 indeterminate 流动条(原先的 1 - e^(-已用时/预估) 伪进度与 0.5 半步兜底一并移除,maxPct 单调钳制不变)。等待中只说一次“只要还在出结果就不会被打断”;每次真实 chunk 完成也保留 elapsed/max,避免两次心跳之间短暂退回旧估时。后端心跳与有效进展使用两套 client-observed 时钟:heartbeat 停止才提示“后台可能断开”,heartbeat 正常但 progress_sequence 长时间不变则提示“后台在线,当前步骤仍在等待结果”,两者都保留取消入口。状态请求失败时保留最后一次已知阶段并显示离线原因;请求本身有 45 秒 deadline,启动 60 秒、取消 15 秒。idle 面板说明严格顺序为「完整画像生成并保存后才开始内容发现、评估和推荐文案」,不给耗时预估——耗时取决于平台数、历史量与 AI 服务快慢,差别很大,期间可离开页面且进度保留。running 判定优先于 initialized,覆盖阶段 3 已保存画像但阶段 4 仍在运行的合法双真窗口。
超时错误态(v0.3.168+):三端均优先展示后端 typed detail,而不是短 reason label。analyze_failed / profile_failed 会在进度区显示具体步骤、本轮墙钟上限的含义、常见原因和恢复动作;后台 account-sync 在探针仍失败时虽然 reason 是 llm_not_ready,只要 detail 以 画像分析失败: 开头也走同一优先级,避免机器码 / 通用门禁提示盖住根因。partial_success + discovery_timeout/discovery_partial 在终态完成面显示部分完成 detail;popup 的 init_completed 事件同样保留 warning。进度 / 原因节点使用 aria-live,硬失败切为 role=alert / assertive,普通进度保持 polite,避免每次轮询都强打断读屏。
Issue #113 空库存解环(v0.3.168+):首次运行时 soul.preference* / soul.profile_build 等 maintenance 调用可能因 canonical durable inventory 为空而停在后台 admission,但首池又依赖阶段 2/3 先完成。run_guided_init() 现在用 task-local ContextVar scope 只放行阶段 2 偏好分析和阶段 3 画像任务;同 scope 内的画像辅助调用一并放行,总 provider gate 仍限制并发。阶段 4 只在完整画像落盘后创建,且不继承 bypass;其中 discovery.explore.queries 与空库存时的 sources.*.extract 归 refill.supply,避免发现内部再形成「探索词等待库存、库存等待发现」的环;api.config_probe 归 interactive,使用户在空库存故障态仍能测试并修复模型配置,但继续受总 gate 限制。普通 account-sync / 画像重建不受影响;阶段 2 自适应、阶段 3 的 360 秒与阶段 4 的 600 秒上限继续兜住 provider 真慢或异常。
测试
tests/test_init_coordinator.py— 协调器生命周期 / 单飞 / 严格 stage 顺序 / 启动与运行期租约 reconcile / 取消 / 双时钟 / indeterminate progress,以及部分成功 reason/detail 的持久化与事件透传。tests/test_init_prereqs.py— 前置探测 TTL / 严格超时 / chat-capability 防漏与 fallback。tests/test_database.py—init_runsCAS / 白名单列 / reconcile。tests/test_api_app.py::TestGuidedInitEndpoints—/api/init、/api/init/cancel守门(403 / 409 各路径、复位不留 stuck 行)+ 写者门控(events 409 / cookie no-op / task-result 放行)+ 真实/api/inithandler 通过InitCoordinator向/api/runtime-stream发init_progress/init_completed的后端契约。tests/test_cli.py—openbiliclaw init全回归(共享流水线零回归)。tests/test_cli.py/tests/test_api_app.py— Bangumi-only username gate、混合来源 warning、公开收藏汇总和source_options兼容/严格校验。- GitHub 的 public username / PAT identity、numeric-id mismatch、公开 Star 分页与 partial 终止证据测试属于本来源的 required gate;当前真实有效 PAT 认证态仍以 GitHub 验收 ledger 的
BLOCKED/NOT_RUN为准,本文不将单元测等同真实账号 E2E。 extension/tests/init-control.test.ts— 清单 / 按钮态 / 进度状态机纯函数;覆盖双时钟、确定型 / 不确定型进度、硬超时、account-sync detail-first、离线与 discovery 部分成功文案。tests/test_web_guided_init.py— 安装包/setup/与桌面/webguided-init 接线;覆盖 richer progress、阶段 1 全局 deadline / collector cancel、批次进度、完整画像先于发现、阶段 2/3 超时取消和阶段 4 超时部分成功。tests/test_desktop_web_init_progress.py— 桌面/web与/setup/镜像契约(双时钟、indeterminate、取消、离线提示、无耗时预估 / 已用时+计数文案 / 无合成 95% 二次等待)。tests/test_web_guided_init_e2e.py— Chromium 中覆盖initialized=true && running=true不提前完成、双真窗口结束后桌面 runtime 非门控补水、三端同语义进度、setup / desktop 取消后可重试。tests/test_web_guided_init_e2e.py— Playwright 驱动真实/setup/与/web页面,stub 外部 HTTP 响应来覆盖浏览器交互:成功进度、前置失败、启动冲突、终态重试、runtime-stream 静默 watchdog、普通完成直接进入应用、部分完成可见提示,以及 PC Web 与插件一致的未初始化入口判断(已有推荐 / 候选池信号时不再弹引导);v0.3.168+ 直接断言超时原因、Base URL / 模型设置恢复动作、重试 / 设置入口、role=alert,以及 discovery 超时的部分完成 / 后台补池提示。CI 的web-guided-init-e2ejob 安装[browser]extra + Chromium 后单独运行。- 2026-07-15 已用隔离数据目录、真 B 站登录和真 LLM 完成 GUI init:采集 1100 条信号、6 个偏好批次、完整画像落盘、发现 20 条候选并生成首轮 canonical 推荐,最终四阶段全
ok;同轮还验证了阶段 4 双真窗口、进程中断后的interrupted回收 / 可重试,以及完成后页面状态读取不阻塞。扩展真实完成 Cookie 同步;popup 视觉态由扩展测试覆盖(浏览器自动化安全策略不允许直接打开chrome-extension://页面)。