@roll-agent/browser-use-agent 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -76,6 +76,15 @@ export type NativeRecommendCardInspection = {
76
76
  readonly cardSelector: string;
77
77
  readonly candidateId: string;
78
78
  readonly name: string;
79
+ readonly age?: string;
80
+ readonly experience?: string;
81
+ readonly education?: string;
82
+ readonly workStatus?: string;
83
+ readonly company?: string;
84
+ readonly currentPosition?: string;
85
+ readonly expectedLocation?: string;
86
+ readonly expectedPosition?: string;
87
+ readonly expectedSalary?: string;
79
88
  readonly hasGreetButton: boolean;
80
89
  readonly error?: string;
81
90
  };
@@ -96,11 +105,13 @@ export type NativeRecommendJobSelectorState = {
96
105
  readonly options: readonly NativeRecommendJobOption[];
97
106
  };
98
107
  export type NativeRecommendJobSelectRequest = {
108
+ readonly jobRef?: string;
99
109
  readonly jobValue?: string;
100
110
  readonly jobName?: string;
101
111
  readonly index?: number;
102
112
  readonly searchKeyword?: string;
103
113
  readonly useSearch?: boolean;
114
+ readonly forceClick?: boolean;
104
115
  };
105
116
  export type NativeRecommendJobSelectResult = {
106
117
  readonly success: boolean;
@@ -112,6 +123,15 @@ export type NativeRecommendJobSelectResult = {
112
123
  readonly matchedCount: number;
113
124
  readonly error?: string;
114
125
  };
126
+ export type NativeRecommendJobListResult = {
127
+ readonly success: boolean;
128
+ readonly status: "listed" | "recommend_not_ready" | "selector_not_found";
129
+ readonly current?: NativeRecommendJobOption;
130
+ readonly options: readonly NativeRecommendJobOption[];
131
+ readonly availableCount: number;
132
+ readonly canSwitch: boolean;
133
+ readonly error?: string;
134
+ };
115
135
  export type NativeWechatExchangeResult = {
116
136
  readonly success: boolean;
117
137
  readonly exchanged: boolean;
@@ -173,7 +193,9 @@ export declare class ZhipinNativePagePort {
173
193
  private selectRecommendJobMatch;
174
194
  private getCurrentRecommendJobOption;
175
195
  private currentRecommendJobMatchesRequest;
196
+ private hasRecommendJobAlternative;
176
197
  private clickRecommendJobOption;
198
+ listRecommendJobs(options?: NativeClickOptions): Promise<NativeRecommendJobListResult>;
177
199
  selectRecommendJob(request: NativeRecommendJobSelectRequest, options?: NativeClickOptions): Promise<NativeRecommendJobSelectResult>;
178
200
  clickSidebarSection(section: "chat" | "recommend", options?: NativeClickOptions): Promise<boolean>;
179
201
  scrollSurface(surface: ZhipinListSurface, options?: {
@@ -0,0 +1,48 @@
1
+ export declare const ZHIPIN_CANDIDATE_REF_PREFIX: "@c";
2
+ export declare const ZHIPIN_RECOMMEND_JOB_REF_PREFIX: "@j";
3
+ export declare const ZHIPIN_CANDIDATE_REF_PATTERN: RegExp;
4
+ export declare const ZHIPIN_RECOMMEND_JOB_REF_PATTERN: RegExp;
5
+ export interface ZhipinCandidateRefSource {
6
+ readonly index: number;
7
+ readonly candidateId: string;
8
+ readonly name?: string;
9
+ }
10
+ export interface ZhipinCandidateRefTarget extends ZhipinCandidateRefSource {
11
+ readonly candidateRef: string;
12
+ }
13
+ export interface ZhipinRecommendJobRefSource {
14
+ readonly index: number;
15
+ readonly value: string;
16
+ readonly label: string;
17
+ readonly isCurrent: boolean;
18
+ }
19
+ export interface ZhipinRecommendJobRefTarget extends ZhipinRecommendJobRefSource {
20
+ readonly jobRef: string;
21
+ }
22
+ export declare function buildZhipinCandidateRef(index: number): string;
23
+ export declare function buildZhipinRecommendJobRef(index: number): string;
24
+ export declare function parseZhipinCandidateRef(ref: string): number | undefined;
25
+ export declare function parseZhipinRecommendJobRef(ref: string): number | undefined;
26
+ export declare function resolveZhipinCandidateIndex(input: {
27
+ readonly index?: number | undefined;
28
+ readonly candidateRef?: string | undefined;
29
+ }): number;
30
+ export declare function resolveZhipinCandidateIndices(input: {
31
+ readonly indices?: readonly number[] | undefined;
32
+ readonly candidateRefs?: readonly string[] | undefined;
33
+ }): number[];
34
+ export declare function rememberZhipinCandidateRefs(candidates: readonly ZhipinCandidateRefSource[]): ZhipinCandidateRefTarget[];
35
+ export declare function rememberZhipinRecommendJobRefs(jobs: readonly ZhipinRecommendJobRefSource[]): ZhipinRecommendJobRefTarget[];
36
+ export declare function clearZhipinCandidateRefsForTests(): void;
37
+ export declare function clearZhipinRecommendJobRefsForTests(): void;
38
+ export declare function resolveZhipinCandidateRefTarget(candidateRef: string): ZhipinCandidateRefTarget;
39
+ export declare function resolveZhipinRecommendJobRefTarget(jobRef: string): ZhipinRecommendJobRefTarget;
40
+ export declare function resolveZhipinCandidateTargets(input: {
41
+ readonly indices?: readonly number[] | undefined;
42
+ readonly candidateRefs?: readonly string[] | undefined;
43
+ }): ZhipinCandidateRefTarget[];
44
+ export declare function isZhipinCandidateTargetCurrent(target: ZhipinCandidateRefTarget, current: {
45
+ readonly found?: boolean;
46
+ readonly candidateId: string;
47
+ readonly name?: string;
48
+ }): boolean;
@@ -0,0 +1,41 @@
1
+ import type { AgentLogger } from "@roll-agent/sdk";
2
+ export declare const RECRUITMENT_EVENT_TYPES: readonly ["message_received", "message_sent", "candidate_contacted", "wechat_exchanged"];
3
+ type RecruitmentEventType = (typeof RECRUITMENT_EVENT_TYPES)[number];
4
+ type RecruitmentCandidate = {
5
+ readonly name: string;
6
+ readonly position: string;
7
+ readonly age?: string;
8
+ readonly gender?: string;
9
+ readonly education?: string;
10
+ readonly expectedSalary?: string;
11
+ readonly expectedLocation?: string;
12
+ };
13
+ type RecruitmentJob = {
14
+ readonly jobId?: number;
15
+ readonly jobName?: string;
16
+ };
17
+ type RecruitmentEventDetails = Readonly<Record<string, string | number | boolean | undefined>>;
18
+ export type RecruitmentEventPayload = {
19
+ readonly idempotencyKey: string;
20
+ readonly agentId: string;
21
+ readonly sourcePlatform: "zhipin";
22
+ readonly dataSource: "api_callback";
23
+ readonly eventType: RecruitmentEventType;
24
+ readonly eventTime: string;
25
+ readonly candidate: RecruitmentCandidate;
26
+ readonly job?: RecruitmentJob;
27
+ readonly details: RecruitmentEventDetails;
28
+ };
29
+ export type RecruitmentEventDraft = Omit<RecruitmentEventPayload, "agentId" | "eventTime"> & {
30
+ readonly eventTime?: string;
31
+ };
32
+ type RecruitmentEventPostDeps = {
33
+ readonly fetch: typeof fetch;
34
+ readonly env: NodeJS.ProcessEnv;
35
+ };
36
+ type RecruitmentEventRecorder = (event: RecruitmentEventDraft, logger: AgentLogger) => Promise<void> | void;
37
+ export declare function buildRecruitmentIdempotencyKey(prefix: string, parts: ReadonlyArray<string | number | boolean | undefined>): string;
38
+ export declare function recordRecruitmentEventAsync(event: RecruitmentEventDraft, logger: AgentLogger): void;
39
+ export declare function setRecruitmentEventRecorderForTests(recorder: RecruitmentEventRecorder | undefined): void;
40
+ export declare function setRecruitmentEventPostDepsForTests(deps: Partial<RecruitmentEventPostDeps> | undefined): void;
41
+ export {};
@@ -0,0 +1,34 @@
1
+ import type { AgentLogger } from "@roll-agent/sdk";
2
+ import type { NativeCandidateChatDetails, NativeRecommendGreetResult } from "../pages/zhipin/native-page.ts";
3
+ type ZhipinUnreadCandidate = {
4
+ readonly conversationId: string;
5
+ readonly candidateId: string;
6
+ readonly name: string;
7
+ readonly position: string;
8
+ readonly preview: string;
9
+ readonly unreadCount: number;
10
+ readonly hasUnread: boolean;
11
+ };
12
+ type ZhipinMessageSentInput = {
13
+ readonly conversationId: string;
14
+ readonly candidateId: string;
15
+ readonly replyId: string;
16
+ readonly candidateName: string;
17
+ readonly message: string;
18
+ readonly unreadCountBeforeReply: number;
19
+ readonly candidateDetails?: NativeCandidateChatDetails;
20
+ };
21
+ type ZhipinWechatRequestedInput = {
22
+ readonly conversationId: string;
23
+ readonly candidateId: string;
24
+ readonly candidateName: string;
25
+ readonly exchangeType?: "requested" | "accepted";
26
+ readonly wechatNumber?: string;
27
+ readonly candidateDetails?: NativeCandidateChatDetails;
28
+ };
29
+ export declare function recordZhipinMessageReceivedEvents(candidates: ReadonlyArray<ZhipinUnreadCandidate>, logger: AgentLogger): void;
30
+ export declare function recordZhipinMessageSentEvent(input: ZhipinMessageSentInput, logger: AgentLogger): void;
31
+ export declare function recordZhipinCandidateContactedEvent(result: NativeRecommendGreetResult, logger: AgentLogger): void;
32
+ export declare function recordZhipinWechatRequestedEvent(input: ZhipinWechatRequestedInput, logger: AgentLogger): void;
33
+ export declare function recordZhipinWechatCompletedEvents(details: NativeCandidateChatDetails, conversationId: string, candidateId: string, logger: AgentLogger): void;
34
+ export {};
@@ -1,13 +1,14 @@
1
- import { findTrackedPlatformPage } from "../pages/platform-page.ts";
2
- import { getContextManager } from "../runtime-holder.ts";
1
+ import { setTimeout as delay } from "node:timers/promises";
2
+ import { getContextManager, getRuntime } from "../runtime-holder.ts";
3
3
  import { detectPlatformFromUrl, matchesPlatformHost } from "../platforms.ts";
4
- import { toAttachedPageInfo } from "../page-info.ts";
4
+ import { toNativePageInfo } from "../page-info.ts";
5
5
  type NavigateActiveTabDeps = {
6
6
  readonly getContextManager: typeof getContextManager;
7
+ readonly getRuntime: typeof getRuntime;
7
8
  readonly detectPlatformFromUrl: typeof detectPlatformFromUrl;
8
9
  readonly matchesPlatformHost: typeof matchesPlatformHost;
9
- readonly findTrackedPlatformPage: typeof findTrackedPlatformPage;
10
- readonly toAttachedPageInfo: typeof toAttachedPageInfo;
10
+ readonly toNativePageInfo: typeof toNativePageInfo;
11
+ readonly delay: typeof delay;
11
12
  };
12
13
  export declare function setNavigateActiveTabDepsForTests(override: Partial<NavigateActiveTabDeps> | undefined): void;
13
14
  export declare const navigateActiveTab: import("@roll-agent/sdk").ToolDefinition<{
@@ -28,6 +28,7 @@ export declare const zhipinGetCandidateList: import("@roll-agent/sdk").ToolDefin
28
28
  expectedSalary: string;
29
29
  tags: string[];
30
30
  buttonText: string;
31
+ candidateRef: string;
31
32
  }[];
32
33
  total: number;
33
34
  error?: string | undefined;
@@ -0,0 +1,31 @@
1
+ import { NativeVisualActivitySession } from "../native-visual-activity-session.ts";
2
+ import { openZhipinNativePagePort } from "../pages/zhipin/native-page.ts";
3
+ import type { ZhipinNativePagePort } from "../pages/zhipin/native-page.ts";
4
+ type NativeVisualActivitySessionLike = Pick<NativeVisualActivitySession, "begin" | "highlightSelector" | "previewMouseMotion" | "succeed" | "fail">;
5
+ type ZhipinListRecommendJobsDeps = {
6
+ readonly openNativePagePort: typeof openZhipinNativePagePort;
7
+ readonly createNativeVisualActivitySession: (page: ZhipinNativePagePort) => NativeVisualActivitySessionLike;
8
+ };
9
+ export declare function setZhipinListRecommendJobsDepsForTests(override: Partial<ZhipinListRecommendJobsDeps> | undefined): void;
10
+ export declare const zhipinListRecommendJobs: import("@roll-agent/sdk").ToolDefinition<{}, {
11
+ status: "recommend_not_ready" | "selector_not_found" | "listed";
12
+ success: boolean;
13
+ availableCount: number;
14
+ canSwitch: boolean;
15
+ jobs: {
16
+ value: string;
17
+ index: number;
18
+ label: string;
19
+ isCurrent: boolean;
20
+ jobRef?: string | undefined;
21
+ }[];
22
+ error?: string | undefined;
23
+ current?: {
24
+ value: string;
25
+ index: number;
26
+ label: string;
27
+ isCurrent: boolean;
28
+ jobRef?: string | undefined;
29
+ } | undefined;
30
+ }>;
31
+ export {};
@@ -1,8 +1,10 @@
1
1
  export declare const zhipinOpenResume: import("@roll-agent/sdk").ToolDefinition<{
2
- index: number;
2
+ index?: number | undefined;
3
+ candidateRef?: string | undefined;
3
4
  }, {
4
5
  success: boolean;
5
6
  candidateId: string;
6
7
  candidateName: string;
7
8
  error?: string | undefined;
9
+ candidateRef?: string | undefined;
8
10
  }>;
@@ -9,7 +9,8 @@ type ZhipinSayHelloDeps = {
9
9
  };
10
10
  export declare function setZhipinSayHelloDepsForTests(override: Partial<ZhipinSayHelloDeps> | undefined): void;
11
11
  export declare const zhipinSayHello: import("@roll-agent/sdk").ToolDefinition<{
12
- indices: number[];
12
+ indices?: number[] | undefined;
13
+ candidateRefs?: string[] | undefined;
13
14
  }, {
14
15
  success: boolean;
15
16
  results: {
@@ -17,6 +18,7 @@ export declare const zhipinSayHello: import("@roll-agent/sdk").ToolDefinition<{
17
18
  success: boolean;
18
19
  candidateId: string;
19
20
  candidateName: string;
21
+ candidateRef: string;
20
22
  error?: string | undefined;
21
23
  }[];
22
24
  summary: {
@@ -9,10 +9,12 @@ type ZhipinSelectRecommendJobDeps = {
9
9
  export declare function setZhipinSelectRecommendJobDepsForTests(override: Partial<ZhipinSelectRecommendJobDeps> | undefined): void;
10
10
  export declare const zhipinSelectRecommendJob: import("@roll-agent/sdk").ToolDefinition<{
11
11
  index?: number | undefined;
12
+ jobRef?: string | undefined;
12
13
  jobValue?: string | undefined;
13
14
  jobName?: string | undefined;
14
15
  searchKeyword?: string | undefined;
15
16
  useSearch?: boolean | undefined;
17
+ forceClick?: boolean | undefined;
16
18
  }, {
17
19
  options: {
18
20
  value: string;
@@ -24,10 +26,12 @@ export declare const zhipinSelectRecommendJob: import("@roll-agent/sdk").ToolDef
24
26
  success: boolean;
25
27
  requested: {
26
28
  index?: number | undefined;
29
+ jobRef?: string | undefined;
27
30
  jobValue?: string | undefined;
28
31
  jobName?: string | undefined;
29
32
  searchKeyword?: string | undefined;
30
33
  useSearch?: boolean | undefined;
34
+ forceClick?: boolean | undefined;
31
35
  };
32
36
  matchedCount: number;
33
37
  error?: string | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roll-agent/browser-use-agent",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "zod": "^3.25.76",
48
- "@roll-agent/browser": "0.3.0",
48
+ "@roll-agent/browser": "0.3.1",
49
49
  "@roll-agent/sdk": "0.1.6"
50
50
  },
51
51
  "devDependencies": {
@@ -2,7 +2,21 @@ required:
2
2
  - name: REPLY_AUTHORITY_KEYS_URL
3
3
  purpose: Reply Authority Service 公钥分发端点;`zhipin_send_reply` 启动预热和本地 Ed25519 验签都依赖它
4
4
  example: https://reply-authority.duliday.com/.well-known/reply-authority-keys
5
+ - name: RECRUITMENT_EVENTS_DEFAULT_AGENT_ID
6
+ purpose: 单 browser-use 实例 / 单 Boss 账号部署下的默认招聘业务 Agent ID;招聘事件默认启用,上报时必须用它做归因
7
+ example: zhipin-shanghai-kfc-001
8
+ - name: RECRUITMENT_EVENTS_API_TOKEN
9
+ purpose: 招聘事件 Open API Bearer token;公开 npm 包不内置兜底 token,必须由部署环境提供
10
+ example: token_xxx
5
11
  optional:
12
+ - name: RECRUITMENT_EVENTS_ENABLED
13
+ purpose: 是否启用招聘事件埋点上报;未设置时默认启用,设为 `"false"` 可关闭
14
+ default: "true"
15
+ example: "false"
16
+ - name: RECRUITMENT_EVENTS_API_BASE_URL
17
+ purpose: 招聘事件 Open API base URL;工具成功后会调用 `/api/v1/recruitment-events`
18
+ default: https://huajune.duliday.com
19
+ example: https://example.com
6
20
  - name: BROWSER_VISUAL_CURSOR
7
21
  purpose: 在可见浏览器页面内显示 browser-use 的页内虚拟指针和点击波纹;默认开启,设为 `"false"` 可关闭
8
22
  default: "true"
@@ -4,6 +4,17 @@
4
4
 
5
5
  `conversationId` / `candidateId` 是跨 tool 的稳定主键,`index` 只表示当前 DOM 快照。
6
6
 
7
+ ## BOSS 页面入口
8
+
9
+ `navigate_active_tab(url)` 是通用 native CDP 导航工具,但不能直接跳转 BOSS `/web/chat/*` 后台路径。
10
+
11
+ 页面切换规则:
12
+
13
+ 1. 进入聊天页:调用 `zhipin_open_chat_page()`,由工具点击站内「沟通」导航。
14
+ 2. 进入推荐牛人页:调用 `zhipin_open_recommend_page()`,由工具点击站内「推荐牛人」导航。
15
+ 3. 如果页面没有可用 BOSS 后台 target,不要用 `navigate_active_tab("https://www.zhipin.com/web/chat/recommend")` 补救;让用户先恢复登录后的 BOSS 页面或调用 `open_platform("zhipin")` 进入平台主页。
16
+ 4. `navigate_active_tab` 不触发 Playwright attach,也不调用 `Runtime.enable`,但它不替代 BOSS 业务语义导航工具。
17
+
7
18
  错误做法:
8
19
 
9
20
  - 把 `zhipin_read_messages` 返回数组里的 `index` 当作后续点击主键。
@@ -25,19 +36,50 @@
25
36
 
26
37
  优先级:
27
38
 
28
- 1. 已知岗位 `value` 时,调用 `zhipin_select_recommend_job({ jobValue })`。
29
- 2. 只知道标题时,调用 `zhipin_select_recommend_job({ jobName })`;工具会先匹配当前下拉项,未命中再使用下拉搜索框。
30
- 3. `index` 只表示当前下拉快照顺序,筛选、搜索或岗位列表刷新后必须重新读取/选择,不要跨步骤长期保存。
39
+ 1. 先调用 `zhipin_list_recommend_jobs()` 读取当前岗位下拉,只读不切换。
40
+ 2. 如果 `canSwitch:false`,当前账号/页面没有可切换岗位,跳过岗位切换。
41
+ 3. 已知 `jobRef` 时,调用 `zhipin_select_recommend_job({ jobRef })`。
42
+ 4. 已知岗位 `value` 时,调用 `zhipin_select_recommend_job({ jobValue })`。
43
+ 5. 只知道标题时,调用 `zhipin_select_recommend_job({ jobName })`;工具会先匹配当前下拉项,未命中再使用下拉搜索框。
44
+ 6. `index` 只表示当前下拉快照顺序,筛选、搜索或岗位列表刷新后必须重新读取/选择,不要跨步骤长期保存。
45
+
46
+ `jobRef` 规则:
47
+
48
+ - `jobRef` 格式如 `@j1`,只来自 `zhipin_list_recommend_jobs()` 输出。
49
+ - `jobRef` 只对最近一次岗位下拉快照有效;筛选、搜索、刷新或页面重开后必须重新读取。
50
+ - orchestrator 不要自行构造 `jobRef`。
51
+ - `jobRef` 不是安全边界,只是降低编排认知负担;真实 DOM 点击仍由工具解析到 `value` / `index` 后执行。
52
+ - 默认不要传 `forceClick:true`;只有需要重新点击已选中的岗位项时才使用。
31
53
 
32
54
  推荐链路:
33
55
 
34
56
  ```text
35
57
  zhipin_open_recommend_page()
36
- -> zhipin_select_recommend_job({ jobValue | jobName })
37
- -> zhipin_filter_recommend_candidates(...)
58
+ -> zhipin_list_recommend_jobs()
59
+ -> zhipin_select_recommend_job({ jobRef | jobValue | jobName }) # 可选;canSwitch=false 时跳过
60
+ -> zhipin_filter_recommend_candidates(...) # 可选;requires_vip 时跳过
38
61
  -> zhipin_get_candidate_list(...)
39
62
  ```
40
63
 
64
+ ## 推荐候选人筛选
65
+
66
+ `zhipin_filter_recommend_candidates(ageMin?, ageMax?, gender?, activity?)` 只处理年龄、性别、活跃度三个维度。
67
+
68
+ 返回状态处理:
69
+
70
+ | `status` | 含义 | 编排动作 |
71
+ | --- | --- | --- |
72
+ | `applied` | 筛选已提交 | 继续 `zhipin_get_candidate_list` |
73
+ | `recommend_not_ready` | 推荐页未就绪 | 先调用 `zhipin_open_recommend_page()` |
74
+ | `requires_vip` | 当前账号没有权限使用该筛选,页面触发 VIP 弹窗 | 不要反复尝试绕过;跳过筛选或调整业务策略 |
75
+ | `error` | 操作失败 | 读取 `error` 后决定是否重试 |
76
+
77
+ 实操规则:
78
+
79
+ 1. `requires_vip` 是硬边界,不是暂态加载失败。
80
+ 2. 如果筛选不可用,直接读取当前推荐列表,再由 orchestrator 按 `age`、`buttonText`、岗位信号等字段做结果过滤。
81
+ 3. 列表字段过滤不能替代平台筛选的完整语义;它只用于降低明显不合适目标的后续操作风险。
82
+
41
83
  ## 动态列表
42
84
 
43
85
  BOSS 页面通常不是整页滚动,而是内部容器滚动:
@@ -115,8 +157,25 @@ recommend-list -> 推荐牛人列表,默认向下滚动,去重主键 candid
115
157
  ## 推荐候选人链路
116
158
 
117
159
  1. `zhipin_open_recommend_page()`。
118
- 2. `zhipin_filter_recommend_candidates(ageMin?, ageMax?, gender?, activity?)`。
160
+ 2. `zhipin_filter_recommend_candidates(ageMin?, ageMax?, gender?, activity?)`(可选;`requires_vip` 时跳过)。
119
161
  3. `zhipin_get_candidate_list(maxResults?, autoScroll=true, maxScrolls=4)`。
120
- 4. `zhipin_say_hello(indices)`。
162
+ 4. orchestrator 过滤 `buttonText` / 年龄 / 业务资格。
163
+ 5. `zhipin_say_hello(candidateRefs)`。
164
+
165
+ `zhipin_get_candidate_list` 会给每个候选人返回 `candidateRef`,例如 `@c1`。
166
+
167
+ 优先级:
168
+
169
+ 1. 上层 orchestrator 优先把 `candidateRef` 传给 `zhipin_say_hello({ candidateRefs })` 或 `zhipin_open_resume({ candidateRef })`。
170
+ 2. `indices` / `index` 只作为当前 DOM 快照兜底。
171
+ 3. 筛选、滚动加载、搜索、刷新或页面重开后必须重新调用 `zhipin_get_candidate_list`,不要复用旧 `candidateRef`。
172
+ 4. 不要由 orchestrator 自己构造 `@c1`;只使用 tool 输出中的 `candidateRef`。
173
+ 5. 聊天消息列表没有 `candidateRef`;聊天回复链路继续使用 `conversationId` / `candidateId`。
174
+ 6. 调 `zhipin_say_hello` 前先过滤 `buttonText:"打招呼"`;`buttonText` 为空通常表示已打过招呼,不应重复点击。
175
+ 7. 如果业务有年龄、资格或岗位匹配约束,先按 `age` / `expectedPosition` / `tags` 等字段过滤,不要把所有 `candidateRefs` 盲目提交。
176
+
177
+ 失效保护:
121
178
 
122
- `zhipin_say_hello(indices)` `indices` 也是当前 DOM 快照索引;筛选、滚动或列表刷新后必须重新读取列表。
179
+ - 如果 `candidateRef` 对应的 `candidateId` / `name` 与当前 DOM 不一致,工具会返回 `success:false` 并提示“候选人引用已过期”。
180
+ - 收到过期提示后,重新执行推荐候选人链路的第 3 步,再提交新的 `candidateRefs`。
181
+ - 同一快照内可以一次提交多个 `candidateRefs` 连续打招呼;如果 BOSS 在点击后重排列表,工具会拒绝过期 ref,orchestrator 应刷新列表后只重试剩余目标。