Repository navigation
Architecture and Development
面向想理解 BossClaw 内部结构、参与开发或排查线上问题的读者。 文档基线:
desktop-app/package.json的version = 2.5.7(2026-10-13 发布),对应main分支实现;文中所有路径均相对仓库根目录。 只想使用?先看快速开始与功能指南;安全边界见安全与合规。
读法建议
- 第一次读:一 → 二 → 三 → 七,先建立「技术栈 / 进程 / 目录 / IPC」的空间感。
- 改业务逻辑:四(模块地图)→ 五(数据与状态机)→ 六(链路),改完按十跑门禁。
- 排查问题:十二(排障手册)→ 十(回归矩阵)→ 六(对应链路)。
- 接新平台:九(扩展指南)是逐项清单,照做即可。
- 一、技术栈与运行环境
- 二、进程模型与信任边界
- 三、目录结构(v2.5.7)
- 四、核心模块地图(唯一权威)
- 五、数据模型与持久化
- 六、核心链路
- 七、IPC 与外部通道
- 八、安全不变量(不可协商)
- 九、扩展指南:新增一个招聘平台
- 十、测试与回归(门禁)
- 十一、构建、打包与发布
- 十二、排障手册
- 十三、术语表
| 层 | 选型 | 说明 |
|---|---|---|
| 桌面框架 |
Electron ^42.11.10(Chromium 148) |
主进程用 CommonJS(electron/*.cjs);electron-builder ^26 打包 |
| 渲染层 | React 18.3 + TypeScript 5.6 + Vite 5.4 | SPA;生产加载 dist/index.html,开发加载 http://localhost:5173
|
| UI / 状态 | Ant Design 5.21 + Zustand(persist → localStorage) | 主题 Teal-on-Neutral,深浅色双套变量 |
| 运行环境 | Node.js 20+(推荐 22,.nvmrc 固定) |
仅源码构建 / 打包需要 |
| 可选引擎 | Python 3.10+(Camoufox / Playwright) | 仅「隐身引擎」批量沟通需要;不装不影响内置浏览器链路 |
| 数据 | localStorage,零后端 | 备份写盘由主进程完成;不依赖任何云服务 |
| 语音 / 模拟面试 |
sherpa-onnx-node(N-API 原生模块) + edge-tts |
依赖不再是「全纯 JS」;语音模型不随包分发,运行时下载到 <userData>/speech/models/
|
Electron 42 的两处 API 适配已收敛:
console-message首参改为对象(parseConsoleMessage()),session.setPreloads/getPreloads改为registerPreloadScript(旧接口保留双版本兼容)。升级评测见仓库 Issue / Release 记录。
┌──────────────────────── 主进程 electron/main.cjs(CommonJS,Node 全权限) ────────────────────────┐
│ 单窗口生命周期 · BrowserWindow + 内置 <webview> · 112 条白名单 IPC(safeHandle 包装) │
│ 子进程管理:Camoufox(Python,127.0.0.1:18767)· CloakBrowser · OpenClaw Node 桥(127.0.0.1:18765)│
│ 文件能力:PDF 打印、CSV/JSON 导出、备份、技能库读写、素材读取(全部经用户选择或白名单) │
│ 本地控制桥 control-bridge.cjs(127.0.0.1:17650,默认关闭,供外部 Agent / MCP 使用) │
├───────────────────────────────────────────────────────────────────────────────────────────────┤
│ 预加载 preload/app.cjs 主窗口唯一出口:contextBridge → window.electron(IPC 白名单 + LLM 代理)│
│ 预加载 preload/webview.cjs guest 页注入:采集、回传、真实输入、投递状态机(平台差异走适配表) │
│ 预加载 platform-adapters.cjs 多平台 DOM 适配表(纯数据 + 纯函数,零 DOM 依赖,可离线单测) │
│ 预加载 liepin-im.cjs 猎聘 IM 只读状态(判断「是否新增本人文字气泡」) │
├───────────────────────────────────────────────────────────────────────────────────────────────┤
│ 渲染进程 src/(ESM):React SPA;**只能**通过 window.electron 与主进程交互,无 Node 权限 │
└───────────────────────────────────────────────────────────────────────────────────────────────┘
安全基线(不可放宽)
| 配置 | 值 | 原因 |
|---|---|---|
contextIsolation |
true |
渲染层拿不到 Node / Electron 内部对象 |
nodeIntegration |
false |
同上;页面脚本无法直接 require |
webviewTag |
true |
内置浏览器依赖 <webview>;其 preload 必须 sandbox: false 才能注入 |
| IPC 白名单 |
preload/app.cjs::ALLOWED_IPC_CHANNELS(66 项) |
未登记通道在运行时直接抛错;新增 jc: 通道必须同步登记 |
| 控制桥监听 | 仅 127.0.0.1
|
不暴露局域网;除 /health 外全部要求令牌 |
| OpenClaw 桥来源校验 | Host 白名单 + Origin 白名单(null / localhost / 127.0.0.1) |
防 DNS rebinding,且不再反射任意 Origin |
| 令牌 | 随机 24 字节 hex,写入 <userData>/control-bridge.json(0600) |
不用硬编码令牌;可用 BOSSCLAW_CONTROL_TOKEN 覆盖 |
三条本地桥的令牌不要混用:
| 桥 | 端口 | 令牌位置 |
|---|---|---|
| 控制桥(Agent / MCP) | 17650 | <userData>/control-bridge.json |
| OpenClaw Node 桥 | 18765 |
~/.bossclaw/bridge-token(渲染层经 jc:bridge-token 取) |
| Camoufox Python 桥 | 18767 |
~/.bossclaw/camoufox-token(跨启动稳定) |
Boss-claw/
├── desktop-app/ 主应用(Electron + React)
│ ├── electron/
│ │ ├── main.cjs 主进程:窗口 / webview / IPC / 子进程 / 文件能力
│ │ ├── control-bridge.cjs 本地控制桥(/health /state /action,默认关闭)
│ │ ├── mockInterviewRepo.cjs 模拟面试:会话全量 + 轻量索引(可重建)
│ │ ├── mockInterviewBankRepo.cjs 题库注册表 / 用户题库 / BM25 索引缓存
│ │ ├── mockInterviewVoiceRepo.cjs + VoicePack.cjs 克隆音色仓库与音色包
│ │ ├── speechService.cjs + speechWorker.cjs + speechInstall.cjs 本地 ASR/TTS 服务、模型下载
│ │ ├── edgeTts.cjs 在线 TTS 通道
│ │ ├── preload/
│ │ │ ├── app.cjs 主窗口安全接口(IPC 白名单 + LLM 代理)
│ │ │ ├── webview.cjs guest 页采集 / 真实输入 / BOSS 投递状态机
│ │ │ ├── platform-adapters.cjs 多平台 DOM 适配表(零 DOM 依赖)
│ │ │ ├── liepin-im.cjs 猎聘 IM 只读状态(确认本人文字消息)
│ │ │ ├── humanize.cjs 人类化输入节奏
│ │ │ └── stealth.cjs 内置浏览器反检测补丁
│ │ └── cloakbrowser/ CloakBrowser 生命周期与页面预加载
│ ├── bridge/ OpenClaw Node 桥(mammoth / 文件 / 任务恢复)
│ ├── camoufox/
│ │ ├── camoufox_server.py 多平台调度基座(/search /send /login /chat-watch)
│ │ ├── platforms/ base.py 三段骨架 + 各平台差异声明 + 能力/筛选码值
│ │ └── tests/ Playwright DOM 回归(聊天上下文、猎聘投递)
│ ├── skills/ AI 技能库(9 个 SKILL.md,只读随包;含模拟面试出题 / 报告)
│ ├── resources/ 随包资源(docs「使用前必读」/ bank 内置题库 / speech 语音配置)
│ ├── scripts/ 回归脚本(见第十节)
│ ├── src/
│ │ ├── store/ useAppStore / useDataStore / useSettingsStore / useScheduleStore / useRuntimeLogsStore / useAutoChatStore / useMockInterviewStore / useBankStore
│ │ ├── lib/ storage / electronApi / bridgeClient / localBackup / scheduler / controlRuntime / browserRegistry
│ │ ├── lib/bossclaw/ 业务核心(模块地图见第四节);模拟面试 = mockInterview* 全家桶
│ │ ├── components/ TitleBar / Sidebar / StatusBar / BrowserView / PlatformChip / MarkdownView / MockBankPanel
│ │ └── pages/ Home / Workbench / Resume / Directions / Tasks / ScheduleTasks / Stats / Assistant / OpenClaw / AutoChat / MockInterview / Settings
│ └── package.json 依赖、scripts、electron-builder 配置
├── mcp/bossclaw-mcp/ 零依赖 stdio MCP 服务器(9 工具 / 3 组)
├── start-bossclaw.cmd Windows 一键启动(默认开控制桥;--dev / --visible / --no-agent)
├── install-deps.cmd 依赖安装(Node + Electron,可选 Python 引擎)
└── README.md 项目主页与使用说明
docs/目录为本地开发文档,不随仓库分发(.gitignore),因此 README 不链接其中任何文件;对外公开的技术内容以本 Wiki 为准。
改业务逻辑前先在表里找到权威模块,只改权威处,调用方不复制逻辑。
| 领域 | 权威模块 | 关键口径 |
|---|---|---|
| 任务阶段机 |
lib/bossclaw/taskState.ts + types.ts::TaskStage
|
阶段 → 文案/进度映射;PHASE_LABELS 五段 |
| 岗位状态 |
lib/bossclaw/types.ts::PendingStatus + statusMeta.ts
|
状态词汇见 5.3,UI 文案不得另造 |
| 岗位优先级 | lib/bossclaw/priority.ts |
approved(0) → approved_queue(1) → pending(2) → failed(3);risks×-90 / gaps×-45
|
| 岗位去重 / 投递锁 |
store/useDataStore.ts::addPendingItem + deliveryLock.ts
|
去重 = 归一化 URL 或 jobId 或 sameJobSoft;投递锁 claim/release 必须成对 |
| 账号级节流 | deliveryThrottle.ts |
进程级单例 sharedPacer();禁止各引擎 new ActionPacer
|
| 风控冷却 | safety.ts::nextCooldownUntil |
单调合并,禁止裸 Date.now() + cooldownMs 覆盖写 |
| 安全上限 | limits.ts::SAFETY_LIMITS |
150/日、8/分钟、间隔基数 15s;agent 不得修改 |
| 匹配与评分 |
matching.ts / jobMatch.ts / fitLevel.ts
|
AI 四层裁决;AI 分即最终分,本地分只展示/兜底 |
| 技能本体 | skillTaxonomy.ts |
禁止再自建别名表 |
| 薪资 |
helpers.ts::cleanSalary + workSchedule.ts
|
外送前必清洗;月工作日 = 周天数 × 52/12 |
| JD 学历 | jobMatch.ts::jdRequiredDegreeLevel |
「本科及以上」不升档;「优先」不是要求 |
| 岗位过期 | jobExpiry.ts |
仅 JD 显式写截止日期才判定;当天仍有效 |
| 面试方式 | interviewMode.ts |
未披露 = 合格,不误杀 |
| 校名披露 | schoolTier.ts |
仅 985/211 写校名;判不出即禁止 |
| 目标城市 |
targetLocations.ts + targetLocationSync.ts
|
设置页与画像 hardConstraints.locations 同源 |
| 平台注册 / 能力 |
platforms.ts ↔ camoufox/platforms/capabilities.py
|
双源同步;判定走 platformSupports()
|
| 搜索 URL |
platformUrls.ts / searchUrl.ts
|
各平台参数构造唯一入口 |
| 跨平台筛选码值 | camoufox/platforms/filters.py |
平台条件码映射唯一权威 |
| 采集故障范围 | platforms.ts::collectFaultScope() |
见 6.1;未知码 fail-safe 归队列级 |
| webview DOM | electron/preload/platform-adapters.cjs |
webview.cjs 禁止内联平台选择器
|
| jobId | platform-adapters.cjs::jobIdFromUrl() |
唯一入口;各平台 URL 形态不同 |
| AI 调用 |
llm.ts(callModel / cachedCallModel) |
禁止在别处直接发 HTTP,禁止绕过缓存 |
| 思考强度 | thinkingCapability.ts |
未验证模型 = unsupported,绝不发参数 |
| 上下文预算 | contextBudget.ts |
窗口 × 用量档位;预算签名进缓存 key |
| 超窗分片 | oversizedContext.ts::prepareContextText |
≤3 片;主裁决恒一次调用 |
| AI 降级 / 代答 |
agentAnswer.ts + llm.ts 的 !apiKey 分支 |
只搬「提示词 ↔ 文本」 |
| 控制桥白名单 | src/lib/controlRuntime.ts |
约 60 个动作;发送类受 executionMode 守卫 |
| 招呼语 | greetings.ts::clampGreetingText |
全文 120–250 字,250 为硬上限;禁止裸 slice
|
| 浏览器白名单 | browserWhitelist.ts |
browserOpenJob 必须过白名单 |
| 本地备份 |
localBackup.ts + storage.ts::STORAGE_KEYS / BACKUP_KEYS
|
5 分钟脏检查;STORAGE_KEYS 是唯一登记表,BACKUP_KEYS 由 backup 标记派生 |
| 作息保护(活跃时段 / 批次休息) | activityWindow.ts |
默认 8:00–23:00(±25 分钟抖动)、每 15 个岗位休 8–15 分钟;两引擎同口径;只约束时间,不改速率上限 |
| 定制简历管线 |
tailorJob.ts / tailorBatch.ts + electron/resume-pdf-batch.cjs
|
单岗位 / 批量 / 迭代三档;批量 PDF 由主进程独占写盘(wx + 同名编号) |
| 模拟面试出题 / 评分 |
mockInterviewEngine.ts + mockInterviewTypes.ts
|
两段式;模型只给档位,分数由本地五维权重算;N/A 不参与加权 |
| 模拟面试题库 / 会话 |
electron/mockInterviewBankRepo.cjs / mockInterviewRepo.cjs
|
内置题库只读不复制;index.json 是派生缓存,可重建 |
| 内置浏览器反检测 |
electron/preload/stealth.cjs + TARGET_PROFILE
|
一份画像驱动 UA / 请求头 / 主世界补丁;BOSCLAW_STEALTH 可逐项开关 |
| 键 | 内容 | 换机导出 | 本地备份 |
|---|---|---|---|
bossclaw-app |
运行时 UI 状态(主题、当前路由、侧栏、桥状态、登录态) | 是 | 是 |
bossclaw-settings-v2 |
用户配置(模型密钥、求职条件、平台开关/优先级/每日目标、节奏与安全参数) | 是 | 是 |
bossclaw-data |
业务数据(岗位 /pending、任务 /taskRuns、画像、方向、招呼语、沟通信息) |
是 | 是 |
bossclaw-schedule |
定时任务(动作 / 时间 / 星期 / 平台 / 单轮上限)+ 采集请求 | 是 | 是 |
bossclaw-runtime-logs |
运行日志 / 聊天日志(独立键,避免日志膨胀拖慢业务数据序列化) | 否 | 否 |
bossclaw-skills-v1、bossclaw-city-codes、bossclaw-ai-cache-*
|
技能启用态、城市码缓存、AI 缓存(可重建) | 否 | 否 |
bossclaw-tailor-history-v1 |
定制简历历史 | 否 | 是 |
bossclaw-resume-module-order-v1、-template-v1、-photo-v1
|
简历模块排序 / 模板 / 照片 | 否 | 否 |
bossclaw-data-version |
数据版本号(启动迁移依赖,不可清空) | 否 | 否 |
备份由 localBackup.ts 每 5 分钟做一次脏检查(persistDirtyEpoch),写盘到用户指定目录;主存储缺失时按代数锚定回签恢复。所有键只在 storage.ts::STORAGE_KEYS 登记一次(export / backup / clear 三个开关),EXPORT_KEYS / BACKUP_KEYS 由其派生;新增持久化键必须登记,否则导出 / 恢复 / 清空会漏。
PendingItem {
id; runId?; job: JobMeta; analysis?: JobAnalysis; deliveryGreeting;
status: PendingStatus; createdAt; approvedAt?; priorityScore?; priorityRank?;
retryCount?; error?; retryable?;
sentAt?; // 成功投递时间(计入每日上限)
replySentAt?; // AI 跟聊回复时间(不计入每日上限)
openedAt?; riskBlocked?;
hrRepliedFingerprint?; hrRejectedAt?;
}
JobAnalysis {
score; fitLevel; decision; hardBlocks[]; matchedEvidence[]; gaps[]; risks[];
reason; greeting; dimensions?; scoreSource?: 'ai' | 'local';
}-
JobMeta保留平台、URL、jobId、JD 文本、HR 活跃度、福利标签、面试方式、招聘方姓名等采集字段;platform缺省视为boss(兼容存量数据)。 -
TaskRun是采集/分析批次记录:status(running / success / failed / skipped / ignored / waiting_review / queued / pending)+stage+progress+ 计数(discovered / analyzed / failed)。 - 评分与决策:
fitLevel四档strong / match / cautious / unfit↔ 分数区间固定;decision由档位与硬约束共同决定。
| 状态 | UI 文案 | 含义 |
|---|---|---|
pending |
待确认 | AI 已分析,等待人工批准 |
approved |
待投递 | 已批准,尚未进入投递循环 |
approved_queue |
投递中 | 引擎已认领,按优先级排队投递 |
opened |
已打开沟通窗口 | 已打开聊天页,等待发送/核对 |
sent |
已投递 | 文字气泡已确认(或平台等价成功信号) |
failed |
投递失败 | 可重试的失败;风控失败另有 riskBlocked
|
skipped |
未达到推荐条件 | 外部网申 / 已投递 / 条件不符 |
ignored |
已忽略 | 用户主动忽略或过期 |
rejected |
不推荐 | 硬约束拦截,不入队 |
状态流转不变量
- 批准即入队:批准时招呼语必须非空,
approved只由promoteApprovedToQueue()单向提升为approved_queue。 -
approved_queue不得回写approved(会造成重复投递与队列卡死)。 - 首次成功投递后必须
pauseAssist(强制暂停验收),该分支不能裸return。 - 同一岗位同一时刻只允许一个引擎持有投递锁;
claimDelivery失败即放弃本轮,不重试抢锁。 - 任何按
status分支的 UI,改动口径后必须全仓 grep(tsc不会报错,架空态会静默失效)。
| 阶段 | 文案 | 进度 |
|---|---|---|
discovered |
已发现岗位 | 12 |
collect_detail |
读取岗位详情 | 24 |
ai_analyze |
AI 匹配分析 | 42 |
ai_complete |
AI 分析完成 | 56 |
waiting_review |
等待人工确认 | 60 |
queued / retry_queued
|
等待投递 / 等待重新投递 | 64 / 66 |
open_job |
打开岗位页面 | 70 |
open_chat |
打开沟通窗口 | 78 |
opened |
已打开沟通窗口,等待发送/核对 | 80 |
verify_chat_target |
核对 HR 与岗位 | 82 |
fill_message |
填写求职招呼语 | 86 |
send_message |
发送求职招呼语 | 90 |
verify_message |
确认文字已发送 | 94 |
send_resume |
发送简历附件 | 97 |
verify_result |
确认投递结果 | 98 |
success / failed / ignored / skipped
|
终态 | 100 |
PHASE_LABELS = ['搜索','匹配','确认','投递','完成'] 是进度条五段口径;stageToPhase() 是唯一映射。新增阶段必须同时补 TASK_STAGE_META 与 STAGE_TO_PHASE(缺一个会回退到「处理中 / 60%」)。
| 通道 | 触发 | 覆盖 | 详情 JD |
|---|---|---|---|
| 内置浏览器(webview) | 默认;未启用隐身引擎时采集也走这条 | 四平台列表级;BOSS 列表内联详情 | 智联 = 同源详情 JSON;猎聘 = 同源 SSR 详情页 HTML;前程无忧 = 仅列表字段 |
| Camoufox(Python) | 设置页启用隐身引擎后,采集由它接管 | 四平台列表级(BOSS 保留既有采集/投递链路) | 非 BOSS 仍由内置浏览器 enrichJobDetail() 补齐;与是否安装 Camoufox 无关
|
口径更正:
camoufox/platforms/liepin.py / zhaopin.py / job51.py只实现列表级采集;不要以为启用隐身引擎就能拿到非 BOSS 详情 JD。详情补齐的唯一入口是内置浏览器的平台同源接口。
-
BOSS 是唯一「列表内联详情」平台,禁止给非 BOSS 加「逐卡点开详情」;非 BOSS 详情补齐唯一入口是
webview.cjs::enrichJobDetail()。 -
登录墙即时收口:判定未登录 =
loginWall且cards <= 0;命中即reason='login',不重试,立即收口本平台,后续平台继续。失败日志必须带describeProbe()诊断。 -
故障范围唯一权威
collectFaultScope(code):- 平台级(只收口当前平台):
31 / 400 / 403 / 404 / 500 / 501 / 600 / 1006; - 队列级(整批中止交人工):
32 / 35 / 36 / 37 / 38; - 无码 / 0 → 平台级;未知码 → 队列级(fail-safe,宁停不错)。
- 所有采集循环遇到
'abort'必须break。
- 平台级(只收口当前平台):
-
断点续采只做词级:
(platform, city, keyword),TTL 24h,存~/.bossclaw/collection-progress.json。页级断点禁止加回(整批返回架构下会静默丢岗位);命中风控必须clear_combo从头采,宁重复不遗漏。 -
并发:采集后的 AI 分析走有界队列(
analysisQueue.ts+analysisConcurrency,默认 3),禁止 fire-and-forget 造成无界 LLM 调用。
岗位 JD + 简历 + 画像 + 方向
→ 硬门槛(学历/经验/城市/薪资/过期/面试方式…)──命中→ 直接 reject(不消耗 AI)
→ AI 四层整体裁决:硬门槛 → 优先条件 → 职责信号 → 团队信号
→ fitLevel 档位(strong / match / cautious / unfit)→ 分数随档映射
→ decision + 硬拦截说明 + 匹配证据 + 缺口 + 风险 + 招呼语
→ 本地五维分(仅展示/兜底,不与 AI 融合)
- AI 分即最终分;本地唯一改分权是
hardBlocks(score ≤ 35或reject)。 - AI 输出必须走
llm.ts的 JSON 管线:response_format: json_object+ prompt 含json+ 填充版样例 +max_tokens(≥2000,封顶 8192);截断内容不做宽松截尾修复。 - 失败语义:
AI_EMPTY/AI_TRUNCATED/AI_INVALID_JSON由上层走本地规则兜底;AI 缺字段时不回填本地候选(除明确的兜底路径)。 - 思考强度只由
thinkingCapability.ts决定;未知模型一律不发思考参数(部分模型传disabled会 400)。
- 采集 → 分析与硬约束 → 分数低于
minQueueScore不入队。 - 入队后状态
pending,按priority.ts计算priorityRank;入队时按「归一化 URL /jobId/ 软匹配」三路去重(addPendingItem),同一岗位不叠卡。 - 用户批准(招呼语非空)→
approved;投递引擎起跑时promoteApprovedToQueue()→approved_queue。 - 重排
rerankPending()只影响approved / approved_queue / pending,opened不参与排名。
Workbench.runNext 只做「选岗位 + 预检 + 分派」,不承载具体点击逻辑:
| 通道 | 平台 | 实现 | 成功判定 |
|---|---|---|---|
| BOSS DOM | boss |
webview.cjs 聊天发送状态机 |
本次新增的完整本人文字气泡 |
| Camoufox | 仅 boss(设置页「优先走隐身通道」) |
Python /send(官方接口 + 真实点击兜底) |
接口结果 + 气泡/会话确认,风控码即停 |
| 非 BOSS DOM |
liepin / zhaopin / job51
|
webviewApi.platformApply() |
猎聘 = 新增本人文字消息;智联/前程无忧 = 简历投递成功页/按钮终态 |
BOSS 文字发送(唯一权威 = webview.cjs 的聊天发送上下文状态机)
- 上下文判定:
chatSendVisible / chatSendEditors / chatSendPanel / createChatSendContext / currentChatSendInput→sendButton→trustedClickElement→confirmOwnMessage。 - 硬不变量:编辑器、面板、头部、消息列表、发送按钮都必须唯一;任一不唯一立即放弃(宁可不发,不可发错会话)。
- 确认口径:
entry.text === needle+ 节点身份 / 消息 ID +DOCUMENT_POSITION_FOLLOWING,必须是本次新增的完整己方气泡;长度阈值已整体退场,禁止重新引入;禁止整页innerText.includes()兜底。 - 坐标换算唯一入口
main.cjs::nativeMousePoint:CSS 视口坐标只乘 guestzoomFactor,不得叠 Windows DPR / 宿主缩放 / webview 偏移。 - 输入主通道是主进程
sendInputEvent(isTrusted: true);页面内仍有少量合成事件(el.click()/dispatchEvent)仅用于非关键兜底,不得用于发送确认;把剩余交互全部收口到真事件通道仍是待办项。
投递并发与节奏
- 投递锁:
deliveryLock.ts按id + platform认领,try/finally释放;platformApply另有busy/activeSeq守卫。 - 账号级节流:
deliveryThrottle.ts的sharedPacer()是跨两套引擎的进程级单例,预算天花板SAFETY_LIMITS.MAX_ACTIONS_PER_MINUTE = 8;批次休息计数同样跨引擎累计。 - 冷却:
safety.ts::nextCooldownUntil单调合并(旧冷却未结束不会被新值缩短);isLockedOut / cooldownRemaining是所有引擎的同一判据。 - 每日上限:
min(平台每日目标, 平台侧上限, 150);BOSS / 猎聘 / 前程无忧默认 120,智联 100。 -
作息保护:唯一权威
activityWindow.ts。活跃时段默认 8:00–23:00(每日 ±25 分钟抖动,避免「每天精确 8:00」的机器特征),批次休息默认每 15 个岗位休 8–15 分钟;工作台与自动沟通两个引擎同口径,窗口外主动让路。它只约束作业时间,不改变任何速率上限与安全不变量;两项配置经控制桥CONFIG_DENY拒绝写入,只能设置页手动改。 - 猎聘
platform-apply超时放宽到 55s;超时带needsReview且保留标签交人工,分支必须早于closeTab。
- 自动沟通(可选 Camoufox):按平台优先级串行;BOSS 发文字招呼语(气泡确认)、猎聘「聊一聊」注入本岗位招呼语、智联/前程无忧投递简历;各平台独立计数,遇风控码立即中止本批交人工。
-
AI 跟聊(BOSS 专属,可选):
/chat-watch单一常驻浏览器停在会话页(start / scan / open / send / stop);渲染层 2 分钟一轮、每轮 ≤6 会话、两次回复最小间隔 20s;与批量投递共用 pacer 与冷却(批量投递运行时监听让路)。 - 跟聊只写
replySentAt,不改sentAt、不计入每日投递上限;HR 明确拒绝写hrRejectedAt并收口该会话。
-
入口唯一:
electron/preload/stealth.cjs。<webview>preload 跑在隔离世界,改navigator对页面无效,因此该模块只产出源码字符串,由main.cjs::injectStealth在did-start-loading+dom-ready两个时机executeJavaScript注入主世界(脚本自带__bossclawStealth幂等守卫)。不要再走 preload 阶段注入(实测时机不早于主进程通道)。 -
画像单一权威:
stealth.cjs::DEFAULT_PROFILE(uaChromeVersion/brands/acceptLanguage/languages)一份同时驱动session.setUserAgent、webRequest.onBeforeSendHeaders、主世界补丁三处。任何一处单独改都会造出「JS 说一套、请求头另一套」的新矛盾;覆盖顺序 默认值 <BOSSCLAW_UA_VERSION<BOSSCLAW_PROFILE_JSON。 -
升级内核必同步:
uaChromeVersion与brands(含 grease 品牌名 / 版本,随 Chromium 主版本轮换)必须等于当前内核真实形态(Electron 42 = Chromium 148 →Not/A)Brand/99),resolveProfile()启动时自检并告警。 -
逐项开关:
BOSSCLAW_STEALTH=off/only=a,b/skip=a,b,项名见PATCH_KEYS,用于二分定位副作用。 -
输入通道:文本优先走主进程
sendInputEvent(isTrusted: true);页面内合成事件只作非关键兜底,不得用于发送确认。坐标换算唯一入口main.cjs::nativeMousePoint。
- 单岗位
tailorJob.ts、批量 / 迭代tailorBatch.ts,视图在pages/JobAssistant.tsx+TailorResultView;输出必须挂source(原文摘录),能力描述语原样保留。 - 批量导出 PDF 走主进程
electron/resume-pdf-batch.cjs,独占写盘防竞态(wx打开 + 同名自动编号),草稿异常保留待人工核对。 - 回归:
test:tailor-job/test:tailor-batch/test:tailor-iteration三支脚本。
落地状态:2026-10-20 合入
main,未包含在 v2.5.7 安装包(该包 2026-10-14 构建)。
页面 pages/MockInterview.tsx + 引擎 lib/bossclaw/mockInterviewEngine.ts;输入 = 目标岗位 JD +(可选)简历原文。
- 两段式出题:阶段一按 JD 出基础题;答完阶段一后用你的回答生成针对性深挖题(阶段二),阶段二评分会参考简历原文。
- 只记录、不逐题评分:每题答完仅落库,全部结束后一次性出报告,避免「边答边被判分」影响临场状态。
-
评分口径:模型只给档位,分数由本地按五维权重计算 ——
relevance/evidence(证据具体性,权重 0.25)/structure/depth/reflection;N/A 维度不参与加权、剩余权重按比例归一(不按 0 分处理),同一份回答重算结果恒定。回答中引用的原句若无法在你的回答里定位,相关维度按「无证据」压分(防模型替你编造引用)。 -
语音:
mockInterviewRecorder.ts采集 →mockInterviewAsr.ts调本地 ASR(speechService.cjs,sherpa-onnx-node,模型下载到<userData>/speech/models/);朗读走本地 TTS 或edgeTts.cjs在线通道(<userData>/speech/settings.json里可切在线高音质档,默认关);克隆音色由mockInterviewVoiceRepo.cjs/mockInterviewVoicePack.cjs管理。 -
落盘:会话
<userData>/mock-interview/sessions/<id>.json(全量)+index.json(派生索引,损坏可扫目录重建);题库<userData>/mock-interview/bank/banks.json+<bankId>.json+index.cache.json(BM25 缓存,可删),内置题库只读<appPath>/resources/bank/zh-interview-small.json、不复制到用户目录。 -
IPC / 回归:
jc:mock-interview-*共 46 条(会话 / 题库 / 语音 / ASR / TTS);回归test:mock-interview(评分)、-tts、-asr、-clone、-delivery、-stream,以及test:voice、test:edge-tts。
| 分组 | 代表通道 | 用途 |
|---|---|---|
| 应用 / 窗口 |
jc:app-info、jc:window-*(含 jc:window-always-on-top*) |
版本信息、自绘标题栏、置顶 |
| webview |
jc:webview-input(-done)、jc:webview-diag、jc:webview-source、jc:webview-open-link
|
真实输入、诊断、源码、链接 |
| Camoufox | jc:camoufox-call / status / stop / restart |
Python 桥调度(按平台分发) |
| CloakBrowser | jc:cloak-start / -status / -page-* |
隐身浏览器生命周期与页面操作 |
| 文件 / 数据 |
jc:read-doc、jc:save-pdf(-batch)、jc:save-file、jc:save-report-pdf、jc:material-*、jc:qualified-jobs-*、jc:show-item
|
只读文档、PDF/CSV 导出、素材路径、定位文件 |
| 技能 / 代理 |
jc:skills-*、jc:llm-proxy、jc:fetch-url
|
技能库读写、LLM 代理、跨域抓取 |
| 备份 / 自启 |
jc:backup-*、jc:autostart-*
|
本地备份目录与写盘、Windows 登录项 |
| 桥 |
jc:bridge-control、jc:bridge-token
|
OpenClaw 桥启停、运行时令牌 |
| 模拟面试 |
jc:mock-interview-*(46 条) |
会话 / 题库 CRUD、题目导入、语音安装与状态、ASR / TTS、导出 |
硬约束:新增
jc:通道必须同时加入preload/app.cjs::ALLOWED_IPC_CHANNELS,否则运行时抛「拒绝非白名单 IPC 通道」。不要把安全敏感能力做成无白名单的通用invoke。 已知例外:jc:save-pdf-batch*仍由 preload 直接invoke,未过assertIpcChannel(待统一,不应作为新通道的样板)。
- 端点:
GET /health(免令牌,仅存活探测)、GET /state(渲染层实时快照,支持path裁剪)、POST /action(白名单动作)。 - 开启:
BOSSCLAW_CONTROL=1或--control-bridge;--no-control-bridge/BOSSCLAW_CONTROL=0优先关闭。start-bossclaw.cmd默认开启,--no-agent可关。 - 白名单:
src/lib/controlRuntime.ts的handlers(约 60 个动作),分为状态读取、页面导航/主题、配置补丁(patchConfig,DENY 列表外且字段必须已存在)、业务数据写入、任务/队列操作、AI 生成、浏览器只读与白名单操作、统计导出。 -
禁止:发消息、批量投递、绕验证码、改
SAFETY_LIMITS、直接改写备份文件。发送类动作仅当config.executionMode === 'auto'时可用,且复用应用自带安全引擎。 -
CONFIG_DENY至少包含:executionMode、apiKey、pausedUntil、platforms、activeHours、batchRest、requireSingleJobValidation、singleJobValidationCompletedAt、maxDailySent、dailyTarget。
| 组 | 工具 | 说明 |
|---|---|---|
| runtime(3) |
bossclaw_app_start / bossclaw_app_stop / bossclaw_app_status
|
启停应用(默认带 --control-bridge)、进程与桥健康 |
| control(2) |
bossclaw_app_state / bossclaw_app_action
|
读实时快照、执行白名单动作(含截图) |
| agent(4) |
bossclaw_agent_tasks / bossclaw_agent_submit / bossclaw_agent_cancel / bossclaw_agent_send
|
代答领取(领取即心跳)/ 回填 / 取消 / 全自动发送(受 executionMode 守卫) |
-
代答在线判定:最近 90s 内调用过
bossclaw_agent_tasks才算在线;单任务等待 30s–240s,超时 / 离线 / 取消立即回落本地规则。 - 只搬运「提示词 ↔ 生成文本」:回填内容仍过事实、口吻、校名、招呼语长度等全部校验;不合规照样被本地规则替换。
- 验证:
cd mcp/bossclaw-mcp && node test/selftest.mjs(协议 + 工具自检)、node test/bridge-e2e.mjs(隔离实例全链路)。
| 不变量 | 落地位置 |
|---|---|
| 未确认本人文字气泡 → 不发附件、不计成功、不跳下一岗位 |
webview.cjs::confirmOwnMessage / liepin-im.cjs
|
| 首次成功投递后强制暂停验收 |
Workbench.handleDelivered 首次分支 |
| 目标会话 / HR 与岗位不一致 → 不发 | 聊天上下文状态机 + 会话身份 |
| 外部网申 / 已投递 → 跳过,不消耗配额 | 投递预检 |
| 风控码 32 / 35 / 36 → 立即停止并交人工 |
safety.ts::CODE_MAP + 各引擎 |
| 环境异常 37 / 38、1006 限速 → 退避,不重试登录 |
nextCooldownUntil + 采集故障范围 |
| 日上限 150、分钟 8、岗位间隔基数 ≥15s(再叠加人类化抖动) |
limits.ts(agent 不可改) |
| 招呼语 120–250 字、必须求职者口吻、只引用真实事实 |
greetings.ts + prompts |
| 校名仅 985/211 可披露 | schoolTier.ts |
| 不绕过验证码 / 登录验证 / 速率限制 / 账号限制 | 全链路设计约束;无任何相关实现 |
| 控制桥默认关闭、仅回环、令牌鉴权 | control-bridge.cjs |
| Agent 不得改安全参数或代替用户确认 |
controlRuntime.ts 白名单 + DENY |
出现验证码、安全验证、账号异常、页面结构变化导致目标不明确、发送结果无法确认时,立即暂停是设计行为,不是缺陷。
按顺序做,缺一项就会出现「有界面但采不到 / 投不了」:
-
平台元数据:
src/lib/bossclaw/platforms.ts增加PLATFORM_META(名称、品牌色、能力矩阵、deliveryKind、阶段标签、每日上限、外部网申文案)。 -
能力双源:
camoufox/platforms/capabilities.py::PLATFORM_CAPABILITIES与PLATFORM_DELIVERY_KIND同步,业务判定只用platformSupports(),禁止硬编码platform === 'boss'。 -
搜索 URL:
platformUrls.ts/ Python 侧搜索差异声明;参数码值进camoufox/platforms/filters.py。 -
DOM 适配:
electron/preload/platform-adapters.cjs增加卡片 / 容器 / 链接 / 详情 / 投递选择器与jobIdFromUrl;卡片选择器与容器选择器必须同时改(失配会把整页塌成 1 条且不报错)。 -
投递动作:
platformApply规格(按钮定位、终态、确认信号);需要 IM 确认的写独立只读模块(参考liepin-im.cjs)。 -
Python 差异声明:
camoufox/platforms/<platform>.py只写差异(搜索附加、投递、登录),骨架复用base.py;注册进registry.py。 - UI:平台筛选 / 标签 / 配额 / 登录卡片由注册层驱动,检查侧栏与设置页是否有硬编码平台列表。
-
能力守卫:采集通道注册
visual-collect/collect-control必须全平台注册(漏注册会让界面静默卡到 15 分钟超时)。 -
测试:URL / 解析 / 投递确认各补离线回归;至少跑
webview-send-regression、liepin-im-regression(若为 chat 型)与 Python 单测。 - 文档:更新功能指南、安全与合规的平台差异说明。
交付前必跑(desktop-app/ 下):
npm run typecheck # tsc -b
npm run build # tsc -b + vite build
# 或 npm run verify沙箱内 npm 不可用时直调:node node_modules/typescript/bin/tsc -b + node node_modules/vite/bin/vite.js build。
回归脚本矩阵(desktop-app/scripts/,按改动面选择):
| 脚本 | 守住的边界 |
|---|---|
score-regression.mjs |
AI 档位 / 本地五维 / 硬约束评分 |
greeting-regression.mjs |
招呼语长度、口吻、校名披露 |
cooldown-regression.mjs |
风控冷却、配额、冷却写入单调合并 |
delivery-throttle-regression.mjs |
双引擎共享 pacer / 计数、分钟与日上限 |
llm-json-regression.mjs |
JSON 解析、截断不判成功、二次补齐 |
context-budget-regression.mjs |
上下文预算与分片 |
css-guard-regression.mjs / theme-vars-regression.mjs
|
死类、语义色、主题变量悬空 |
pdf-parse-regression.mjs / doc-parse-regression.mjs
|
PDF / 旧版 .doc 解析(字体流、分词回溯、正文还原) |
webview-send-regression.mjs |
BOSS 文字发送状态机(真 Chromium,可用 BOSSCLAW_TEST_BROWSER 指向本机 Chrome) |
webview-native-send-regression.mjs |
真 Electron + Playwright 原生输入(仅 Windows 真机/CI) |
liepin-im-regression.mjs |
猎聘 IM 发送与「新增本人消息」确认 |
zhaopin-jd-regression.mjs / liepin-jd-regression.mjs
|
详情 JD 补齐与解析 |
tailor-*-regression.mjs |
定制简历(单岗位 / 批量 / 迭代)保真与结构 |
mock-interview-*-regression.mjs / -smoke.mjs
|
模拟面试评分(同答重算恒定)、出题 / 深挖、ASR / TTS / 克隆音色、交付节奏 |
voice-*-regression.mjs / edge-tts-*-regression.mjs / tts-*-regression.mjs
|
音色仓库与音色包、在线 TTS 协议、流式与 PCM 输出 |
jsx-key-regression.mjs / humanize-regression.mjs
|
UI key 与人类化行为 |
Python 侧:
cd desktop-app
python -m unittest discover -s camoufox/tests -p "test_liepin_delivery.py"
python -m unittest discover -s camoufox/tests -p "test_chat_context.py"判据:脚本失败 = 回归未通过,不算「环境问题跳过」;真机专用脚本(
webview-native-send)缺 Electron 运行时按失败处理。
| 命令 | 作用 |
|---|---|
npm run dev |
Vite dev server(5173) |
npm run dev:electron |
构建 renderer 并以 Electron 打开 |
npm run typecheck / build / verify
|
类型检查 / 构建 / 二者组合 |
npm run package |
Windows NSIS + 便携版 |
npm run package:portable / package:dir
|
仅便携版 / 仅解压目录 |
npm run package:linux / package:mac / package:all
|
对应平台产物(macOS 只能在 macOS 构建) |
| 变量 | 作用 |
|---|---|
BOSSCLAW_DEBUG=1 |
写 userData/bossclaw-debug.log,启用白屏 / webview 诊断日志 |
BOSSCLAW_CONTROL=1 |
开启本地控制桥(等价 --control-bridge;=0 显式关闭优先) |
BOSSCLAW_CONTROL_PORT |
覆盖控制桥端口 17650(被占用自动 +1…+9) |
BOSSCLAW_STEALTH |
反检测补丁开关:off / only=a,b / skip=a,b
|
BOSSCLAW_UA_VERSION / BOSSCLAW_PROFILE_JSON
|
覆盖浏览器画像(UA 版本 / 整份画像,后者优先级最高) |
BOSSCLAW_TEST_BROWSER |
发送回归脚本指向本机 Chrome/Edge 可执行文件 |
ELECTRON_MIRROR |
构建期加速 Electron 二进制下载(npm 安装阶段生效) |
-
Electron 二进制下载失败(
app-builder.exe走代理异常):用已下载好的官方 zip 绕过:日志出现node node_modules/electron-builder/cli.js --win -c.electronDist=tmp/electron-upgrade
resolved electronDist即命中;产物等价标准路径。 -
依赖三处一致:
package.json/package-lock.json/node_modules。当前锁为 Electron 42 / electron-builder 26;若npm ci后版本回退,先修 lock 再打包(否则会打出 Electron 31 的包)。 -
产物:
release/BossClaw-<version>-x64.exe、BossClaw-<version>-portable.exe、win-unpacked/。
- GitHub Wiki 是独立仓库
Boss-Claw.wiki.git(默认分支master),页面源文件由开发者本地docs/wiki/维护(docs/不随仓库分发,见文首说明),含_Sidebar.md。 - 同步 = 覆盖页面 → 用仓库 noreply 身份提交 → 推送 wiki 仓库;缺
_Sidebar.md会被镜像删除导航。 - 主仓库的
docs/被.gitignore忽略,README 不得链接docs/...(公开页会 404)。 - 推送必须带本机代理,并用 noreply 身份,否则报
push declined due to email privacy restrictions。 - 发布后可用
https://raw.githubusercontent.com/wiki/<owner>/<repo>/<Page>.md校验是否已上线。
隐身引擎(Camoufox / Playwright)——需 Python 3.10+:
cd desktop-app/camoufox
python -m venv .venv
.venv\Scripts\pip install -r requirements.txt
.venv\Scripts\python -m camoufox fetch # 必装:Camoufox 原生内核(~150MB),本地 Chrome/Edge 不可复用CloakBrowser——首次启用时下载隐身 Chromium(~200MB)到用户目录并校验签名;离线环境按包内说明指向本地二进制。两者都是可选增强,不装不影响 BOSS 默认通道。
| 现象 | 最可能原因 | 处理 |
|---|---|---|
| 启动白屏 | preload 未注入 / renderer 资源缺失 / 沙箱 GPU | 看 userData/bossclaw-debug.log(BOSSCLAW_DEBUG=1);确认 dist/ 已构建;无 GPU 环境加 --no-sandbox
|
| 采不到岗位、候选只有 1 条 | 卡片选择器与容器选择器失配(整页塌成 1 条,不报错) | 用离线 DOM 样本跑解析;两处选择器同时改;核对 CSS Modules 驼峰类名 |
| 界面一直转圈到 15 分钟 | 某个平台漏注册 visual-collect / collect-control,或 preload 未注入 |
检查通道是否全平台注册;看 describeProbe() 诊断 |
| 提示未登录但页面已登录 | 判定用了「登录墙 或 无卡片」 | 必须是 loginWall && cards <= 0 双条件;确认会话分区未被清空 |
| webview 高度只剩约 150px | 改了 display(应为 flex) |
恢复 display:flex;尺寸变化走 ResizeObserver + force-resize
|
| 聊天发送「成功」但实际没发 | 确认逻辑用了长度阈值 / 整页文本兜底 | 只能认「本次新增完整本人气泡」;检查编辑器/面板/按钮是否唯一 |
| 猎聘投递超时且标签被关 |
needsReview 分支晚于 closeTab
|
保留标签交人工;超时上限 55s |
| 投递频率突然翻倍 | 某引擎自建了 ActionPacer
|
全部改用 sharedPacer();检查冷却是否被裸赋值覆盖 |
| 触发风控后仍在投 | 冷却被新值缩短 | 冷却只走 nextCooldownUntil 单调合并;检查 isLockedOut 是否被绕过 |
| AI 请求 400(思考参数) | 给不支持的模型发了 thinking
|
查 thinkingCapability.ts;未验证模型一律 unsupported
|
| AI 返回半截 JSON 被当成功 | 截断后做了宽松截尾 | 只接受完整 JSON;finish_reason=length 抬 max_tokens 重试一次,仍失败抛 AI_TRUNCATED
|
| 外部 Agent 连不上(401) | 令牌文件不匹配 / 用了旧实例令牌 | 读 <userData>/control-bridge.json 的当前 token;确认没有第二个实例抢占端口 |
| MCP 工具全部超时 | 应用未以 --control-bridge 启动,或渲染层未装控制运行时 |
用 start-bossclaw.cmd 启动;先跑 bossclaw_app_status
|
打包报 Bad Gateway 拉 Electron |
app-builder 代理行为异常 | 用 -c.electronDist= 指向本地已下载 zip(见 11.3) |
| 术语 | 含义 |
|---|---|
| 待确认 / 待投递 / 投递中 / 已投递 | 对应 pending / approved / approved_queue / sent,UI 只用这组词 |
| 硬约束 | 本地确定性拦截(学历、薪资、城市、过期、面试方式等),不消耗 AI |
| 四层裁决 | 硬门槛 → 优先条件 → 职责信号 → 团队信号,一次给出档位分 |
| 投递锁 | 按岗位 + 平台的互斥认领,防双引擎重复投递 |
| pacer | 账号级动作限速器(8 次/分钟上限),两套引擎共享 |
| 词级断点 |
(平台, 城市, 关键词) 维度的续采进度;页级断点已禁用 |
| 登录墙 | 平台未登录的信号;必须与「零卡片」同时成立才判未登录 |
| 收口 | 停止当前平台/队列并交人工,不自动重试 |
| agent 代答 | 未配置 API Key 时,由外部 Agent 用自有模型回答应用内 AI 请求 |
本文与代码同步维护:改模块口径、状态、限额、端口、工具数量时,请在同一提交内更新对应章节;
docs/wiki/是 Wiki 发布源,docs/其余文件为本地历史资料。