@reconcrap/boss-recommend-mcp 2.0.48 → 2.0.49
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
CHANGED
|
@@ -312,7 +312,7 @@ export async function readRecommendDetailHtml(client, detailState) {
|
|
|
312
312
|
|
|
313
313
|
export function isStaleRecommendNodeError(error) {
|
|
314
314
|
const message = String(error?.message || error || "");
|
|
315
|
-
return /Could not find node with given id|No node with given id|Node is detached|Cannot find node/i.test(message);
|
|
315
|
+
return /Could not find node with given id|No node with given id|Node is detached|Cannot find node|Could not compute box model/i.test(message);
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
export function isRecommendDetailOpenMissError(error) {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
htmlToText,
|
|
13
13
|
normalizeText
|
|
14
14
|
} from "../../core/screening/index.js";
|
|
15
|
+
import { isStaleRecommendNodeError } from "./detail.js";
|
|
15
16
|
|
|
16
17
|
export const RECOMMEND_JOB_SELECTORS = Object.freeze({
|
|
17
18
|
trigger: ".job-selecter-wrap, [class*=\"job-selecter-wrap\"], .ui-dropmenu",
|
|
@@ -52,15 +53,26 @@ function isVisibleBox(box) {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
async function readJobOption(client, nodeId, index) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
let attributes = null;
|
|
57
|
+
let outerHTML = "";
|
|
58
|
+
try {
|
|
59
|
+
[attributes, outerHTML] = await Promise.all([
|
|
60
|
+
getAttributesMap(client, nodeId),
|
|
61
|
+
getOuterHTML(client, nodeId)
|
|
62
|
+
]);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (isStaleRecommendNodeError(error)) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
59
69
|
const label = normalizeText(htmlToText(outerHTML));
|
|
60
70
|
let box = null;
|
|
61
71
|
try {
|
|
62
72
|
box = await getNodeBox(client, nodeId);
|
|
63
|
-
} catch {
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (!isStaleRecommendNodeError(error)) throw error;
|
|
75
|
+
}
|
|
64
76
|
const className = attributes.class || "";
|
|
65
77
|
return {
|
|
66
78
|
node_id: nodeId,
|
|
@@ -75,19 +87,40 @@ async function readJobOption(client, nodeId, index) {
|
|
|
75
87
|
};
|
|
76
88
|
}
|
|
77
89
|
|
|
90
|
+
async function readJobTrigger(client, nodeId) {
|
|
91
|
+
let box = null;
|
|
92
|
+
try {
|
|
93
|
+
box = await getNodeBox(client, nodeId);
|
|
94
|
+
} catch {}
|
|
95
|
+
if (!isVisibleBox(box)) return null;
|
|
96
|
+
|
|
97
|
+
let label = "";
|
|
98
|
+
let className = "";
|
|
99
|
+
try {
|
|
100
|
+
const outerHTML = await getOuterHTML(client, nodeId);
|
|
101
|
+
label = normalizeText(htmlToText(outerHTML));
|
|
102
|
+
} catch {}
|
|
103
|
+
try {
|
|
104
|
+
const attributes = await getAttributesMap(client, nodeId);
|
|
105
|
+
className = attributes.class || "";
|
|
106
|
+
} catch {}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
node_id: nodeId,
|
|
110
|
+
center: box.center,
|
|
111
|
+
rect: box.rect,
|
|
112
|
+
label,
|
|
113
|
+
label_without_salary: trimSalarySuffix(label),
|
|
114
|
+
class_name: className,
|
|
115
|
+
visible: true
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
78
119
|
export async function findRecommendJobTrigger(client, frameNodeId) {
|
|
79
120
|
const nodeIds = await querySelectorAll(client, frameNodeId, RECOMMEND_JOB_SELECTORS.trigger);
|
|
80
121
|
for (const nodeId of nodeIds) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (isVisibleBox(box)) {
|
|
84
|
-
return {
|
|
85
|
-
node_id: nodeId,
|
|
86
|
-
center: box.center,
|
|
87
|
-
rect: box.rect
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
} catch {}
|
|
122
|
+
const trigger = await readJobTrigger(client, nodeId);
|
|
123
|
+
if (trigger) return trigger;
|
|
91
124
|
}
|
|
92
125
|
return null;
|
|
93
126
|
}
|
|
@@ -162,6 +195,7 @@ export async function openRecommendJobDropdown(client, frameNodeId, {
|
|
|
162
195
|
}
|
|
163
196
|
}
|
|
164
197
|
const error = new Error("Recommend job dropdown did not expose visible options after trigger click");
|
|
198
|
+
error.trigger = trigger;
|
|
165
199
|
error.job_dropdown_attempts = attempts;
|
|
166
200
|
throw error;
|
|
167
201
|
}
|
|
@@ -204,6 +238,7 @@ export async function listRecommendJobOptions(client, frameNodeId, {
|
|
|
204
238
|
if (seen.has(nodeId)) continue;
|
|
205
239
|
seen.add(nodeId);
|
|
206
240
|
const option = await readJobOption(client, nodeId, index);
|
|
241
|
+
if (!option) continue;
|
|
207
242
|
if (!option.label) continue;
|
|
208
243
|
if (option.label.length > 120) continue;
|
|
209
244
|
options.push(option);
|
|
@@ -245,10 +280,36 @@ export async function selectRecommendJob(client, frameNodeId, {
|
|
|
245
280
|
};
|
|
246
281
|
}
|
|
247
282
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
283
|
+
let opened = null;
|
|
284
|
+
try {
|
|
285
|
+
opened = await openRecommendJobDropdown(client, frameNodeId, {
|
|
286
|
+
timeoutMs: dropdownTimeoutMs,
|
|
287
|
+
triggerTimeoutMs: dropdownTimeoutMs
|
|
288
|
+
});
|
|
289
|
+
} catch (error) {
|
|
290
|
+
const currentOptions = await listRecommendJobOptions(client, frameNodeId, {
|
|
291
|
+
openDropdown: false
|
|
292
|
+
}).catch(() => []);
|
|
293
|
+
const currentMatch = currentOptions.find((option) => (
|
|
294
|
+
option.current && jobLabelMatches(option.label, target)
|
|
295
|
+
));
|
|
296
|
+
if (currentMatch) {
|
|
297
|
+
await closeRecommendJobDropdown(client);
|
|
298
|
+
return {
|
|
299
|
+
requested: target,
|
|
300
|
+
selected: true,
|
|
301
|
+
already_current: true,
|
|
302
|
+
selected_option: compactJobOption({
|
|
303
|
+
...currentMatch,
|
|
304
|
+
source: "current_option_without_visible_dropdown"
|
|
305
|
+
}),
|
|
306
|
+
options: currentOptions.map(compactJobOption),
|
|
307
|
+
dropdown_error: error?.message || String(error),
|
|
308
|
+
job_dropdown_attempts: error?.job_dropdown_attempts || []
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
252
313
|
const options = opened.options.length
|
|
253
314
|
? opened.options
|
|
254
315
|
: await listRecommendJobOptions(client, frameNodeId, { openDropdown: false });
|
|
@@ -311,6 +372,7 @@ function compactJobOption(option) {
|
|
|
311
372
|
class_name: option.class_name,
|
|
312
373
|
node_id: option.node_id,
|
|
313
374
|
center: option.center,
|
|
314
|
-
rect: option.rect
|
|
375
|
+
rect: option.rect,
|
|
376
|
+
source: option.source || null
|
|
315
377
|
};
|
|
316
378
|
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
getRecommendRoots,
|
|
15
15
|
waitForRecommendRoots
|
|
16
16
|
} from "./roots.js";
|
|
17
|
+
import { isStaleRecommendNodeError } from "./detail.js";
|
|
17
18
|
|
|
18
19
|
function normalizeLabels(labels = []) {
|
|
19
20
|
return labels.map((label) => String(label || "").trim()).filter(Boolean);
|
|
@@ -102,6 +103,7 @@ function compactFilterReapplyError(error) {
|
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
export function isRetryableRecommendJobSelectionError(error) {
|
|
106
|
+
if (isStaleRecommendNodeError(error)) return true;
|
|
105
107
|
const message = String(error?.message || error || "");
|
|
106
108
|
return /Recommend job trigger was not found|Recommend job dropdown did not mount options|Recommend job dropdown did not expose visible options|Matched recommend job has no clickable center|Matched recommend job has no visible clickable option/i.test(message);
|
|
107
109
|
}
|
|
@@ -125,6 +127,17 @@ function compactJobSelectionAttempt({
|
|
|
125
127
|
};
|
|
126
128
|
}
|
|
127
129
|
|
|
130
|
+
async function waitForFreshRecommendRoots(client, {
|
|
131
|
+
timeoutMs = 10000,
|
|
132
|
+
intervalMs = 500
|
|
133
|
+
} = {}) {
|
|
134
|
+
const rootState = await waitForRecommendRoots(client, {
|
|
135
|
+
timeoutMs,
|
|
136
|
+
intervalMs
|
|
137
|
+
});
|
|
138
|
+
return rootState?.iframe?.documentNodeId ? rootState : null;
|
|
139
|
+
}
|
|
140
|
+
|
|
128
141
|
export async function selectRecommendJobWithRootRefresh(client, rootState, {
|
|
129
142
|
jobLabel = "",
|
|
130
143
|
settleMs = 6000,
|
|
@@ -141,7 +154,10 @@ export async function selectRecommendJobWithRootRefresh(client, rootState, {
|
|
|
141
154
|
while (Date.now() - started <= totalTimeoutMs) {
|
|
142
155
|
attempt += 1;
|
|
143
156
|
if (!currentRootState?.iframe?.documentNodeId) {
|
|
144
|
-
currentRootState = await
|
|
157
|
+
currentRootState = await waitForFreshRecommendRoots(client, {
|
|
158
|
+
timeoutMs: Math.min(10000, Math.max(2000, totalTimeoutMs - (Date.now() - started))),
|
|
159
|
+
intervalMs: 500
|
|
160
|
+
});
|
|
145
161
|
}
|
|
146
162
|
const iframeDocumentNodeId = currentRootState?.iframe?.documentNodeId || 0;
|
|
147
163
|
try {
|
|
@@ -176,7 +192,10 @@ export async function selectRecommendJobWithRootRefresh(client, rootState, {
|
|
|
176
192
|
break;
|
|
177
193
|
}
|
|
178
194
|
if (retryDelayMs > 0) await sleep(retryDelayMs);
|
|
179
|
-
currentRootState = await
|
|
195
|
+
currentRootState = await waitForFreshRecommendRoots(client, {
|
|
196
|
+
timeoutMs: Math.min(10000, Math.max(2000, totalTimeoutMs - (Date.now() - started))),
|
|
197
|
+
intervalMs: 500
|
|
198
|
+
});
|
|
180
199
|
}
|
|
181
200
|
}
|
|
182
201
|
|
|
@@ -1256,11 +1256,13 @@ export async function runRecommendWorkflow({
|
|
|
1256
1256
|
: useLlmScreening
|
|
1257
1257
|
? llmResultToScreening(llmResult, screeningCandidate)
|
|
1258
1258
|
: screenCandidate(screeningCandidate, { criteria });
|
|
1259
|
-
let actionDiscovery = null;
|
|
1260
|
-
let postActionResult = null;
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1259
|
+
let actionDiscovery = null;
|
|
1260
|
+
let postActionResult = null;
|
|
1261
|
+
let closeFailureError = null;
|
|
1262
|
+
let closeRecoveryFailure = null;
|
|
1263
|
+
if (postActionEnabled && detailResult) {
|
|
1264
|
+
const postActionStarted = Date.now();
|
|
1265
|
+
await runControl.waitIfPaused();
|
|
1264
1266
|
runControl.throwIfCanceled();
|
|
1265
1267
|
runControl.setPhase("recommend:post-action");
|
|
1266
1268
|
await maybeHumanActionCooldown("before_post_action", timings);
|
|
@@ -1288,21 +1290,34 @@ export async function runRecommendWorkflow({
|
|
|
1288
1290
|
detailResult.close_result = await measureTiming(timings, "close_detail_ms", () => closeRecommendDetail(client));
|
|
1289
1291
|
await maybeHumanActionCooldown("after_detail_close", timings);
|
|
1290
1292
|
if (!detailResult.close_result?.closed) {
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1293
|
+
closeFailureError = createRecommendCloseFailureError(detailResult.close_result);
|
|
1294
|
+
try {
|
|
1295
|
+
const recovery = await recoverAndReapplyRecommendContext("detail_close_failed", closeFailureError, {
|
|
1296
|
+
forceRecentNotView: true
|
|
1297
|
+
});
|
|
1298
|
+
detailResult.cv_acquisition = {
|
|
1299
|
+
...(detailResult.cv_acquisition || {}),
|
|
1300
|
+
close_recovery: {
|
|
1301
|
+
ok: Boolean(recovery.ok),
|
|
1302
|
+
method: recovery.method || "",
|
|
1303
|
+
forced_recent_not_view: Boolean(recovery.forced_recent_not_view),
|
|
1304
|
+
card_count: recovery.card_count || 0
|
|
1305
|
+
}
|
|
1306
|
+
};
|
|
1307
|
+
} catch (error) {
|
|
1308
|
+
closeRecoveryFailure = error;
|
|
1309
|
+
detailResult.cv_acquisition = {
|
|
1310
|
+
...(detailResult.cv_acquisition || {}),
|
|
1311
|
+
close_recovery: {
|
|
1312
|
+
ok: false,
|
|
1313
|
+
reason: "context_recovery_failed",
|
|
1314
|
+
error: error?.message || String(error),
|
|
1315
|
+
forced_recent_not_view: true
|
|
1316
|
+
}
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1306
1321
|
timings.total_ms = Date.now() - candidateStarted;
|
|
1307
1322
|
const compactResult = {
|
|
1308
1323
|
index,
|
|
@@ -1313,12 +1328,14 @@ export async function runRecommendWorkflow({
|
|
|
1313
1328
|
llm_screening: detailResult ? null : compactScreeningLlmResult(llmResult),
|
|
1314
1329
|
screening: compactScreening(screening),
|
|
1315
1330
|
action_discovery: compactActionDiscovery(actionDiscovery),
|
|
1316
|
-
post_action: postActionResult,
|
|
1317
|
-
error: recoverableDetailError
|
|
1318
|
-
? compactRecoverableDetailError(recoverableDetailError)
|
|
1319
|
-
:
|
|
1320
|
-
? compactError(
|
|
1321
|
-
|
|
1331
|
+
post_action: postActionResult,
|
|
1332
|
+
error: recoverableDetailError
|
|
1333
|
+
? compactRecoverableDetailError(recoverableDetailError)
|
|
1334
|
+
: closeRecoveryFailure
|
|
1335
|
+
? compactError(closeFailureError, "DETAIL_CLOSE_FAILED")
|
|
1336
|
+
: detailResult?.image_evidence?.ok === false
|
|
1337
|
+
? compactError({
|
|
1338
|
+
code: detailResult.image_evidence.error_code,
|
|
1322
1339
|
message: detailResult.image_evidence.error
|
|
1323
1340
|
}, "IMAGE_CAPTURE_FAILED")
|
|
1324
1341
|
: null,
|
|
@@ -1353,9 +1370,13 @@ export async function runRecommendWorkflow({
|
|
|
1353
1370
|
error: compactResult.error,
|
|
1354
1371
|
post_action: postActionResult
|
|
1355
1372
|
}
|
|
1356
|
-
});
|
|
1357
|
-
addTiming(compactResult.timings, "checkpoint_save_ms", Date.now() - checkpointStarted);
|
|
1358
|
-
|
|
1373
|
+
});
|
|
1374
|
+
addTiming(compactResult.timings, "checkpoint_save_ms", Date.now() - checkpointStarted);
|
|
1375
|
+
|
|
1376
|
+
if (closeRecoveryFailure) {
|
|
1377
|
+
throw closeRecoveryFailure;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1359
1380
|
if (postActionResult?.stop_run) {
|
|
1360
1381
|
listEndReason = postActionResult.reason || "post_action_stop";
|
|
1361
1382
|
break;
|