@reconcrap/boss-recommend-mcp 1.3.27 → 1.3.28
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.
- package/package.json +1 -1
- package/src/test-boss-chat.js +145 -3
- package/vendor/boss-chat-cli/src/app.js +134 -2
package/package.json
CHANGED
package/src/test-boss-chat.js
CHANGED
|
@@ -1089,6 +1089,16 @@ async function testBossChatAppShouldResetPrimaryChatLabelBeforeInitialPrime() {
|
|
|
1089
1089
|
artifactRootDir: os.tmpdir(),
|
|
1090
1090
|
resumeOpenCooldownMs: 0,
|
|
1091
1091
|
});
|
|
1092
|
+
app.waitForCandidateList = async ({ reason } = {}) => {
|
|
1093
|
+
calls.push(`waitForCandidateList:${reason || "unknown"}`);
|
|
1094
|
+
return {
|
|
1095
|
+
ready: true,
|
|
1096
|
+
waitedMs: 0,
|
|
1097
|
+
attempts: 1,
|
|
1098
|
+
listItemCount: 1,
|
|
1099
|
+
lastError: "",
|
|
1100
|
+
};
|
|
1101
|
+
};
|
|
1092
1102
|
app.processCustomer = async (_customer, _profile, _runId, options = {}) => {
|
|
1093
1103
|
calls.push(`processCustomer:${options.skipCardClick === true ? "skip" : "click"}`);
|
|
1094
1104
|
return {
|
|
@@ -1110,13 +1120,13 @@ async function testBossChatAppShouldResetPrimaryChatLabelBeforeInitialPrime() {
|
|
|
1110
1120
|
llm: { model: "gpt-test" },
|
|
1111
1121
|
});
|
|
1112
1122
|
|
|
1113
|
-
assert.deepEqual(calls.slice(0,
|
|
1123
|
+
assert.deepEqual(calls.slice(0, 4), [
|
|
1114
1124
|
"ensureReady",
|
|
1115
1125
|
"activatePrimaryChatLabel:全部",
|
|
1116
1126
|
"selectJob:算法工程师",
|
|
1117
1127
|
"activateUnreadFilter",
|
|
1118
|
-
"primeConversationByFirstCandidate:1",
|
|
1119
1128
|
]);
|
|
1129
|
+
assert.equal(calls.includes("primeConversationByFirstCandidate:1"), true);
|
|
1120
1130
|
assert.equal(calls.includes("processCustomer:skip"), true);
|
|
1121
1131
|
assert.equal(summary.inspected, 1);
|
|
1122
1132
|
assert.equal(summary.skipped, 1);
|
|
@@ -1209,6 +1219,22 @@ async function testBossChatAppShouldRestoreListContextAfterRecovery() {
|
|
|
1209
1219
|
artifactRootDir: os.tmpdir(),
|
|
1210
1220
|
resumeOpenCooldownMs: 0,
|
|
1211
1221
|
});
|
|
1222
|
+
app.waitForCandidateList = async ({ reason } = {}) => {
|
|
1223
|
+
calls.push(`waitForCandidateList:${reason || "unknown"}`);
|
|
1224
|
+
return {
|
|
1225
|
+
ready:
|
|
1226
|
+
reason === "initial-context-restore" ||
|
|
1227
|
+
reason === "post-recovery-context-restore",
|
|
1228
|
+
waitedMs: 0,
|
|
1229
|
+
attempts: 1,
|
|
1230
|
+
listItemCount:
|
|
1231
|
+
reason === "initial-context-restore" ||
|
|
1232
|
+
reason === "post-recovery-context-restore"
|
|
1233
|
+
? 1
|
|
1234
|
+
: 0,
|
|
1235
|
+
lastError: "",
|
|
1236
|
+
};
|
|
1237
|
+
};
|
|
1212
1238
|
app.processCustomer = async (_customer, _profile, _runId, options = {}) => {
|
|
1213
1239
|
calls.push(`processCustomer:${options.skipCardClick === true ? "skip" : "click"}`);
|
|
1214
1240
|
return {
|
|
@@ -1236,12 +1262,127 @@ async function testBossChatAppShouldRestoreListContextAfterRecovery() {
|
|
|
1236
1262
|
assert.equal(calls[recoverIndex + 1], "activatePrimaryChatLabel:全部");
|
|
1237
1263
|
assert.equal(calls[recoverIndex + 2], "selectJob:算法工程师");
|
|
1238
1264
|
assert.equal(calls[recoverIndex + 3], "activateUnreadFilter");
|
|
1239
|
-
assert.equal(calls[recoverIndex + 4], "
|
|
1265
|
+
assert.equal(calls[recoverIndex + 4], "waitForCandidateList:post-recovery-context-restore");
|
|
1266
|
+
assert.equal(calls[recoverIndex + 5], "primeConversationByFirstCandidate:2");
|
|
1240
1267
|
assert.equal(calls.includes("processCustomer:skip"), true);
|
|
1241
1268
|
assert.equal(summary.inspected, 1);
|
|
1242
1269
|
assert.equal(summary.skipped, 1);
|
|
1243
1270
|
}
|
|
1244
1271
|
|
|
1272
|
+
async function testBossChatAppShouldWaitForCandidateListBeforePriming() {
|
|
1273
|
+
const calls = [];
|
|
1274
|
+
let pageStateCall = 0;
|
|
1275
|
+
const page = {
|
|
1276
|
+
async ensureReady() {
|
|
1277
|
+
calls.push("ensureReady");
|
|
1278
|
+
return { hasListContainer: false, listItemCount: 0 };
|
|
1279
|
+
},
|
|
1280
|
+
async activatePrimaryChatLabel(label) {
|
|
1281
|
+
calls.push(`activatePrimaryChatLabel:${label}`);
|
|
1282
|
+
return { changed: false, verified: true, activeLabel: label };
|
|
1283
|
+
},
|
|
1284
|
+
async selectJob(jobSelection) {
|
|
1285
|
+
calls.push(`selectJob:${jobSelection.label}`);
|
|
1286
|
+
return jobSelection;
|
|
1287
|
+
},
|
|
1288
|
+
async activateUnreadFilter() {
|
|
1289
|
+
calls.push("activateUnreadFilter");
|
|
1290
|
+
return { changed: true, verified: true, activeLabel: "未读" };
|
|
1291
|
+
},
|
|
1292
|
+
async getPageState() {
|
|
1293
|
+
pageStateCall += 1;
|
|
1294
|
+
calls.push(`getPageState:${pageStateCall}`);
|
|
1295
|
+
return {
|
|
1296
|
+
href: "https://www.zhipin.com/web/chat/index",
|
|
1297
|
+
readyState: "complete",
|
|
1298
|
+
hasListContainer: pageStateCall >= 3,
|
|
1299
|
+
listItemCount: pageStateCall >= 3 ? 2 : 0,
|
|
1300
|
+
};
|
|
1301
|
+
},
|
|
1302
|
+
async primeConversationByFirstCandidate() {
|
|
1303
|
+
calls.push("primeConversationByFirstCandidate:1");
|
|
1304
|
+
return {
|
|
1305
|
+
candidate: {
|
|
1306
|
+
customerId: "1003",
|
|
1307
|
+
name: "候选人C",
|
|
1308
|
+
sourceJob: "算法工程师",
|
|
1309
|
+
domIndex: 0,
|
|
1310
|
+
},
|
|
1311
|
+
totalVisibleCandidates: 2,
|
|
1312
|
+
readyState: {
|
|
1313
|
+
hasOnlineResume: true,
|
|
1314
|
+
hasAskResume: true,
|
|
1315
|
+
hasAttachmentResume: false,
|
|
1316
|
+
},
|
|
1317
|
+
};
|
|
1318
|
+
},
|
|
1319
|
+
async getLoadedCustomers() {
|
|
1320
|
+
calls.push("getLoadedCustomers:1");
|
|
1321
|
+
return [];
|
|
1322
|
+
},
|
|
1323
|
+
async closeResumeModalDomOnce() {
|
|
1324
|
+
return {
|
|
1325
|
+
closed: true,
|
|
1326
|
+
method: "already-closed",
|
|
1327
|
+
finalState: { scopeCount: 0, iframeCount: 0, closeCount: 0, topScopeClass: "" },
|
|
1328
|
+
};
|
|
1329
|
+
},
|
|
1330
|
+
};
|
|
1331
|
+
const stateStore = {
|
|
1332
|
+
async load() {},
|
|
1333
|
+
hasAny() {
|
|
1334
|
+
return false;
|
|
1335
|
+
},
|
|
1336
|
+
async record() {},
|
|
1337
|
+
};
|
|
1338
|
+
const app = new BossChatApp({
|
|
1339
|
+
page,
|
|
1340
|
+
llmClient: {},
|
|
1341
|
+
interaction: {
|
|
1342
|
+
async sleepRange() {},
|
|
1343
|
+
async maybeRest() {},
|
|
1344
|
+
},
|
|
1345
|
+
resumeCaptureService: {},
|
|
1346
|
+
stateStore,
|
|
1347
|
+
reportStore: {
|
|
1348
|
+
async write() {
|
|
1349
|
+
return "report.json";
|
|
1350
|
+
},
|
|
1351
|
+
},
|
|
1352
|
+
logger: { log() {} },
|
|
1353
|
+
dryRun: true,
|
|
1354
|
+
artifactRootDir: os.tmpdir(),
|
|
1355
|
+
resumeOpenCooldownMs: 0,
|
|
1356
|
+
});
|
|
1357
|
+
app.processCustomer = async (_customer, _profile, _runId, options = {}) => {
|
|
1358
|
+
calls.push(`processCustomer:${options.skipCardClick === true ? "skip" : "click"}`);
|
|
1359
|
+
return {
|
|
1360
|
+
name: "候选人C",
|
|
1361
|
+
passed: false,
|
|
1362
|
+
requested: false,
|
|
1363
|
+
reason: "skip",
|
|
1364
|
+
error: "",
|
|
1365
|
+
artifacts: {},
|
|
1366
|
+
};
|
|
1367
|
+
};
|
|
1368
|
+
|
|
1369
|
+
const summary = await app.run({
|
|
1370
|
+
screeningCriteria: "有 AI 项目经验",
|
|
1371
|
+
targetCount: 1,
|
|
1372
|
+
startFrom: "unread",
|
|
1373
|
+
jobSelection: { label: "算法工程师", value: "job-1" },
|
|
1374
|
+
chrome: { port: 9222 },
|
|
1375
|
+
llm: { model: "gpt-test" },
|
|
1376
|
+
});
|
|
1377
|
+
|
|
1378
|
+
const primeIndex = calls.indexOf("primeConversationByFirstCandidate:1");
|
|
1379
|
+
const thirdStateIndex = calls.indexOf("getPageState:3");
|
|
1380
|
+
assert.equal(thirdStateIndex >= 0, true);
|
|
1381
|
+
assert.equal(primeIndex > thirdStateIndex, true);
|
|
1382
|
+
assert.equal(summary.inspected, 1);
|
|
1383
|
+
assert.equal(summary.skipped, 1);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1245
1386
|
async function testBossChatAppShouldPersistEvidenceArtifacts() {
|
|
1246
1387
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-chat-artifacts-"));
|
|
1247
1388
|
await mkdir(tempDir, { recursive: true });
|
|
@@ -1379,6 +1520,7 @@ async function main() {
|
|
|
1379
1520
|
await testBossChatLlmShouldApplyThinkingDefaultsAndOverrides();
|
|
1380
1521
|
await testBossChatAppShouldResetPrimaryChatLabelBeforeInitialPrime();
|
|
1381
1522
|
await testBossChatAppShouldRestoreListContextAfterRecovery();
|
|
1523
|
+
await testBossChatAppShouldWaitForCandidateListBeforePriming();
|
|
1382
1524
|
await testBossChatAppShouldPersistEvidenceArtifacts();
|
|
1383
1525
|
console.log("boss-chat tests passed");
|
|
1384
1526
|
}
|
|
@@ -64,6 +64,9 @@ function hasResumeRequestSentMessage(state = {}) {
|
|
|
64
64
|
return recent.some((item) => normalizeText(item).includes('简历请求已发送'));
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
const CANDIDATE_LIST_WAIT_AFTER_CONTEXT_MS = 5000;
|
|
68
|
+
const CANDIDATE_LIST_WAIT_POLL_MS = 500;
|
|
69
|
+
|
|
67
70
|
export class BossChatApp {
|
|
68
71
|
constructor({
|
|
69
72
|
page,
|
|
@@ -152,6 +155,65 @@ export class BossChatApp {
|
|
|
152
155
|
: this.page.activateUnreadFilter();
|
|
153
156
|
}
|
|
154
157
|
|
|
158
|
+
async waitForCandidateList({
|
|
159
|
+
reason = 'unknown',
|
|
160
|
+
maxWaitMs = CANDIDATE_LIST_WAIT_AFTER_CONTEXT_MS,
|
|
161
|
+
pollMs = CANDIDATE_LIST_WAIT_POLL_MS,
|
|
162
|
+
} = {}) {
|
|
163
|
+
const startedAt = Date.now();
|
|
164
|
+
let attempts = 0;
|
|
165
|
+
let lastState = null;
|
|
166
|
+
let lastError = '';
|
|
167
|
+
|
|
168
|
+
while (Date.now() - startedAt <= maxWaitMs) {
|
|
169
|
+
attempts += 1;
|
|
170
|
+
try {
|
|
171
|
+
if (typeof this.page.getPageState === 'function') {
|
|
172
|
+
lastState = await this.page.getPageState();
|
|
173
|
+
if (Number(lastState?.listItemCount || 0) > 0) {
|
|
174
|
+
return {
|
|
175
|
+
ready: true,
|
|
176
|
+
waitedMs: Date.now() - startedAt,
|
|
177
|
+
attempts,
|
|
178
|
+
listItemCount: Number(lastState?.listItemCount || 0),
|
|
179
|
+
lastState,
|
|
180
|
+
lastError,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
} else if (typeof this.page.getLoadedCustomers === 'function') {
|
|
184
|
+
const customers = await this.page.getLoadedCustomers();
|
|
185
|
+
if (Array.isArray(customers) && customers.length > 0) {
|
|
186
|
+
return {
|
|
187
|
+
ready: true,
|
|
188
|
+
waitedMs: Date.now() - startedAt,
|
|
189
|
+
attempts,
|
|
190
|
+
listItemCount: customers.length,
|
|
191
|
+
lastState,
|
|
192
|
+
lastError,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
} catch (error) {
|
|
197
|
+
lastError = String(error?.message || error || '');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (Date.now() - startedAt >= maxWaitMs) {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
ready: false,
|
|
208
|
+
waitedMs: Date.now() - startedAt,
|
|
209
|
+
attempts,
|
|
210
|
+
listItemCount: Number(lastState?.listItemCount || 0),
|
|
211
|
+
lastState,
|
|
212
|
+
lastError,
|
|
213
|
+
reason,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
155
217
|
async run(profile) {
|
|
156
218
|
const startedAt = new Date().toISOString();
|
|
157
219
|
const runId = runToken(new Date());
|
|
@@ -168,8 +230,34 @@ export class BossChatApp {
|
|
|
168
230
|
} catch (error) {
|
|
169
231
|
this.logger.log(`页面就绪检查告警:${error?.message || error},将继续执行预热恢复流程。`);
|
|
170
232
|
}
|
|
171
|
-
|
|
233
|
+
let filterResult = await this.restoreListContext(profile);
|
|
172
234
|
await this.interaction.sleepRange(420, 160);
|
|
235
|
+
let initialListWait = await this.waitForCandidateList({
|
|
236
|
+
reason: 'initial-context-restore',
|
|
237
|
+
});
|
|
238
|
+
if (initialListWait.ready) {
|
|
239
|
+
this.logger.log(
|
|
240
|
+
`候选人列表已就绪:reason=initial-context-restore | waited=${initialListWait.waitedMs}ms | attempts=${initialListWait.attempts} | count=${initialListWait.listItemCount}`,
|
|
241
|
+
);
|
|
242
|
+
} else {
|
|
243
|
+
this.logger.log(
|
|
244
|
+
`候选人列表等待超时:reason=initial-context-restore | waited=${initialListWait.waitedMs}ms | attempts=${initialListWait.attempts} | count=${initialListWait.listItemCount} | lastError=${initialListWait.lastError || 'n/a'},继续尝试预热。`,
|
|
245
|
+
);
|
|
246
|
+
filterResult = await this.restoreListContext(profile);
|
|
247
|
+
await this.interaction.sleepRange(420, 160);
|
|
248
|
+
initialListWait = await this.waitForCandidateList({
|
|
249
|
+
reason: 'initial-context-restore-reapply',
|
|
250
|
+
});
|
|
251
|
+
if (initialListWait.ready) {
|
|
252
|
+
this.logger.log(
|
|
253
|
+
`候选人列表二次恢复成功:reason=initial-context-restore-reapply | waited=${initialListWait.waitedMs}ms | attempts=${initialListWait.attempts} | count=${initialListWait.listItemCount}`,
|
|
254
|
+
);
|
|
255
|
+
} else {
|
|
256
|
+
this.logger.log(
|
|
257
|
+
`候选人列表二次等待仍超时:reason=initial-context-restore-reapply | waited=${initialListWait.waitedMs}ms | attempts=${initialListWait.attempts} | count=${initialListWait.listItemCount} | lastError=${initialListWait.lastError || 'n/a'},继续尝试预热。`,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
173
261
|
this.logger.log('预热步骤:准备点击首位人选初始化聊天容器...');
|
|
174
262
|
let primedCustomer = null;
|
|
175
263
|
|
|
@@ -269,13 +357,22 @@ export class BossChatApp {
|
|
|
269
357
|
message,
|
|
270
358
|
)
|
|
271
359
|
) {
|
|
360
|
+
const delayedListWait = await this.waitForCandidateList({
|
|
361
|
+
reason: `main-loop:${message}`,
|
|
362
|
+
});
|
|
363
|
+
if (delayedListWait.ready) {
|
|
364
|
+
this.logger.log(
|
|
365
|
+
`候选人列表延迟恢复成功:reason=main-loop:${message} | waited=${delayedListWait.waitedMs}ms | attempts=${delayedListWait.attempts} | count=${delayedListWait.listItemCount},继续重试扫描。`,
|
|
366
|
+
);
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
272
369
|
try {
|
|
273
370
|
const recover = await this.page.recoverToChatIndex();
|
|
274
371
|
this.logger.log(
|
|
275
372
|
`页面恢复:changed=${recover.changed} | href=${recover.href || 'unknown'},准备重新预热并继续。`,
|
|
276
373
|
);
|
|
277
374
|
await this.interaction.sleepRange(900, 220);
|
|
278
|
-
|
|
375
|
+
let recoveredFilterResult = await this.restoreListContext(profile);
|
|
279
376
|
this.logger.log(
|
|
280
377
|
`恢复后列表上下文:岗位=${profile.jobSelection?.label || profile.jobSelection?.value || '未知'};列表范围: ${filterLabel}${
|
|
281
378
|
recoveredFilterResult.changed
|
|
@@ -285,6 +382,41 @@ export class BossChatApp {
|
|
|
285
382
|
: '(已在目标筛选)'
|
|
286
383
|
}${recoveredFilterResult?.activeLabel ? ` | active=${recoveredFilterResult.activeLabel}` : ''}`,
|
|
287
384
|
);
|
|
385
|
+
let recoveredListWait = await this.waitForCandidateList({
|
|
386
|
+
reason: 'post-recovery-context-restore',
|
|
387
|
+
});
|
|
388
|
+
if (recoveredListWait.ready) {
|
|
389
|
+
this.logger.log(
|
|
390
|
+
`恢复后候选人列表已就绪:reason=post-recovery-context-restore | waited=${recoveredListWait.waitedMs}ms | attempts=${recoveredListWait.attempts} | count=${recoveredListWait.listItemCount}`,
|
|
391
|
+
);
|
|
392
|
+
} else {
|
|
393
|
+
this.logger.log(
|
|
394
|
+
`恢复后候选人列表等待超时:reason=post-recovery-context-restore | waited=${recoveredListWait.waitedMs}ms | attempts=${recoveredListWait.attempts} | count=${recoveredListWait.listItemCount} | lastError=${recoveredListWait.lastError || 'n/a'},继续尝试预热。`,
|
|
395
|
+
);
|
|
396
|
+
recoveredFilterResult = await this.restoreListContext(profile);
|
|
397
|
+
this.logger.log(
|
|
398
|
+
`恢复后二次应用列表上下文:岗位=${profile.jobSelection?.label || profile.jobSelection?.value || '未知'};列表范围: ${filterLabel}${
|
|
399
|
+
recoveredFilterResult.changed
|
|
400
|
+
? recoveredFilterResult.verified === false
|
|
401
|
+
? '(已尝试切换,未验证 active)'
|
|
402
|
+
: '(已切换)'
|
|
403
|
+
: '(已在目标筛选)'
|
|
404
|
+
}${recoveredFilterResult?.activeLabel ? ` | active=${recoveredFilterResult.activeLabel}` : ''}`,
|
|
405
|
+
);
|
|
406
|
+
await this.interaction.sleepRange(420, 160);
|
|
407
|
+
recoveredListWait = await this.waitForCandidateList({
|
|
408
|
+
reason: 'post-recovery-context-restore-reapply',
|
|
409
|
+
});
|
|
410
|
+
if (recoveredListWait.ready) {
|
|
411
|
+
this.logger.log(
|
|
412
|
+
`恢复后二次候选人列表恢复成功:reason=post-recovery-context-restore-reapply | waited=${recoveredListWait.waitedMs}ms | attempts=${recoveredListWait.attempts} | count=${recoveredListWait.listItemCount}`,
|
|
413
|
+
);
|
|
414
|
+
} else {
|
|
415
|
+
this.logger.log(
|
|
416
|
+
`恢复后二次候选人列表等待仍超时:reason=post-recovery-context-restore-reapply | waited=${recoveredListWait.waitedMs}ms | attempts=${recoveredListWait.attempts} | count=${recoveredListWait.listItemCount} | lastError=${recoveredListWait.lastError || 'n/a'},继续尝试预热。`,
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
288
420
|
const prime = await this.page.primeConversationByFirstCandidate();
|
|
289
421
|
const candidate = prime?.candidate || {};
|
|
290
422
|
const candidateBase = {
|