@reconcrap/boss-recommend-mcp 1.3.30 → 1.3.32
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/config/screening-config.example.json +2 -0
- package/package.json +1 -1
- package/src/adapters.js +22 -0
- package/src/boss-chat.js +14 -1
- package/src/test-adapters-runtime.js +90 -0
- package/src/test-boss-chat.js +719 -58
- package/vendor/boss-chat-cli/src/app.js +411 -175
- package/vendor/boss-chat-cli/src/cli.js +20 -0
- package/vendor/boss-chat-cli/src/services/chrome-client.js +8 -2
- package/vendor/boss-chat-cli/src/services/llm.js +252 -84
- package/vendor/boss-chat-cli/src/services/profile-store.js +6 -0
- package/vendor/boss-chat-cli/src/services/report-store.js +301 -3
- package/vendor/boss-chat-cli/src/services/resume-capture.js +41 -126
- package/vendor/boss-chat-cli/src/services/resume-network.js +727 -0
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +44 -2
package/src/test-boss-chat.js
CHANGED
|
@@ -18,7 +18,14 @@ import { __testables as indexTestables } from "./index.js";
|
|
|
18
18
|
import { BossChatApp } from "../vendor/boss-chat-cli/src/app.js";
|
|
19
19
|
import { __testables as vendorCliTestables } from "../vendor/boss-chat-cli/src/cli.js";
|
|
20
20
|
import { BossChatPage } from "../vendor/boss-chat-cli/src/browser/chat-page.js";
|
|
21
|
-
import { LlmClient, parseLlmJson } from "../vendor/boss-chat-cli/src/services/llm.js";
|
|
21
|
+
import { LlmClient, parseLlmJson, __testables as llmTestables } from "../vendor/boss-chat-cli/src/services/llm.js";
|
|
22
|
+
import { ReportStore } from "../vendor/boss-chat-cli/src/services/report-store.js";
|
|
23
|
+
import {
|
|
24
|
+
NETWORK_RESUME_IMAGE_MODE_GRACE_MS,
|
|
25
|
+
NETWORK_RESUME_RETRY_WAIT_MS,
|
|
26
|
+
NETWORK_RESUME_WAIT_MS,
|
|
27
|
+
ResumeNetworkTracker,
|
|
28
|
+
} from "../vendor/boss-chat-cli/src/services/resume-network.js";
|
|
22
29
|
|
|
23
30
|
const { handleRequest } = indexTestables;
|
|
24
31
|
|
|
@@ -29,6 +36,7 @@ const TOOL_BOSS_CHAT_GET_RUN = "get_boss_chat_run";
|
|
|
29
36
|
const TOOL_BOSS_CHAT_PAUSE_RUN = "pause_boss_chat_run";
|
|
30
37
|
const TOOL_BOSS_CHAT_RESUME_RUN = "resume_boss_chat_run";
|
|
31
38
|
const TOOL_BOSS_CHAT_CANCEL_RUN = "cancel_boss_chat_run";
|
|
39
|
+
const { extractCompletionReasoningText, extractResponsesReasoningText } = llmTestables;
|
|
32
40
|
|
|
33
41
|
function makeToolCall(id, name, args = {}) {
|
|
34
42
|
return {
|
|
@@ -58,6 +66,8 @@ function createBossChatTestWorkspace() {
|
|
|
58
66
|
baseUrl: "https://api.example.com/v1",
|
|
59
67
|
apiKey: "sk-test-key",
|
|
60
68
|
model: "gpt-4.1-mini",
|
|
69
|
+
llmTimeoutMs: 65000,
|
|
70
|
+
llmMaxRetries: 4,
|
|
61
71
|
debugPort: 9666
|
|
62
72
|
}, null, 2));
|
|
63
73
|
|
|
@@ -254,6 +264,8 @@ async function testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli() {
|
|
|
254
264
|
assert.equal(stateAfterPrepare.last_prepare_args.baseurl, "https://api.example.com/v1");
|
|
255
265
|
assert.equal(stateAfterPrepare.last_prepare_args.apikey, "sk-test-key");
|
|
256
266
|
assert.equal(stateAfterPrepare.last_prepare_args.model, "gpt-4.1-mini");
|
|
267
|
+
assert.equal(stateAfterPrepare.last_prepare_args["llm-timeout-ms"], "65000");
|
|
268
|
+
assert.equal(stateAfterPrepare.last_prepare_args["llm-max-retries"], "4");
|
|
257
269
|
|
|
258
270
|
const started = await startBossChatRun({
|
|
259
271
|
workspaceRoot,
|
|
@@ -278,6 +290,8 @@ async function testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli() {
|
|
|
278
290
|
assert.equal(stateAfterStart.last_start_args.apikey, "sk-test-key");
|
|
279
291
|
assert.equal(stateAfterStart.last_start_args.model, "gpt-4.1-mini");
|
|
280
292
|
assert.equal(stateAfterStart.last_start_args.port, "9666");
|
|
293
|
+
assert.equal(stateAfterStart.last_start_args["llm-timeout-ms"], "65000");
|
|
294
|
+
assert.equal(stateAfterStart.last_start_args["llm-max-retries"], "4");
|
|
281
295
|
|
|
282
296
|
const startedAll = await startBossChatRun({
|
|
283
297
|
workspaceRoot,
|
|
@@ -897,41 +911,87 @@ function testCliShouldPinInstalledPackageVersionInGeneratedMcpConfig() {
|
|
|
897
911
|
assert.equal(launchConfig.args[0], "-y");
|
|
898
912
|
}
|
|
899
913
|
|
|
900
|
-
function
|
|
914
|
+
function testVendorBossChatCliShouldParseSharedLlmTransportArgs() {
|
|
915
|
+
const parsed = vendorCliTestables.parseArgs([
|
|
916
|
+
"start-run",
|
|
917
|
+
"--llm-timeout-ms",
|
|
918
|
+
"70000",
|
|
919
|
+
"--llm-max-retries",
|
|
920
|
+
"5",
|
|
921
|
+
]);
|
|
922
|
+
assert.equal(parsed.command, "start-run");
|
|
923
|
+
assert.equal(parsed.overrides.llm.timeoutMs, 70000);
|
|
924
|
+
assert.equal(parsed.overrides.llm.maxRetries, 5);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function testBossChatLlmParserShouldAcceptMinimalDecisionJson() {
|
|
901
928
|
const parsed = parseLlmJson(
|
|
902
929
|
JSON.stringify({
|
|
903
930
|
passed: true,
|
|
904
|
-
reason: "命中标准",
|
|
905
|
-
summary: "命中",
|
|
906
|
-
evidence: [],
|
|
907
931
|
}),
|
|
908
|
-
{
|
|
909
|
-
evidenceCorpus: "南京大学 机器学习 项目经历",
|
|
910
|
-
},
|
|
911
932
|
);
|
|
912
|
-
assert.equal(parsed.
|
|
933
|
+
assert.equal(parsed.passed, true);
|
|
934
|
+
assert.equal(parsed.rawOutputText.includes('"passed":true'), true);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function testBossChatLlmParserShouldAcceptPlainPassFailText() {
|
|
938
|
+
const passed = parseLlmJson("PASS");
|
|
939
|
+
assert.equal(passed.passed, true);
|
|
940
|
+
const failed = parseLlmJson("false");
|
|
941
|
+
assert.equal(failed.passed, false);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
function testBossChatLlmParserShouldAcceptDecisionField() {
|
|
945
|
+
const parsed = parseLlmJson(
|
|
946
|
+
JSON.stringify({
|
|
947
|
+
decision: "fail",
|
|
948
|
+
}),
|
|
949
|
+
);
|
|
913
950
|
assert.equal(parsed.passed, false);
|
|
914
|
-
assert.equal(parsed.evidenceGateDemoted, true);
|
|
915
|
-
assert.equal(parsed.evidenceMatchedCount, 0);
|
|
916
951
|
}
|
|
917
952
|
|
|
918
|
-
function
|
|
953
|
+
function testBossChatLlmParserShouldPreserveReasoningFields() {
|
|
919
954
|
const parsed = parseLlmJson(
|
|
920
955
|
JSON.stringify({
|
|
921
956
|
passed: true,
|
|
922
|
-
reason: "
|
|
923
|
-
summary: "
|
|
924
|
-
evidence: ["
|
|
957
|
+
reason: "候选人具备 2 段 AI Agent 项目经验",
|
|
958
|
+
summary: "符合筛选要求",
|
|
959
|
+
evidence: ["AI Agent", "MCP"],
|
|
925
960
|
}),
|
|
926
961
|
{
|
|
927
|
-
|
|
962
|
+
reasoningText: "先检查项目经历,再核对技能栈,结论为通过。",
|
|
928
963
|
},
|
|
929
964
|
);
|
|
930
|
-
assert.equal(parsed.
|
|
931
|
-
assert.equal(parsed.
|
|
932
|
-
assert.equal(parsed.
|
|
933
|
-
assert.equal(parsed.
|
|
934
|
-
assert.
|
|
965
|
+
assert.equal(parsed.passed, true);
|
|
966
|
+
assert.equal(parsed.reason, "候选人具备 2 段 AI Agent 项目经验");
|
|
967
|
+
assert.equal(parsed.summary, "符合筛选要求");
|
|
968
|
+
assert.equal(parsed.cot, "先检查项目经历,再核对技能栈,结论为通过。");
|
|
969
|
+
assert.deepEqual(parsed.evidence, ["AI Agent", "MCP"]);
|
|
970
|
+
assert.equal(parsed.rawReasoningText, "先检查项目经历,再核对技能栈,结论为通过。");
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
function testBossChatLlmExtractorsShouldReadProviderReasoningFields() {
|
|
974
|
+
const completionReasoning = extractCompletionReasoningText({
|
|
975
|
+
choices: [
|
|
976
|
+
{
|
|
977
|
+
message: {
|
|
978
|
+
content: [{ type: "text", text: "{\"passed\":true}" }],
|
|
979
|
+
reasoning_content: [{ text: "先核对教育背景,再核对项目经历。" }],
|
|
980
|
+
},
|
|
981
|
+
},
|
|
982
|
+
],
|
|
983
|
+
});
|
|
984
|
+
assert.equal(completionReasoning.includes("教育背景"), true);
|
|
985
|
+
|
|
986
|
+
const responsesReasoning = extractResponsesReasoningText({
|
|
987
|
+
output: [
|
|
988
|
+
{
|
|
989
|
+
type: "reasoning",
|
|
990
|
+
summary: [{ text: "根据项目经历与技能关键词判断为通过。" }],
|
|
991
|
+
},
|
|
992
|
+
],
|
|
993
|
+
});
|
|
994
|
+
assert.equal(responsesReasoning.includes("技能关键词"), true);
|
|
935
995
|
}
|
|
936
996
|
|
|
937
997
|
async function testBossChatLlmTextChunkFallbackShouldWork() {
|
|
@@ -962,39 +1022,21 @@ async function testBossChatLlmTextChunkFallbackShouldWork() {
|
|
|
962
1022
|
if (payload.chunkIndex === 2) {
|
|
963
1023
|
return {
|
|
964
1024
|
passed: true,
|
|
965
|
-
|
|
966
|
-
reason: "命中分段证据",
|
|
967
|
-
summary: "命中",
|
|
968
|
-
evidence: ["PASS_MARKER_ABC"],
|
|
969
|
-
evidenceRawCount: 1,
|
|
970
|
-
evidenceMatchedCount: 1,
|
|
971
|
-
evidenceGateDemoted: false,
|
|
1025
|
+
rawOutputText: '{"passed":true}',
|
|
972
1026
|
chunkIndex: payload.chunkIndex,
|
|
973
1027
|
chunkTotal: payload.chunkTotal,
|
|
974
1028
|
};
|
|
975
1029
|
}
|
|
976
1030
|
return {
|
|
977
1031
|
passed: false,
|
|
978
|
-
|
|
979
|
-
reason: "本段证据不足",
|
|
980
|
-
summary: "不足",
|
|
981
|
-
evidence: [],
|
|
982
|
-
evidenceRawCount: 0,
|
|
983
|
-
evidenceMatchedCount: 0,
|
|
984
|
-
evidenceGateDemoted: false,
|
|
1032
|
+
rawOutputText: '{"passed":false}',
|
|
985
1033
|
chunkIndex: payload.chunkIndex,
|
|
986
1034
|
chunkTotal: payload.chunkTotal,
|
|
987
1035
|
};
|
|
988
1036
|
}
|
|
989
1037
|
return {
|
|
990
1038
|
passed: false,
|
|
991
|
-
|
|
992
|
-
reason: "unexpected",
|
|
993
|
-
summary: "unexpected",
|
|
994
|
-
evidence: [],
|
|
995
|
-
evidenceRawCount: 0,
|
|
996
|
-
evidenceMatchedCount: 0,
|
|
997
|
-
evidenceGateDemoted: false,
|
|
1039
|
+
rawOutputText: '{"passed":false}',
|
|
998
1040
|
chunkIndex: 1,
|
|
999
1041
|
chunkTotal: 1,
|
|
1000
1042
|
};
|
|
@@ -1115,6 +1157,70 @@ async function testBossChatLlmShouldApplyThinkingDefaultsAndOverrides() {
|
|
|
1115
1157
|
assert.deepEqual(responsesPayload.reasoning, { effort: "low" });
|
|
1116
1158
|
}
|
|
1117
1159
|
|
|
1160
|
+
async function testBossChatLlmShouldSendAllImageChunksInSingleRequest() {
|
|
1161
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-chat-image-chunks-"));
|
|
1162
|
+
const firstImage = path.join(tempDir, "chunk-1.png");
|
|
1163
|
+
const secondImage = path.join(tempDir, "chunk-2.png");
|
|
1164
|
+
fs.writeFileSync(
|
|
1165
|
+
firstImage,
|
|
1166
|
+
Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aN4QAAAAASUVORK5CYII=", "base64"),
|
|
1167
|
+
);
|
|
1168
|
+
fs.writeFileSync(
|
|
1169
|
+
secondImage,
|
|
1170
|
+
Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aN4QAAAAASUVORK5CYII=", "base64"),
|
|
1171
|
+
);
|
|
1172
|
+
|
|
1173
|
+
let completionPayload = null;
|
|
1174
|
+
const client = new LlmClient({
|
|
1175
|
+
baseUrl: "https://api.openai.com/v1",
|
|
1176
|
+
apiKey: "sk-test",
|
|
1177
|
+
model: "gpt-test",
|
|
1178
|
+
}, {
|
|
1179
|
+
fetchImpl: async (_url, options = {}) => {
|
|
1180
|
+
completionPayload = JSON.parse(String(options.body || "{}"));
|
|
1181
|
+
return {
|
|
1182
|
+
ok: true,
|
|
1183
|
+
status: 200,
|
|
1184
|
+
async json() {
|
|
1185
|
+
return {
|
|
1186
|
+
choices: [
|
|
1187
|
+
{
|
|
1188
|
+
message: {
|
|
1189
|
+
content: "{\"passed\":true}",
|
|
1190
|
+
},
|
|
1191
|
+
},
|
|
1192
|
+
],
|
|
1193
|
+
};
|
|
1194
|
+
},
|
|
1195
|
+
};
|
|
1196
|
+
},
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
try {
|
|
1200
|
+
const result = await client.evaluateResume({
|
|
1201
|
+
screeningCriteria: "有 AI 项目经验",
|
|
1202
|
+
candidate: {
|
|
1203
|
+
name: "候选人A",
|
|
1204
|
+
sourceJob: "算法工程师",
|
|
1205
|
+
resumeText: "",
|
|
1206
|
+
evidenceCorpus: "",
|
|
1207
|
+
},
|
|
1208
|
+
imagePaths: [firstImage, secondImage],
|
|
1209
|
+
});
|
|
1210
|
+
|
|
1211
|
+
assert.equal(result.passed, true);
|
|
1212
|
+
assert.equal(result.evaluationMode, "image-multi-chunk");
|
|
1213
|
+
assert.equal(result.imageCount, 2);
|
|
1214
|
+
assert.equal(Array.isArray(completionPayload.messages?.[0]?.content), true);
|
|
1215
|
+
assert.equal(
|
|
1216
|
+
completionPayload.messages[0].content.filter((item) => item.type === "image_url").length,
|
|
1217
|
+
2,
|
|
1218
|
+
);
|
|
1219
|
+
} finally {
|
|
1220
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1118
1224
|
async function testBossChatAppShouldResetPrimaryChatLabelBeforeInitialPrime() {
|
|
1119
1225
|
const calls = [];
|
|
1120
1226
|
const page = {
|
|
@@ -1599,6 +1705,431 @@ async function testBossChatAppShouldWaitForCandidateListBeforePriming() {
|
|
|
1599
1705
|
assert.equal(summary.skipped, 1);
|
|
1600
1706
|
}
|
|
1601
1707
|
|
|
1708
|
+
function createProcessCustomerHarness({
|
|
1709
|
+
llmEvaluate,
|
|
1710
|
+
captureResume,
|
|
1711
|
+
tracker,
|
|
1712
|
+
pageOverrides = {},
|
|
1713
|
+
} = {}) {
|
|
1714
|
+
const recorded = [];
|
|
1715
|
+
const page = {
|
|
1716
|
+
async closeResumeModalDomOnce() {
|
|
1717
|
+
recorded.push("closeResumeModalDomOnce");
|
|
1718
|
+
return {
|
|
1719
|
+
closed: true,
|
|
1720
|
+
method: "dom",
|
|
1721
|
+
finalState: { scopeCount: 0, iframeCount: 0, closeCount: 0, topScopeClass: "" },
|
|
1722
|
+
};
|
|
1723
|
+
},
|
|
1724
|
+
async waitForConversationReady() {
|
|
1725
|
+
recorded.push("waitForConversationReady");
|
|
1726
|
+
return {
|
|
1727
|
+
hasOnlineResume: true,
|
|
1728
|
+
hasAskResume: true,
|
|
1729
|
+
hasAttachmentResume: false,
|
|
1730
|
+
attachmentResumeEnabled: false,
|
|
1731
|
+
};
|
|
1732
|
+
},
|
|
1733
|
+
async openOnlineResume() {
|
|
1734
|
+
recorded.push("openOnlineResume");
|
|
1735
|
+
return { clicked: true, detectedOpen: true, by: "dom" };
|
|
1736
|
+
},
|
|
1737
|
+
async getResumeRateLimitWarning() {
|
|
1738
|
+
return { hit: false, text: "" };
|
|
1739
|
+
},
|
|
1740
|
+
async getResumeModalState() {
|
|
1741
|
+
return { open: true, iframeCount: 1, scopeCount: 1, closeCount: 1 };
|
|
1742
|
+
},
|
|
1743
|
+
async waitForCandidateActivated() {
|
|
1744
|
+
recorded.push("waitForCandidateActivated");
|
|
1745
|
+
return { matched: true };
|
|
1746
|
+
},
|
|
1747
|
+
async activateCandidate() {
|
|
1748
|
+
recorded.push("activateCandidate");
|
|
1749
|
+
return { ok: true };
|
|
1750
|
+
},
|
|
1751
|
+
...pageOverrides,
|
|
1752
|
+
};
|
|
1753
|
+
const llmCalls = [];
|
|
1754
|
+
const llmClient = {
|
|
1755
|
+
async evaluateResume(payload) {
|
|
1756
|
+
llmCalls.push(payload);
|
|
1757
|
+
return llmEvaluate(payload);
|
|
1758
|
+
},
|
|
1759
|
+
};
|
|
1760
|
+
const resumeCaptureService = {
|
|
1761
|
+
async captureResume(payload) {
|
|
1762
|
+
recorded.push("captureResume");
|
|
1763
|
+
return captureResume(payload);
|
|
1764
|
+
},
|
|
1765
|
+
};
|
|
1766
|
+
const stateStore = {
|
|
1767
|
+
async record(_key, result) {
|
|
1768
|
+
recorded.push(`record:${result.decision}`);
|
|
1769
|
+
},
|
|
1770
|
+
};
|
|
1771
|
+
const app = new BossChatApp({
|
|
1772
|
+
page,
|
|
1773
|
+
llmClient,
|
|
1774
|
+
interaction: {
|
|
1775
|
+
async sleepRange() {},
|
|
1776
|
+
async clickRect() {},
|
|
1777
|
+
},
|
|
1778
|
+
resumeCaptureService,
|
|
1779
|
+
resumeNetworkTracker: tracker || null,
|
|
1780
|
+
stateStore,
|
|
1781
|
+
reportStore: { async write() { return ""; } },
|
|
1782
|
+
dryRun: true,
|
|
1783
|
+
artifactRootDir: os.tmpdir(),
|
|
1784
|
+
resumeOpenCooldownMs: 0,
|
|
1785
|
+
logger: { log() {} },
|
|
1786
|
+
});
|
|
1787
|
+
app.waitResumeOpenCooldown = async () => {};
|
|
1788
|
+
return { app, llmCalls, recorded };
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
async function testBossChatResumeTrackerShouldRetryInitialNetworkWait() {
|
|
1792
|
+
const tracker = new ResumeNetworkTracker({
|
|
1793
|
+
chromeClient: { Network: null },
|
|
1794
|
+
logger: { log() {} },
|
|
1795
|
+
});
|
|
1796
|
+
const waits = [];
|
|
1797
|
+
let callCount = 0;
|
|
1798
|
+
tracker.waitForNetworkResumeCandidateInfo = async (_candidate, timeoutMs) => {
|
|
1799
|
+
waits.push(timeoutMs);
|
|
1800
|
+
callCount += 1;
|
|
1801
|
+
if (callCount === 2) {
|
|
1802
|
+
return {
|
|
1803
|
+
candidateInfo: { resumeText: "network resume" },
|
|
1804
|
+
source: "geek_id_map",
|
|
1805
|
+
waitedMs: 80,
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
return null;
|
|
1809
|
+
};
|
|
1810
|
+
const result = await tracker.waitForResumeNetworkByMode({ customerId: "1001" });
|
|
1811
|
+
assert.deepEqual(waits, [NETWORK_RESUME_WAIT_MS, NETWORK_RESUME_RETRY_WAIT_MS]);
|
|
1812
|
+
assert.equal(result.acquisitionReason, "network_retry_hit");
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
async function testBossChatResumeTrackerShouldUseImageModeGraceWindow() {
|
|
1816
|
+
const tracker = new ResumeNetworkTracker({
|
|
1817
|
+
chromeClient: { Network: null },
|
|
1818
|
+
logger: { log() {} },
|
|
1819
|
+
});
|
|
1820
|
+
tracker.setResumeAcquisitionMode("image", "previous_image_fallback");
|
|
1821
|
+
const waits = [];
|
|
1822
|
+
tracker.waitForNetworkResumeCandidateInfo = async (_candidate, timeoutMs) => {
|
|
1823
|
+
waits.push(timeoutMs);
|
|
1824
|
+
return null;
|
|
1825
|
+
};
|
|
1826
|
+
const result = await tracker.waitForResumeNetworkByMode({ customerId: "1002" });
|
|
1827
|
+
assert.deepEqual(waits, [NETWORK_RESUME_IMAGE_MODE_GRACE_MS]);
|
|
1828
|
+
assert.equal(result.initialWaitMs >= 0, true);
|
|
1829
|
+
assert.equal(result.retryWaitMs, 0);
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
async function testBossChatAppShouldUseNetworkBeforeImageFallback() {
|
|
1833
|
+
const tracker = {
|
|
1834
|
+
resumeNetworkDiagnostics: [],
|
|
1835
|
+
getResumeAcquisitionState() {
|
|
1836
|
+
return { mode: "network", reason: "initial_network_hit" };
|
|
1837
|
+
},
|
|
1838
|
+
async waitForResumeNetworkByMode() {
|
|
1839
|
+
return {
|
|
1840
|
+
candidateInfo: {
|
|
1841
|
+
name: "候选人A",
|
|
1842
|
+
school: "清华大学",
|
|
1843
|
+
major: "计算机",
|
|
1844
|
+
company: "OpenAI",
|
|
1845
|
+
position: "工程师",
|
|
1846
|
+
resumeText: "清华大学 计算机 OpenAI",
|
|
1847
|
+
evidenceCorpus: "清华大学 计算机 OpenAI",
|
|
1848
|
+
},
|
|
1849
|
+
acquisitionReason: "initial_network_hit",
|
|
1850
|
+
initialWaitMs: 12,
|
|
1851
|
+
retryWaitMs: 0,
|
|
1852
|
+
};
|
|
1853
|
+
},
|
|
1854
|
+
async waitForLateNetworkResumeCandidateInfo() {
|
|
1855
|
+
throw new Error("late network retry should not run");
|
|
1856
|
+
},
|
|
1857
|
+
};
|
|
1858
|
+
const { app, llmCalls, recorded } = createProcessCustomerHarness({
|
|
1859
|
+
tracker,
|
|
1860
|
+
llmEvaluate: async () => ({
|
|
1861
|
+
passed: true,
|
|
1862
|
+
rawOutputText: '{"passed":true}',
|
|
1863
|
+
evaluationMode: "text",
|
|
1864
|
+
chunkIndex: 1,
|
|
1865
|
+
chunkTotal: 1,
|
|
1866
|
+
}),
|
|
1867
|
+
captureResume: async () => {
|
|
1868
|
+
throw new Error("image capture should not run");
|
|
1869
|
+
},
|
|
1870
|
+
});
|
|
1871
|
+
|
|
1872
|
+
const result = await app.processCustomer(
|
|
1873
|
+
{
|
|
1874
|
+
customerKey: "candidate-network",
|
|
1875
|
+
name: "候选人A",
|
|
1876
|
+
sourceJob: "算法工程师",
|
|
1877
|
+
domIndex: 0,
|
|
1878
|
+
customerId: "1001",
|
|
1879
|
+
textSnippet: "",
|
|
1880
|
+
},
|
|
1881
|
+
{ screeningCriteria: "有 AI 项目经验" },
|
|
1882
|
+
"run-network",
|
|
1883
|
+
{ skipCardClick: true },
|
|
1884
|
+
);
|
|
1885
|
+
|
|
1886
|
+
assert.equal(result.artifacts.resumeAcquisitionMode, "network");
|
|
1887
|
+
assert.equal(result.artifacts.resumeAcquisitionReason, "initial_network_hit");
|
|
1888
|
+
assert.equal(llmCalls.length, 1);
|
|
1889
|
+
assert.equal(llmCalls[0].candidate.resumeText.includes("清华大学"), true);
|
|
1890
|
+
assert.equal(Array.isArray(llmCalls[0].imagePaths), false);
|
|
1891
|
+
assert.equal(recorded.includes("captureResume"), false);
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
async function testBossChatAppShouldFallbackToImageAfterNetworkMiss() {
|
|
1895
|
+
const tracker = {
|
|
1896
|
+
resumeNetworkDiagnostics: [],
|
|
1897
|
+
setResumeAcquisitionMode(mode, reason) {
|
|
1898
|
+
this.state = { mode, reason };
|
|
1899
|
+
},
|
|
1900
|
+
getResumeAcquisitionState() {
|
|
1901
|
+
return this.state || { mode: "image", reason: "image_capture_success" };
|
|
1902
|
+
},
|
|
1903
|
+
async waitForResumeNetworkByMode() {
|
|
1904
|
+
return {
|
|
1905
|
+
candidateInfo: null,
|
|
1906
|
+
acquisitionReason: "",
|
|
1907
|
+
initialWaitMs: 10,
|
|
1908
|
+
retryWaitMs: 20,
|
|
1909
|
+
};
|
|
1910
|
+
},
|
|
1911
|
+
async waitForLateNetworkResumeCandidateInfo() {
|
|
1912
|
+
return {
|
|
1913
|
+
candidateInfo: null,
|
|
1914
|
+
acquisitionReason: "",
|
|
1915
|
+
lateRetryMs: 0,
|
|
1916
|
+
};
|
|
1917
|
+
},
|
|
1918
|
+
};
|
|
1919
|
+
const { app, llmCalls } = createProcessCustomerHarness({
|
|
1920
|
+
tracker,
|
|
1921
|
+
llmEvaluate: async () => ({
|
|
1922
|
+
passed: false,
|
|
1923
|
+
rawOutputText: '{"passed":false}',
|
|
1924
|
+
evaluationMode: "image-multi-chunk",
|
|
1925
|
+
imageCount: 2,
|
|
1926
|
+
chunkIndex: 1,
|
|
1927
|
+
chunkTotal: 1,
|
|
1928
|
+
}),
|
|
1929
|
+
captureResume: async ({ artifactDir }) => ({
|
|
1930
|
+
metadataFile: path.join(artifactDir, "chunks.json"),
|
|
1931
|
+
chunkDir: path.join(artifactDir, "chunks"),
|
|
1932
|
+
chunkCount: 2,
|
|
1933
|
+
modelImagePaths: [
|
|
1934
|
+
path.join(artifactDir, "chunks", "chunk_000.png"),
|
|
1935
|
+
path.join(artifactDir, "chunks", "chunk_001.png"),
|
|
1936
|
+
],
|
|
1937
|
+
stitchedImage: "",
|
|
1938
|
+
quality: { likelyBlank: false },
|
|
1939
|
+
}),
|
|
1940
|
+
});
|
|
1941
|
+
|
|
1942
|
+
const result = await app.processCustomer(
|
|
1943
|
+
{
|
|
1944
|
+
customerKey: "candidate-image",
|
|
1945
|
+
name: "候选人B",
|
|
1946
|
+
sourceJob: "算法工程师",
|
|
1947
|
+
domIndex: 0,
|
|
1948
|
+
customerId: "1002",
|
|
1949
|
+
textSnippet: "",
|
|
1950
|
+
},
|
|
1951
|
+
{ screeningCriteria: "有 AI 项目经验" },
|
|
1952
|
+
"run-image",
|
|
1953
|
+
{ skipCardClick: true },
|
|
1954
|
+
);
|
|
1955
|
+
|
|
1956
|
+
assert.equal(result.artifacts.resumeAcquisitionMode, "image_fallback");
|
|
1957
|
+
assert.equal(result.artifacts.resumeAcquisitionReason, "image_capture_success");
|
|
1958
|
+
assert.equal(Array.isArray(llmCalls[0].imagePaths), true);
|
|
1959
|
+
assert.equal(llmCalls[0].imagePaths.length, 2);
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
async function testBossChatAppShouldRetryLateNetworkBeforeDomFallback() {
|
|
1963
|
+
const tracker = {
|
|
1964
|
+
resumeNetworkDiagnostics: [],
|
|
1965
|
+
getResumeAcquisitionState() {
|
|
1966
|
+
return { mode: "network", reason: "late_network_hit" };
|
|
1967
|
+
},
|
|
1968
|
+
setResumeAcquisitionMode() {},
|
|
1969
|
+
async waitForResumeNetworkByMode() {
|
|
1970
|
+
return {
|
|
1971
|
+
candidateInfo: null,
|
|
1972
|
+
acquisitionReason: "",
|
|
1973
|
+
initialWaitMs: 10,
|
|
1974
|
+
retryWaitMs: 20,
|
|
1975
|
+
};
|
|
1976
|
+
},
|
|
1977
|
+
async waitForLateNetworkResumeCandidateInfo() {
|
|
1978
|
+
return {
|
|
1979
|
+
candidateInfo: {
|
|
1980
|
+
name: "候选人C",
|
|
1981
|
+
school: "上海交大",
|
|
1982
|
+
major: "软件工程",
|
|
1983
|
+
resumeText: "上海交大 软件工程",
|
|
1984
|
+
evidenceCorpus: "上海交大 软件工程",
|
|
1985
|
+
},
|
|
1986
|
+
acquisitionReason: "late_network_hit",
|
|
1987
|
+
lateRetryMs: 30,
|
|
1988
|
+
};
|
|
1989
|
+
},
|
|
1990
|
+
};
|
|
1991
|
+
let imageAttempt = 0;
|
|
1992
|
+
const { app, llmCalls } = createProcessCustomerHarness({
|
|
1993
|
+
tracker,
|
|
1994
|
+
llmEvaluate: async (payload) => {
|
|
1995
|
+
imageAttempt += 1;
|
|
1996
|
+
if (Array.isArray(payload.imagePaths) && payload.imagePaths.length > 0) {
|
|
1997
|
+
throw new Error("VISION_MODEL_FAILED");
|
|
1998
|
+
}
|
|
1999
|
+
return {
|
|
2000
|
+
passed: true,
|
|
2001
|
+
rawOutputText: '{"passed":true}',
|
|
2002
|
+
evaluationMode: "text",
|
|
2003
|
+
chunkIndex: 1,
|
|
2004
|
+
chunkTotal: 1,
|
|
2005
|
+
};
|
|
2006
|
+
},
|
|
2007
|
+
captureResume: async ({ artifactDir }) => ({
|
|
2008
|
+
metadataFile: path.join(artifactDir, "chunks.json"),
|
|
2009
|
+
chunkDir: path.join(artifactDir, "chunks"),
|
|
2010
|
+
chunkCount: 1,
|
|
2011
|
+
modelImagePaths: [path.join(artifactDir, "chunks", "chunk_000.png")],
|
|
2012
|
+
stitchedImage: "",
|
|
2013
|
+
quality: { likelyBlank: false },
|
|
2014
|
+
}),
|
|
2015
|
+
});
|
|
2016
|
+
|
|
2017
|
+
const result = await app.processCustomer(
|
|
2018
|
+
{
|
|
2019
|
+
customerKey: "candidate-late-network",
|
|
2020
|
+
name: "候选人C",
|
|
2021
|
+
sourceJob: "算法工程师",
|
|
2022
|
+
domIndex: 0,
|
|
2023
|
+
customerId: "1003",
|
|
2024
|
+
textSnippet: "",
|
|
2025
|
+
},
|
|
2026
|
+
{ screeningCriteria: "有 AI 项目经验" },
|
|
2027
|
+
"run-late-network",
|
|
2028
|
+
{ skipCardClick: true },
|
|
2029
|
+
);
|
|
2030
|
+
|
|
2031
|
+
assert.equal(imageAttempt >= 2, true);
|
|
2032
|
+
assert.equal(result.artifacts.resumeAcquisitionMode, "network");
|
|
2033
|
+
assert.equal(result.artifacts.resumeAcquisitionReason, "late_network_hit");
|
|
2034
|
+
assert.equal(llmCalls[llmCalls.length - 1].candidate.resumeText.includes("上海交大"), true);
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
async function testBossChatAppShouldUseDomOnlyAfterHigherPriorityPathsFail() {
|
|
2038
|
+
let domReadCount = 0;
|
|
2039
|
+
const tracker = {
|
|
2040
|
+
resumeNetworkDiagnostics: [],
|
|
2041
|
+
getResumeAcquisitionState() {
|
|
2042
|
+
return { mode: "image", reason: "image_capture_success" };
|
|
2043
|
+
},
|
|
2044
|
+
setResumeAcquisitionMode() {},
|
|
2045
|
+
async waitForResumeNetworkByMode() {
|
|
2046
|
+
return {
|
|
2047
|
+
candidateInfo: null,
|
|
2048
|
+
acquisitionReason: "",
|
|
2049
|
+
initialWaitMs: 10,
|
|
2050
|
+
retryWaitMs: 20,
|
|
2051
|
+
};
|
|
2052
|
+
},
|
|
2053
|
+
async waitForLateNetworkResumeCandidateInfo() {
|
|
2054
|
+
return {
|
|
2055
|
+
candidateInfo: null,
|
|
2056
|
+
acquisitionReason: "",
|
|
2057
|
+
lateRetryMs: 15,
|
|
2058
|
+
};
|
|
2059
|
+
},
|
|
2060
|
+
async waitForNetworkResumeCandidateInfo() {
|
|
2061
|
+
return null;
|
|
2062
|
+
},
|
|
2063
|
+
};
|
|
2064
|
+
const { app, recorded } = createProcessCustomerHarness({
|
|
2065
|
+
tracker,
|
|
2066
|
+
llmEvaluate: async (payload) => ({
|
|
2067
|
+
passed: false,
|
|
2068
|
+
rawOutputText: '{"passed":false}',
|
|
2069
|
+
evaluationMode: "text",
|
|
2070
|
+
chunkIndex: 1,
|
|
2071
|
+
chunkTotal: 1,
|
|
2072
|
+
imageCount: 0,
|
|
2073
|
+
}),
|
|
2074
|
+
captureResume: async () => {
|
|
2075
|
+
throw new Error("IMAGE_CAPTURE_FAILED");
|
|
2076
|
+
},
|
|
2077
|
+
pageOverrides: {
|
|
2078
|
+
async getResumeProfileFromDom() {
|
|
2079
|
+
domReadCount += 1;
|
|
2080
|
+
if (domReadCount === 1) {
|
|
2081
|
+
return {
|
|
2082
|
+
ok: true,
|
|
2083
|
+
name: "李同学",
|
|
2084
|
+
primarySchool: "北京大学",
|
|
2085
|
+
schools: ["北京大学"],
|
|
2086
|
+
major: "数学",
|
|
2087
|
+
majors: ["数学"],
|
|
2088
|
+
company: "",
|
|
2089
|
+
position: "",
|
|
2090
|
+
resumeText: "北京大学 数学",
|
|
2091
|
+
evidenceCorpus: "北京大学 数学",
|
|
2092
|
+
};
|
|
2093
|
+
}
|
|
2094
|
+
return {
|
|
2095
|
+
ok: true,
|
|
2096
|
+
name: "候选人D",
|
|
2097
|
+
primarySchool: "浙江大学",
|
|
2098
|
+
schools: ["浙江大学"],
|
|
2099
|
+
major: "计算机",
|
|
2100
|
+
majors: ["计算机"],
|
|
2101
|
+
company: "",
|
|
2102
|
+
position: "",
|
|
2103
|
+
resumeText: "浙江大学 计算机",
|
|
2104
|
+
evidenceCorpus: "浙江大学 计算机",
|
|
2105
|
+
};
|
|
2106
|
+
},
|
|
2107
|
+
},
|
|
2108
|
+
});
|
|
2109
|
+
|
|
2110
|
+
const result = await app.processCustomer(
|
|
2111
|
+
{
|
|
2112
|
+
customerKey: "candidate-dom",
|
|
2113
|
+
name: "候选人D",
|
|
2114
|
+
school: "浙江大学",
|
|
2115
|
+
major: "计算机",
|
|
2116
|
+
sourceJob: "算法工程师",
|
|
2117
|
+
domIndex: 0,
|
|
2118
|
+
customerId: "1004",
|
|
2119
|
+
textSnippet: "",
|
|
2120
|
+
},
|
|
2121
|
+
{ screeningCriteria: "有 AI 项目经验" },
|
|
2122
|
+
"run-dom",
|
|
2123
|
+
{ skipCardClick: true },
|
|
2124
|
+
);
|
|
2125
|
+
|
|
2126
|
+
assert.equal(result.artifacts.resumeAcquisitionMode, "dom_fallback");
|
|
2127
|
+
assert.equal(result.artifacts.resumeAcquisitionReason, "dom_retry_hit");
|
|
2128
|
+
assert.equal(domReadCount, 2);
|
|
2129
|
+
assert.equal(recorded.includes("activateCandidate"), true);
|
|
2130
|
+
assert.equal(recorded.includes("openOnlineResume"), true);
|
|
2131
|
+
}
|
|
2132
|
+
|
|
1602
2133
|
async function testBossChatAppShouldPersistEvidenceArtifacts() {
|
|
1603
2134
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-chat-artifacts-"));
|
|
1604
2135
|
await mkdir(tempDir, { recursive: true });
|
|
@@ -1646,14 +2177,14 @@ async function testBossChatAppShouldPersistEvidenceArtifacts() {
|
|
|
1646
2177
|
async evaluateResume() {
|
|
1647
2178
|
return {
|
|
1648
2179
|
passed: false,
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
2180
|
+
rawOutputText: '{"passed":false}',
|
|
2181
|
+
rawReasoningText: "先看项目经验,再看技能,结论不通过。",
|
|
2182
|
+
cot: "先看项目经验,再看技能,结论不通过。",
|
|
2183
|
+
reason: "项目经验与岗位要求不符",
|
|
2184
|
+
summary: "不符合要求",
|
|
2185
|
+
evidence: ["Python"],
|
|
2186
|
+
evaluationMode: "image-multi-chunk",
|
|
2187
|
+
imageCount: 3,
|
|
1657
2188
|
chunkIndex: 1,
|
|
1658
2189
|
chunkTotal: 1,
|
|
1659
2190
|
};
|
|
@@ -1666,10 +2197,15 @@ async function testBossChatAppShouldPersistEvidenceArtifacts() {
|
|
|
1666
2197
|
const resumeCaptureService = {
|
|
1667
2198
|
async captureResume({ artifactDir }) {
|
|
1668
2199
|
return {
|
|
1669
|
-
stitchedImage: path.join(artifactDir, "resume.png"),
|
|
1670
2200
|
metadataFile: path.join(artifactDir, "chunks.json"),
|
|
1671
2201
|
chunkDir: path.join(artifactDir, "chunks"),
|
|
1672
2202
|
chunkCount: 1,
|
|
2203
|
+
modelImagePaths: [
|
|
2204
|
+
path.join(artifactDir, "chunks", "chunk_000.png"),
|
|
2205
|
+
path.join(artifactDir, "chunks", "chunk_001.png"),
|
|
2206
|
+
path.join(artifactDir, "chunks", "chunk_002.png"),
|
|
2207
|
+
],
|
|
2208
|
+
stitchedImage: "",
|
|
1673
2209
|
quality: { likelyBlank: false },
|
|
1674
2210
|
};
|
|
1675
2211
|
},
|
|
@@ -1710,16 +2246,129 @@ async function testBossChatAppShouldPersistEvidenceArtifacts() {
|
|
|
1710
2246
|
);
|
|
1711
2247
|
|
|
1712
2248
|
assert.equal(result.passed, false);
|
|
1713
|
-
assert.equal(result.artifacts.rawPassed, true);
|
|
1714
2249
|
assert.equal(result.artifacts.finalPassed, false);
|
|
1715
|
-
assert.equal(result.
|
|
1716
|
-
assert.equal(result.artifacts.
|
|
1717
|
-
assert.equal(result.artifacts.
|
|
1718
|
-
assert.equal(result.artifacts.
|
|
2250
|
+
assert.equal(result.reason, "项目经验与岗位要求不符");
|
|
2251
|
+
assert.equal(result.artifacts.evaluationMode, "image-multi-chunk");
|
|
2252
|
+
assert.equal(result.artifacts.evaluationImageCount, 3);
|
|
2253
|
+
assert.equal(result.artifacts.llmReason, "项目经验与岗位要求不符");
|
|
2254
|
+
assert.equal(result.artifacts.llmSummary, "不符合要求");
|
|
2255
|
+
assert.equal(result.artifacts.llmCot, "先看项目经验,再看技能,结论不通过。");
|
|
2256
|
+
assert.deepEqual(result.artifacts.llmEvidence, ["Python"]);
|
|
2257
|
+
assert.equal(result.artifacts.llmRawReasoning, "先看项目经验,再看技能,结论不通过。");
|
|
2258
|
+
assert.equal(result.artifacts.llmRawOutput, '{"passed":false}');
|
|
2259
|
+
assert.equal(Array.isArray(result.artifacts.modelImagePaths), true);
|
|
2260
|
+
assert.equal(result.artifacts.modelImagePaths.length, 3);
|
|
1719
2261
|
assert.equal(Array.isArray(records), true);
|
|
1720
2262
|
assert.equal(records.length, 1);
|
|
1721
2263
|
}
|
|
1722
2264
|
|
|
2265
|
+
async function testBossChatReportStoreShouldWriteReadableMarkdownAndCsv() {
|
|
2266
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-chat-report-store-"));
|
|
2267
|
+
const reportStore = new ReportStore(tempDir);
|
|
2268
|
+
const summary = {
|
|
2269
|
+
startedAt: "2026-04-17T10:00:00.000Z",
|
|
2270
|
+
finishedAt: "2026-04-17T10:01:05.000Z",
|
|
2271
|
+
dryRun: true,
|
|
2272
|
+
profile: {
|
|
2273
|
+
targetCount: 5,
|
|
2274
|
+
screeningCriteria: "有 AI 项目经验",
|
|
2275
|
+
},
|
|
2276
|
+
inspected: 2,
|
|
2277
|
+
passed: 1,
|
|
2278
|
+
requested: 0,
|
|
2279
|
+
skipped: 1,
|
|
2280
|
+
errors: 0,
|
|
2281
|
+
exhausted: false,
|
|
2282
|
+
stopped: false,
|
|
2283
|
+
stopReason: "",
|
|
2284
|
+
results: [
|
|
2285
|
+
{
|
|
2286
|
+
name: "候选人A",
|
|
2287
|
+
sourceJob: "算法工程师",
|
|
2288
|
+
decision: "passed",
|
|
2289
|
+
passed: true,
|
|
2290
|
+
requested: false,
|
|
2291
|
+
reason: "符合要求",
|
|
2292
|
+
error: "",
|
|
2293
|
+
artifacts: {
|
|
2294
|
+
resumeAcquisitionMode: "network",
|
|
2295
|
+
resumeAcquisitionReason: "initial_hit",
|
|
2296
|
+
textModelMs: 18234,
|
|
2297
|
+
initialNetworkWaitMs: 4200,
|
|
2298
|
+
evaluationMode: "text",
|
|
2299
|
+
llmSummary: "教育与项目经历匹配",
|
|
2300
|
+
llmCot: "先看教育背景,再看项目经历,结论通过。",
|
|
2301
|
+
llmEvidence: ["AI Agent", "MCP"],
|
|
2302
|
+
llmRawReasoning: "先看教育背景,再看项目经历,结论通过。",
|
|
2303
|
+
llmRawOutput: '{"passed":true}',
|
|
2304
|
+
},
|
|
2305
|
+
},
|
|
2306
|
+
{
|
|
2307
|
+
name: "候选人B",
|
|
2308
|
+
sourceJob: "大模型算法",
|
|
2309
|
+
decision: "skipped",
|
|
2310
|
+
passed: false,
|
|
2311
|
+
requested: false,
|
|
2312
|
+
reason: "LLM判定不通过",
|
|
2313
|
+
error: "",
|
|
2314
|
+
artifacts: {
|
|
2315
|
+
resumeAcquisitionMode: "image_fallback",
|
|
2316
|
+
resumeAcquisitionReason: "late_network_miss",
|
|
2317
|
+
imageCaptureMs: 2300,
|
|
2318
|
+
imageModelMs: 19500,
|
|
2319
|
+
lateNetworkRetryMs: 3000,
|
|
2320
|
+
evaluationMode: "image-multi-chunk",
|
|
2321
|
+
evaluationImageCount: 3,
|
|
2322
|
+
llmSummary: "项目经历不足",
|
|
2323
|
+
llmCot: "先看项目经历,再看实习时长,结论不通过。",
|
|
2324
|
+
llmEvidence: ["数据分析"],
|
|
2325
|
+
llmRawReasoning: "先看项目经历,再看实习时长,结论不通过。",
|
|
2326
|
+
llmRawOutput: '{"passed":false}',
|
|
2327
|
+
},
|
|
2328
|
+
},
|
|
2329
|
+
],
|
|
2330
|
+
reportPath: null,
|
|
2331
|
+
};
|
|
2332
|
+
|
|
2333
|
+
const jsonPath = await reportStore.write(summary);
|
|
2334
|
+
const markdownPath = summary.reportMarkdownPath;
|
|
2335
|
+
const csvPath = summary.reportCsvPath;
|
|
2336
|
+
const jsonContent = fs.readFileSync(jsonPath, "utf8");
|
|
2337
|
+
const markdownContent = fs.readFileSync(markdownPath, "utf8");
|
|
2338
|
+
const csvContent = fs.readFileSync(csvPath, "utf8");
|
|
2339
|
+
|
|
2340
|
+
assert.equal(path.extname(jsonPath), ".json");
|
|
2341
|
+
assert.equal(path.extname(markdownPath), ".md");
|
|
2342
|
+
assert.equal(path.extname(csvPath), ".csv");
|
|
2343
|
+
assert.equal(summary.reportPath, jsonPath);
|
|
2344
|
+
assert.equal(typeof summary.reportArtifacts, "object");
|
|
2345
|
+
assert.equal(summary.reportArtifacts.markdownPath, markdownPath);
|
|
2346
|
+
assert.equal(summary.reportArtifacts.csvPath, csvPath);
|
|
2347
|
+
|
|
2348
|
+
const parsedJson = JSON.parse(jsonContent);
|
|
2349
|
+
assert.equal(parsedJson.reportPath, jsonPath);
|
|
2350
|
+
assert.equal(parsedJson.reportMarkdownPath, markdownPath);
|
|
2351
|
+
assert.equal(parsedJson.reportCsvPath, csvPath);
|
|
2352
|
+
|
|
2353
|
+
assert.match(markdownContent, /# Boss Chat 运行报告/);
|
|
2354
|
+
assert.match(markdownContent, /Resume Acquisition 汇总/);
|
|
2355
|
+
assert.match(markdownContent, /Timing 汇总/);
|
|
2356
|
+
assert.match(markdownContent, /候选人A/);
|
|
2357
|
+
assert.match(markdownContent, /image_fallback/);
|
|
2358
|
+
assert.match(markdownContent, /图片模型 19500ms/);
|
|
2359
|
+
|
|
2360
|
+
assert.match(csvContent, /resume_acquisition_mode/);
|
|
2361
|
+
assert.match(csvContent, /initial_network_wait_ms/);
|
|
2362
|
+
assert.match(csvContent, /late_network_retry_ms/);
|
|
2363
|
+
assert.match(csvContent, /llm_summary/);
|
|
2364
|
+
assert.match(csvContent, /llm_cot/);
|
|
2365
|
+
assert.match(csvContent, /llm_raw_reasoning/);
|
|
2366
|
+
assert.match(csvContent, /llm_raw_output/);
|
|
2367
|
+
assert.match(csvContent, /候选人B/);
|
|
2368
|
+
assert.match(csvContent, /image-multi-chunk/);
|
|
2369
|
+
assert.match(csvContent, /先看项目经历,再看实习时长/);
|
|
2370
|
+
}
|
|
2371
|
+
|
|
1723
2372
|
async function main() {
|
|
1724
2373
|
await testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli();
|
|
1725
2374
|
await testBossChatPrepareShouldRetryWhenChatPageIsNotReady();
|
|
@@ -1731,15 +2380,27 @@ async function main() {
|
|
|
1731
2380
|
await testVendorBossChatCliShouldWaitForHydratedChatShell();
|
|
1732
2381
|
await testVendorBossChatCliShouldRetryJobListDuringPromptRunProfile();
|
|
1733
2382
|
testCliShouldPinInstalledPackageVersionInGeneratedMcpConfig();
|
|
1734
|
-
|
|
1735
|
-
|
|
2383
|
+
testVendorBossChatCliShouldParseSharedLlmTransportArgs();
|
|
2384
|
+
testBossChatLlmParserShouldAcceptMinimalDecisionJson();
|
|
2385
|
+
testBossChatLlmParserShouldAcceptPlainPassFailText();
|
|
2386
|
+
testBossChatLlmParserShouldAcceptDecisionField();
|
|
2387
|
+
testBossChatLlmParserShouldPreserveReasoningFields();
|
|
2388
|
+
testBossChatLlmExtractorsShouldReadProviderReasoningFields();
|
|
1736
2389
|
await testBossChatLlmTextChunkFallbackShouldWork();
|
|
1737
2390
|
await testBossChatLlmShouldApplyThinkingDefaultsAndOverrides();
|
|
2391
|
+
await testBossChatLlmShouldSendAllImageChunksInSingleRequest();
|
|
1738
2392
|
await testBossChatAppShouldResetPrimaryChatLabelBeforeInitialPrime();
|
|
1739
2393
|
await testBossChatAppShouldCloseCandidateDetailDuringRunCleanup();
|
|
1740
2394
|
await testBossChatAppShouldRestoreListContextAfterRecovery();
|
|
1741
2395
|
await testBossChatAppShouldWaitForCandidateListBeforePriming();
|
|
2396
|
+
await testBossChatResumeTrackerShouldRetryInitialNetworkWait();
|
|
2397
|
+
await testBossChatResumeTrackerShouldUseImageModeGraceWindow();
|
|
2398
|
+
await testBossChatAppShouldUseNetworkBeforeImageFallback();
|
|
2399
|
+
await testBossChatAppShouldFallbackToImageAfterNetworkMiss();
|
|
2400
|
+
await testBossChatAppShouldRetryLateNetworkBeforeDomFallback();
|
|
2401
|
+
await testBossChatAppShouldUseDomOnlyAfterHigherPriorityPathsFail();
|
|
1742
2402
|
await testBossChatAppShouldPersistEvidenceArtifacts();
|
|
2403
|
+
await testBossChatReportStoreShouldWriteReadableMarkdownAndCsv();
|
|
1743
2404
|
console.log("boss-chat tests passed");
|
|
1744
2405
|
}
|
|
1745
2406
|
|