@reconcrap/boss-recommend-mcp 1.3.16 → 1.3.17
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 +2 -1
- package/config/screening-config.example.json +1 -0
- package/package.json +1 -1
- package/src/adapters.js +28 -9
- package/src/boss-chat.js +22 -0
- package/src/cli.js +6 -1
- package/src/test-boss-chat.js +89 -0
- package/vendor/boss-chat-cli/src/cli.js +10 -0
- package/vendor/boss-chat-cli/src/mcp/server.js +1 -1
- package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +1 -1
- package/vendor/boss-chat-cli/src/services/llm.js +119 -24
- package/vendor/boss-chat-cli/src/services/profile-store.js +5 -0
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +400 -69
- package/vendor/boss-recommend-screen-cli/scripts/capture-full-resume-canvas.cjs +109 -41
- package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +204 -0
|
@@ -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
|
|
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
|
|
640
|
-
|
|
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 = "
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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
|
-
|
|
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);
|
|
@@ -1348,6 +1431,75 @@ async function testCallTextModelShouldFallbackToChunkModeOnContextLimit() {
|
|
|
1348
1431
|
}
|
|
1349
1432
|
}
|
|
1350
1433
|
|
|
1434
|
+
async function testTextModelShouldDefaultThinkingOffForVolcengine() {
|
|
1435
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-thinking-off-"));
|
|
1436
|
+
const cli = new RecommendScreenCli(createArgs(tempDir));
|
|
1437
|
+
cli.args.baseUrl = "https://ark.cn-beijing.volces.com/api/v3";
|
|
1438
|
+
cli.args.model = "doubao-seed-2-0-mini-260215";
|
|
1439
|
+
const originalFetch = global.fetch;
|
|
1440
|
+
let capturedPayload = null;
|
|
1441
|
+
global.fetch = async (_url, options = {}) => {
|
|
1442
|
+
capturedPayload = JSON.parse(String(options.body || "{}"));
|
|
1443
|
+
return {
|
|
1444
|
+
ok: true,
|
|
1445
|
+
status: 200,
|
|
1446
|
+
async json() {
|
|
1447
|
+
return {
|
|
1448
|
+
choices: [
|
|
1449
|
+
{
|
|
1450
|
+
message: {
|
|
1451
|
+
content: "{\"passed\": false, \"reason\": \"not matched\", \"summary\": \"not matched\", \"evidence\": [\"resume\"]}"
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
]
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
};
|
|
1458
|
+
};
|
|
1459
|
+
try {
|
|
1460
|
+
await cli.callTextModel("resume");
|
|
1461
|
+
assert.deepEqual(capturedPayload?.thinking, { type: "disabled" });
|
|
1462
|
+
assert.equal(capturedPayload?.reasoning_effort, "minimal");
|
|
1463
|
+
} finally {
|
|
1464
|
+
global.fetch = originalFetch;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
async function testTextModelShouldSupportLowThinkingForVolcengine() {
|
|
1469
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-thinking-low-"));
|
|
1470
|
+
const cli = new RecommendScreenCli(createArgs(tempDir));
|
|
1471
|
+
cli.args.baseUrl = "https://ark.cn-beijing.volces.com/api/v3";
|
|
1472
|
+
cli.args.model = "doubao-seed-2-0-mini-260215";
|
|
1473
|
+
cli.args.thinkingLevel = "low";
|
|
1474
|
+
const originalFetch = global.fetch;
|
|
1475
|
+
let capturedPayload = null;
|
|
1476
|
+
global.fetch = async (_url, options = {}) => {
|
|
1477
|
+
capturedPayload = JSON.parse(String(options.body || "{}"));
|
|
1478
|
+
return {
|
|
1479
|
+
ok: true,
|
|
1480
|
+
status: 200,
|
|
1481
|
+
async json() {
|
|
1482
|
+
return {
|
|
1483
|
+
choices: [
|
|
1484
|
+
{
|
|
1485
|
+
message: {
|
|
1486
|
+
content: "{\"passed\": false, \"reason\": \"not matched\", \"summary\": \"not matched\", \"evidence\": [\"resume\"]}"
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
]
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
};
|
|
1494
|
+
try {
|
|
1495
|
+
await cli.callTextModel("resume");
|
|
1496
|
+
assert.deepEqual(capturedPayload?.thinking, { type: "enabled" });
|
|
1497
|
+
assert.equal(capturedPayload?.reasoning_effort, "low");
|
|
1498
|
+
} finally {
|
|
1499
|
+
global.fetch = originalFetch;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1351
1503
|
async function testPrepareVisionImageSegmentsShouldSplitLongImage() {
|
|
1352
1504
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-vision-segments-"));
|
|
1353
1505
|
const cli = new RecommendScreenCli(createArgs(tempDir));
|
|
@@ -1473,8 +1625,55 @@ async function testVisionEvidenceGateShouldDemoteImageFallbackWithoutEvidence()
|
|
|
1473
1625
|
assert.equal(result.evidenceMatchedCount, 0);
|
|
1474
1626
|
}
|
|
1475
1627
|
|
|
1628
|
+
async function testVisionModelShouldSendAllOrderedChunks() {
|
|
1629
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-vision-all-chunks-"));
|
|
1630
|
+
const chunkPaths = [];
|
|
1631
|
+
for (let index = 0; index < 3; index += 1) {
|
|
1632
|
+
const chunkPath = path.join(tempDir, `chunk-${index + 1}.png`);
|
|
1633
|
+
await sharp({
|
|
1634
|
+
create: { width: 16, height: 16, channels: 3, background: { r: 255 - index, g: 250, b: 245 } }
|
|
1635
|
+
}).png().toFile(chunkPath);
|
|
1636
|
+
chunkPaths.push(chunkPath);
|
|
1637
|
+
}
|
|
1638
|
+
const cli = new RecommendScreenCli(createArgs(tempDir));
|
|
1639
|
+
const originalFetch = global.fetch;
|
|
1640
|
+
let capturedPayload = null;
|
|
1641
|
+
global.fetch = async (_url, options = {}) => {
|
|
1642
|
+
capturedPayload = JSON.parse(String(options.body || "{}"));
|
|
1643
|
+
return {
|
|
1644
|
+
ok: true,
|
|
1645
|
+
status: 200,
|
|
1646
|
+
async json() {
|
|
1647
|
+
return {
|
|
1648
|
+
choices: [
|
|
1649
|
+
{
|
|
1650
|
+
message: {
|
|
1651
|
+
content: "{\"passed\": false, \"reason\": \"checked all chunks\", \"summary\": \"checked\", \"evidence\": [\"chunk evidence\", \"more evidence\"]}"
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
]
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
};
|
|
1658
|
+
};
|
|
1659
|
+
try {
|
|
1660
|
+
const result = await cli.requestVisionModel(chunkPaths);
|
|
1661
|
+
assert.equal(result.passed, false);
|
|
1662
|
+
const userContent = capturedPayload?.messages?.[1]?.content || [];
|
|
1663
|
+
assert.equal(userContent.filter((item) => item?.type === "image_url").length, 3);
|
|
1664
|
+
const text = userContent.map((item) => item?.text || "").join("\n");
|
|
1665
|
+
assert.equal(text.includes("简历分段 1/3"), true);
|
|
1666
|
+
assert.equal(text.includes("简历分段 2/3"), true);
|
|
1667
|
+
assert.equal(text.includes("简历分段 3/3"), true);
|
|
1668
|
+
assert.equal(text.includes("不能只根据前几段下结论"), true);
|
|
1669
|
+
} finally {
|
|
1670
|
+
global.fetch = originalFetch;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1476
1674
|
async function main() {
|
|
1477
1675
|
testShouldAbortResumeProbeEarly();
|
|
1676
|
+
testResumeViewportStabilityRequiresSettledScrollAndClip();
|
|
1478
1677
|
await testSingleResumeCaptureFailureIsSkipped();
|
|
1479
1678
|
await testConsecutiveResumeCaptureFailuresStillAbort();
|
|
1480
1679
|
await testPageExhaustedBeforeTargetShouldRaiseRecoverableError();
|
|
@@ -1485,6 +1684,8 @@ async function main() {
|
|
|
1485
1684
|
await testRecommendShouldPreferNetworkResumeWhenAvailable();
|
|
1486
1685
|
await testNetworkMissShouldFallbackToImageThenDom();
|
|
1487
1686
|
await testNetworkMissShouldFallbackToImageCapture();
|
|
1687
|
+
await testImageModeShouldUseShortNetworkGraceWindow();
|
|
1688
|
+
await testImageFailureShouldLateRetryNetworkBeforeDomFallback();
|
|
1488
1689
|
await testLatestShouldPreferNetworkResumeWhenAvailable();
|
|
1489
1690
|
await testLatestNetworkMissShouldFallbackToImageCapture();
|
|
1490
1691
|
testLatestPayloadShouldNotLeakAcrossCandidates();
|
|
@@ -1515,8 +1716,11 @@ async function main() {
|
|
|
1515
1716
|
testParseArgsShouldSupportInputSummaryJson();
|
|
1516
1717
|
await testCallTextModelShouldNotTruncateLongResume();
|
|
1517
1718
|
await testCallTextModelShouldFallbackToChunkModeOnContextLimit();
|
|
1719
|
+
await testTextModelShouldDefaultThinkingOffForVolcengine();
|
|
1720
|
+
await testTextModelShouldSupportLowThinkingForVolcengine();
|
|
1518
1721
|
await testPrepareVisionImageSegmentsShouldSplitLongImage();
|
|
1519
1722
|
await testVisionEvidenceGateShouldDemoteImageFallbackWithoutEvidence();
|
|
1723
|
+
await testVisionModelShouldSendAllOrderedChunks();
|
|
1520
1724
|
testRecoverablePostActionErrorShouldTreatGreetContinueAndNoButtonAsRecoverable();
|
|
1521
1725
|
await testRecoverableGreetContinueButtonShouldNotAbortWhenDetailCloseFails();
|
|
1522
1726
|
await testRecoverableGreetButtonNotFoundShouldNotAbortWhenDetailCloseFails();
|