@reconcrap/boss-recommend-mcp 1.3.16 → 1.3.18

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.
@@ -23,6 +23,8 @@ const SHOULD_BRING_TO_FRONT = shouldBringChromeToFront();
23
23
 
24
24
  const EARLY_FAIL_NO_RESUME_IFRAME_MIN_WAIT_MS = 5000;
25
25
  const EARLY_FAIL_NO_RESUME_IFRAME_STABLE_POLLS = 4;
26
+ const RESUME_VIEWPORT_STABILITY_POLL_MS = 80;
27
+ const RESUME_VIEWPORT_STABLE_POLLS = 2;
26
28
 
27
29
  function clampInteger(value, low, high) {
28
30
  return Math.max(low, Math.min(high, value));
@@ -105,14 +107,71 @@ function isStableNoResumeIframeProbe(probe) {
105
107
  return activeScopeCount === 0 && totalResumeIframes === 0 && visibleResumeIframes === 0;
106
108
  }
107
109
 
108
- function shouldAbortResumeProbeEarly({ probe, stableNoResumeIframePolls, elapsedMs, waitResumeMs }) {
110
+ function shouldAbortResumeProbeEarly({ probe, stableNoResumeIframePolls, elapsedMs, waitResumeMs }) {
109
111
  if (!isStableNoResumeIframeProbe(probe)) {
110
112
  return false;
111
113
  }
112
114
  const minWaitMs = Math.min(waitResumeMs, EARLY_FAIL_NO_RESUME_IFRAME_MIN_WAIT_MS);
113
115
  return stableNoResumeIframePolls >= EARLY_FAIL_NO_RESUME_IFRAME_STABLE_POLLS
114
- && elapsedMs >= minWaitMs;
115
- }
116
+ && elapsedMs >= minWaitMs;
117
+ }
118
+
119
+ function parseBooleanOption(value, fallback = false) {
120
+ if (value === undefined || value === null || value === "") return fallback;
121
+ if (typeof value === "boolean") return value;
122
+ const normalized = String(value).trim().toLowerCase();
123
+ if (["1", "true", "yes", "y", "on"].includes(normalized)) return true;
124
+ if (["0", "false", "no", "n", "off"].includes(normalized)) return false;
125
+ return fallback;
126
+ }
127
+
128
+ function numbersClose(left, right, tolerance = 1) {
129
+ return Math.abs(Number(left || 0) - Number(right || 0)) <= tolerance;
130
+ }
131
+
132
+ function isStableResumeViewport(previous, current, targetScroll) {
133
+ if (!previous?.ok || !current?.ok) return false;
134
+ const target = Number(targetScroll || 0);
135
+ const scrollTop = Number(current.scrollTop || 0);
136
+ const maxScroll = Number(current.maxScroll || 0);
137
+ const targetReached = numbersClose(scrollTop, target, 2)
138
+ || (target >= maxScroll && numbersClose(scrollTop, maxScroll, 2));
139
+ if (!targetReached) return false;
140
+ const prevClip = previous.clip || {};
141
+ const currentClip = current.clip || {};
142
+ return numbersClose(previous.scrollTop, current.scrollTop, 1)
143
+ && numbersClose(previous.scrollHeight, current.scrollHeight, 1)
144
+ && numbersClose(previous.clientHeight, current.clientHeight, 1)
145
+ && numbersClose(prevClip.x, currentClip.x, 1)
146
+ && numbersClose(prevClip.y, currentClip.y, 1)
147
+ && numbersClose(prevClip.width, currentClip.width, 1)
148
+ && numbersClose(prevClip.height, currentClip.height, 1);
149
+ }
150
+
151
+ async function waitForStableResumeViewport(evaluate, targetScroll, maxWaitMs) {
152
+ const maxWait = Math.max(160, Number(maxWaitMs || 0));
153
+ const start = Date.now();
154
+ let previous = null;
155
+ let latest = null;
156
+ let stablePolls = 0;
157
+ while (Date.now() - start < maxWait) {
158
+ await sleep(RESUME_VIEWPORT_STABILITY_POLL_MS);
159
+ const current = await evaluate(buildResumeProbeExpr({ init: false, targetScroll: null }));
160
+ if (current?.ok) {
161
+ latest = current;
162
+ if (isStableResumeViewport(previous, current, targetScroll)) {
163
+ stablePolls += 1;
164
+ if (stablePolls >= RESUME_VIEWPORT_STABLE_POLLS) {
165
+ return current;
166
+ }
167
+ } else {
168
+ stablePolls = 0;
169
+ }
170
+ previous = current;
171
+ }
172
+ }
173
+ return latest;
174
+ }
116
175
 
117
176
  async function stitchWithSharp(metadataFile, stitchedImage) {
118
177
  const sharp = loadSharp();
@@ -490,10 +549,14 @@ function buildResumeProbeExpr({ init, targetScroll }) {
490
549
 
491
550
  async function captureFullResumeCanvas(options = {}) {
492
551
  const host = options.host || process.env.CDP_HOST || "127.0.0.1";
493
- const port = Number(options.port || process.env.CDP_PORT || 9222);
494
- const waitResumeMs = Number(options.waitResumeMs || process.env.WAIT_RESUME_MS || 30000);
495
- const scrollSettleMs = Number(options.scrollSettleMs || process.env.SCROLL_SETTLE_MS || 500);
496
- const outPrefix = options.outPrefix || process.env.OUT_PREFIX || path.resolve(process.cwd(), "recommend_resume_full");
552
+ const port = Number(options.port || process.env.CDP_PORT || 9222);
553
+ const waitResumeMs = Number(options.waitResumeMs || process.env.WAIT_RESUME_MS || 30000);
554
+ const scrollSettleMs = Number(options.scrollSettleMs || process.env.SCROLL_SETTLE_MS || 500);
555
+ const stitchFullImage = parseBooleanOption(
556
+ options.stitchFullImage,
557
+ parseBooleanOption(process.env.BOSS_RECOMMEND_STITCH_FULL_IMAGE, true)
558
+ );
559
+ const outPrefix = options.outPrefix || process.env.OUT_PREFIX || path.resolve(process.cwd(), "recommend_resume_full");
497
560
  const targetPattern = options.targetPattern || process.env.TARGET_PATTERN || "/web/chat/recommend";
498
561
  const stitchScript = path.resolve(__dirname, "stitch_resume_chunks.py");
499
562
  const chunkDir = `${outPrefix}_chunks`;
@@ -633,12 +696,11 @@ async function captureFullResumeCanvas(options = {}) {
633
696
  const chunks = [];
634
697
  const seenScroll = [];
635
698
 
636
- for (let index = 0; index < uniquePositions.length; index += 1) {
637
- const targetScroll = uniquePositions[index];
638
- await evaluate(buildResumeProbeExpr({ init: false, targetScroll }));
639
- await sleep(scrollSettleMs);
640
- const current = await evaluate(buildResumeProbeExpr({ init: false, targetScroll: null }));
641
- if (!current?.ok) continue;
699
+ for (let index = 0; index < uniquePositions.length; index += 1) {
700
+ const targetScroll = uniquePositions[index];
701
+ await evaluate(buildResumeProbeExpr({ init: false, targetScroll }));
702
+ const current = await waitForStableResumeViewport(evaluate, targetScroll, scrollSettleMs);
703
+ if (!current?.ok) continue;
642
704
 
643
705
  const actualScroll = Number(current.scrollTop || 0);
644
706
  if (seenScroll.some((value) => Math.abs(value - actualScroll) < 1)) {
@@ -688,31 +750,36 @@ async function captureFullResumeCanvas(options = {}) {
688
750
  };
689
751
  fs.writeFileSync(metadataFile, JSON.stringify(metadata, null, 2), "utf8");
690
752
 
691
- let stitchEngine = "sharp";
692
- try {
693
- await stitchWithSharp(metadataFile, stitchedImage);
694
- } catch (sharpError) {
695
- const fallback = stitchWithAvailablePython(stitchScript, metadataFile, stitchedImage);
696
- if (!fallback.ok) {
697
- const fallbackSummary = fallback.attempts
698
- .map((item) => {
699
- const message = item.stderr || item.stdout || item.error || "unknown error";
700
- return `${item.command}(status=${item.status ?? "null"}): ${message}`;
701
- })
702
- .join(" | ");
703
- throw new Error(
704
- `Stitch failed (sharp + python fallback). sharp=${sharpError?.message || sharpError}; fallback=${fallbackSummary}`
705
- );
706
- }
707
- stitchEngine = fallback.command || "python";
708
- }
709
-
710
- return {
711
- stitchedImage,
712
- metadataFile,
713
- chunkDir,
714
- chunkCount: chunks.length,
715
- stitch_engine: stitchEngine,
753
+ let stitchEngine = "skipped";
754
+ if (stitchFullImage) {
755
+ stitchEngine = "sharp";
756
+ try {
757
+ await stitchWithSharp(metadataFile, stitchedImage);
758
+ } catch (sharpError) {
759
+ const fallback = stitchWithAvailablePython(stitchScript, metadataFile, stitchedImage);
760
+ if (!fallback.ok) {
761
+ const fallbackSummary = fallback.attempts
762
+ .map((item) => {
763
+ const message = item.stderr || item.stdout || item.error || "unknown error";
764
+ return `${item.command}(status=${item.status ?? "null"}): ${message}`;
765
+ })
766
+ .join(" | ");
767
+ throw new Error(
768
+ `Stitch failed (sharp + python fallback). sharp=${sharpError?.message || sharpError}; fallback=${fallbackSummary}`
769
+ );
770
+ }
771
+ stitchEngine = fallback.command || "python";
772
+ }
773
+ }
774
+
775
+ return {
776
+ stitchedImage: stitchFullImage ? stitchedImage : "",
777
+ metadataFile,
778
+ chunkDir,
779
+ chunkCount: chunks.length,
780
+ chunkFiles: chunks.map((chunk) => chunk.file),
781
+ modelImagePaths: chunks.map((chunk) => chunk.file),
782
+ stitch_engine: stitchEngine,
716
783
  target: {
717
784
  title: target.title,
718
785
  url: target.url
@@ -729,9 +796,10 @@ module.exports = {
729
796
  captureFullResumeCanvas,
730
797
  __testables: {
731
798
  EARLY_FAIL_NO_RESUME_IFRAME_MIN_WAIT_MS,
732
- EARLY_FAIL_NO_RESUME_IFRAME_STABLE_POLLS,
733
- isStableNoResumeIframeProbe,
734
- shouldAbortResumeProbeEarly,
799
+ EARLY_FAIL_NO_RESUME_IFRAME_STABLE_POLLS,
800
+ isStableNoResumeIframeProbe,
801
+ isStableResumeViewport,
802
+ shouldAbortResumeProbeEarly,
735
803
  stitchWithAvailablePython,
736
804
  stitchWithSharp
737
805
  }
@@ -238,6 +238,31 @@ function testShouldAbortResumeProbeEarly() {
238
238
  assert.equal(shouldAbort, true);
239
239
  }
240
240
 
241
+ function testResumeViewportStabilityRequiresSettledScrollAndClip() {
242
+ const previous = {
243
+ ok: true,
244
+ scrollTop: 200,
245
+ scrollHeight: 1000,
246
+ clientHeight: 400,
247
+ maxScroll: 600,
248
+ clip: { x: 10, y: 20, width: 300, height: 400 }
249
+ };
250
+ const current = {
251
+ ok: true,
252
+ scrollTop: 200.5,
253
+ scrollHeight: 1000,
254
+ clientHeight: 400,
255
+ maxScroll: 600,
256
+ clip: { x: 10, y: 20, width: 300, height: 400 }
257
+ };
258
+ assert.equal(captureTestables.isStableResumeViewport(previous, current, 200), true);
259
+ assert.equal(captureTestables.isStableResumeViewport(previous, { ...current, scrollTop: 180 }, 200), false);
260
+ assert.equal(
261
+ captureTestables.isStableResumeViewport(previous, { ...current, clip: { ...current.clip, height: 360 } }, 200),
262
+ false
263
+ );
264
+ }
265
+
241
266
  async function testSingleResumeCaptureFailureIsSkipped() {
242
267
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-skip-"));
243
268
  const badCandidate = { key: "bad", geek_id: "bad", name: "bad candidate" };
@@ -525,6 +550,64 @@ async function testNetworkMissShouldFallbackToImageCapture() {
525
550
  assert.equal(result.result.resume_source, "image_fallback");
526
551
  }
527
552
 
553
+ async function testImageModeShouldUseShortNetworkGraceWindow() {
554
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-image-mode-grace-"));
555
+ const first = { key: "img-mode-1", geek_id: "img-mode-1", name: "image mode one" };
556
+ const second = { key: "img-mode-2", geek_id: "img-mode-2", name: "image mode two" };
557
+ const cli = new FakeRecommendScreenCli(createArgs(tempDir), {
558
+ candidates: [first, second],
559
+ captureOutcomes: new Map([
560
+ [first.key, { stitchedImage: path.join(tempDir, "img-mode-1.png") }],
561
+ [second.key, { stitchedImage: path.join(tempDir, "img-mode-2.png") }]
562
+ ]),
563
+ screeningByKey: new Map([
564
+ [first.key, { passed: false, reason: "image one", summary: "image one" }],
565
+ [second.key, { passed: false, reason: "image two", summary: "image two" }]
566
+ ])
567
+ });
568
+ const waits = [];
569
+ cli.waitForNetworkResumeCandidateInfo = async (_candidate, timeoutMs) => {
570
+ waits.push(timeoutMs);
571
+ return null;
572
+ };
573
+
574
+ const result = await cli.run();
575
+ assert.equal(result.status, "COMPLETED");
576
+ assert.equal(cli.resumeAcquisitionMode, "image");
577
+ assert.deepEqual(waits.slice(-1), [__testables.NETWORK_RESUME_IMAGE_MODE_GRACE_MS]);
578
+ }
579
+
580
+ async function testImageFailureShouldLateRetryNetworkBeforeDomFallback() {
581
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-image-fail-late-network-"));
582
+ const candidate = { key: "late-network-1", geek_id: "late-network-1", name: "late network candidate" };
583
+ const cli = new FakeRecommendScreenCli(createArgs(tempDir), {
584
+ candidates: [candidate]
585
+ });
586
+ let domUsed = false;
587
+ cli.waitForNetworkResumeCandidateInfo = async (_candidate, timeoutMs) => (
588
+ timeoutMs === __testables.NETWORK_RESUME_LATE_RETRY_MS
589
+ ? { resumeText: "late network resume text" }
590
+ : null
591
+ );
592
+ cli.captureResumeImage = async () => {
593
+ throw createResumeCaptureError("image capture failed before late network");
594
+ };
595
+ cli.extractResumeTextFromDom = async () => {
596
+ domUsed = true;
597
+ return null;
598
+ };
599
+ cli.callTextModel = async () => ({
600
+ passed: true,
601
+ reason: "late network used",
602
+ summary: "late network used"
603
+ });
604
+
605
+ const result = await cli.run();
606
+ assert.equal(result.status, "COMPLETED");
607
+ assert.equal(result.result.resume_source, "network");
608
+ assert.equal(domUsed, false);
609
+ }
610
+
528
611
  async function testLatestShouldPreferNetworkResumeWhenAvailable() {
529
612
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-latest-network-main-"));
530
613
  const args = createArgs(tempDir);
@@ -876,6 +959,60 @@ function testFormatResumeApiDataShouldPreserveEducationTagsAndProjectDescription
876
959
  assert.equal(formatted.includes("描述: 采用stable diffusion进行编辑实验"), true);
877
960
  }
878
961
 
962
+ function testFormatResumeApiDataShouldIncludeStructuredJudgementHints() {
963
+ const source = {
964
+ geekDetailInfo: {
965
+ geekBaseInfo: {
966
+ name: "测试候选人",
967
+ degreeCategory: "硕士"
968
+ },
969
+ geekWorkExpList: [
970
+ {
971
+ company: "中科院",
972
+ positionName: "科研助理",
973
+ startDate: "20241001",
974
+ endDate: "",
975
+ responsibility: "科研以及项目"
976
+ }
977
+ ],
978
+ geekProjExpList: [],
979
+ geekEduExpList: [
980
+ {
981
+ school: "科克大学",
982
+ major: "理学",
983
+ degreeName: "硕士",
984
+ startDateDesc: "2020",
985
+ endDateDesc: "2023"
986
+ },
987
+ {
988
+ school: "东北大学",
989
+ major: "数学与应用数学",
990
+ degreeName: "本科",
991
+ startDate: "20140101",
992
+ endDate: "20180101"
993
+ }
994
+ ],
995
+ workExpCheckRes: [
996
+ {
997
+ desc: "毕业同年未填写工作经历"
998
+ }
999
+ ],
1000
+ jobCompetitive: {
1001
+ tips: [{ content: "受欢迎程度高" }]
1002
+ }
1003
+ }
1004
+ };
1005
+ const formatted = __testables.formatResumeApiData(source);
1006
+ assert.equal(formatted.includes("=== 结构化判定线索 ==="), true);
1007
+ assert.equal(formatted.includes("最高学历: 硕士"), true);
1008
+ assert.equal(formatted.includes("最高学历毕业年份: 2023"), true);
1009
+ assert.equal(formatted.includes("是否有工作经历: 是"), true);
1010
+ assert.equal(formatted.includes("是否有项目经历: 否"), true);
1011
+ assert.equal(formatted.includes("相关经验硬判口径"), true);
1012
+ assert.equal(formatted.includes("软风险提示(需追问,不直接淘汰): 毕业同年未填写工作经历"), true);
1013
+ assert.equal(formatted.includes("判定忽略项: 活跃度/沟通热度/受欢迎度等运营指标不参与通过判定。"), true);
1014
+ }
1015
+
879
1016
  function testEvidenceTokenMatcherShouldSupportParaphrasedEvidence() {
880
1017
  const resume = [
881
1018
  "南京大学 专业: 数学",
@@ -1348,6 +1485,75 @@ async function testCallTextModelShouldFallbackToChunkModeOnContextLimit() {
1348
1485
  }
1349
1486
  }
1350
1487
 
1488
+ async function testTextModelShouldDefaultThinkingOffForVolcengine() {
1489
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-thinking-off-"));
1490
+ const cli = new RecommendScreenCli(createArgs(tempDir));
1491
+ cli.args.baseUrl = "https://ark.cn-beijing.volces.com/api/v3";
1492
+ cli.args.model = "doubao-seed-2-0-mini-260215";
1493
+ const originalFetch = global.fetch;
1494
+ let capturedPayload = null;
1495
+ global.fetch = async (_url, options = {}) => {
1496
+ capturedPayload = JSON.parse(String(options.body || "{}"));
1497
+ return {
1498
+ ok: true,
1499
+ status: 200,
1500
+ async json() {
1501
+ return {
1502
+ choices: [
1503
+ {
1504
+ message: {
1505
+ content: "{\"passed\": false, \"reason\": \"not matched\", \"summary\": \"not matched\", \"evidence\": [\"resume\"]}"
1506
+ }
1507
+ }
1508
+ ]
1509
+ };
1510
+ }
1511
+ };
1512
+ };
1513
+ try {
1514
+ await cli.callTextModel("resume");
1515
+ assert.deepEqual(capturedPayload?.thinking, { type: "disabled" });
1516
+ assert.equal(capturedPayload?.reasoning_effort, "minimal");
1517
+ } finally {
1518
+ global.fetch = originalFetch;
1519
+ }
1520
+ }
1521
+
1522
+ async function testTextModelShouldSupportLowThinkingForVolcengine() {
1523
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-thinking-low-"));
1524
+ const cli = new RecommendScreenCli(createArgs(tempDir));
1525
+ cli.args.baseUrl = "https://ark.cn-beijing.volces.com/api/v3";
1526
+ cli.args.model = "doubao-seed-2-0-mini-260215";
1527
+ cli.args.thinkingLevel = "low";
1528
+ const originalFetch = global.fetch;
1529
+ let capturedPayload = null;
1530
+ global.fetch = async (_url, options = {}) => {
1531
+ capturedPayload = JSON.parse(String(options.body || "{}"));
1532
+ return {
1533
+ ok: true,
1534
+ status: 200,
1535
+ async json() {
1536
+ return {
1537
+ choices: [
1538
+ {
1539
+ message: {
1540
+ content: "{\"passed\": false, \"reason\": \"not matched\", \"summary\": \"not matched\", \"evidence\": [\"resume\"]}"
1541
+ }
1542
+ }
1543
+ ]
1544
+ };
1545
+ }
1546
+ };
1547
+ };
1548
+ try {
1549
+ await cli.callTextModel("resume");
1550
+ assert.deepEqual(capturedPayload?.thinking, { type: "enabled" });
1551
+ assert.equal(capturedPayload?.reasoning_effort, "low");
1552
+ } finally {
1553
+ global.fetch = originalFetch;
1554
+ }
1555
+ }
1556
+
1351
1557
  async function testPrepareVisionImageSegmentsShouldSplitLongImage() {
1352
1558
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-vision-segments-"));
1353
1559
  const cli = new RecommendScreenCli(createArgs(tempDir));
@@ -1473,8 +1679,55 @@ async function testVisionEvidenceGateShouldDemoteImageFallbackWithoutEvidence()
1473
1679
  assert.equal(result.evidenceMatchedCount, 0);
1474
1680
  }
1475
1681
 
1682
+ async function testVisionModelShouldSendAllOrderedChunks() {
1683
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-vision-all-chunks-"));
1684
+ const chunkPaths = [];
1685
+ for (let index = 0; index < 3; index += 1) {
1686
+ const chunkPath = path.join(tempDir, `chunk-${index + 1}.png`);
1687
+ await sharp({
1688
+ create: { width: 16, height: 16, channels: 3, background: { r: 255 - index, g: 250, b: 245 } }
1689
+ }).png().toFile(chunkPath);
1690
+ chunkPaths.push(chunkPath);
1691
+ }
1692
+ const cli = new RecommendScreenCli(createArgs(tempDir));
1693
+ const originalFetch = global.fetch;
1694
+ let capturedPayload = null;
1695
+ global.fetch = async (_url, options = {}) => {
1696
+ capturedPayload = JSON.parse(String(options.body || "{}"));
1697
+ return {
1698
+ ok: true,
1699
+ status: 200,
1700
+ async json() {
1701
+ return {
1702
+ choices: [
1703
+ {
1704
+ message: {
1705
+ content: "{\"passed\": false, \"reason\": \"checked all chunks\", \"summary\": \"checked\", \"evidence\": [\"chunk evidence\", \"more evidence\"]}"
1706
+ }
1707
+ }
1708
+ ]
1709
+ };
1710
+ }
1711
+ };
1712
+ };
1713
+ try {
1714
+ const result = await cli.requestVisionModel(chunkPaths);
1715
+ assert.equal(result.passed, false);
1716
+ const userContent = capturedPayload?.messages?.[1]?.content || [];
1717
+ assert.equal(userContent.filter((item) => item?.type === "image_url").length, 3);
1718
+ const text = userContent.map((item) => item?.text || "").join("\n");
1719
+ assert.equal(text.includes("简历分段 1/3"), true);
1720
+ assert.equal(text.includes("简历分段 2/3"), true);
1721
+ assert.equal(text.includes("简历分段 3/3"), true);
1722
+ assert.equal(text.includes("不能只根据前几段下结论"), true);
1723
+ } finally {
1724
+ global.fetch = originalFetch;
1725
+ }
1726
+ }
1727
+
1476
1728
  async function main() {
1477
1729
  testShouldAbortResumeProbeEarly();
1730
+ testResumeViewportStabilityRequiresSettledScrollAndClip();
1478
1731
  await testSingleResumeCaptureFailureIsSkipped();
1479
1732
  await testConsecutiveResumeCaptureFailuresStillAbort();
1480
1733
  await testPageExhaustedBeforeTargetShouldRaiseRecoverableError();
@@ -1485,6 +1738,8 @@ async function main() {
1485
1738
  await testRecommendShouldPreferNetworkResumeWhenAvailable();
1486
1739
  await testNetworkMissShouldFallbackToImageThenDom();
1487
1740
  await testNetworkMissShouldFallbackToImageCapture();
1741
+ await testImageModeShouldUseShortNetworkGraceWindow();
1742
+ await testImageFailureShouldLateRetryNetworkBeforeDomFallback();
1488
1743
  await testLatestShouldPreferNetworkResumeWhenAvailable();
1489
1744
  await testLatestNetworkMissShouldFallbackToImageCapture();
1490
1745
  testLatestPayloadShouldNotLeakAcrossCandidates();
@@ -1501,6 +1756,7 @@ async function main() {
1501
1756
  testFavoriteActionParserShouldOnlyTrustKnownRequestShapes();
1502
1757
  testFinishedWrapClassifierShouldNotTreatLoadMoreAsBottom();
1503
1758
  testFormatResumeApiDataShouldPreserveEducationTagsAndProjectDescription();
1759
+ testFormatResumeApiDataShouldIncludeStructuredJudgementHints();
1504
1760
  testEvidenceTokenMatcherShouldSupportParaphrasedEvidence();
1505
1761
  testCheckpointPayloadShouldIncludeCandidateAudits();
1506
1762
  testCheckpointShouldPersistAndRestoreInputSummary();
@@ -1515,8 +1771,11 @@ async function main() {
1515
1771
  testParseArgsShouldSupportInputSummaryJson();
1516
1772
  await testCallTextModelShouldNotTruncateLongResume();
1517
1773
  await testCallTextModelShouldFallbackToChunkModeOnContextLimit();
1774
+ await testTextModelShouldDefaultThinkingOffForVolcengine();
1775
+ await testTextModelShouldSupportLowThinkingForVolcengine();
1518
1776
  await testPrepareVisionImageSegmentsShouldSplitLongImage();
1519
1777
  await testVisionEvidenceGateShouldDemoteImageFallbackWithoutEvidence();
1778
+ await testVisionModelShouldSendAllOrderedChunks();
1520
1779
  testRecoverablePostActionErrorShouldTreatGreetContinueAndNoButtonAsRecoverable();
1521
1780
  await testRecoverableGreetContinueButtonShouldNotAbortWhenDetailCloseFails();
1522
1781
  await testRecoverableGreetButtonNotFoundShouldNotAbortWhenDetailCloseFails();