@reconcrap/boss-recommend-mcp 2.1.22 → 2.1.24

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.
Files changed (66) hide show
  1. package/README.md +5 -0
  2. package/bin/boss-recommend-mcp.js +4 -4
  3. package/package.json +14 -8
  4. package/scripts/install-macos.sh +280 -280
  5. package/scripts/postinstall.cjs +44 -44
  6. package/skills/boss-chat/README.md +43 -42
  7. package/skills/boss-chat/SKILL.md +107 -106
  8. package/skills/boss-recommend-pipeline/README.md +13 -13
  9. package/skills/boss-recommend-pipeline/SKILL.md +47 -47
  10. package/skills/boss-recruit-pipeline/README.md +19 -19
  11. package/skills/boss-recruit-pipeline/SKILL.md +89 -89
  12. package/src/chat-mcp.js +301 -127
  13. package/src/core/boss-cards/index.js +199 -199
  14. package/src/core/browser/index.js +291 -114
  15. package/src/core/capture/index.js +2930 -1201
  16. package/src/core/cv-acquisition/index.js +512 -238
  17. package/src/core/cv-capture-target/index.js +513 -299
  18. package/src/core/greet-quota/index.js +71 -71
  19. package/src/core/infinite-list/index.js +11 -2
  20. package/src/core/reporting/legacy-csv.js +12 -12
  21. package/src/core/run/detached-launcher.js +305 -0
  22. package/src/core/run/index.js +112 -42
  23. package/src/core/run/timing.js +33 -33
  24. package/src/core/run/windows-detached-worker.ps1 +106 -0
  25. package/src/core/screening/index.js +2135 -2135
  26. package/src/core/self-heal/index.js +989 -973
  27. package/src/core/self-heal/viewport.js +1505 -564
  28. package/src/detached-worker.js +99 -99
  29. package/src/domains/chat/action-journal.js +443 -0
  30. package/src/domains/chat/cards.js +137 -137
  31. package/src/domains/chat/constants.js +9 -9
  32. package/src/domains/chat/detail.js +1684 -401
  33. package/src/domains/chat/index.js +8 -7
  34. package/src/domains/chat/jobs.js +620 -620
  35. package/src/domains/chat/page-guard.js +157 -122
  36. package/src/domains/chat/roots.js +56 -56
  37. package/src/domains/chat/run-service.js +1801 -760
  38. package/src/domains/common/account-rights-panel.js +314 -314
  39. package/src/domains/common/recovery-settle.js +159 -159
  40. package/src/domains/recommend/actions.js +515 -472
  41. package/src/domains/recommend/cards.js +243 -243
  42. package/src/domains/recommend/colleague-contact.js +333 -333
  43. package/src/domains/recommend/constants.js +92 -92
  44. package/src/domains/recommend/detail.js +12 -3
  45. package/src/domains/recommend/filters.js +611 -611
  46. package/src/domains/recommend/index.js +9 -9
  47. package/src/domains/recommend/jobs.js +542 -542
  48. package/src/domains/recommend/location.js +736 -736
  49. package/src/domains/recommend/refresh.js +410 -329
  50. package/src/domains/recommend/roots.js +80 -80
  51. package/src/domains/recommend/run-service.js +1783 -592
  52. package/src/domains/recommend/scopes.js +246 -246
  53. package/src/domains/recruit/actions.js +277 -277
  54. package/src/domains/recruit/cards.js +74 -74
  55. package/src/domains/recruit/constants.js +236 -236
  56. package/src/domains/recruit/detail.js +588 -588
  57. package/src/domains/recruit/index.js +9 -9
  58. package/src/domains/recruit/instruction-parser.js +866 -866
  59. package/src/domains/recruit/refresh.js +45 -45
  60. package/src/domains/recruit/roots.js +68 -68
  61. package/src/domains/recruit/run-service.js +1817 -1620
  62. package/src/domains/recruit/search.js +3229 -3229
  63. package/src/index.js +124 -5
  64. package/src/parser.js +1296 -1296
  65. package/src/recommend-mcp.js +515 -80
  66. package/src/recommend-scheduler.js +66 -0
@@ -8,6 +8,7 @@ import {
8
8
  createBossLoginRequiredError,
9
9
  detectBossLoginState,
10
10
  enableDomains,
11
+ FORBIDDEN_CDP_METHODS,
11
12
  getMainFrameUrl,
12
13
  isBossLoginUrl,
13
14
  waitForMainFrameUrl,
@@ -284,6 +285,15 @@ function collectRecommendDebugTestOptions(args = {}, normalized = {}) {
284
285
  if (parseNonNegativeInteger(args.detail_limit, null) === 0) reasons.push("detail_limit=0");
285
286
  if (args.no_filter === true) reasons.push("no_filter");
286
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
+ }
287
297
  if (args.dry_run_post_action === true) reasons.push("dry_run_post_action");
288
298
  if (args.execute_post_action === false && normalized.postAction && normalized.postAction !== "none") {
289
299
  reasons.push("execute_post_action=false");
@@ -306,18 +316,44 @@ function resolveRecommendDetailLimit(args = {}, normalized = {}) {
306
316
  function methodSummary(methodLog = []) {
307
317
  const summary = {};
308
318
  for (const entry of methodLog || []) {
309
- summary[entry.method] = (summary[entry.method] || 0) + 1;
319
+ const method = compactCdpMethodName(entry?.method || entry || "");
320
+ if (!method) continue;
321
+ summary[method] = (summary[method] || 0) + 1;
310
322
  }
311
323
  return summary;
312
324
  }
313
325
 
314
- function compactMethodLogForStatus(methodLog = []) {
315
- if (!Array.isArray(methodLog)) return [];
316
- return methodLog.slice(-STATUS_METHOD_LOG_TAIL_LIMIT).map((entry) => ({
317
- method: normalizeText(entry?.method || entry || ""),
318
- at: normalizeText(entry?.at || "")
319
- }));
320
- }
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
+ const compact = {
344
+ method: compactCdpMethodName(entry?.method || entry || ""),
345
+ at: compactCdpMethodTimestamp(entry?.at || "")
346
+ };
347
+ for (const key of ["connection_epoch", "replay_of_connection_epoch"]) {
348
+ if (Number.isInteger(entry?.[key]) && entry[key] >= 0) compact[key] = entry[key];
349
+ }
350
+ const replayPolicy = normalizeText(entry?.replay_policy || "");
351
+ if (replayPolicy) compact.replay_policy = replayPolicy.slice(0, 100);
352
+ return compact;
353
+ })
354
+ .filter((entry) => Boolean(entry.method))
355
+ .slice(-STATUS_METHOD_LOG_TAIL_LIMIT);
356
+ }
321
357
 
322
358
  function clonePlain(value, fallback = null) {
323
359
  try {
@@ -336,6 +372,126 @@ function nonEmptyRecord(value) {
336
372
  return Object.keys(record).length ? record : null;
337
373
  }
338
374
 
375
+ function compactMethodSummaryForStatus(value = {}) {
376
+ const compact = {};
377
+ for (const [rawMethod, rawCount] of Object.entries(plainRecord(value))) {
378
+ const method = compactCdpMethodName(rawMethod);
379
+ const count = Number(rawCount);
380
+ if (!method || !Number.isInteger(count) || count < 1) continue;
381
+ compact[method] = count;
382
+ }
383
+ return compact;
384
+ }
385
+
386
+ function compactChromeEvidenceUrl(value) {
387
+ const text = normalizeText(value);
388
+ if (!text) return null;
389
+ try {
390
+ const parsed = new URL(text);
391
+ parsed.username = "";
392
+ parsed.password = "";
393
+ parsed.search = "";
394
+ parsed.hash = "";
395
+ return parsed.toString();
396
+ } catch {
397
+ return text.split(/[?#]/, 1)[0] || null;
398
+ }
399
+ }
400
+
401
+ function compactChromeAutoLaunchEvidence(value = {}) {
402
+ const source = plainRecord(value);
403
+ if (!Object.keys(source).length) return null;
404
+ const compact = {};
405
+ const booleanKeys = [
406
+ "launched",
407
+ "reused",
408
+ "guard_checked",
409
+ "required_flags_ok",
410
+ "replaced",
411
+ "target_created",
412
+ "fallback_target",
413
+ "fallback_any_page"
414
+ ];
415
+ for (const key of booleanKeys) {
416
+ if (typeof source[key] === "boolean") compact[key] = source[key];
417
+ }
418
+ for (const key of ["port", "target_count", "command_line_args_count"]) {
419
+ if (Number.isInteger(source[key]) && source[key] >= 0) compact[key] = source[key];
420
+ }
421
+ for (const key of ["close_method", "command_line_source"]) {
422
+ const text = normalizeText(source[key]);
423
+ if (text) compact[key] = text;
424
+ }
425
+ return Object.keys(compact).length ? compact : null;
426
+ }
427
+
428
+ function compactChromeEvidence(value = {}) {
429
+ const source = plainRecord(value);
430
+ if (!Object.keys(source).length) return null;
431
+ const compact = {};
432
+ const host = normalizeText(source.host);
433
+ const targetId = normalizeText(source.target_id);
434
+ const targetUrl = compactChromeEvidenceUrl(source.target_url);
435
+ if (host) compact.host = host;
436
+ if (Number.isInteger(source.port) && source.port > 0) compact.port = source.port;
437
+ if (targetUrl) compact.target_url = targetUrl;
438
+ if (targetId) compact.target_id = targetId;
439
+ const autoLaunch = compactChromeAutoLaunchEvidence(source.auto_launch);
440
+ if (autoLaunch) compact.auto_launch = autoLaunch;
441
+ return Object.keys(compact).length ? compact : null;
442
+ }
443
+
444
+ function methodEvidenceFlags(methodSummaryValue = {}) {
445
+ const methods = Object.keys(methodSummaryValue);
446
+ return {
447
+ runtime_evaluate_used: methods.some((method) => method.split(".", 1)[0] === "Runtime"),
448
+ script_injection_used: methods.some((method) => (
449
+ FORBIDDEN_CDP_METHODS.has(method.replace(/:retry_after_reconnect$/, ""))
450
+ ))
451
+ };
452
+ }
453
+
454
+ function buildPersistedCdpEvidence({ meta = {}, persisted = {} } = {}) {
455
+ const hasLiveMethodLog = Array.isArray(meta.methodLog);
456
+ const persistedMethodLog = compactMethodLogForStatus(persisted.method_log);
457
+ const methodLog = hasLiveMethodLog
458
+ ? compactMethodLogForStatus(meta.methodLog)
459
+ : persistedMethodLog;
460
+ const persistedSummary = compactMethodSummaryForStatus(persisted.method_summary);
461
+ const summary = hasLiveMethodLog
462
+ ? methodSummary(meta.methodLog)
463
+ : Object.keys(persistedSummary).length
464
+ ? persistedSummary
465
+ : methodSummary(methodLog);
466
+ const liveTotal = hasLiveMethodLog ? meta.methodLog.length : null;
467
+ const persistedTotal = Number(persisted.method_log_total);
468
+ const methodLogTotal = Number.isInteger(liveTotal)
469
+ ? liveTotal
470
+ : Number.isInteger(persistedTotal) && persistedTotal >= methodLog.length
471
+ ? persistedTotal
472
+ : methodLog.length;
473
+ const inferredFlags = methodEvidenceFlags(summary);
474
+ return {
475
+ runtime_evaluate_used: hasLiveMethodLog
476
+ ? inferredFlags.runtime_evaluate_used
477
+ : persisted.runtime_evaluate_used === true || inferredFlags.runtime_evaluate_used,
478
+ script_injection_used: hasLiveMethodLog
479
+ ? inferredFlags.script_injection_used
480
+ : persisted.script_injection_used === true || inferredFlags.script_injection_used,
481
+ method_summary: summary,
482
+ method_log: methodLog,
483
+ method_log_total: methodLogTotal,
484
+ chrome: compactChromeEvidence(meta.chrome || persisted.chrome)
485
+ };
486
+ }
487
+
488
+ function attachPersistedCdpEvidence(payload, persisted = {}) {
489
+ return {
490
+ ...payload,
491
+ ...buildPersistedCdpEvidence({ persisted })
492
+ };
493
+ }
494
+
339
495
  function normalizeRunId(runId) {
340
496
  const normalized = normalizeText(runId);
341
497
  if (!normalized || normalized.includes("/") || normalized.includes("\\")) return "";
@@ -382,12 +538,12 @@ function readJsonFile(filePath) {
382
538
 
383
539
  function recommendSearchParamsForCsv(searchParams = {}) {
384
540
  return {
385
- school_tag: Object.prototype.hasOwnProperty.call(searchParams, "school_tag") ? searchParams.school_tag : "不限",
386
- degree: Object.prototype.hasOwnProperty.call(searchParams, "degree") ? searchParams.degree : "不限",
387
- gender: Object.prototype.hasOwnProperty.call(searchParams, "gender") ? searchParams.gender : "不限",
388
- recent_not_view: Object.prototype.hasOwnProperty.call(searchParams, "recent_not_view") ? searchParams.recent_not_view : "不限",
389
- current_city_only: Object.prototype.hasOwnProperty.call(searchParams, "current_city_only") ? searchParams.current_city_only : false,
390
- activity_level: Object.prototype.hasOwnProperty.call(searchParams, "activity_level") ? searchParams.activity_level : "不限"
541
+ school_tag: Object.prototype.hasOwnProperty.call(searchParams, "school_tag") ? searchParams.school_tag : "不限",
542
+ degree: Object.prototype.hasOwnProperty.call(searchParams, "degree") ? searchParams.degree : "不限",
543
+ gender: Object.prototype.hasOwnProperty.call(searchParams, "gender") ? searchParams.gender : "不限",
544
+ recent_not_view: Object.prototype.hasOwnProperty.call(searchParams, "recent_not_view") ? searchParams.recent_not_view : "不限",
545
+ current_city_only: Object.prototype.hasOwnProperty.call(searchParams, "current_city_only") ? searchParams.current_city_only : false,
546
+ activity_level: Object.prototype.hasOwnProperty.call(searchParams, "activity_level") ? searchParams.activity_level : "不限"
391
547
  };
392
548
  }
393
549
 
@@ -426,12 +582,12 @@ function selectedRecommendJobForCsv(meta = {}, snapshot = {}) {
426
582
  function buildRecommendCsvInputRows(snapshot = {}, meta = {}) {
427
583
  const { context, confirmation, overrides, followUp, shared } = getSnapshotRequestContext(snapshot);
428
584
  const searchParams = recommendSearchParamsForCsv(meta.parsed?.searchParams || {
429
- school_tag: overrides.school_tag ?? confirmation.school_tag_value,
430
- degree: overrides.degree ?? confirmation.degree_value,
431
- gender: overrides.gender ?? confirmation.gender_value,
432
- recent_not_view: overrides.recent_not_view ?? confirmation.recent_not_view_value,
433
- current_city_only: overrides.current_city_only ?? false,
434
- activity_level: overrides.activity_level ?? "不限"
585
+ school_tag: overrides.school_tag ?? confirmation.school_tag_value,
586
+ degree: overrides.degree ?? confirmation.degree_value,
587
+ gender: overrides.gender ?? confirmation.gender_value,
588
+ recent_not_view: overrides.recent_not_view ?? confirmation.recent_not_view_value,
589
+ current_city_only: overrides.current_city_only ?? false,
590
+ activity_level: overrides.activity_level ?? "不限"
435
591
  });
436
592
  const parsedScreenParams = meta.parsed?.screenParams || {};
437
593
  const screenParams = {
@@ -506,7 +662,7 @@ function normalizeLegacyProgress(progress = {}, summary = null) {
506
662
  : Number.isInteger(summary?.passed)
507
663
  ? summary.passed
508
664
  : 0;
509
- return {
665
+ const normalized = {
510
666
  ...progress,
511
667
  processed,
512
668
  inspected: processed,
@@ -516,7 +672,28 @@ function normalizeLegacyProgress(progress = {}, summary = null) {
516
672
  greet_count: Number.isInteger(progress.greet_count) ? progress.greet_count : 0,
517
673
  post_action_clicked: Number.isInteger(progress.post_action_clicked) ? progress.post_action_clicked : 0
518
674
  };
519
- }
675
+ if (Object.prototype.hasOwnProperty.call(normalized, "last_list_read_stale_diagnostic")) {
676
+ normalized.last_list_read_stale_diagnostic = compactListReadStaleDiagnosticForStatus(
677
+ normalized.last_list_read_stale_diagnostic
678
+ );
679
+ }
680
+ if (Object.prototype.hasOwnProperty.call(normalized, "list_read_stale_diagnostics")) {
681
+ normalized.list_read_stale_diagnostics = compactListReadStaleDiagnosticsForStatus(
682
+ normalized.list_read_stale_diagnostics
683
+ );
684
+ }
685
+ if (Object.prototype.hasOwnProperty.call(normalized, "dom_stale_forensic")) {
686
+ normalized.dom_stale_forensic = compactDomStaleForensicForStatus(
687
+ normalized.dom_stale_forensic
688
+ );
689
+ }
690
+ if (Object.prototype.hasOwnProperty.call(normalized, "dom_stale_forensics")) {
691
+ normalized.dom_stale_forensics = compactDomStaleForensicsForStatus(
692
+ normalized.dom_stale_forensics
693
+ );
694
+ }
695
+ return normalized;
696
+ }
520
697
 
521
698
  const STATUS_COUNT_KEYS = [
522
699
  "processed",
@@ -550,6 +727,231 @@ function compactSmallRecord(value, fallback = null) {
550
727
  return clonePlain(record, fallback);
551
728
  }
552
729
 
730
+ function compactListReadStaleDiagnosticForStatus(value) {
731
+ const source = plainRecord(value);
732
+ if (!Object.keys(source).length) return null;
733
+ const compact = {};
734
+ for (const key of [
735
+ "name",
736
+ "code",
737
+ "message",
738
+ "phase",
739
+ "cdp_method",
740
+ "cdp_at",
741
+ "cdp_search_id",
742
+ "cdp_replay_policy",
743
+ "recovery_mode"
744
+ ]) {
745
+ const text = normalizeText(source[key] || "");
746
+ if (text) compact[key] = text.slice(0, key === "message" ? 500 : 200);
747
+ }
748
+ for (const key of [
749
+ "cdp_node_id",
750
+ "cdp_backend_node_id",
751
+ "cdp_connection_epoch",
752
+ "cdp_reconnected_epoch",
753
+ "attempt"
754
+ ]) {
755
+ if (Number.isInteger(source[key]) && source[key] >= 0) compact[key] = source[key];
756
+ }
757
+ for (const key of [
758
+ "exhausted",
759
+ "recovery_applied",
760
+ "recovered",
761
+ "cdp_replay_suppressed",
762
+ "cdp_outcome_unknown",
763
+ "cdp_reconnected",
764
+ "cdp_replayed_after_reconnect"
765
+ ]) {
766
+ if (typeof source[key] === "boolean") compact[key] = source[key];
767
+ }
768
+ for (const key of ["at", "recovery_applied_at", "recovered_at"]) {
769
+ const text = normalizeText(source[key] || "");
770
+ if (text && Number.isFinite(Date.parse(text))) compact[key] = new Date(text).toISOString();
771
+ }
772
+ if (Array.isArray(source.cdp_param_keys)) {
773
+ compact.cdp_param_keys = source.cdp_param_keys
774
+ .map((key) => normalizeText(key))
775
+ .filter((key) => /^[A-Za-z][A-Za-z0-9_]*$/.test(key))
776
+ .slice(0, 20);
777
+ }
778
+ if (source.cdp_reconnect_error !== undefined && source.cdp_reconnect_error !== null) {
779
+ const reconnectError = plainRecord(source.cdp_reconnect_error);
780
+ if (Object.keys(reconnectError).length) {
781
+ const safeReconnectError = {};
782
+ for (const key of ["name", "code", "message"]) {
783
+ const text = normalizeText(reconnectError[key] || "");
784
+ if (text) safeReconnectError[key] = text.slice(0, key === "message" ? 500 : 200);
785
+ }
786
+ if (Object.keys(safeReconnectError).length) compact.cdp_reconnect_error = safeReconnectError;
787
+ } else {
788
+ const reconnectErrorText = normalizeText(source.cdp_reconnect_error);
789
+ if (reconnectErrorText) compact.cdp_reconnect_error = reconnectErrorText.slice(0, 500);
790
+ }
791
+ }
792
+ return Object.keys(compact).length ? compact : null;
793
+ }
794
+
795
+ function compactDomStaleRootIdentityForStatus(value) {
796
+ const source = plainRecord(value);
797
+ if (!Object.keys(source).length) return null;
798
+ const compact = {};
799
+ for (const key of [
800
+ "connection_epoch",
801
+ "top_document_node_id",
802
+ "iframe_owner_node_id",
803
+ "iframe_document_node_id"
804
+ ]) {
805
+ if (Number.isInteger(source[key]) && source[key] >= 0) compact[key] = source[key];
806
+ }
807
+ const iframeSelector = normalizeText(source.iframe_selector || "");
808
+ if (iframeSelector) compact.iframe_selector = iframeSelector.slice(0, 300);
809
+ return Object.keys(compact).length ? compact : null;
810
+ }
811
+
812
+ function compactDomStaleNumericRecordForStatus(value) {
813
+ const source = plainRecord(value);
814
+ if (!Object.keys(source).length) return null;
815
+ const compact = {};
816
+ for (const [key, rawValue] of Object.entries(source)) {
817
+ if (!/^[a-z][a-z0-9_]*$/i.test(key)) continue;
818
+ if (!Number.isInteger(rawValue) || rawValue < 0) continue;
819
+ compact[key] = rawValue;
820
+ if (Object.keys(compact).length >= 30) break;
821
+ }
822
+ return Object.keys(compact).length ? compact : null;
823
+ }
824
+
825
+ function compactDomStaleLifecycleTimelineForStatus(value) {
826
+ if (!Array.isArray(value)) return [];
827
+ return value
828
+ .map((rawEntry) => {
829
+ const entry = plainRecord(rawEntry);
830
+ const compact = {};
831
+ const at = compactCdpMethodTimestamp(entry.at || "");
832
+ if (at) compact.at = at;
833
+ for (const key of ["type", "operation", "frame_id", "parent_frame_id", "loader_id"]) {
834
+ const text = normalizeText(entry[key] || "");
835
+ if (text) compact[key] = text.slice(0, 300);
836
+ }
837
+ if (Number.isInteger(entry.connection_epoch) && entry.connection_epoch >= 0) {
838
+ compact.connection_epoch = entry.connection_epoch;
839
+ }
840
+ const url = compactChromeEvidenceUrl(entry.url);
841
+ if (url) compact.url = url;
842
+ return Object.keys(compact).length ? compact : null;
843
+ })
844
+ .filter(Boolean)
845
+ .slice(-20);
846
+ }
847
+
848
+ function compactDomStaleCandidateForStatus(value) {
849
+ const source = plainRecord(value);
850
+ if (!Object.keys(source).length) return null;
851
+ const compact = {};
852
+ for (const key of ["index", "card_node_id", "visible_index", "failing_list_node_id"]) {
853
+ if (Number.isInteger(source[key]) && source[key] >= 0) compact[key] = source[key];
854
+ }
855
+ const candidateKey = normalizeText(source.key || "");
856
+ if (candidateKey) compact.key = candidateKey.slice(0, 300);
857
+ return Object.keys(compact).length ? compact : null;
858
+ }
859
+
860
+ function compactDomStaleRecoveryForStatus(value) {
861
+ const source = plainRecord(value);
862
+ if (!Object.keys(source).length) return null;
863
+ const compact = {};
864
+ for (const key of ["status", "mode", "escalated_from", "method"]) {
865
+ const text = normalizeText(source[key] || "");
866
+ if (text) compact[key] = text.slice(0, 200);
867
+ }
868
+ const at = compactCdpMethodTimestamp(source.at || "");
869
+ if (at) compact.at = at;
870
+ if (typeof source.ok === "boolean") compact.ok = source.ok;
871
+ if (Number.isInteger(source.card_count) && source.card_count >= 0) {
872
+ compact.card_count = source.card_count;
873
+ }
874
+ const error = compactListReadStaleDiagnosticForStatus(source.error);
875
+ if (error) compact.error = error;
876
+ return Object.keys(compact).length ? compact : null;
877
+ }
878
+
879
+ function compactDomStaleForensicForStatus(value) {
880
+ const source = plainRecord(value);
881
+ if (!Object.keys(source).length) return null;
882
+ const compact = {};
883
+ if (Number.isInteger(source.schema_version) && source.schema_version >= 0) {
884
+ compact.schema_version = source.schema_version;
885
+ }
886
+ for (const key of ["event_id", "event_type", "phase", "operation", "detail_step"]) {
887
+ const text = normalizeText(source[key] || "");
888
+ if (text) compact[key] = text.slice(0, key === "event_id" ? 300 : 200);
889
+ }
890
+ const at = compactCdpMethodTimestamp(source.at || "");
891
+ if (at) compact.at = at;
892
+
893
+ const candidate = compactDomStaleCandidateForStatus(source.candidate);
894
+ if (candidate) compact.candidate = candidate;
895
+ const error = compactListReadStaleDiagnosticForStatus(source.error);
896
+ if (error) compact.error = error;
897
+ const preRecoveryRoots = compactDomStaleRootIdentityForStatus(source.pre_recovery_roots);
898
+ if (preRecoveryRoots) compact.pre_recovery_roots = preRecoveryRoots;
899
+ const postRecoveryRoots = compactDomStaleRootIdentityForStatus(source.post_recovery_roots);
900
+ if (postRecoveryRoots) compact.post_recovery_roots = postRecoveryRoots;
901
+ const candidateList = compactDomStaleNumericRecordForStatus(source.candidate_list);
902
+ if (candidateList) compact.candidate_list = candidateList;
903
+ const counters = compactDomStaleNumericRecordForStatus(source.counters);
904
+ if (counters) compact.counters = counters;
905
+ const lifecycleTimeline = compactDomStaleLifecycleTimelineForStatus(source.lifecycle_timeline);
906
+ if (lifecycleTimeline.length) compact.lifecycle_timeline = lifecycleTimeline;
907
+ const recovery = compactDomStaleRecoveryForStatus(source.recovery);
908
+ if (recovery) compact.recovery = recovery;
909
+ return Object.keys(compact).length ? compact : null;
910
+ }
911
+
912
+ function compactDomStaleForensicsForStatus(value) {
913
+ if (!Array.isArray(value)) return [];
914
+ return value
915
+ .map((item) => compactDomStaleForensicForStatus(item))
916
+ .filter(Boolean)
917
+ .slice(-12);
918
+ }
919
+
920
+ function compactListReadStaleDiagnosticsForStatus(value) {
921
+ if (!Array.isArray(value)) return [];
922
+ return value
923
+ .map((item) => compactListReadStaleDiagnosticForStatus(item))
924
+ .filter(Boolean)
925
+ .slice(-12);
926
+ }
927
+
928
+ function compactListReadStaleCheckpointEvent(value) {
929
+ const source = plainRecord(value);
930
+ if (!Object.keys(source).length) return null;
931
+ const compact = {};
932
+ const diagnostic = compactListReadStaleDiagnosticForStatus(source.diagnostic || source.trigger);
933
+ if (diagnostic) compact.diagnostic = diagnostic;
934
+ const recentDiagnostics = compactListReadStaleDiagnosticsForStatus(source.recent_diagnostics);
935
+ if (recentDiagnostics.length) compact.recent_diagnostics = recentDiagnostics;
936
+ for (const key of ["recovery_count", "recovery_applied_count"]) {
937
+ if (Number.isInteger(source[key]) && source[key] >= 0) compact[key] = source[key];
938
+ }
939
+ if (source.recovery_error) {
940
+ const recoveryError = compactListReadStaleDiagnosticForStatus(source.recovery_error);
941
+ if (recoveryError) compact.recovery_error = recoveryError;
942
+ }
943
+ if (source.candidate_list) {
944
+ const candidateList = plainRecord(source.candidate_list);
945
+ compact.candidate_list = {};
946
+ for (const key of ["seen_count", "queued_count", "processed_count", "read_error_count", "scroll_count"]) {
947
+ if (Number.isInteger(candidateList[key]) && candidateList[key] >= 0) {
948
+ compact.candidate_list[key] = candidateList[key];
949
+ }
950
+ }
951
+ }
952
+ return Object.keys(compact).length ? compact : null;
953
+ }
954
+
553
955
  function compactRecommendSummaryForStatus(summary) {
554
956
  if (!summary || typeof summary !== "object" || Array.isArray(summary)) return null;
555
957
  const compact = {
@@ -605,8 +1007,28 @@ function compactRecommendSummaryForStatus(summary) {
605
1007
  total_pause_ms: compactPositiveInteger(humanRest.total_pause_ms ?? humanRest.pause_ms, null)
606
1008
  };
607
1009
  }
608
- return compact;
609
- }
1010
+ for (const key of [
1011
+ "list_read_stale_recovery_attempts",
1012
+ "list_read_stale_recovery_applied",
1013
+ "list_read_stale_recoveries"
1014
+ ]) {
1015
+ if (Number.isInteger(summary[key]) && summary[key] >= 0) compact[key] = summary[key];
1016
+ }
1017
+ if (summary.last_list_read_recovery_mode) {
1018
+ compact.last_list_read_recovery_mode = normalizeText(summary.last_list_read_recovery_mode).slice(0, 100);
1019
+ }
1020
+ const lastStaleDiagnostic = compactListReadStaleDiagnosticForStatus(
1021
+ summary.last_list_read_stale_diagnostic
1022
+ );
1023
+ if (lastStaleDiagnostic) compact.last_list_read_stale_diagnostic = lastStaleDiagnostic;
1024
+ const staleDiagnostics = compactListReadStaleDiagnosticsForStatus(summary.list_read_stale_diagnostics);
1025
+ if (staleDiagnostics.length) compact.list_read_stale_diagnostics = staleDiagnostics;
1026
+ const domStaleForensic = compactDomStaleForensicForStatus(summary.dom_stale_forensic);
1027
+ if (domStaleForensic) compact.dom_stale_forensic = domStaleForensic;
1028
+ const domStaleForensics = compactDomStaleForensicsForStatus(summary.dom_stale_forensics);
1029
+ if (domStaleForensics.length) compact.dom_stale_forensics = domStaleForensics;
1030
+ return compact;
1031
+ }
610
1032
 
611
1033
  function compactRecommendCheckpointForStatus(checkpoint) {
612
1034
  if (!checkpoint || typeof checkpoint !== "object" || Array.isArray(checkpoint)) return {};
@@ -622,8 +1044,22 @@ function compactRecommendCheckpointForStatus(checkpoint) {
622
1044
  for (const key of STATUS_COUNT_KEYS) {
623
1045
  if (Number.isInteger(checkpoint[key])) compact[key] = checkpoint[key];
624
1046
  }
625
- return compact;
626
- }
1047
+ for (const key of [
1048
+ "list_read_stale_recovery",
1049
+ "list_read_stale_recovery_applied",
1050
+ "list_read_stale_recovered",
1051
+ "list_read_stale_recovery_exhausted",
1052
+ "list_read_stale_recovery_failed"
1053
+ ]) {
1054
+ const event = compactListReadStaleCheckpointEvent(checkpoint[key]);
1055
+ if (event) compact[key] = event;
1056
+ }
1057
+ const domStaleForensic = compactDomStaleForensicForStatus(checkpoint.dom_stale_forensic);
1058
+ if (domStaleForensic) compact.dom_stale_forensic = domStaleForensic;
1059
+ const domStaleForensics = compactDomStaleForensicsForStatus(checkpoint.dom_stale_forensics);
1060
+ if (domStaleForensics.length) compact.dom_stale_forensics = domStaleForensics;
1061
+ return compact;
1062
+ }
627
1063
 
628
1064
  function compactRecommendResultForStatus(result) {
629
1065
  if (!result || typeof result !== "object" || Array.isArray(result)) return result || null;
@@ -1026,6 +1462,10 @@ function persistRecommendRunSnapshot(snapshot, {
1026
1462
  if (persistActiveCheckpoint) {
1027
1463
  persistRecommendCheckpointSnapshot(snapshot);
1028
1464
  }
1465
+ const cdpEvidence = buildPersistedCdpEvidence({
1466
+ meta: getRecommendRunMeta(normalized.run_id),
1467
+ persisted: existing || {}
1468
+ });
1029
1469
  const payload = {
1030
1470
  run_id: normalized.run_id,
1031
1471
  mode: normalized.mode,
@@ -1046,7 +1486,9 @@ function persistRecommendRunSnapshot(snapshot, {
1046
1486
  recovery: normalized.recovery,
1047
1487
  result: normalized.result,
1048
1488
  summary: normalized.summary,
1049
- artifacts: normalized.artifacts
1489
+ checkpoint: normalized.checkpoint,
1490
+ artifacts: normalized.artifacts,
1491
+ ...cdpEvidence
1050
1492
  };
1051
1493
  writeJsonAtomic(artifacts.run_state_path, payload);
1052
1494
  return normalized;
@@ -1126,11 +1568,10 @@ function attachMethodEvidence(payload, runId) {
1126
1568
  assertNoForbiddenCdpCalls(methodLog);
1127
1569
  return {
1128
1570
  ...payload,
1129
- runtime_evaluate_used: false,
1130
- method_summary: methodSummary(methodLog),
1131
- method_log: compactMethodLogForStatus(methodLog),
1132
- method_log_total: Array.isArray(methodLog) ? methodLog.length : 0,
1133
- chrome: meta.chrome || null
1571
+ ...buildPersistedCdpEvidence({
1572
+ meta,
1573
+ persisted: readRecommendRunState(runId) || {}
1574
+ })
1134
1575
  };
1135
1576
  }
1136
1577
 
@@ -1683,13 +2124,13 @@ function buildRecommendFilter(parsed, args = {}) {
1683
2124
  return { enabled: false };
1684
2125
  }
1685
2126
 
1686
- const groups = [{
1687
- group: "activity",
1688
- labels: [parsed.searchParams?.activity_level || "不限"],
1689
- selectAllLabels: false,
1690
- allowUnlimited: true,
1691
- verifySticky: true
1692
- }];
2127
+ const groups = [{
2128
+ group: "activity",
2129
+ labels: [parsed.searchParams?.activity_level || "不限"],
2130
+ selectAllLabels: false,
2131
+ allowUnlimited: true,
2132
+ verifySticky: true
2133
+ }];
1693
2134
  const recentNotView = withoutUnlimited(parsed.searchParams?.recent_not_view);
1694
2135
  if (recentNotView.length) {
1695
2136
  groups.push({
@@ -1726,12 +2167,12 @@ function buildRecommendFilter(parsed, args = {}) {
1726
2167
  });
1727
2168
  }
1728
2169
 
1729
- return {
1730
- enabled: true,
1731
- currentCityOnly: parsed.searchParams?.current_city_only === true,
1732
- filterGroups: groups
1733
- };
1734
- }
2170
+ return {
2171
+ enabled: true,
2172
+ currentCityOnly: parsed.searchParams?.current_city_only === true,
2173
+ filterGroups: groups
2174
+ };
2175
+ }
1735
2176
 
1736
2177
  function normalizeRecommendStartInput(args = {}, parsed, configResolution = null) {
1737
2178
  const confirmation = args.confirmation || {};
@@ -1822,6 +2263,19 @@ function getRunOptions(args, parsed, normalized, session, configResolution = nul
1822
2263
  humanBehavior,
1823
2264
  skipRecentColleagueContacted: normalized.skipRecentColleagueContacted,
1824
2265
  colleagueContactWindowDays: normalized.colleagueContactWindowDays,
2266
+ debugTestMode: isDebugTestMode(args),
2267
+ debugForceListEndAfterProcessed: parsePositiveInteger(
2268
+ args.debug_force_list_end_after_processed,
2269
+ null
2270
+ ),
2271
+ debugForceContextRecoveryAfterProcessed: parsePositiveInteger(
2272
+ args.debug_force_context_recovery_after_processed,
2273
+ null
2274
+ ),
2275
+ debugForceCdpReconnectAfterProcessed: parsePositiveInteger(
2276
+ args.debug_force_cdp_reconnect_after_processed,
2277
+ null
2278
+ ),
1825
2279
  name: "mcp-recommend-pipeline-run",
1826
2280
  parsed
1827
2281
  };
@@ -2117,19 +2571,16 @@ export function getRecommendPipelineRunTool({ args = {} } = {}) {
2117
2571
  const persisted = readRecommendRunState(runId);
2118
2572
  if (persisted) {
2119
2573
  const reconciled = compactRecommendRunForStatus(reconcilePersistedRecommendRunIfNeeded(persisted));
2120
- return {
2574
+ const persistedEvidence = readRecommendRunState(runId) || persisted;
2575
+ return attachPersistedCdpEvidence({
2121
2576
  status: "RUN_STATUS",
2122
2577
  run: reconciled,
2123
2578
  persistence: {
2124
2579
  source: "disk",
2125
2580
  active_control_available: false,
2126
2581
  stale_process_reconciled: reconciled?.state !== persisted.state
2127
- },
2128
- runtime_evaluate_used: false,
2129
- method_summary: {},
2130
- method_log: [],
2131
- chrome: null
2132
- };
2582
+ }
2583
+ }, persistedEvidence);
2133
2584
  }
2134
2585
  return {
2135
2586
  status: "FAILED",
@@ -2172,15 +2623,11 @@ export function pauseRecommendPipelineRunTool({ args = {} } = {}) {
2172
2623
  } catch {
2173
2624
  const persisted = readRecommendRunState(runId);
2174
2625
  if (persisted && TERMINAL_STATUSES.has(persisted.state)) {
2175
- return {
2626
+ return attachPersistedCdpEvidence({
2176
2627
  status: "PAUSE_IGNORED",
2177
2628
  run: compactRecommendRunForStatus(persisted),
2178
- message: "目标任务已结束,无需暂停。",
2179
- runtime_evaluate_used: false,
2180
- method_summary: {},
2181
- method_log: [],
2182
- chrome: null
2183
- };
2629
+ message: "目标任务已结束,无需暂停。"
2630
+ }, persisted);
2184
2631
  }
2185
2632
  return getRecommendPipelineRunTool({ args });
2186
2633
  }
@@ -2230,7 +2677,7 @@ export function resumeRecommendPipelineRunTool({ args = {} } = {}) {
2230
2677
  } catch {
2231
2678
  const persisted = readRecommendRunState(runId);
2232
2679
  if (persisted) {
2233
- return {
2680
+ return attachPersistedCdpEvidence({
2234
2681
  status: "FAILED",
2235
2682
  error: {
2236
2683
  code: TERMINAL_STATUSES.has(persisted.state) ? "RUN_ALREADY_TERMINATED" : "RUN_NOT_ACTIVE",
@@ -2243,12 +2690,8 @@ export function resumeRecommendPipelineRunTool({ args = {} } = {}) {
2243
2690
  persistence: {
2244
2691
  source: "disk",
2245
2692
  active_control_available: false
2246
- },
2247
- runtime_evaluate_used: false,
2248
- method_summary: {},
2249
- method_log: [],
2250
- chrome: null
2251
- };
2693
+ }
2694
+ }, persisted);
2252
2695
  }
2253
2696
  return getRecommendPipelineRunTool({ args });
2254
2697
  }
@@ -2276,15 +2719,11 @@ export function cancelRecommendPipelineRunTool({ args = {} } = {}) {
2276
2719
  } catch {
2277
2720
  const persisted = readRecommendRunState(runId);
2278
2721
  if (persisted && TERMINAL_STATUSES.has(persisted.state)) {
2279
- return {
2722
+ return attachPersistedCdpEvidence({
2280
2723
  status: "CANCEL_IGNORED",
2281
2724
  run: compactRecommendRunForStatus(persisted),
2282
- message: "目标任务已结束,无需取消。",
2283
- runtime_evaluate_used: false,
2284
- method_summary: {},
2285
- method_log: [],
2286
- chrome: null
2287
- };
2725
+ message: "目标任务已结束,无需取消。"
2726
+ }, persisted);
2288
2727
  }
2289
2728
  const cancelMessage = "已收到取消请求,将由 detached worker 在下一个安全边界停止。";
2290
2729
  const patched = patchPersistedRecommendRunControl(runId, {
@@ -2296,7 +2735,7 @@ export function cancelRecommendPipelineRunTool({ args = {} } = {}) {
2296
2735
  message: cancelMessage
2297
2736
  });
2298
2737
  if (patched) {
2299
- return {
2738
+ return attachPersistedCdpEvidence({
2300
2739
  status: "CANCEL_REQUESTED",
2301
2740
  run: compactRecommendRunForStatus(patched),
2302
2741
  message: cancelMessage,
@@ -2304,12 +2743,8 @@ export function cancelRecommendPipelineRunTool({ args = {} } = {}) {
2304
2743
  source: "disk",
2305
2744
  active_control_available: false,
2306
2745
  detached_control_requested: true
2307
- },
2308
- runtime_evaluate_used: false,
2309
- method_summary: {},
2310
- method_log: [],
2311
- chrome: null
2312
- };
2746
+ }
2747
+ }, patched);
2313
2748
  }
2314
2749
  return getRecommendPipelineRunTool({ args });
2315
2750
  }