@reconcrap/boss-recommend-mcp 2.1.23 → 2.1.25
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/README.md +5 -0
- package/bin/boss-recommend-mcp.js +4 -4
- package/package.json +6 -1
- package/scripts/install-macos.sh +280 -280
- package/scripts/postinstall.cjs +44 -44
- package/skills/boss-chat/README.md +43 -42
- package/skills/boss-chat/SKILL.md +107 -106
- package/skills/boss-recommend-pipeline/README.md +13 -13
- package/skills/boss-recommend-pipeline/SKILL.md +47 -47
- package/skills/boss-recruit-pipeline/README.md +19 -19
- package/skills/boss-recruit-pipeline/SKILL.md +89 -89
- package/src/chat-mcp.js +301 -127
- package/src/chat-runtime-config.js +7 -5
- package/src/core/boss-cards/index.js +199 -199
- package/src/core/browser/index.js +302 -145
- package/src/core/capture/index.js +2930 -1201
- package/src/core/cv-acquisition/index.js +512 -238
- package/src/core/cv-capture-target/index.js +513 -299
- package/src/core/greet-quota/index.js +71 -71
- package/src/core/infinite-list/index.js +31 -31
- package/src/core/reporting/legacy-csv.js +12 -12
- package/src/core/run/detached-launcher.js +305 -0
- package/src/core/run/index.js +109 -55
- package/src/core/run/timing.js +33 -33
- package/src/core/run/windows-detached-worker.ps1 +106 -0
- package/src/core/screening/index.js +2400 -2135
- package/src/core/self-heal/index.js +989 -973
- package/src/core/self-heal/viewport.js +1505 -564
- package/src/detached-worker.js +99 -99
- package/src/domains/chat/action-journal.js +536 -0
- package/src/domains/chat/cards.js +137 -137
- package/src/domains/chat/constants.js +9 -9
- package/src/domains/chat/detail.js +1684 -401
- package/src/domains/chat/index.js +8 -7
- package/src/domains/chat/jobs.js +620 -620
- package/src/domains/chat/page-guard.js +157 -122
- package/src/domains/chat/roots.js +56 -56
- package/src/domains/chat/run-service.js +1801 -760
- package/src/domains/common/account-rights-panel.js +314 -314
- package/src/domains/common/recovery-settle.js +159 -159
- package/src/domains/recommend/actions.js +1219 -472
- package/src/domains/recommend/cards.js +243 -243
- package/src/domains/recommend/colleague-contact.js +1079 -333
- package/src/domains/recommend/constants.js +92 -92
- package/src/domains/recommend/detail.js +4037 -136
- package/src/domains/recommend/filters.js +608 -590
- package/src/domains/recommend/index.js +9 -9
- package/src/domains/recommend/jobs.js +571 -542
- package/src/domains/recommend/location.js +754 -707
- package/src/domains/recommend/refresh.js +677 -392
- package/src/domains/recommend/roots.js +80 -80
- package/src/domains/recommend/run-service.js +4048 -1447
- package/src/domains/recommend/scopes.js +246 -246
- package/src/domains/recruit/actions.js +277 -277
- package/src/domains/recruit/cards.js +74 -74
- package/src/domains/recruit/constants.js +236 -236
- package/src/domains/recruit/detail.js +588 -588
- package/src/domains/recruit/index.js +9 -9
- package/src/domains/recruit/instruction-parser.js +866 -866
- package/src/domains/recruit/refresh.js +45 -45
- package/src/domains/recruit/roots.js +68 -68
- package/src/domains/recruit/run-service.js +1817 -1620
- package/src/domains/recruit/search.js +3229 -3229
- package/src/index.js +16 -1
- package/src/parser.js +1296 -1296
- package/src/recommend-mcp.js +1061 -450
- package/src/recommend-scheduler.js +75 -75
package/src/recommend-mcp.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
import
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import process from "node:process";
|
|
4
|
-
import {
|
|
5
|
-
assertNoForbiddenCdpCalls,
|
|
6
|
-
bringPageToFront,
|
|
7
|
-
connectToChromeTargetOrOpen,
|
|
8
|
-
createBossLoginRequiredError,
|
|
9
|
-
detectBossLoginState,
|
|
10
|
-
enableDomains,
|
|
11
|
-
FORBIDDEN_CDP_METHODS,
|
|
5
|
+
import {
|
|
6
|
+
assertNoForbiddenCdpCalls,
|
|
7
|
+
bringPageToFront,
|
|
8
|
+
connectToChromeTargetOrOpen,
|
|
9
|
+
createBossLoginRequiredError,
|
|
10
|
+
detectBossLoginState,
|
|
11
|
+
enableDomains,
|
|
12
|
+
FORBIDDEN_CDP_METHODS,
|
|
12
13
|
getMainFrameUrl,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
inspectChromeDebugCommandLine,
|
|
15
|
+
isBossLoginUrl,
|
|
16
|
+
waitForMainFrameUrl,
|
|
17
|
+
sleep
|
|
18
|
+
} from "./core/browser/index.js";
|
|
17
19
|
import {
|
|
18
20
|
RUN_STATUS_CANCELING,
|
|
19
21
|
RUN_STATUS_CANCELED,
|
|
@@ -33,14 +35,15 @@ import {
|
|
|
33
35
|
runSelfHealCheck
|
|
34
36
|
} from "./core/self-heal/index.js";
|
|
35
37
|
import {
|
|
36
|
-
closeRecommendJobDropdown,
|
|
37
|
-
closeRecommendDetail,
|
|
38
|
-
createRecommendRunService,
|
|
39
|
-
getRecommendRoots,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
closeRecommendJobDropdown,
|
|
39
|
+
closeRecommendDetail,
|
|
40
|
+
createRecommendRunService,
|
|
41
|
+
getRecommendRoots,
|
|
42
|
+
inspectRecommendFilteredEmptyState,
|
|
43
|
+
listRecommendJobOptions,
|
|
44
|
+
RECOMMEND_TARGET_URL,
|
|
45
|
+
runRecommendWorkflow
|
|
46
|
+
} from "./domains/recommend/index.js";
|
|
44
47
|
import {
|
|
45
48
|
parseRecommendInstruction
|
|
46
49
|
} from "./parser.js";
|
|
@@ -77,7 +80,7 @@ let recommendRunService = createRecommendRunService({
|
|
|
77
80
|
});
|
|
78
81
|
const recommendRunMeta = new Map();
|
|
79
82
|
|
|
80
|
-
function normalizeText(value) {
|
|
83
|
+
function normalizeText(value) {
|
|
81
84
|
return String(value || "").replace(/\s+/g, " ").trim();
|
|
82
85
|
}
|
|
83
86
|
|
|
@@ -278,22 +281,22 @@ function normalizeScreeningModeArg(args = {}) {
|
|
|
278
281
|
: "llm";
|
|
279
282
|
}
|
|
280
283
|
|
|
281
|
-
function collectRecommendDebugTestOptions(args = {}, normalized = {}) {
|
|
282
|
-
const reasons = [];
|
|
284
|
+
function collectRecommendDebugTestOptions(args = {}, normalized = {}) {
|
|
285
|
+
const reasons = [];
|
|
283
286
|
if (normalizeScreeningModeArg(args) === "deterministic") reasons.push("deterministic_screening");
|
|
284
287
|
if (args.allow_card_only_screening === true) reasons.push("allow_card_only_screening");
|
|
285
288
|
if (parseNonNegativeInteger(args.detail_limit, null) === 0) reasons.push("detail_limit=0");
|
|
286
289
|
if (args.no_filter === true) reasons.push("no_filter");
|
|
287
|
-
if (args.filter_enabled === false) reasons.push("filter_enabled=false");
|
|
288
|
-
if (args.debug_force_list_end_after_processed != null) {
|
|
289
|
-
reasons.push("debug_force_list_end_after_processed");
|
|
290
|
-
}
|
|
291
|
-
if (args.debug_force_context_recovery_after_processed != null) {
|
|
292
|
-
reasons.push("debug_force_context_recovery_after_processed");
|
|
293
|
-
}
|
|
294
|
-
if (args.debug_force_cdp_reconnect_after_processed != null) {
|
|
295
|
-
reasons.push("debug_force_cdp_reconnect_after_processed");
|
|
296
|
-
}
|
|
290
|
+
if (args.filter_enabled === false) reasons.push("filter_enabled=false");
|
|
291
|
+
if (args.debug_force_list_end_after_processed != null) {
|
|
292
|
+
reasons.push("debug_force_list_end_after_processed");
|
|
293
|
+
}
|
|
294
|
+
if (args.debug_force_context_recovery_after_processed != null) {
|
|
295
|
+
reasons.push("debug_force_context_recovery_after_processed");
|
|
296
|
+
}
|
|
297
|
+
if (args.debug_force_cdp_reconnect_after_processed != null) {
|
|
298
|
+
reasons.push("debug_force_cdp_reconnect_after_processed");
|
|
299
|
+
}
|
|
297
300
|
if (args.dry_run_post_action === true) reasons.push("dry_run_post_action");
|
|
298
301
|
if (args.execute_post_action === false && normalized.postAction && normalized.postAction !== "none") {
|
|
299
302
|
reasons.push("execute_post_action=false");
|
|
@@ -313,177 +316,347 @@ function resolveRecommendDetailLimit(args = {}, normalized = {}) {
|
|
|
313
316
|
return requested;
|
|
314
317
|
}
|
|
315
318
|
|
|
316
|
-
function methodSummary(methodLog = []) {
|
|
317
|
-
const summary = {};
|
|
318
|
-
for (const entry of methodLog || []) {
|
|
319
|
-
const method = compactCdpMethodName(entry?.method || entry || "");
|
|
320
|
-
if (!method) continue;
|
|
321
|
-
summary[method] = (summary[method] || 0) + 1;
|
|
322
|
-
}
|
|
323
|
-
return summary;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function compactCdpMethodName(value) {
|
|
327
|
-
const method = normalizeText(value);
|
|
328
|
-
return /^[A-Za-z][A-Za-z0-9_]*\.[A-Za-z][A-Za-z0-9_]*(?::retry_after_reconnect)?$/.test(method)
|
|
329
|
-
? method
|
|
330
|
-
: "";
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function compactCdpMethodTimestamp(value) {
|
|
334
|
-
const timestamp = normalizeText(value);
|
|
335
|
-
const parsed = Date.parse(timestamp);
|
|
336
|
-
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : "";
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function compactMethodLogForStatus(methodLog = []) {
|
|
340
|
-
if (!Array.isArray(methodLog)) return [];
|
|
341
|
-
return methodLog
|
|
342
|
-
.map((entry) => ({
|
|
343
|
-
method: compactCdpMethodName(entry?.method || entry || ""),
|
|
344
|
-
at: compactCdpMethodTimestamp(entry?.at || "")
|
|
345
|
-
}))
|
|
346
|
-
.filter((entry) => Boolean(entry.method))
|
|
347
|
-
.slice(-STATUS_METHOD_LOG_TAIL_LIMIT);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
function clonePlain(value, fallback = null) {
|
|
351
|
-
try {
|
|
352
|
-
return value === undefined ? fallback : JSON.parse(JSON.stringify(value));
|
|
353
|
-
} catch {
|
|
354
|
-
return fallback;
|
|
319
|
+
function methodSummary(methodLog = []) {
|
|
320
|
+
const summary = {};
|
|
321
|
+
for (const entry of methodLog || []) {
|
|
322
|
+
const method = compactCdpMethodName(entry?.method || entry || "");
|
|
323
|
+
if (!method) continue;
|
|
324
|
+
summary[method] = (summary[method] || 0) + 1;
|
|
355
325
|
}
|
|
326
|
+
return summary;
|
|
356
327
|
}
|
|
357
328
|
|
|
358
|
-
function
|
|
359
|
-
|
|
329
|
+
function compactCdpMethodName(value) {
|
|
330
|
+
const method = normalizeText(value);
|
|
331
|
+
return /^[A-Za-z][A-Za-z0-9_]*\.[A-Za-z][A-Za-z0-9_]*(?::retry_after_reconnect)?$/.test(method)
|
|
332
|
+
? method
|
|
333
|
+
: "";
|
|
360
334
|
}
|
|
361
335
|
|
|
362
|
-
function
|
|
363
|
-
const
|
|
364
|
-
|
|
336
|
+
function compactCdpMethodTimestamp(value) {
|
|
337
|
+
const timestamp = normalizeText(value);
|
|
338
|
+
const parsed = Date.parse(timestamp);
|
|
339
|
+
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : "";
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function compactMethodLogForStatus(methodLog = []) {
|
|
343
|
+
if (!Array.isArray(methodLog)) return [];
|
|
344
|
+
return methodLog
|
|
345
|
+
.map((entry) => {
|
|
346
|
+
const compact = {
|
|
347
|
+
method: compactCdpMethodName(entry?.method || entry || ""),
|
|
348
|
+
at: compactCdpMethodTimestamp(entry?.at || "")
|
|
349
|
+
};
|
|
350
|
+
for (const key of ["connection_epoch", "replay_of_connection_epoch"]) {
|
|
351
|
+
if (Number.isInteger(entry?.[key]) && entry[key] >= 0) compact[key] = entry[key];
|
|
352
|
+
}
|
|
353
|
+
const replayPolicy = normalizeText(entry?.replay_policy || "");
|
|
354
|
+
if (replayPolicy) compact.replay_policy = replayPolicy.slice(0, 100);
|
|
355
|
+
return compact;
|
|
356
|
+
})
|
|
357
|
+
.filter((entry) => Boolean(entry.method))
|
|
358
|
+
.slice(-STATUS_METHOD_LOG_TAIL_LIMIT);
|
|
365
359
|
}
|
|
366
360
|
|
|
367
|
-
function
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
if (!method || !Number.isInteger(count) || count < 1) continue;
|
|
373
|
-
compact[method] = count;
|
|
374
|
-
}
|
|
375
|
-
return compact;
|
|
361
|
+
function canonicalRecommendChromeHost(host = "") {
|
|
362
|
+
const normalized = String(host || "").trim().toLowerCase();
|
|
363
|
+
return ["", "localhost", "127.0.0.1", "::1"].includes(normalized)
|
|
364
|
+
? "127.0.0.1"
|
|
365
|
+
: normalized;
|
|
376
366
|
}
|
|
377
367
|
|
|
378
|
-
function
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
parsed.password = "";
|
|
385
|
-
parsed.search = "";
|
|
386
|
-
parsed.hash = "";
|
|
387
|
-
return parsed.toString();
|
|
388
|
-
} catch {
|
|
389
|
-
return text.split(/[?#]/, 1)[0] || null;
|
|
368
|
+
function readChromeCommandLineValue(args = [], name = "") {
|
|
369
|
+
const prefix = `--${name}=`;
|
|
370
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
371
|
+
const value = String(args[index] || "");
|
|
372
|
+
if (value.startsWith(prefix)) return value.slice(prefix.length).trim();
|
|
373
|
+
if (value === `--${name}`) return String(args[index + 1] || "").trim();
|
|
390
374
|
}
|
|
375
|
+
return "";
|
|
391
376
|
}
|
|
392
377
|
|
|
393
|
-
function
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
"
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
378
|
+
function resolveExactChromeProfileDirectory(userDataDir, explicitProfileDirectory = "") {
|
|
379
|
+
const resolvedUserDataDir = fs.realpathSync(path.resolve(userDataDir));
|
|
380
|
+
let profileDirectory = String(explicitProfileDirectory || "").trim();
|
|
381
|
+
if (!profileDirectory) {
|
|
382
|
+
const localStatePath = path.join(resolvedUserDataDir, "Local State");
|
|
383
|
+
const localState = JSON.parse(fs.readFileSync(localStatePath, "utf8"));
|
|
384
|
+
const lastActive = Array.isArray(localState?.profile?.last_active_profiles)
|
|
385
|
+
? localState.profile.last_active_profiles.map((item) => String(item || "").trim()).filter(Boolean)
|
|
386
|
+
: [];
|
|
387
|
+
const cachedProfiles = Object.keys(localState?.profile?.info_cache || {}).filter(Boolean);
|
|
388
|
+
const candidates = Array.from(new Set([...lastActive, ...cachedProfiles]));
|
|
389
|
+
if (candidates.length !== 1) {
|
|
390
|
+
const error = new Error(
|
|
391
|
+
"Chrome profile directory is not explicit and Local State does not prove exactly one active profile"
|
|
392
|
+
);
|
|
393
|
+
error.code = "RECOMMEND_ACTION_PROFILE_IDENTITY_UNVERIFIED";
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
396
|
+
profileDirectory = candidates[0];
|
|
412
397
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
398
|
+
const resolvedProfileDir = fs.realpathSync(path.resolve(resolvedUserDataDir, profileDirectory));
|
|
399
|
+
const relativeProfile = path.relative(resolvedUserDataDir, resolvedProfileDir);
|
|
400
|
+
if (!relativeProfile || relativeProfile.startsWith("..") || path.isAbsolute(relativeProfile)) {
|
|
401
|
+
const error = new Error("Chrome profile directory is not a child of the exact user-data directory");
|
|
402
|
+
error.code = "RECOMMEND_ACTION_PROFILE_IDENTITY_UNVERIFIED";
|
|
403
|
+
throw error;
|
|
416
404
|
}
|
|
417
|
-
return Object.keys(compact).length ? compact : null;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
function compactChromeEvidence(value = {}) {
|
|
421
|
-
const source = plainRecord(value);
|
|
422
|
-
if (!Object.keys(source).length) return null;
|
|
423
|
-
const compact = {};
|
|
424
|
-
const host = normalizeText(source.host);
|
|
425
|
-
const targetId = normalizeText(source.target_id);
|
|
426
|
-
const targetUrl = compactChromeEvidenceUrl(source.target_url);
|
|
427
|
-
if (host) compact.host = host;
|
|
428
|
-
if (Number.isInteger(source.port) && source.port > 0) compact.port = source.port;
|
|
429
|
-
if (targetUrl) compact.target_url = targetUrl;
|
|
430
|
-
if (targetId) compact.target_id = targetId;
|
|
431
|
-
const autoLaunch = compactChromeAutoLaunchEvidence(source.auto_launch);
|
|
432
|
-
if (autoLaunch) compact.auto_launch = autoLaunch;
|
|
433
|
-
return Object.keys(compact).length ? compact : null;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function methodEvidenceFlags(methodSummaryValue = {}) {
|
|
437
|
-
const methods = Object.keys(methodSummaryValue);
|
|
438
|
-
return {
|
|
439
|
-
runtime_evaluate_used: methods.some((method) => method.split(".", 1)[0] === "Runtime"),
|
|
440
|
-
script_injection_used: methods.some((method) => (
|
|
441
|
-
FORBIDDEN_CDP_METHODS.has(method.replace(/:retry_after_reconnect$/, ""))
|
|
442
|
-
))
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
function buildPersistedCdpEvidence({ meta = {}, persisted = {} } = {}) {
|
|
447
|
-
const hasLiveMethodLog = Array.isArray(meta.methodLog);
|
|
448
|
-
const persistedMethodLog = compactMethodLogForStatus(persisted.method_log);
|
|
449
|
-
const methodLog = hasLiveMethodLog
|
|
450
|
-
? compactMethodLogForStatus(meta.methodLog)
|
|
451
|
-
: persistedMethodLog;
|
|
452
|
-
const persistedSummary = compactMethodSummaryForStatus(persisted.method_summary);
|
|
453
|
-
const summary = hasLiveMethodLog
|
|
454
|
-
? methodSummary(meta.methodLog)
|
|
455
|
-
: Object.keys(persistedSummary).length
|
|
456
|
-
? persistedSummary
|
|
457
|
-
: methodSummary(methodLog);
|
|
458
|
-
const liveTotal = hasLiveMethodLog ? meta.methodLog.length : null;
|
|
459
|
-
const persistedTotal = Number(persisted.method_log_total);
|
|
460
|
-
const methodLogTotal = Number.isInteger(liveTotal)
|
|
461
|
-
? liveTotal
|
|
462
|
-
: Number.isInteger(persistedTotal) && persistedTotal >= methodLog.length
|
|
463
|
-
? persistedTotal
|
|
464
|
-
: methodLog.length;
|
|
465
|
-
const inferredFlags = methodEvidenceFlags(summary);
|
|
466
405
|
return {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
: persisted.runtime_evaluate_used === true || inferredFlags.runtime_evaluate_used,
|
|
470
|
-
script_injection_used: hasLiveMethodLog
|
|
471
|
-
? inferredFlags.script_injection_used
|
|
472
|
-
: persisted.script_injection_used === true || inferredFlags.script_injection_used,
|
|
473
|
-
method_summary: summary,
|
|
474
|
-
method_log: methodLog,
|
|
475
|
-
method_log_total: methodLogTotal,
|
|
476
|
-
chrome: compactChromeEvidence(meta.chrome || persisted.chrome)
|
|
406
|
+
user_data_dir: process.platform === "win32" ? resolvedUserDataDir.toLowerCase() : resolvedUserDataDir,
|
|
407
|
+
profile_directory: relativeProfile.replaceAll("\\", "/")
|
|
477
408
|
};
|
|
478
409
|
}
|
|
479
410
|
|
|
480
|
-
function
|
|
411
|
+
export async function resolveRecommendActionJournalScope({
|
|
412
|
+
host = DEFAULT_RECOMMEND_HOST,
|
|
413
|
+
port = DEFAULT_RECOMMEND_PORT,
|
|
414
|
+
session = null,
|
|
415
|
+
inspectCommandLine = inspectChromeDebugCommandLine,
|
|
416
|
+
strictFresh = false
|
|
417
|
+
} = {}) {
|
|
418
|
+
const canonicalHost = canonicalRecommendChromeHost(host);
|
|
419
|
+
if (canonicalHost !== "127.0.0.1") {
|
|
420
|
+
const error = new Error("Live recommend greetings require a local canonical Chrome profile identity");
|
|
421
|
+
error.code = "RECOMMEND_ACTION_PROFILE_IDENTITY_UNVERIFIED";
|
|
422
|
+
throw error;
|
|
423
|
+
}
|
|
424
|
+
let userDataDir = "";
|
|
425
|
+
let profileDirectory = "";
|
|
426
|
+
let source = "";
|
|
427
|
+
if (strictFresh === true) {
|
|
428
|
+
let commandLine = null;
|
|
429
|
+
try {
|
|
430
|
+
commandLine = await inspectCommandLine({ host: canonicalHost, port });
|
|
431
|
+
} catch (error) {
|
|
432
|
+
const inspectionError = new Error(
|
|
433
|
+
"RECOMMEND_ACTION_PROFILE_IDENTITY_UNVERIFIED: fresh Chrome command-line inspection failed"
|
|
434
|
+
);
|
|
435
|
+
inspectionError.code = "RECOMMEND_ACTION_PROFILE_IDENTITY_UNVERIFIED";
|
|
436
|
+
inspectionError.retryable = false;
|
|
437
|
+
inspectionError.cause = error;
|
|
438
|
+
throw inspectionError;
|
|
439
|
+
}
|
|
440
|
+
const observedPort = Number(readChromeCommandLineValue(
|
|
441
|
+
commandLine?.arguments,
|
|
442
|
+
"remote-debugging-port"
|
|
443
|
+
));
|
|
444
|
+
if (commandLine?.ok !== true || observedPort !== Number(port)) {
|
|
445
|
+
const inspectionError = new Error(
|
|
446
|
+
"RECOMMEND_ACTION_PROFILE_IDENTITY_UNVERIFIED: fresh Chrome process/port identity could not be proven"
|
|
447
|
+
);
|
|
448
|
+
inspectionError.code = "RECOMMEND_ACTION_PROFILE_IDENTITY_UNVERIFIED";
|
|
449
|
+
inspectionError.retryable = false;
|
|
450
|
+
inspectionError.expected_port = Number(port);
|
|
451
|
+
inspectionError.observed_port = Number.isFinite(observedPort) ? observedPort : null;
|
|
452
|
+
throw inspectionError;
|
|
453
|
+
}
|
|
454
|
+
userDataDir = readChromeCommandLineValue(commandLine.arguments, "user-data-dir");
|
|
455
|
+
profileDirectory = readChromeCommandLineValue(commandLine.arguments, "profile-directory");
|
|
456
|
+
source = `fresh_${commandLine.source || "chrome_command_line"}`;
|
|
457
|
+
} else {
|
|
458
|
+
const injected = session?.profile_identity;
|
|
459
|
+
if (injected?.verified === true) {
|
|
460
|
+
userDataDir = String(injected.user_data_dir || "").trim();
|
|
461
|
+
profileDirectory = String(injected.profile_directory || "").trim();
|
|
462
|
+
source = "session_verified_profile_identity";
|
|
463
|
+
} else {
|
|
464
|
+
let commandLine = null;
|
|
465
|
+
try {
|
|
466
|
+
commandLine = await inspectCommandLine({ host: canonicalHost, port });
|
|
467
|
+
} catch {
|
|
468
|
+
commandLine = null;
|
|
469
|
+
}
|
|
470
|
+
if (commandLine?.ok === true) {
|
|
471
|
+
userDataDir = readChromeCommandLineValue(commandLine.arguments, "user-data-dir");
|
|
472
|
+
profileDirectory = readChromeCommandLineValue(commandLine.arguments, "profile-directory");
|
|
473
|
+
source = commandLine.source || "chrome_command_line";
|
|
474
|
+
}
|
|
475
|
+
if (!userDataDir && session?.chrome?.launched === true) {
|
|
476
|
+
userDataDir = String(session.chrome.user_data_dir || "").trim();
|
|
477
|
+
source = "same_call_chrome_launch";
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (!userDataDir) {
|
|
482
|
+
const error = new Error(
|
|
483
|
+
"RECOMMEND_ACTION_PROFILE_IDENTITY_UNVERIFIED: exact Chrome user-data/profile directory could not be proven"
|
|
484
|
+
);
|
|
485
|
+
error.code = "RECOMMEND_ACTION_PROFILE_IDENTITY_UNVERIFIED";
|
|
486
|
+
error.retryable = false;
|
|
487
|
+
throw error;
|
|
488
|
+
}
|
|
489
|
+
let exactProfile;
|
|
490
|
+
try {
|
|
491
|
+
exactProfile = resolveExactChromeProfileDirectory(userDataDir, profileDirectory);
|
|
492
|
+
} catch (error) {
|
|
493
|
+
const safeError = new Error(
|
|
494
|
+
"RECOMMEND_ACTION_PROFILE_IDENTITY_UNVERIFIED: exact Chrome profile directory could not be proven"
|
|
495
|
+
);
|
|
496
|
+
safeError.code = "RECOMMEND_ACTION_PROFILE_IDENTITY_UNVERIFIED";
|
|
497
|
+
safeError.retryable = false;
|
|
498
|
+
throw safeError;
|
|
499
|
+
}
|
|
500
|
+
if (!exactProfile.user_data_dir || !exactProfile.profile_directory) {
|
|
501
|
+
const error = new Error("Exact Chrome user-data/profile directory could not be proven");
|
|
502
|
+
error.code = "RECOMMEND_ACTION_PROFILE_IDENTITY_UNVERIFIED";
|
|
503
|
+
throw error;
|
|
504
|
+
}
|
|
505
|
+
const profileHash = crypto.createHash("sha256")
|
|
506
|
+
.update(
|
|
507
|
+
`boss-chrome-profile-v1\u0000${exactProfile.user_data_dir}\u0000${exactProfile.profile_directory}`,
|
|
508
|
+
"utf8"
|
|
509
|
+
)
|
|
510
|
+
.digest("hex");
|
|
481
511
|
return {
|
|
482
|
-
|
|
483
|
-
|
|
512
|
+
scope: `boss-recommend-profile-v2:${canonicalHost}:profile-sha256:${profileHash}`,
|
|
513
|
+
identity: {
|
|
514
|
+
verified: true,
|
|
515
|
+
source,
|
|
516
|
+
canonical_host: canonicalHost,
|
|
517
|
+
profile_sha256: profileHash,
|
|
518
|
+
profile_directory: exactProfile.profile_directory
|
|
519
|
+
}
|
|
484
520
|
};
|
|
485
521
|
}
|
|
486
522
|
|
|
523
|
+
function clonePlain(value, fallback = null) {
|
|
524
|
+
try {
|
|
525
|
+
return value === undefined ? fallback : JSON.parse(JSON.stringify(value));
|
|
526
|
+
} catch {
|
|
527
|
+
return fallback;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function plainRecord(value) {
|
|
532
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function nonEmptyRecord(value) {
|
|
536
|
+
const record = plainRecord(value);
|
|
537
|
+
return Object.keys(record).length ? record : null;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function compactMethodSummaryForStatus(value = {}) {
|
|
541
|
+
const compact = {};
|
|
542
|
+
for (const [rawMethod, rawCount] of Object.entries(plainRecord(value))) {
|
|
543
|
+
const method = compactCdpMethodName(rawMethod);
|
|
544
|
+
const count = Number(rawCount);
|
|
545
|
+
if (!method || !Number.isInteger(count) || count < 1) continue;
|
|
546
|
+
compact[method] = count;
|
|
547
|
+
}
|
|
548
|
+
return compact;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function compactChromeEvidenceUrl(value) {
|
|
552
|
+
const text = normalizeText(value);
|
|
553
|
+
if (!text) return null;
|
|
554
|
+
try {
|
|
555
|
+
const parsed = new URL(text);
|
|
556
|
+
parsed.username = "";
|
|
557
|
+
parsed.password = "";
|
|
558
|
+
parsed.search = "";
|
|
559
|
+
parsed.hash = "";
|
|
560
|
+
return parsed.toString();
|
|
561
|
+
} catch {
|
|
562
|
+
return text.split(/[?#]/, 1)[0] || null;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function compactChromeAutoLaunchEvidence(value = {}) {
|
|
567
|
+
const source = plainRecord(value);
|
|
568
|
+
if (!Object.keys(source).length) return null;
|
|
569
|
+
const compact = {};
|
|
570
|
+
const booleanKeys = [
|
|
571
|
+
"launched",
|
|
572
|
+
"reused",
|
|
573
|
+
"guard_checked",
|
|
574
|
+
"required_flags_ok",
|
|
575
|
+
"replaced",
|
|
576
|
+
"target_created",
|
|
577
|
+
"fallback_target",
|
|
578
|
+
"fallback_any_page"
|
|
579
|
+
];
|
|
580
|
+
for (const key of booleanKeys) {
|
|
581
|
+
if (typeof source[key] === "boolean") compact[key] = source[key];
|
|
582
|
+
}
|
|
583
|
+
for (const key of ["port", "target_count", "command_line_args_count"]) {
|
|
584
|
+
if (Number.isInteger(source[key]) && source[key] >= 0) compact[key] = source[key];
|
|
585
|
+
}
|
|
586
|
+
for (const key of ["close_method", "command_line_source"]) {
|
|
587
|
+
const text = normalizeText(source[key]);
|
|
588
|
+
if (text) compact[key] = text;
|
|
589
|
+
}
|
|
590
|
+
return Object.keys(compact).length ? compact : null;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function compactChromeEvidence(value = {}) {
|
|
594
|
+
const source = plainRecord(value);
|
|
595
|
+
if (!Object.keys(source).length) return null;
|
|
596
|
+
const compact = {};
|
|
597
|
+
const host = normalizeText(source.host);
|
|
598
|
+
const targetId = normalizeText(source.target_id);
|
|
599
|
+
const targetUrl = compactChromeEvidenceUrl(source.target_url);
|
|
600
|
+
if (host) compact.host = host;
|
|
601
|
+
if (Number.isInteger(source.port) && source.port > 0) compact.port = source.port;
|
|
602
|
+
if (targetUrl) compact.target_url = targetUrl;
|
|
603
|
+
if (targetId) compact.target_id = targetId;
|
|
604
|
+
const autoLaunch = compactChromeAutoLaunchEvidence(source.auto_launch);
|
|
605
|
+
if (autoLaunch) compact.auto_launch = autoLaunch;
|
|
606
|
+
return Object.keys(compact).length ? compact : null;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function methodEvidenceFlags(methodSummaryValue = {}) {
|
|
610
|
+
const methods = Object.keys(methodSummaryValue);
|
|
611
|
+
return {
|
|
612
|
+
runtime_evaluate_used: methods.some((method) => method.split(".", 1)[0] === "Runtime"),
|
|
613
|
+
script_injection_used: methods.some((method) => (
|
|
614
|
+
FORBIDDEN_CDP_METHODS.has(method.replace(/:retry_after_reconnect$/, ""))
|
|
615
|
+
))
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function buildPersistedCdpEvidence({ meta = {}, persisted = {} } = {}) {
|
|
620
|
+
const hasLiveMethodLog = Array.isArray(meta.methodLog);
|
|
621
|
+
const persistedMethodLog = compactMethodLogForStatus(persisted.method_log);
|
|
622
|
+
const methodLog = hasLiveMethodLog
|
|
623
|
+
? compactMethodLogForStatus(meta.methodLog)
|
|
624
|
+
: persistedMethodLog;
|
|
625
|
+
const persistedSummary = compactMethodSummaryForStatus(persisted.method_summary);
|
|
626
|
+
const summary = hasLiveMethodLog
|
|
627
|
+
? methodSummary(meta.methodLog)
|
|
628
|
+
: Object.keys(persistedSummary).length
|
|
629
|
+
? persistedSummary
|
|
630
|
+
: methodSummary(methodLog);
|
|
631
|
+
const liveTotal = hasLiveMethodLog ? meta.methodLog.length : null;
|
|
632
|
+
const persistedTotal = Number(persisted.method_log_total);
|
|
633
|
+
const methodLogTotal = Number.isInteger(liveTotal)
|
|
634
|
+
? liveTotal
|
|
635
|
+
: Number.isInteger(persistedTotal) && persistedTotal >= methodLog.length
|
|
636
|
+
? persistedTotal
|
|
637
|
+
: methodLog.length;
|
|
638
|
+
const inferredFlags = methodEvidenceFlags(summary);
|
|
639
|
+
return {
|
|
640
|
+
runtime_evaluate_used: hasLiveMethodLog
|
|
641
|
+
? inferredFlags.runtime_evaluate_used
|
|
642
|
+
: persisted.runtime_evaluate_used === true || inferredFlags.runtime_evaluate_used,
|
|
643
|
+
script_injection_used: hasLiveMethodLog
|
|
644
|
+
? inferredFlags.script_injection_used
|
|
645
|
+
: persisted.script_injection_used === true || inferredFlags.script_injection_used,
|
|
646
|
+
method_summary: summary,
|
|
647
|
+
method_log: methodLog,
|
|
648
|
+
method_log_total: methodLogTotal,
|
|
649
|
+
chrome: compactChromeEvidence(meta.chrome || persisted.chrome)
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
function attachPersistedCdpEvidence(payload, persisted = {}) {
|
|
654
|
+
return {
|
|
655
|
+
...payload,
|
|
656
|
+
...buildPersistedCdpEvidence({ persisted })
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
487
660
|
function normalizeRunId(runId) {
|
|
488
661
|
const normalized = normalizeText(runId);
|
|
489
662
|
if (!normalized || normalized.includes("/") || normalized.includes("\\")) return "";
|
|
@@ -530,12 +703,12 @@ function readJsonFile(filePath) {
|
|
|
530
703
|
|
|
531
704
|
function recommendSearchParamsForCsv(searchParams = {}) {
|
|
532
705
|
return {
|
|
533
|
-
school_tag: Object.prototype.hasOwnProperty.call(searchParams, "school_tag") ? searchParams.school_tag : "不限",
|
|
534
|
-
degree: Object.prototype.hasOwnProperty.call(searchParams, "degree") ? searchParams.degree : "不限",
|
|
535
|
-
gender: Object.prototype.hasOwnProperty.call(searchParams, "gender") ? searchParams.gender : "不限",
|
|
536
|
-
recent_not_view: Object.prototype.hasOwnProperty.call(searchParams, "recent_not_view") ? searchParams.recent_not_view : "不限",
|
|
537
|
-
current_city_only: Object.prototype.hasOwnProperty.call(searchParams, "current_city_only") ? searchParams.current_city_only : false,
|
|
538
|
-
activity_level: Object.prototype.hasOwnProperty.call(searchParams, "activity_level") ? searchParams.activity_level : "不限"
|
|
706
|
+
school_tag: Object.prototype.hasOwnProperty.call(searchParams, "school_tag") ? searchParams.school_tag : "不限",
|
|
707
|
+
degree: Object.prototype.hasOwnProperty.call(searchParams, "degree") ? searchParams.degree : "不限",
|
|
708
|
+
gender: Object.prototype.hasOwnProperty.call(searchParams, "gender") ? searchParams.gender : "不限",
|
|
709
|
+
recent_not_view: Object.prototype.hasOwnProperty.call(searchParams, "recent_not_view") ? searchParams.recent_not_view : "不限",
|
|
710
|
+
current_city_only: Object.prototype.hasOwnProperty.call(searchParams, "current_city_only") ? searchParams.current_city_only : false,
|
|
711
|
+
activity_level: Object.prototype.hasOwnProperty.call(searchParams, "activity_level") ? searchParams.activity_level : "不限"
|
|
539
712
|
};
|
|
540
713
|
}
|
|
541
714
|
|
|
@@ -574,12 +747,12 @@ function selectedRecommendJobForCsv(meta = {}, snapshot = {}) {
|
|
|
574
747
|
function buildRecommendCsvInputRows(snapshot = {}, meta = {}) {
|
|
575
748
|
const { context, confirmation, overrides, followUp, shared } = getSnapshotRequestContext(snapshot);
|
|
576
749
|
const searchParams = recommendSearchParamsForCsv(meta.parsed?.searchParams || {
|
|
577
|
-
school_tag: overrides.school_tag ?? confirmation.school_tag_value,
|
|
578
|
-
degree: overrides.degree ?? confirmation.degree_value,
|
|
579
|
-
gender: overrides.gender ?? confirmation.gender_value,
|
|
580
|
-
recent_not_view: overrides.recent_not_view ?? confirmation.recent_not_view_value,
|
|
581
|
-
current_city_only: overrides.current_city_only ?? false,
|
|
582
|
-
activity_level: overrides.activity_level ?? "不限"
|
|
750
|
+
school_tag: overrides.school_tag ?? confirmation.school_tag_value,
|
|
751
|
+
degree: overrides.degree ?? confirmation.degree_value,
|
|
752
|
+
gender: overrides.gender ?? confirmation.gender_value,
|
|
753
|
+
recent_not_view: overrides.recent_not_view ?? confirmation.recent_not_view_value,
|
|
754
|
+
current_city_only: overrides.current_city_only ?? false,
|
|
755
|
+
activity_level: overrides.activity_level ?? "不限"
|
|
583
756
|
});
|
|
584
757
|
const parsedScreenParams = meta.parsed?.screenParams || {};
|
|
585
758
|
const screenParams = {
|
|
@@ -654,26 +827,36 @@ function normalizeLegacyProgress(progress = {}, summary = null) {
|
|
|
654
827
|
: Number.isInteger(summary?.passed)
|
|
655
828
|
? summary.passed
|
|
656
829
|
: 0;
|
|
657
|
-
const normalized = {
|
|
658
|
-
...progress,
|
|
830
|
+
const normalized = {
|
|
831
|
+
...progress,
|
|
659
832
|
processed,
|
|
660
833
|
inspected: processed,
|
|
661
834
|
screened,
|
|
662
835
|
passed,
|
|
663
836
|
skipped: Number.isInteger(progress.skipped) ? progress.skipped : Math.max(processed - passed, 0),
|
|
664
|
-
greet_count: Number.isInteger(progress.greet_count) ? progress.greet_count : 0,
|
|
665
|
-
post_action_clicked: Number.isInteger(progress.post_action_clicked) ? progress.post_action_clicked : 0
|
|
666
|
-
};
|
|
667
|
-
if (Object.prototype.hasOwnProperty.call(normalized, "last_list_read_stale_diagnostic")) {
|
|
668
|
-
normalized.last_list_read_stale_diagnostic = compactListReadStaleDiagnosticForStatus(
|
|
669
|
-
normalized.last_list_read_stale_diagnostic
|
|
670
|
-
);
|
|
671
|
-
}
|
|
837
|
+
greet_count: Number.isInteger(progress.greet_count) ? progress.greet_count : 0,
|
|
838
|
+
post_action_clicked: Number.isInteger(progress.post_action_clicked) ? progress.post_action_clicked : 0
|
|
839
|
+
};
|
|
840
|
+
if (Object.prototype.hasOwnProperty.call(normalized, "last_list_read_stale_diagnostic")) {
|
|
841
|
+
normalized.last_list_read_stale_diagnostic = compactListReadStaleDiagnosticForStatus(
|
|
842
|
+
normalized.last_list_read_stale_diagnostic
|
|
843
|
+
);
|
|
844
|
+
}
|
|
672
845
|
if (Object.prototype.hasOwnProperty.call(normalized, "list_read_stale_diagnostics")) {
|
|
673
846
|
normalized.list_read_stale_diagnostics = compactListReadStaleDiagnosticsForStatus(
|
|
674
847
|
normalized.list_read_stale_diagnostics
|
|
675
848
|
);
|
|
676
849
|
}
|
|
850
|
+
if (Object.prototype.hasOwnProperty.call(normalized, "dom_stale_forensic")) {
|
|
851
|
+
normalized.dom_stale_forensic = compactDomStaleForensicForStatus(
|
|
852
|
+
normalized.dom_stale_forensic
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
if (Object.prototype.hasOwnProperty.call(normalized, "dom_stale_forensics")) {
|
|
856
|
+
normalized.dom_stale_forensics = compactDomStaleForensicsForStatus(
|
|
857
|
+
normalized.dom_stale_forensics
|
|
858
|
+
);
|
|
859
|
+
}
|
|
677
860
|
return normalized;
|
|
678
861
|
}
|
|
679
862
|
|
|
@@ -703,73 +886,236 @@ function compactPositiveInteger(value, fallback = null) {
|
|
|
703
886
|
return Number.isInteger(value) && value >= 0 ? value : fallback;
|
|
704
887
|
}
|
|
705
888
|
|
|
706
|
-
function compactSmallRecord(value, fallback = null) {
|
|
889
|
+
function compactSmallRecord(value, fallback = null) {
|
|
707
890
|
const record = plainRecord(value);
|
|
708
891
|
if (!Object.keys(record).length) return fallback;
|
|
709
|
-
return clonePlain(record, fallback);
|
|
710
|
-
}
|
|
711
|
-
|
|
892
|
+
return clonePlain(record, fallback);
|
|
893
|
+
}
|
|
894
|
+
|
|
712
895
|
function compactListReadStaleDiagnosticForStatus(value) {
|
|
713
896
|
const source = plainRecord(value);
|
|
714
897
|
if (!Object.keys(source).length) return null;
|
|
715
898
|
const compact = {};
|
|
716
|
-
for (const key of [
|
|
899
|
+
for (const key of [
|
|
900
|
+
"name",
|
|
901
|
+
"code",
|
|
902
|
+
"message",
|
|
903
|
+
"phase",
|
|
904
|
+
"cdp_method",
|
|
905
|
+
"cdp_at",
|
|
906
|
+
"cdp_search_id",
|
|
907
|
+
"cdp_replay_policy",
|
|
908
|
+
"recovery_mode"
|
|
909
|
+
]) {
|
|
717
910
|
const text = normalizeText(source[key] || "");
|
|
718
911
|
if (text) compact[key] = text.slice(0, key === "message" ? 500 : 200);
|
|
719
912
|
}
|
|
720
|
-
for (const key of [
|
|
913
|
+
for (const key of [
|
|
914
|
+
"cdp_node_id",
|
|
915
|
+
"cdp_backend_node_id",
|
|
916
|
+
"cdp_connection_epoch",
|
|
917
|
+
"cdp_reconnected_epoch",
|
|
918
|
+
"attempt"
|
|
919
|
+
]) {
|
|
721
920
|
if (Number.isInteger(source[key]) && source[key] >= 0) compact[key] = source[key];
|
|
722
921
|
}
|
|
723
|
-
for (const key of [
|
|
922
|
+
for (const key of [
|
|
923
|
+
"exhausted",
|
|
924
|
+
"recovery_applied",
|
|
925
|
+
"recovered",
|
|
926
|
+
"cdp_replay_suppressed",
|
|
927
|
+
"cdp_outcome_unknown",
|
|
928
|
+
"cdp_reconnected",
|
|
929
|
+
"cdp_replayed_after_reconnect"
|
|
930
|
+
]) {
|
|
724
931
|
if (typeof source[key] === "boolean") compact[key] = source[key];
|
|
725
932
|
}
|
|
726
|
-
for (const key of ["at", "recovery_applied_at", "recovered_at"]) {
|
|
727
|
-
const text = normalizeText(source[key] || "");
|
|
728
|
-
if (text && Number.isFinite(Date.parse(text))) compact[key] = new Date(text).toISOString();
|
|
729
|
-
}
|
|
933
|
+
for (const key of ["at", "recovery_applied_at", "recovered_at"]) {
|
|
934
|
+
const text = normalizeText(source[key] || "");
|
|
935
|
+
if (text && Number.isFinite(Date.parse(text))) compact[key] = new Date(text).toISOString();
|
|
936
|
+
}
|
|
730
937
|
if (Array.isArray(source.cdp_param_keys)) {
|
|
731
|
-
compact.cdp_param_keys = source.cdp_param_keys
|
|
732
|
-
.map((key) => normalizeText(key))
|
|
938
|
+
compact.cdp_param_keys = source.cdp_param_keys
|
|
939
|
+
.map((key) => normalizeText(key))
|
|
733
940
|
.filter((key) => /^[A-Za-z][A-Za-z0-9_]*$/.test(key))
|
|
734
941
|
.slice(0, 20);
|
|
735
942
|
}
|
|
943
|
+
if (source.cdp_reconnect_error !== undefined && source.cdp_reconnect_error !== null) {
|
|
944
|
+
const reconnectError = plainRecord(source.cdp_reconnect_error);
|
|
945
|
+
if (Object.keys(reconnectError).length) {
|
|
946
|
+
const safeReconnectError = {};
|
|
947
|
+
for (const key of ["name", "code", "message"]) {
|
|
948
|
+
const text = normalizeText(reconnectError[key] || "");
|
|
949
|
+
if (text) safeReconnectError[key] = text.slice(0, key === "message" ? 500 : 200);
|
|
950
|
+
}
|
|
951
|
+
if (Object.keys(safeReconnectError).length) compact.cdp_reconnect_error = safeReconnectError;
|
|
952
|
+
} else {
|
|
953
|
+
const reconnectErrorText = normalizeText(source.cdp_reconnect_error);
|
|
954
|
+
if (reconnectErrorText) compact.cdp_reconnect_error = reconnectErrorText.slice(0, 500);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
return Object.keys(compact).length ? compact : null;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
function compactDomStaleRootIdentityForStatus(value) {
|
|
961
|
+
const source = plainRecord(value);
|
|
962
|
+
if (!Object.keys(source).length) return null;
|
|
963
|
+
const compact = {};
|
|
964
|
+
for (const key of [
|
|
965
|
+
"connection_epoch",
|
|
966
|
+
"top_document_node_id",
|
|
967
|
+
"iframe_owner_node_id",
|
|
968
|
+
"iframe_document_node_id"
|
|
969
|
+
]) {
|
|
970
|
+
if (Number.isInteger(source[key]) && source[key] >= 0) compact[key] = source[key];
|
|
971
|
+
}
|
|
972
|
+
const iframeSelector = normalizeText(source.iframe_selector || "");
|
|
973
|
+
if (iframeSelector) compact.iframe_selector = iframeSelector.slice(0, 300);
|
|
736
974
|
return Object.keys(compact).length ? compact : null;
|
|
737
975
|
}
|
|
738
976
|
|
|
739
|
-
function
|
|
977
|
+
function compactDomStaleNumericRecordForStatus(value) {
|
|
978
|
+
const source = plainRecord(value);
|
|
979
|
+
if (!Object.keys(source).length) return null;
|
|
980
|
+
const compact = {};
|
|
981
|
+
for (const [key, rawValue] of Object.entries(source)) {
|
|
982
|
+
if (!/^[a-z][a-z0-9_]*$/i.test(key)) continue;
|
|
983
|
+
if (!Number.isInteger(rawValue) || rawValue < 0) continue;
|
|
984
|
+
compact[key] = rawValue;
|
|
985
|
+
if (Object.keys(compact).length >= 30) break;
|
|
986
|
+
}
|
|
987
|
+
return Object.keys(compact).length ? compact : null;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function compactDomStaleLifecycleTimelineForStatus(value) {
|
|
740
991
|
if (!Array.isArray(value)) return [];
|
|
741
992
|
return value
|
|
742
|
-
.map((
|
|
993
|
+
.map((rawEntry) => {
|
|
994
|
+
const entry = plainRecord(rawEntry);
|
|
995
|
+
const compact = {};
|
|
996
|
+
const at = compactCdpMethodTimestamp(entry.at || "");
|
|
997
|
+
if (at) compact.at = at;
|
|
998
|
+
for (const key of ["type", "operation", "frame_id", "parent_frame_id", "loader_id"]) {
|
|
999
|
+
const text = normalizeText(entry[key] || "");
|
|
1000
|
+
if (text) compact[key] = text.slice(0, 300);
|
|
1001
|
+
}
|
|
1002
|
+
if (Number.isInteger(entry.connection_epoch) && entry.connection_epoch >= 0) {
|
|
1003
|
+
compact.connection_epoch = entry.connection_epoch;
|
|
1004
|
+
}
|
|
1005
|
+
const url = compactChromeEvidenceUrl(entry.url);
|
|
1006
|
+
if (url) compact.url = url;
|
|
1007
|
+
return Object.keys(compact).length ? compact : null;
|
|
1008
|
+
})
|
|
743
1009
|
.filter(Boolean)
|
|
744
|
-
.slice(-
|
|
1010
|
+
.slice(-20);
|
|
745
1011
|
}
|
|
746
1012
|
|
|
747
|
-
function
|
|
1013
|
+
function compactDomStaleCandidateForStatus(value) {
|
|
748
1014
|
const source = plainRecord(value);
|
|
749
1015
|
if (!Object.keys(source).length) return null;
|
|
750
1016
|
const compact = {};
|
|
751
|
-
const
|
|
752
|
-
if (diagnostic) compact.diagnostic = diagnostic;
|
|
753
|
-
const recentDiagnostics = compactListReadStaleDiagnosticsForStatus(source.recent_diagnostics);
|
|
754
|
-
if (recentDiagnostics.length) compact.recent_diagnostics = recentDiagnostics;
|
|
755
|
-
for (const key of ["recovery_count", "recovery_applied_count"]) {
|
|
1017
|
+
for (const key of ["index", "card_node_id", "visible_index", "failing_list_node_id"]) {
|
|
756
1018
|
if (Number.isInteger(source[key]) && source[key] >= 0) compact[key] = source[key];
|
|
757
1019
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
1020
|
+
const candidateKey = normalizeText(source.key || "");
|
|
1021
|
+
if (candidateKey) compact.key = candidateKey.slice(0, 300);
|
|
1022
|
+
return Object.keys(compact).length ? compact : null;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
function compactDomStaleRecoveryForStatus(value) {
|
|
1026
|
+
const source = plainRecord(value);
|
|
1027
|
+
if (!Object.keys(source).length) return null;
|
|
1028
|
+
const compact = {};
|
|
1029
|
+
for (const key of ["status", "mode", "escalated_from", "method"]) {
|
|
1030
|
+
const text = normalizeText(source[key] || "");
|
|
1031
|
+
if (text) compact[key] = text.slice(0, 200);
|
|
1032
|
+
}
|
|
1033
|
+
const at = compactCdpMethodTimestamp(source.at || "");
|
|
1034
|
+
if (at) compact.at = at;
|
|
1035
|
+
if (typeof source.ok === "boolean") compact.ok = source.ok;
|
|
1036
|
+
if (Number.isInteger(source.card_count) && source.card_count >= 0) {
|
|
1037
|
+
compact.card_count = source.card_count;
|
|
761
1038
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
1039
|
+
const error = compactListReadStaleDiagnosticForStatus(source.error);
|
|
1040
|
+
if (error) compact.error = error;
|
|
1041
|
+
return Object.keys(compact).length ? compact : null;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
function compactDomStaleForensicForStatus(value) {
|
|
1045
|
+
const source = plainRecord(value);
|
|
1046
|
+
if (!Object.keys(source).length) return null;
|
|
1047
|
+
const compact = {};
|
|
1048
|
+
if (Number.isInteger(source.schema_version) && source.schema_version >= 0) {
|
|
1049
|
+
compact.schema_version = source.schema_version;
|
|
1050
|
+
}
|
|
1051
|
+
for (const key of ["event_id", "event_type", "phase", "operation", "detail_step"]) {
|
|
1052
|
+
const text = normalizeText(source[key] || "");
|
|
1053
|
+
if (text) compact[key] = text.slice(0, key === "event_id" ? 300 : 200);
|
|
770
1054
|
}
|
|
1055
|
+
const at = compactCdpMethodTimestamp(source.at || "");
|
|
1056
|
+
if (at) compact.at = at;
|
|
1057
|
+
|
|
1058
|
+
const candidate = compactDomStaleCandidateForStatus(source.candidate);
|
|
1059
|
+
if (candidate) compact.candidate = candidate;
|
|
1060
|
+
const error = compactListReadStaleDiagnosticForStatus(source.error);
|
|
1061
|
+
if (error) compact.error = error;
|
|
1062
|
+
const preRecoveryRoots = compactDomStaleRootIdentityForStatus(source.pre_recovery_roots);
|
|
1063
|
+
if (preRecoveryRoots) compact.pre_recovery_roots = preRecoveryRoots;
|
|
1064
|
+
const postRecoveryRoots = compactDomStaleRootIdentityForStatus(source.post_recovery_roots);
|
|
1065
|
+
if (postRecoveryRoots) compact.post_recovery_roots = postRecoveryRoots;
|
|
1066
|
+
const candidateList = compactDomStaleNumericRecordForStatus(source.candidate_list);
|
|
1067
|
+
if (candidateList) compact.candidate_list = candidateList;
|
|
1068
|
+
const counters = compactDomStaleNumericRecordForStatus(source.counters);
|
|
1069
|
+
if (counters) compact.counters = counters;
|
|
1070
|
+
const lifecycleTimeline = compactDomStaleLifecycleTimelineForStatus(source.lifecycle_timeline);
|
|
1071
|
+
if (lifecycleTimeline.length) compact.lifecycle_timeline = lifecycleTimeline;
|
|
1072
|
+
const recovery = compactDomStaleRecoveryForStatus(source.recovery);
|
|
1073
|
+
if (recovery) compact.recovery = recovery;
|
|
771
1074
|
return Object.keys(compact).length ? compact : null;
|
|
772
1075
|
}
|
|
1076
|
+
|
|
1077
|
+
function compactDomStaleForensicsForStatus(value) {
|
|
1078
|
+
if (!Array.isArray(value)) return [];
|
|
1079
|
+
return value
|
|
1080
|
+
.map((item) => compactDomStaleForensicForStatus(item))
|
|
1081
|
+
.filter(Boolean)
|
|
1082
|
+
.slice(-12);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
function compactListReadStaleDiagnosticsForStatus(value) {
|
|
1086
|
+
if (!Array.isArray(value)) return [];
|
|
1087
|
+
return value
|
|
1088
|
+
.map((item) => compactListReadStaleDiagnosticForStatus(item))
|
|
1089
|
+
.filter(Boolean)
|
|
1090
|
+
.slice(-12);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
function compactListReadStaleCheckpointEvent(value) {
|
|
1094
|
+
const source = plainRecord(value);
|
|
1095
|
+
if (!Object.keys(source).length) return null;
|
|
1096
|
+
const compact = {};
|
|
1097
|
+
const diagnostic = compactListReadStaleDiagnosticForStatus(source.diagnostic || source.trigger);
|
|
1098
|
+
if (diagnostic) compact.diagnostic = diagnostic;
|
|
1099
|
+
const recentDiagnostics = compactListReadStaleDiagnosticsForStatus(source.recent_diagnostics);
|
|
1100
|
+
if (recentDiagnostics.length) compact.recent_diagnostics = recentDiagnostics;
|
|
1101
|
+
for (const key of ["recovery_count", "recovery_applied_count"]) {
|
|
1102
|
+
if (Number.isInteger(source[key]) && source[key] >= 0) compact[key] = source[key];
|
|
1103
|
+
}
|
|
1104
|
+
if (source.recovery_error) {
|
|
1105
|
+
const recoveryError = compactListReadStaleDiagnosticForStatus(source.recovery_error);
|
|
1106
|
+
if (recoveryError) compact.recovery_error = recoveryError;
|
|
1107
|
+
}
|
|
1108
|
+
if (source.candidate_list) {
|
|
1109
|
+
const candidateList = plainRecord(source.candidate_list);
|
|
1110
|
+
compact.candidate_list = {};
|
|
1111
|
+
for (const key of ["seen_count", "queued_count", "processed_count", "read_error_count", "scroll_count"]) {
|
|
1112
|
+
if (Number.isInteger(candidateList[key]) && candidateList[key] >= 0) {
|
|
1113
|
+
compact.candidate_list[key] = candidateList[key];
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return Object.keys(compact).length ? compact : null;
|
|
1118
|
+
}
|
|
773
1119
|
|
|
774
1120
|
function compactRecommendSummaryForStatus(summary) {
|
|
775
1121
|
if (!summary || typeof summary !== "object" || Array.isArray(summary)) return null;
|
|
@@ -817,7 +1163,7 @@ function compactRecommendSummaryForStatus(summary) {
|
|
|
817
1163
|
restLevel: normalizeText(summary.human_behavior.restLevel || summary.human_behavior.rest_level || "")
|
|
818
1164
|
};
|
|
819
1165
|
}
|
|
820
|
-
if (summary.human_rest) {
|
|
1166
|
+
if (summary.human_rest) {
|
|
821
1167
|
const humanRest = plainRecord(summary.human_rest);
|
|
822
1168
|
compact.human_rest = {
|
|
823
1169
|
enabled: humanRest.enabled === true,
|
|
@@ -825,23 +1171,27 @@ function compactRecommendSummaryForStatus(summary) {
|
|
|
825
1171
|
rest_count: compactPositiveInteger(humanRest.rest_count ?? humanRest.count, null),
|
|
826
1172
|
total_pause_ms: compactPositiveInteger(humanRest.total_pause_ms ?? humanRest.pause_ms, null)
|
|
827
1173
|
};
|
|
828
|
-
}
|
|
829
|
-
for (const key of [
|
|
830
|
-
"list_read_stale_recovery_attempts",
|
|
831
|
-
"list_read_stale_recovery_applied",
|
|
832
|
-
"list_read_stale_recoveries"
|
|
833
|
-
]) {
|
|
834
|
-
if (Number.isInteger(summary[key]) && summary[key] >= 0) compact[key] = summary[key];
|
|
835
|
-
}
|
|
836
|
-
if (summary.last_list_read_recovery_mode) {
|
|
837
|
-
compact.last_list_read_recovery_mode = normalizeText(summary.last_list_read_recovery_mode).slice(0, 100);
|
|
838
|
-
}
|
|
839
|
-
const lastStaleDiagnostic = compactListReadStaleDiagnosticForStatus(
|
|
840
|
-
summary.last_list_read_stale_diagnostic
|
|
841
|
-
);
|
|
842
|
-
if (lastStaleDiagnostic) compact.last_list_read_stale_diagnostic = lastStaleDiagnostic;
|
|
1174
|
+
}
|
|
1175
|
+
for (const key of [
|
|
1176
|
+
"list_read_stale_recovery_attempts",
|
|
1177
|
+
"list_read_stale_recovery_applied",
|
|
1178
|
+
"list_read_stale_recoveries"
|
|
1179
|
+
]) {
|
|
1180
|
+
if (Number.isInteger(summary[key]) && summary[key] >= 0) compact[key] = summary[key];
|
|
1181
|
+
}
|
|
1182
|
+
if (summary.last_list_read_recovery_mode) {
|
|
1183
|
+
compact.last_list_read_recovery_mode = normalizeText(summary.last_list_read_recovery_mode).slice(0, 100);
|
|
1184
|
+
}
|
|
1185
|
+
const lastStaleDiagnostic = compactListReadStaleDiagnosticForStatus(
|
|
1186
|
+
summary.last_list_read_stale_diagnostic
|
|
1187
|
+
);
|
|
1188
|
+
if (lastStaleDiagnostic) compact.last_list_read_stale_diagnostic = lastStaleDiagnostic;
|
|
843
1189
|
const staleDiagnostics = compactListReadStaleDiagnosticsForStatus(summary.list_read_stale_diagnostics);
|
|
844
1190
|
if (staleDiagnostics.length) compact.list_read_stale_diagnostics = staleDiagnostics;
|
|
1191
|
+
const domStaleForensic = compactDomStaleForensicForStatus(summary.dom_stale_forensic);
|
|
1192
|
+
if (domStaleForensic) compact.dom_stale_forensic = domStaleForensic;
|
|
1193
|
+
const domStaleForensics = compactDomStaleForensicsForStatus(summary.dom_stale_forensics);
|
|
1194
|
+
if (domStaleForensics.length) compact.dom_stale_forensics = domStaleForensics;
|
|
845
1195
|
return compact;
|
|
846
1196
|
}
|
|
847
1197
|
|
|
@@ -856,19 +1206,23 @@ function compactRecommendCheckpointForStatus(checkpoint) {
|
|
|
856
1206
|
} else if (Number.isInteger(checkpoint.results_count)) {
|
|
857
1207
|
compact.results_count = checkpoint.results_count;
|
|
858
1208
|
}
|
|
859
|
-
for (const key of STATUS_COUNT_KEYS) {
|
|
860
|
-
if (Number.isInteger(checkpoint[key])) compact[key] = checkpoint[key];
|
|
861
|
-
}
|
|
1209
|
+
for (const key of STATUS_COUNT_KEYS) {
|
|
1210
|
+
if (Number.isInteger(checkpoint[key])) compact[key] = checkpoint[key];
|
|
1211
|
+
}
|
|
862
1212
|
for (const key of [
|
|
863
|
-
"list_read_stale_recovery",
|
|
864
|
-
"list_read_stale_recovery_applied",
|
|
865
|
-
"list_read_stale_recovered",
|
|
866
|
-
"list_read_stale_recovery_exhausted",
|
|
867
|
-
"list_read_stale_recovery_failed"
|
|
868
|
-
]) {
|
|
1213
|
+
"list_read_stale_recovery",
|
|
1214
|
+
"list_read_stale_recovery_applied",
|
|
1215
|
+
"list_read_stale_recovered",
|
|
1216
|
+
"list_read_stale_recovery_exhausted",
|
|
1217
|
+
"list_read_stale_recovery_failed"
|
|
1218
|
+
]) {
|
|
869
1219
|
const event = compactListReadStaleCheckpointEvent(checkpoint[key]);
|
|
870
1220
|
if (event) compact[key] = event;
|
|
871
1221
|
}
|
|
1222
|
+
const domStaleForensic = compactDomStaleForensicForStatus(checkpoint.dom_stale_forensic);
|
|
1223
|
+
if (domStaleForensic) compact.dom_stale_forensic = domStaleForensic;
|
|
1224
|
+
const domStaleForensics = compactDomStaleForensicsForStatus(checkpoint.dom_stale_forensics);
|
|
1225
|
+
if (domStaleForensics.length) compact.dom_stale_forensics = domStaleForensics;
|
|
872
1226
|
return compact;
|
|
873
1227
|
}
|
|
874
1228
|
|
|
@@ -1268,16 +1622,16 @@ function persistRecommendRunSnapshot(snapshot, {
|
|
|
1268
1622
|
const artifacts = getRecommendRunArtifacts(normalized.run_id);
|
|
1269
1623
|
if (!artifacts) return normalized;
|
|
1270
1624
|
const existing = readJsonFile(artifacts.run_state_path);
|
|
1271
|
-
normalized.control = mergePersistedControlRequest(normalized, existing);
|
|
1272
|
-
normalized = coerceCanceledTerminalSnapshot(normalized, existing);
|
|
1273
|
-
if (persistActiveCheckpoint) {
|
|
1274
|
-
persistRecommendCheckpointSnapshot(snapshot);
|
|
1275
|
-
}
|
|
1276
|
-
const cdpEvidence = buildPersistedCdpEvidence({
|
|
1277
|
-
meta: getRecommendRunMeta(normalized.run_id),
|
|
1278
|
-
persisted: existing || {}
|
|
1279
|
-
});
|
|
1280
|
-
const payload = {
|
|
1625
|
+
normalized.control = mergePersistedControlRequest(normalized, existing);
|
|
1626
|
+
normalized = coerceCanceledTerminalSnapshot(normalized, existing);
|
|
1627
|
+
if (persistActiveCheckpoint) {
|
|
1628
|
+
persistRecommendCheckpointSnapshot(snapshot);
|
|
1629
|
+
}
|
|
1630
|
+
const cdpEvidence = buildPersistedCdpEvidence({
|
|
1631
|
+
meta: getRecommendRunMeta(normalized.run_id),
|
|
1632
|
+
persisted: existing || {}
|
|
1633
|
+
});
|
|
1634
|
+
const payload = {
|
|
1281
1635
|
run_id: normalized.run_id,
|
|
1282
1636
|
mode: normalized.mode,
|
|
1283
1637
|
state: normalized.state,
|
|
@@ -1293,14 +1647,14 @@ function persistRecommendRunSnapshot(snapshot, {
|
|
|
1293
1647
|
context: normalized.context,
|
|
1294
1648
|
control: normalized.control,
|
|
1295
1649
|
resume: normalized.resume,
|
|
1296
|
-
error: normalized.error,
|
|
1297
|
-
recovery: normalized.recovery,
|
|
1298
|
-
result: normalized.result,
|
|
1299
|
-
summary: normalized.summary,
|
|
1300
|
-
checkpoint: normalized.checkpoint,
|
|
1301
|
-
artifacts: normalized.artifacts,
|
|
1302
|
-
...cdpEvidence
|
|
1303
|
-
};
|
|
1650
|
+
error: normalized.error,
|
|
1651
|
+
recovery: normalized.recovery,
|
|
1652
|
+
result: normalized.result,
|
|
1653
|
+
summary: normalized.summary,
|
|
1654
|
+
checkpoint: normalized.checkpoint,
|
|
1655
|
+
artifacts: normalized.artifacts,
|
|
1656
|
+
...cdpEvidence
|
|
1657
|
+
};
|
|
1304
1658
|
writeJsonAtomic(artifacts.run_state_path, payload);
|
|
1305
1659
|
return normalized;
|
|
1306
1660
|
}
|
|
@@ -1373,18 +1727,18 @@ function persistRecommendLifecycleSnapshot(snapshot, event = {}) {
|
|
|
1373
1727
|
});
|
|
1374
1728
|
}
|
|
1375
1729
|
|
|
1376
|
-
function attachMethodEvidence(payload, runId) {
|
|
1377
|
-
const meta = getRecommendRunMeta(runId);
|
|
1378
|
-
const methodLog = meta.methodLog || [];
|
|
1379
|
-
assertNoForbiddenCdpCalls(methodLog);
|
|
1380
|
-
return {
|
|
1381
|
-
...payload,
|
|
1382
|
-
...buildPersistedCdpEvidence({
|
|
1383
|
-
meta,
|
|
1384
|
-
persisted: readRecommendRunState(runId) || {}
|
|
1385
|
-
})
|
|
1386
|
-
};
|
|
1387
|
-
}
|
|
1730
|
+
function attachMethodEvidence(payload, runId) {
|
|
1731
|
+
const meta = getRecommendRunMeta(runId);
|
|
1732
|
+
const methodLog = meta.methodLog || [];
|
|
1733
|
+
assertNoForbiddenCdpCalls(methodLog);
|
|
1734
|
+
return {
|
|
1735
|
+
...payload,
|
|
1736
|
+
...buildPersistedCdpEvidence({
|
|
1737
|
+
meta,
|
|
1738
|
+
persisted: readRecommendRunState(runId) || {}
|
|
1739
|
+
})
|
|
1740
|
+
};
|
|
1741
|
+
}
|
|
1388
1742
|
|
|
1389
1743
|
function compactRecommendJobListOption(option, index) {
|
|
1390
1744
|
const label = normalizeText(option?.label);
|
|
@@ -1539,51 +1893,239 @@ export async function listRecommendJobsTool({ workspaceRoot = "", args = {} } =
|
|
|
1539
1893
|
}
|
|
1540
1894
|
}
|
|
1541
1895
|
|
|
1542
|
-
function compactHealth(check) {
|
|
1543
|
-
if (!check) return null;
|
|
1544
|
-
return {
|
|
1545
|
-
status: check.status,
|
|
1546
|
-
summary: check.summary,
|
|
1547
|
-
drift_report: check.drift_report,
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1896
|
+
function compactHealth(check) {
|
|
1897
|
+
if (!check) return null;
|
|
1898
|
+
return {
|
|
1899
|
+
status: check.status,
|
|
1900
|
+
summary: check.summary,
|
|
1901
|
+
drift_report: check.drift_report,
|
|
1902
|
+
empty_bootstrap_refresh: check.empty_bootstrap_refresh || null,
|
|
1903
|
+
accepted_empty_bootstrap: check.accepted_empty_bootstrap || null,
|
|
1904
|
+
probes: (check.probes || []).map((probe) => ({
|
|
1905
|
+
id: probe.id,
|
|
1906
|
+
type: probe.type,
|
|
1907
|
+
status: probe.status,
|
|
1552
1908
|
count: probe.count,
|
|
1553
1909
|
required: probe.required
|
|
1554
1910
|
}))
|
|
1555
|
-
};
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
const
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
function hasOnlyCandidateCardsRequiredFailure(check) {
|
|
1915
|
+
const failedRequiredIds = Array.isArray(check?.summary?.failed_required_ids)
|
|
1916
|
+
? check.summary.failed_required_ids
|
|
1917
|
+
: [];
|
|
1918
|
+
const blockedRequiredIds = Array.isArray(check?.summary?.blocked_required_ids)
|
|
1919
|
+
? check.summary.blocked_required_ids
|
|
1920
|
+
: [];
|
|
1921
|
+
return check?.status === HEALTH_STATUS.DEGRADED
|
|
1922
|
+
&& failedRequiredIds.length === 1
|
|
1923
|
+
&& failedRequiredIds[0] === "candidate_cards"
|
|
1924
|
+
&& blockedRequiredIds.length === 0;
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
function compactRecommendEmptyStateEvidence(emptyState) {
|
|
1928
|
+
if (!emptyState) return null;
|
|
1929
|
+
return {
|
|
1930
|
+
verified: emptyState.verified === true,
|
|
1931
|
+
reason: emptyState.reason || null,
|
|
1932
|
+
text: emptyState.text || null,
|
|
1933
|
+
node_id: emptyState.node_id ?? null,
|
|
1934
|
+
box: emptyState.box || null,
|
|
1935
|
+
accessibility: emptyState.accessibility ? {
|
|
1936
|
+
available: emptyState.accessibility.available ?? null,
|
|
1937
|
+
verified: emptyState.accessibility.verified === true,
|
|
1938
|
+
reason: emptyState.accessibility.reason || null
|
|
1939
|
+
} : null
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
function hasExactRecommendFilteredEmptyState(emptyState) {
|
|
1944
|
+
return emptyState?.verified === true
|
|
1945
|
+
&& emptyState?.reason === "exact_visible_filtered_empty_state"
|
|
1946
|
+
&& emptyState?.accessibility?.verified === true;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
function attachRecommendEmptyBootstrapRefreshEvidence(check, refreshEvidence, emptyState = null) {
|
|
1950
|
+
if (!check || refreshEvidence?.attempted !== true) return check;
|
|
1951
|
+
return {
|
|
1952
|
+
...check,
|
|
1953
|
+
empty_bootstrap_refresh: {
|
|
1954
|
+
...refreshEvidence,
|
|
1955
|
+
after: {
|
|
1956
|
+
health: compactHealth(check),
|
|
1957
|
+
empty_state: compactRecommendEmptyStateEvidence(emptyState)
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
export function getRecommendEmptyBootstrapPreflightAction(
|
|
1964
|
+
check,
|
|
1965
|
+
emptyState = null,
|
|
1966
|
+
refreshEvidence = null
|
|
1967
|
+
) {
|
|
1968
|
+
if (check?.status === HEALTH_STATUS.HEALTHY) return "accept_healthy";
|
|
1969
|
+
if (!hasOnlyCandidateCardsRequiredFailure(check)) return "wait";
|
|
1970
|
+
if (!hasExactRecommendFilteredEmptyState(emptyState)) return "wait";
|
|
1971
|
+
if (refreshEvidence?.attempted !== true) return "refresh";
|
|
1972
|
+
if (
|
|
1973
|
+
refreshEvidence.completed !== true
|
|
1974
|
+
|| refreshEvidence.ok !== true
|
|
1975
|
+
|| refreshEvidence.method !== "Page.navigate"
|
|
1976
|
+
|| refreshEvidence.target_url !== RECOMMEND_TARGET_URL
|
|
1977
|
+
) return "wait";
|
|
1978
|
+
return "accept_empty_bootstrap";
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
export function acceptRecommendEmptyBootstrapHealth(check, emptyState, refreshEvidence = null) {
|
|
1982
|
+
if (
|
|
1983
|
+
getRecommendEmptyBootstrapPreflightAction(check, emptyState, refreshEvidence)
|
|
1984
|
+
!== "accept_empty_bootstrap"
|
|
1985
|
+
) return null;
|
|
1986
|
+
|
|
1987
|
+
const healthWithRefresh = attachRecommendEmptyBootstrapRefreshEvidence(
|
|
1988
|
+
check,
|
|
1989
|
+
refreshEvidence,
|
|
1990
|
+
emptyState
|
|
1991
|
+
);
|
|
1992
|
+
return {
|
|
1993
|
+
...healthWithRefresh,
|
|
1994
|
+
accepted_empty_bootstrap: {
|
|
1995
|
+
accepted: true,
|
|
1996
|
+
mode: "exact_filtered_empty_bootstrap",
|
|
1997
|
+
reason: emptyState.reason,
|
|
1998
|
+
original_health_status: check.status,
|
|
1999
|
+
failed_required_ids: [...check.summary.failed_required_ids],
|
|
2000
|
+
blocked_required_ids: [...check.summary.blocked_required_ids],
|
|
2001
|
+
root: "frame",
|
|
2002
|
+
empty_state: compactRecommendEmptyStateEvidence(emptyState),
|
|
2003
|
+
refresh: healthWithRefresh.empty_bootstrap_refresh
|
|
2004
|
+
}
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
export function isRecommendConnectorHealthAccepted(health) {
|
|
2009
|
+
if (health?.status === HEALTH_STATUS.HEALTHY) return true;
|
|
2010
|
+
const accepted = health?.accepted_empty_bootstrap;
|
|
2011
|
+
return accepted?.accepted === true
|
|
2012
|
+
&& accepted.mode === "exact_filtered_empty_bootstrap"
|
|
2013
|
+
&& accepted.original_health_status === HEALTH_STATUS.DEGRADED
|
|
2014
|
+
&& getRecommendEmptyBootstrapPreflightAction(
|
|
2015
|
+
health,
|
|
2016
|
+
accepted.empty_state,
|
|
2017
|
+
health.empty_bootstrap_refresh
|
|
2018
|
+
) === "accept_empty_bootstrap";
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
async function waitForHealthyRecommend(client, config, {
|
|
2022
|
+
timeoutMs = 90000,
|
|
2023
|
+
intervalMs = 1000,
|
|
2024
|
+
refreshSettleMs = 5000,
|
|
2025
|
+
refreshNavigationTimeoutMs = 20000
|
|
2026
|
+
} = {}) {
|
|
2027
|
+
const started = Date.now();
|
|
2028
|
+
let lastCheck = null;
|
|
2029
|
+
let refreshEvidence = null;
|
|
2030
|
+
while (Date.now() - started <= timeoutMs) {
|
|
2031
|
+
const loginDetection = await detectBossLoginState(client).catch(() => null);
|
|
2032
|
+
if (loginDetection?.requires_login) {
|
|
2033
|
+
return attachRecommendEmptyBootstrapRefreshEvidence({
|
|
2034
|
+
status: "login_required",
|
|
2035
|
+
summary: "Boss login is required",
|
|
2036
|
+
loginDetection
|
|
2037
|
+
}, refreshEvidence);
|
|
2038
|
+
}
|
|
2039
|
+
const roots = await resolveRecommendSelfHealRoots(client, config);
|
|
1574
2040
|
lastCheck = await runSelfHealCheck({
|
|
1575
2041
|
client,
|
|
1576
2042
|
domain: "recommend",
|
|
1577
2043
|
roots: roots.roots,
|
|
1578
2044
|
selectorProbes: config.selectorProbes,
|
|
1579
|
-
accessibilityProbes: config.accessibilityProbes,
|
|
1580
|
-
viewportProbes: config.viewportProbes
|
|
1581
|
-
});
|
|
1582
|
-
if (lastCheck.status === HEALTH_STATUS.HEALTHY)
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
2045
|
+
accessibilityProbes: config.accessibilityProbes,
|
|
2046
|
+
viewportProbes: config.viewportProbes
|
|
2047
|
+
});
|
|
2048
|
+
if (lastCheck.status === HEALTH_STATUS.HEALTHY) {
|
|
2049
|
+
return attachRecommendEmptyBootstrapRefreshEvidence(lastCheck, refreshEvidence);
|
|
2050
|
+
}
|
|
2051
|
+
let emptyState = null;
|
|
2052
|
+
if (hasOnlyCandidateCardsRequiredFailure(lastCheck) && roots.roots.frame) {
|
|
2053
|
+
emptyState = await inspectRecommendFilteredEmptyState(
|
|
2054
|
+
client,
|
|
2055
|
+
roots.roots.frame
|
|
2056
|
+
).catch((error) => ({
|
|
2057
|
+
verified: false,
|
|
2058
|
+
reason: "filtered_empty_state_probe_failed",
|
|
2059
|
+
error: error?.message || String(error)
|
|
2060
|
+
}));
|
|
2061
|
+
}
|
|
2062
|
+
const action = getRecommendEmptyBootstrapPreflightAction(
|
|
2063
|
+
lastCheck,
|
|
2064
|
+
emptyState,
|
|
2065
|
+
refreshEvidence
|
|
2066
|
+
);
|
|
2067
|
+
if (action === "refresh") {
|
|
2068
|
+
const refreshStarted = Date.now();
|
|
2069
|
+
refreshEvidence = {
|
|
2070
|
+
attempted: true,
|
|
2071
|
+
completed: false,
|
|
2072
|
+
ok: false,
|
|
2073
|
+
method: "Page.navigate",
|
|
2074
|
+
target_url: RECOMMEND_TARGET_URL,
|
|
2075
|
+
before: {
|
|
2076
|
+
health: compactHealth(lastCheck),
|
|
2077
|
+
empty_state: compactRecommendEmptyStateEvidence(emptyState)
|
|
2078
|
+
}
|
|
2079
|
+
};
|
|
2080
|
+
try {
|
|
2081
|
+
const navigationResult = await client.Page.navigate({ url: RECOMMEND_TARGET_URL });
|
|
2082
|
+
if (navigationResult?.errorText) {
|
|
2083
|
+
throw new Error(navigationResult.errorText);
|
|
2084
|
+
}
|
|
2085
|
+
const waited = await waitForMainFrameUrl(
|
|
2086
|
+
client,
|
|
2087
|
+
(url) => isBossLoginUrl(url) || !shouldNavigateToRecommend(url),
|
|
2088
|
+
{
|
|
2089
|
+
timeoutMs: refreshNavigationTimeoutMs,
|
|
2090
|
+
intervalMs: Math.max(250, Math.min(1000, intervalMs))
|
|
2091
|
+
}
|
|
2092
|
+
);
|
|
2093
|
+
if (refreshSettleMs > 0) await sleep(refreshSettleMs);
|
|
2094
|
+
refreshEvidence = {
|
|
2095
|
+
...refreshEvidence,
|
|
2096
|
+
completed: true,
|
|
2097
|
+
ok: waited?.ok === true,
|
|
2098
|
+
navigation: {
|
|
2099
|
+
frame_id: navigationResult?.frameId || null,
|
|
2100
|
+
loader_id: navigationResult?.loaderId || null,
|
|
2101
|
+
observed_url: waited?.url || null,
|
|
2102
|
+
observed_url_ok: waited?.ok === true
|
|
2103
|
+
},
|
|
2104
|
+
elapsed_ms: Date.now() - refreshStarted
|
|
2105
|
+
};
|
|
2106
|
+
} catch (error) {
|
|
2107
|
+
refreshEvidence = {
|
|
2108
|
+
...refreshEvidence,
|
|
2109
|
+
completed: true,
|
|
2110
|
+
ok: false,
|
|
2111
|
+
error: error?.message || String(error),
|
|
2112
|
+
elapsed_ms: Date.now() - refreshStarted
|
|
2113
|
+
};
|
|
2114
|
+
}
|
|
2115
|
+
continue;
|
|
2116
|
+
}
|
|
2117
|
+
if (action === "accept_empty_bootstrap") {
|
|
2118
|
+
const accepted = acceptRecommendEmptyBootstrapHealth(
|
|
2119
|
+
lastCheck,
|
|
2120
|
+
emptyState,
|
|
2121
|
+
refreshEvidence
|
|
2122
|
+
);
|
|
2123
|
+
if (accepted) return accepted;
|
|
2124
|
+
}
|
|
2125
|
+
await sleep(intervalMs);
|
|
2126
|
+
}
|
|
2127
|
+
return attachRecommendEmptyBootstrapRefreshEvidence(lastCheck, refreshEvidence);
|
|
2128
|
+
}
|
|
1587
2129
|
|
|
1588
2130
|
function shouldNavigateToRecommend(url) {
|
|
1589
2131
|
return !String(url || "").includes("/web/chat/recommend");
|
|
@@ -1677,10 +2219,12 @@ async function connectRecommendChromeSession({
|
|
|
1677
2219
|
}
|
|
1678
2220
|
|
|
1679
2221
|
const selfHealConfig = buildRecommendSelfHealConfig();
|
|
1680
|
-
const health = await waitForHealthyRecommend(client, selfHealConfig, {
|
|
1681
|
-
timeoutMs: slowLive ? 180000 : 90000,
|
|
1682
|
-
intervalMs: slowLive ? 1200 : 800
|
|
1683
|
-
|
|
2222
|
+
const health = await waitForHealthyRecommend(client, selfHealConfig, {
|
|
2223
|
+
timeoutMs: slowLive ? 180000 : 90000,
|
|
2224
|
+
intervalMs: slowLive ? 1200 : 800,
|
|
2225
|
+
refreshSettleMs: slowLive ? 12000 : 5000,
|
|
2226
|
+
refreshNavigationTimeoutMs: slowLive ? 45000 : 20000
|
|
2227
|
+
});
|
|
1684
2228
|
if (health?.loginDetection?.requires_login) {
|
|
1685
2229
|
await session.close?.();
|
|
1686
2230
|
throw createBossLoginRequiredError({
|
|
@@ -1691,7 +2235,7 @@ async function connectRecommendChromeSession({
|
|
|
1691
2235
|
chrome: session.chrome || null
|
|
1692
2236
|
});
|
|
1693
2237
|
}
|
|
1694
|
-
if (!health
|
|
2238
|
+
if (!isRecommendConnectorHealthAccepted(health)) {
|
|
1695
2239
|
const latestUrl = await getMainFrameUrl(client).catch(() => currentUrl);
|
|
1696
2240
|
const latestLoginDetection = await detectBossLoginState(client, { currentUrl: latestUrl }).catch(() => ({
|
|
1697
2241
|
requires_login: isBossLoginUrl(latestUrl),
|
|
@@ -1935,55 +2479,59 @@ function buildRecommendFilter(parsed, args = {}) {
|
|
|
1935
2479
|
return { enabled: false };
|
|
1936
2480
|
}
|
|
1937
2481
|
|
|
1938
|
-
const groups = [{
|
|
1939
|
-
group: "activity",
|
|
1940
|
-
labels: [parsed.searchParams?.activity_level || "不限"],
|
|
1941
|
-
selectAllLabels: false,
|
|
1942
|
-
allowUnlimited: true,
|
|
1943
|
-
verifySticky: true
|
|
1944
|
-
}];
|
|
2482
|
+
const groups = [{
|
|
2483
|
+
group: "activity",
|
|
2484
|
+
labels: [parsed.searchParams?.activity_level || "不限"],
|
|
2485
|
+
selectAllLabels: false,
|
|
2486
|
+
allowUnlimited: true,
|
|
2487
|
+
verifySticky: true
|
|
2488
|
+
}];
|
|
1945
2489
|
const recentNotView = withoutUnlimited(parsed.searchParams?.recent_not_view);
|
|
1946
2490
|
if (recentNotView.length) {
|
|
1947
|
-
groups.push({
|
|
1948
|
-
group: "recentNotView",
|
|
1949
|
-
labels: recentNotView,
|
|
1950
|
-
selectAllLabels: true
|
|
1951
|
-
|
|
2491
|
+
groups.push({
|
|
2492
|
+
group: "recentNotView",
|
|
2493
|
+
labels: recentNotView,
|
|
2494
|
+
selectAllLabels: true,
|
|
2495
|
+
verifySticky: true
|
|
2496
|
+
});
|
|
1952
2497
|
}
|
|
1953
2498
|
|
|
1954
2499
|
const degree = withoutUnlimited(parsed.searchParams?.degree);
|
|
1955
2500
|
if (degree.length) {
|
|
1956
|
-
groups.push({
|
|
1957
|
-
group: "degree",
|
|
1958
|
-
labels: degree,
|
|
1959
|
-
selectAllLabels: true
|
|
1960
|
-
|
|
2501
|
+
groups.push({
|
|
2502
|
+
group: "degree",
|
|
2503
|
+
labels: degree,
|
|
2504
|
+
selectAllLabels: true,
|
|
2505
|
+
verifySticky: true
|
|
2506
|
+
});
|
|
1961
2507
|
}
|
|
1962
2508
|
|
|
1963
2509
|
const gender = withoutUnlimited(parsed.searchParams?.gender);
|
|
1964
2510
|
if (gender.length) {
|
|
1965
|
-
groups.push({
|
|
1966
|
-
group: "gender",
|
|
1967
|
-
labels: gender,
|
|
1968
|
-
selectAllLabels: true
|
|
1969
|
-
|
|
2511
|
+
groups.push({
|
|
2512
|
+
group: "gender",
|
|
2513
|
+
labels: gender,
|
|
2514
|
+
selectAllLabels: true,
|
|
2515
|
+
verifySticky: true
|
|
2516
|
+
});
|
|
1970
2517
|
}
|
|
1971
2518
|
|
|
1972
2519
|
const school = withoutUnlimited(parsed.searchParams?.school_tag);
|
|
1973
2520
|
if (school.length) {
|
|
1974
|
-
groups.push({
|
|
1975
|
-
group: "school",
|
|
1976
|
-
labels: school,
|
|
1977
|
-
selectAllLabels: true
|
|
1978
|
-
|
|
2521
|
+
groups.push({
|
|
2522
|
+
group: "school",
|
|
2523
|
+
labels: school,
|
|
2524
|
+
selectAllLabels: true,
|
|
2525
|
+
verifySticky: true
|
|
2526
|
+
});
|
|
1979
2527
|
}
|
|
1980
2528
|
|
|
1981
|
-
return {
|
|
1982
|
-
enabled: true,
|
|
1983
|
-
currentCityOnly: parsed.searchParams?.current_city_only === true,
|
|
1984
|
-
filterGroups: groups
|
|
1985
|
-
};
|
|
1986
|
-
}
|
|
2529
|
+
return {
|
|
2530
|
+
enabled: true,
|
|
2531
|
+
currentCityOnly: parsed.searchParams?.current_city_only === true,
|
|
2532
|
+
filterGroups: groups
|
|
2533
|
+
};
|
|
2534
|
+
}
|
|
1987
2535
|
|
|
1988
2536
|
function normalizeRecommendStartInput(args = {}, parsed, configResolution = null) {
|
|
1989
2537
|
const confirmation = args.confirmation || {};
|
|
@@ -2017,7 +2565,14 @@ function normalizeRecommendStartInput(args = {}, parsed, configResolution = null
|
|
|
2017
2565
|
};
|
|
2018
2566
|
}
|
|
2019
2567
|
|
|
2020
|
-
function getRunOptions(
|
|
2568
|
+
function getRunOptions(
|
|
2569
|
+
args,
|
|
2570
|
+
parsed,
|
|
2571
|
+
normalized,
|
|
2572
|
+
session,
|
|
2573
|
+
configResolution = null,
|
|
2574
|
+
actionJournalScope = ""
|
|
2575
|
+
) {
|
|
2021
2576
|
const slowLive = args.slow_live === true;
|
|
2022
2577
|
const executePostAction = args.dry_run_post_action === true
|
|
2023
2578
|
? false
|
|
@@ -2052,8 +2607,16 @@ function getRunOptions(args, parsed, normalized, session, configResolution = nul
|
|
|
2052
2607
|
maxGreetCount: normalized.maxGreetCount,
|
|
2053
2608
|
executePostAction,
|
|
2054
2609
|
actionTimeoutMs: parsePositiveInteger(args.action_timeout_ms, slowLive ? 12000 : 8000),
|
|
2055
|
-
actionIntervalMs: parsePositiveInteger(args.action_interval_ms, 500),
|
|
2056
|
-
actionAfterClickDelayMs: parseNonNegativeInteger(args.action_after_click_delay_ms, slowLive ? 1200 : 900),
|
|
2610
|
+
actionIntervalMs: parsePositiveInteger(args.action_interval_ms, 500),
|
|
2611
|
+
actionAfterClickDelayMs: parseNonNegativeInteger(args.action_after_click_delay_ms, slowLive ? 1200 : 900),
|
|
2612
|
+
actionJournalScope,
|
|
2613
|
+
reverifyActionJournalScope: actionJournalScope
|
|
2614
|
+
? async () => resolveRecommendActionJournalScope({
|
|
2615
|
+
host: normalized.host,
|
|
2616
|
+
port: normalized.port,
|
|
2617
|
+
strictFresh: true
|
|
2618
|
+
})
|
|
2619
|
+
: null,
|
|
2057
2620
|
screeningMode: normalized.screeningMode,
|
|
2058
2621
|
llmConfig: normalized.screeningMode === "llm" && configResolution?.ok ? {
|
|
2059
2622
|
...configResolution.config
|
|
@@ -2071,23 +2634,23 @@ function getRunOptions(args, parsed, normalized, session, configResolution = nul
|
|
|
2071
2634
|
) || "low",
|
|
2072
2635
|
imageOutputDir: resolveBossConfiguredOutputDir("", getRunsDir()),
|
|
2073
2636
|
humanRestEnabled: humanBehavior.restEnabled,
|
|
2074
|
-
humanBehavior,
|
|
2075
|
-
skipRecentColleagueContacted: normalized.skipRecentColleagueContacted,
|
|
2076
|
-
colleagueContactWindowDays: normalized.colleagueContactWindowDays,
|
|
2077
|
-
debugTestMode: isDebugTestMode(args),
|
|
2078
|
-
debugForceListEndAfterProcessed: parsePositiveInteger(
|
|
2079
|
-
args.debug_force_list_end_after_processed,
|
|
2080
|
-
null
|
|
2081
|
-
),
|
|
2082
|
-
debugForceContextRecoveryAfterProcessed: parsePositiveInteger(
|
|
2083
|
-
args.debug_force_context_recovery_after_processed,
|
|
2084
|
-
null
|
|
2085
|
-
),
|
|
2086
|
-
debugForceCdpReconnectAfterProcessed: parsePositiveInteger(
|
|
2087
|
-
args.debug_force_cdp_reconnect_after_processed,
|
|
2088
|
-
null
|
|
2089
|
-
),
|
|
2090
|
-
name: "mcp-recommend-pipeline-run",
|
|
2637
|
+
humanBehavior,
|
|
2638
|
+
skipRecentColleagueContacted: normalized.skipRecentColleagueContacted,
|
|
2639
|
+
colleagueContactWindowDays: normalized.colleagueContactWindowDays,
|
|
2640
|
+
debugTestMode: isDebugTestMode(args),
|
|
2641
|
+
debugForceListEndAfterProcessed: parsePositiveInteger(
|
|
2642
|
+
args.debug_force_list_end_after_processed,
|
|
2643
|
+
null
|
|
2644
|
+
),
|
|
2645
|
+
debugForceContextRecoveryAfterProcessed: parsePositiveInteger(
|
|
2646
|
+
args.debug_force_context_recovery_after_processed,
|
|
2647
|
+
null
|
|
2648
|
+
),
|
|
2649
|
+
debugForceCdpReconnectAfterProcessed: parsePositiveInteger(
|
|
2650
|
+
args.debug_force_cdp_reconnect_after_processed,
|
|
2651
|
+
null
|
|
2652
|
+
),
|
|
2653
|
+
name: "mcp-recommend-pipeline-run",
|
|
2091
2654
|
parsed
|
|
2092
2655
|
};
|
|
2093
2656
|
}
|
|
@@ -2135,14 +2698,27 @@ function prepareRecommendPipelineStart(args = {}, { workspaceRoot = "" } = {}) {
|
|
|
2135
2698
|
};
|
|
2136
2699
|
}
|
|
2137
2700
|
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2701
|
+
export function shouldPreserveRecommendDetailOnTerminal(snapshot = null) {
|
|
2702
|
+
return snapshot?.checkpoint?.preserve_detail_on_terminal === true
|
|
2703
|
+
&& snapshot?.checkpoint?.action_result_critical_persisted !== true;
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
async function closeRecommendRunSession(runId) {
|
|
2707
|
+
const meta = recommendRunMeta.get(runId);
|
|
2708
|
+
if (!meta || meta.closed) return;
|
|
2709
|
+
try {
|
|
2710
|
+
try {
|
|
2711
|
+
let preserveDetail = false;
|
|
2712
|
+
try {
|
|
2713
|
+
const snapshot = recommendRunService.getRecommendRun(runId);
|
|
2714
|
+
preserveDetail = shouldPreserveRecommendDetailOnTerminal(snapshot);
|
|
2715
|
+
} catch {
|
|
2716
|
+
// If the in-memory run is unavailable, retain the existing best-effort cleanup policy.
|
|
2717
|
+
}
|
|
2718
|
+
meta.preserve_detail_on_terminal = preserveDetail;
|
|
2719
|
+
if (meta.session?.client && !preserveDetail) {
|
|
2720
|
+
await closeRecommendDetail(meta.session.client, { attemptsLimit: 2 });
|
|
2721
|
+
}
|
|
2146
2722
|
} catch {
|
|
2147
2723
|
// Cleanup is best-effort once the run has settled.
|
|
2148
2724
|
}
|
|
@@ -2222,12 +2798,46 @@ async function startRecommendPipelineRunInternal(args = {}, { workspaceRoot = ""
|
|
|
2222
2798
|
},
|
|
2223
2799
|
chrome: error?.chrome || null
|
|
2224
2800
|
};
|
|
2225
|
-
}
|
|
2226
|
-
|
|
2227
|
-
let
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
let actionJournalScope = "";
|
|
2804
|
+
let actionProfileIdentity = null;
|
|
2805
|
+
const liveGreetingRequested = normalized.postAction === "greet"
|
|
2806
|
+
&& args.dry_run_post_action !== true
|
|
2807
|
+
&& args.execute_post_action !== false;
|
|
2808
|
+
if (liveGreetingRequested) {
|
|
2809
|
+
try {
|
|
2810
|
+
const resolvedScope = await resolveRecommendActionJournalScope({
|
|
2811
|
+
host: normalized.host,
|
|
2812
|
+
port: normalized.port,
|
|
2813
|
+
session
|
|
2814
|
+
});
|
|
2815
|
+
actionJournalScope = resolvedScope.scope;
|
|
2816
|
+
actionProfileIdentity = resolvedScope.identity;
|
|
2817
|
+
} catch (error) {
|
|
2818
|
+
await session.close?.();
|
|
2819
|
+
return {
|
|
2820
|
+
status: "FAILED",
|
|
2821
|
+
error: {
|
|
2822
|
+
code: error?.code || "RECOMMEND_ACTION_PROFILE_IDENTITY_UNVERIFIED",
|
|
2823
|
+
message: error?.message || "Exact Chrome profile identity could not be proven",
|
|
2824
|
+
retryable: false
|
|
2825
|
+
}
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
let started;
|
|
2831
|
+
try {
|
|
2832
|
+
started = recommendRunService.startRecommendRun({
|
|
2833
|
+
...getRunOptions(
|
|
2834
|
+
args,
|
|
2835
|
+
parsed,
|
|
2836
|
+
normalized,
|
|
2837
|
+
session,
|
|
2838
|
+
configResolution,
|
|
2839
|
+
actionJournalScope
|
|
2840
|
+
),
|
|
2231
2841
|
runId: fixedRunId || undefined,
|
|
2232
2842
|
pid: process.pid
|
|
2233
2843
|
});
|
|
@@ -2254,8 +2864,9 @@ async function startRecommendPipelineRunInternal(args = {}, { workspaceRoot = ""
|
|
|
2254
2864
|
host: normalized.host,
|
|
2255
2865
|
port: normalized.port,
|
|
2256
2866
|
target_url: session.navigation?.url || session.target?.url || RECOMMEND_TARGET_URL,
|
|
2257
|
-
target_id: session.target?.id || null,
|
|
2258
|
-
auto_launch: session.chrome || null
|
|
2867
|
+
target_id: session.target?.id || null,
|
|
2868
|
+
auto_launch: session.chrome || null,
|
|
2869
|
+
action_profile_identity: actionProfileIdentity
|
|
2259
2870
|
},
|
|
2260
2871
|
health: session.health || null
|
|
2261
2872
|
});
|
|
@@ -2379,19 +2990,19 @@ export function getRecommendPipelineRunTool({ args = {} } = {}) {
|
|
|
2379
2990
|
run: compactRecommendRunForStatus(normalizedRun)
|
|
2380
2991
|
}, runId);
|
|
2381
2992
|
} catch {
|
|
2382
|
-
const persisted = readRecommendRunState(runId);
|
|
2383
|
-
if (persisted) {
|
|
2384
|
-
const reconciled = compactRecommendRunForStatus(reconcilePersistedRecommendRunIfNeeded(persisted));
|
|
2385
|
-
const persistedEvidence = readRecommendRunState(runId) || persisted;
|
|
2386
|
-
return attachPersistedCdpEvidence({
|
|
2387
|
-
status: "RUN_STATUS",
|
|
2388
|
-
run: reconciled,
|
|
2389
|
-
persistence: {
|
|
2390
|
-
source: "disk",
|
|
2391
|
-
active_control_available: false,
|
|
2392
|
-
stale_process_reconciled: reconciled?.state !== persisted.state
|
|
2393
|
-
}
|
|
2394
|
-
}, persistedEvidence);
|
|
2993
|
+
const persisted = readRecommendRunState(runId);
|
|
2994
|
+
if (persisted) {
|
|
2995
|
+
const reconciled = compactRecommendRunForStatus(reconcilePersistedRecommendRunIfNeeded(persisted));
|
|
2996
|
+
const persistedEvidence = readRecommendRunState(runId) || persisted;
|
|
2997
|
+
return attachPersistedCdpEvidence({
|
|
2998
|
+
status: "RUN_STATUS",
|
|
2999
|
+
run: reconciled,
|
|
3000
|
+
persistence: {
|
|
3001
|
+
source: "disk",
|
|
3002
|
+
active_control_available: false,
|
|
3003
|
+
stale_process_reconciled: reconciled?.state !== persisted.state
|
|
3004
|
+
}
|
|
3005
|
+
}, persistedEvidence);
|
|
2395
3006
|
}
|
|
2396
3007
|
return {
|
|
2397
3008
|
status: "FAILED",
|
|
@@ -2432,13 +3043,13 @@ export function pauseRecommendPipelineRunTool({ args = {} } = {}) {
|
|
|
2432
3043
|
message: "暂停请求已接收,将在当前候选人处理完成后进入 paused。"
|
|
2433
3044
|
}, runId);
|
|
2434
3045
|
} catch {
|
|
2435
|
-
const persisted = readRecommendRunState(runId);
|
|
2436
|
-
if (persisted && TERMINAL_STATUSES.has(persisted.state)) {
|
|
2437
|
-
return attachPersistedCdpEvidence({
|
|
2438
|
-
status: "PAUSE_IGNORED",
|
|
2439
|
-
run: compactRecommendRunForStatus(persisted),
|
|
2440
|
-
message: "目标任务已结束,无需暂停。"
|
|
2441
|
-
}, persisted);
|
|
3046
|
+
const persisted = readRecommendRunState(runId);
|
|
3047
|
+
if (persisted && TERMINAL_STATUSES.has(persisted.state)) {
|
|
3048
|
+
return attachPersistedCdpEvidence({
|
|
3049
|
+
status: "PAUSE_IGNORED",
|
|
3050
|
+
run: compactRecommendRunForStatus(persisted),
|
|
3051
|
+
message: "目标任务已结束,无需暂停。"
|
|
3052
|
+
}, persisted);
|
|
2442
3053
|
}
|
|
2443
3054
|
return getRecommendPipelineRunTool({ args });
|
|
2444
3055
|
}
|
|
@@ -2486,11 +3097,11 @@ export function resumeRecommendPipelineRunTool({ args = {} } = {}) {
|
|
|
2486
3097
|
message: "已恢复 Recommend run,请使用 get_recommend_pipeline_run 按需轮询。"
|
|
2487
3098
|
}, runId);
|
|
2488
3099
|
} catch {
|
|
2489
|
-
const persisted = readRecommendRunState(runId);
|
|
2490
|
-
if (persisted) {
|
|
2491
|
-
return attachPersistedCdpEvidence({
|
|
2492
|
-
status: "FAILED",
|
|
2493
|
-
error: {
|
|
3100
|
+
const persisted = readRecommendRunState(runId);
|
|
3101
|
+
if (persisted) {
|
|
3102
|
+
return attachPersistedCdpEvidence({
|
|
3103
|
+
status: "FAILED",
|
|
3104
|
+
error: {
|
|
2494
3105
|
code: TERMINAL_STATUSES.has(persisted.state) ? "RUN_ALREADY_TERMINATED" : "RUN_NOT_ACTIVE",
|
|
2495
3106
|
message: TERMINAL_STATUSES.has(persisted.state)
|
|
2496
3107
|
? "目标任务已结束,无法继续。"
|
|
@@ -2498,11 +3109,11 @@ export function resumeRecommendPipelineRunTool({ args = {} } = {}) {
|
|
|
2498
3109
|
retryable: !TERMINAL_STATUSES.has(persisted.state)
|
|
2499
3110
|
},
|
|
2500
3111
|
run: compactRecommendRunForStatus(persisted),
|
|
2501
|
-
persistence: {
|
|
2502
|
-
source: "disk",
|
|
2503
|
-
active_control_available: false
|
|
2504
|
-
}
|
|
2505
|
-
}, persisted);
|
|
3112
|
+
persistence: {
|
|
3113
|
+
source: "disk",
|
|
3114
|
+
active_control_available: false
|
|
3115
|
+
}
|
|
3116
|
+
}, persisted);
|
|
2506
3117
|
}
|
|
2507
3118
|
return getRecommendPipelineRunTool({ args });
|
|
2508
3119
|
}
|
|
@@ -2528,13 +3139,13 @@ export function cancelRecommendPipelineRunTool({ args = {} } = {}) {
|
|
|
2528
3139
|
message: "已收到取消请求,将在当前候选人处理完成后安全停止。"
|
|
2529
3140
|
}, runId);
|
|
2530
3141
|
} catch {
|
|
2531
|
-
const persisted = readRecommendRunState(runId);
|
|
2532
|
-
if (persisted && TERMINAL_STATUSES.has(persisted.state)) {
|
|
2533
|
-
return attachPersistedCdpEvidence({
|
|
2534
|
-
status: "CANCEL_IGNORED",
|
|
2535
|
-
run: compactRecommendRunForStatus(persisted),
|
|
2536
|
-
message: "目标任务已结束,无需取消。"
|
|
2537
|
-
}, persisted);
|
|
3142
|
+
const persisted = readRecommendRunState(runId);
|
|
3143
|
+
if (persisted && TERMINAL_STATUSES.has(persisted.state)) {
|
|
3144
|
+
return attachPersistedCdpEvidence({
|
|
3145
|
+
status: "CANCEL_IGNORED",
|
|
3146
|
+
run: compactRecommendRunForStatus(persisted),
|
|
3147
|
+
message: "目标任务已结束,无需取消。"
|
|
3148
|
+
}, persisted);
|
|
2538
3149
|
}
|
|
2539
3150
|
const cancelMessage = "已收到取消请求,将由 detached worker 在下一个安全边界停止。";
|
|
2540
3151
|
const patched = patchPersistedRecommendRunControl(runId, {
|
|
@@ -2544,18 +3155,18 @@ export function cancelRecommendPipelineRunTool({ args = {} } = {}) {
|
|
|
2544
3155
|
cancel_requested: true
|
|
2545
3156
|
}, {
|
|
2546
3157
|
message: cancelMessage
|
|
2547
|
-
});
|
|
2548
|
-
if (patched) {
|
|
2549
|
-
return attachPersistedCdpEvidence({
|
|
2550
|
-
status: "CANCEL_REQUESTED",
|
|
2551
|
-
run: compactRecommendRunForStatus(patched),
|
|
3158
|
+
});
|
|
3159
|
+
if (patched) {
|
|
3160
|
+
return attachPersistedCdpEvidence({
|
|
3161
|
+
status: "CANCEL_REQUESTED",
|
|
3162
|
+
run: compactRecommendRunForStatus(patched),
|
|
2552
3163
|
message: cancelMessage,
|
|
2553
3164
|
persistence: {
|
|
2554
3165
|
source: "disk",
|
|
2555
|
-
active_control_available: false,
|
|
2556
|
-
detached_control_requested: true
|
|
2557
|
-
}
|
|
2558
|
-
}, patched);
|
|
3166
|
+
active_control_available: false,
|
|
3167
|
+
detached_control_requested: true
|
|
3168
|
+
}
|
|
3169
|
+
}, patched);
|
|
2559
3170
|
}
|
|
2560
3171
|
return getRecommendPipelineRunTool({ args });
|
|
2561
3172
|
}
|