@reconcrap/boss-recommend-mcp 2.1.24 → 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.
@@ -1,4 +1,5 @@
1
- import fs from "node:fs";
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
5
  import {
@@ -9,7 +10,8 @@ import {
9
10
  detectBossLoginState,
10
11
  enableDomains,
11
12
  FORBIDDEN_CDP_METHODS,
12
- getMainFrameUrl,
13
+ getMainFrameUrl,
14
+ inspectChromeDebugCommandLine,
13
15
  isBossLoginUrl,
14
16
  waitForMainFrameUrl,
15
17
  sleep
@@ -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
- listRecommendJobOptions,
41
- RECOMMEND_TARGET_URL,
42
- runRecommendWorkflow
43
- } from "./domains/recommend/index.js";
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
 
@@ -354,6 +357,168 @@ function compactMethodLogForStatus(methodLog = []) {
354
357
  .filter((entry) => Boolean(entry.method))
355
358
  .slice(-STATUS_METHOD_LOG_TAIL_LIMIT);
356
359
  }
360
+
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;
366
+ }
367
+
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();
374
+ }
375
+ return "";
376
+ }
377
+
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];
397
+ }
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;
404
+ }
405
+ return {
406
+ user_data_dir: process.platform === "win32" ? resolvedUserDataDir.toLowerCase() : resolvedUserDataDir,
407
+ profile_directory: relativeProfile.replaceAll("\\", "/")
408
+ };
409
+ }
410
+
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");
511
+ return {
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
+ }
520
+ };
521
+ }
357
522
 
358
523
  function clonePlain(value, fallback = null) {
359
524
  try {
@@ -1728,51 +1893,239 @@ export async function listRecommendJobsTool({ workspaceRoot = "", args = {} } =
1728
1893
  }
1729
1894
  }
1730
1895
 
1731
- function compactHealth(check) {
1732
- if (!check) return null;
1733
- return {
1734
- status: check.status,
1735
- summary: check.summary,
1736
- drift_report: check.drift_report,
1737
- probes: (check.probes || []).map((probe) => ({
1738
- id: probe.id,
1739
- type: probe.type,
1740
- status: probe.status,
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,
1741
1908
  count: probe.count,
1742
1909
  required: probe.required
1743
1910
  }))
1744
- };
1745
- }
1746
-
1747
- async function waitForHealthyRecommend(client, config, {
1748
- timeoutMs = 90000,
1749
- intervalMs = 1000
1750
- } = {}) {
1751
- const started = Date.now();
1752
- let lastCheck = null;
1753
- while (Date.now() - started <= timeoutMs) {
1754
- const loginDetection = await detectBossLoginState(client).catch(() => null);
1755
- if (loginDetection?.requires_login) {
1756
- return {
1757
- status: "login_required",
1758
- summary: "Boss login is required",
1759
- loginDetection
1760
- };
1761
- }
1762
- const roots = await resolveRecommendSelfHealRoots(client, config);
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);
1763
2040
  lastCheck = await runSelfHealCheck({
1764
2041
  client,
1765
2042
  domain: "recommend",
1766
2043
  roots: roots.roots,
1767
2044
  selectorProbes: config.selectorProbes,
1768
- accessibilityProbes: config.accessibilityProbes,
1769
- viewportProbes: config.viewportProbes
1770
- });
1771
- if (lastCheck.status === HEALTH_STATUS.HEALTHY) return lastCheck;
1772
- await sleep(intervalMs);
1773
- }
1774
- return lastCheck;
1775
- }
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
+ }
1776
2129
 
1777
2130
  function shouldNavigateToRecommend(url) {
1778
2131
  return !String(url || "").includes("/web/chat/recommend");
@@ -1866,10 +2219,12 @@ async function connectRecommendChromeSession({
1866
2219
  }
1867
2220
 
1868
2221
  const selfHealConfig = buildRecommendSelfHealConfig();
1869
- const health = await waitForHealthyRecommend(client, selfHealConfig, {
1870
- timeoutMs: slowLive ? 180000 : 90000,
1871
- intervalMs: slowLive ? 1200 : 800
1872
- });
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
+ });
1873
2228
  if (health?.loginDetection?.requires_login) {
1874
2229
  await session.close?.();
1875
2230
  throw createBossLoginRequiredError({
@@ -1880,7 +2235,7 @@ async function connectRecommendChromeSession({
1880
2235
  chrome: session.chrome || null
1881
2236
  });
1882
2237
  }
1883
- if (!health || health.status !== HEALTH_STATUS.HEALTHY) {
2238
+ if (!isRecommendConnectorHealthAccepted(health)) {
1884
2239
  const latestUrl = await getMainFrameUrl(client).catch(() => currentUrl);
1885
2240
  const latestLoginDetection = await detectBossLoginState(client, { currentUrl: latestUrl }).catch(() => ({
1886
2241
  requires_login: isBossLoginUrl(latestUrl),
@@ -2133,38 +2488,42 @@ function buildRecommendFilter(parsed, args = {}) {
2133
2488
  }];
2134
2489
  const recentNotView = withoutUnlimited(parsed.searchParams?.recent_not_view);
2135
2490
  if (recentNotView.length) {
2136
- groups.push({
2137
- group: "recentNotView",
2138
- labels: recentNotView,
2139
- selectAllLabels: true
2140
- });
2491
+ groups.push({
2492
+ group: "recentNotView",
2493
+ labels: recentNotView,
2494
+ selectAllLabels: true,
2495
+ verifySticky: true
2496
+ });
2141
2497
  }
2142
2498
 
2143
2499
  const degree = withoutUnlimited(parsed.searchParams?.degree);
2144
2500
  if (degree.length) {
2145
- groups.push({
2146
- group: "degree",
2147
- labels: degree,
2148
- selectAllLabels: true
2149
- });
2501
+ groups.push({
2502
+ group: "degree",
2503
+ labels: degree,
2504
+ selectAllLabels: true,
2505
+ verifySticky: true
2506
+ });
2150
2507
  }
2151
2508
 
2152
2509
  const gender = withoutUnlimited(parsed.searchParams?.gender);
2153
2510
  if (gender.length) {
2154
- groups.push({
2155
- group: "gender",
2156
- labels: gender,
2157
- selectAllLabels: true
2158
- });
2511
+ groups.push({
2512
+ group: "gender",
2513
+ labels: gender,
2514
+ selectAllLabels: true,
2515
+ verifySticky: true
2516
+ });
2159
2517
  }
2160
2518
 
2161
2519
  const school = withoutUnlimited(parsed.searchParams?.school_tag);
2162
2520
  if (school.length) {
2163
- groups.push({
2164
- group: "school",
2165
- labels: school,
2166
- selectAllLabels: true
2167
- });
2521
+ groups.push({
2522
+ group: "school",
2523
+ labels: school,
2524
+ selectAllLabels: true,
2525
+ verifySticky: true
2526
+ });
2168
2527
  }
2169
2528
 
2170
2529
  return {
@@ -2206,7 +2565,14 @@ function normalizeRecommendStartInput(args = {}, parsed, configResolution = null
2206
2565
  };
2207
2566
  }
2208
2567
 
2209
- function getRunOptions(args, parsed, normalized, session, configResolution = null) {
2568
+ function getRunOptions(
2569
+ args,
2570
+ parsed,
2571
+ normalized,
2572
+ session,
2573
+ configResolution = null,
2574
+ actionJournalScope = ""
2575
+ ) {
2210
2576
  const slowLive = args.slow_live === true;
2211
2577
  const executePostAction = args.dry_run_post_action === true
2212
2578
  ? false
@@ -2241,8 +2607,16 @@ function getRunOptions(args, parsed, normalized, session, configResolution = nul
2241
2607
  maxGreetCount: normalized.maxGreetCount,
2242
2608
  executePostAction,
2243
2609
  actionTimeoutMs: parsePositiveInteger(args.action_timeout_ms, slowLive ? 12000 : 8000),
2244
- actionIntervalMs: parsePositiveInteger(args.action_interval_ms, 500),
2245
- 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,
2246
2620
  screeningMode: normalized.screeningMode,
2247
2621
  llmConfig: normalized.screeningMode === "llm" && configResolution?.ok ? {
2248
2622
  ...configResolution.config
@@ -2324,14 +2698,27 @@ function prepareRecommendPipelineStart(args = {}, { workspaceRoot = "" } = {}) {
2324
2698
  };
2325
2699
  }
2326
2700
 
2327
- async function closeRecommendRunSession(runId) {
2328
- const meta = recommendRunMeta.get(runId);
2329
- if (!meta || meta.closed) return;
2330
- try {
2331
- try {
2332
- if (meta.session?.client) {
2333
- await closeRecommendDetail(meta.session.client, { attemptsLimit: 2 });
2334
- }
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
+ }
2335
2722
  } catch {
2336
2723
  // Cleanup is best-effort once the run has settled.
2337
2724
  }
@@ -2411,12 +2798,46 @@ async function startRecommendPipelineRunInternal(args = {}, { workspaceRoot = ""
2411
2798
  },
2412
2799
  chrome: error?.chrome || null
2413
2800
  };
2414
- }
2415
-
2416
- let started;
2417
- try {
2418
- started = recommendRunService.startRecommendRun({
2419
- ...getRunOptions(args, parsed, normalized, session, configResolution),
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
+ ),
2420
2841
  runId: fixedRunId || undefined,
2421
2842
  pid: process.pid
2422
2843
  });
@@ -2443,8 +2864,9 @@ async function startRecommendPipelineRunInternal(args = {}, { workspaceRoot = ""
2443
2864
  host: normalized.host,
2444
2865
  port: normalized.port,
2445
2866
  target_url: session.navigation?.url || session.target?.url || RECOMMEND_TARGET_URL,
2446
- target_id: session.target?.id || null,
2447
- auto_launch: session.chrome || null
2867
+ target_id: session.target?.id || null,
2868
+ auto_launch: session.chrome || null,
2869
+ action_profile_identity: actionProfileIdentity
2448
2870
  },
2449
2871
  health: session.health || null
2450
2872
  });