@reconcrap/boss-recommend-mcp 1.3.30 → 1.3.31
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 +651 -60
- package/vendor/boss-chat-cli/src/app.js +395 -178
- 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 +96 -86
- package/vendor/boss-chat-cli/src/services/profile-store.js +6 -0
- package/vendor/boss-chat-cli/src/services/report-store.js +267 -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
|
@@ -19,6 +19,13 @@ 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
21
|
import { LlmClient, parseLlmJson } 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
|
|
|
@@ -58,6 +65,8 @@ function createBossChatTestWorkspace() {
|
|
|
58
65
|
baseUrl: "https://api.example.com/v1",
|
|
59
66
|
apiKey: "sk-test-key",
|
|
60
67
|
model: "gpt-4.1-mini",
|
|
68
|
+
llmTimeoutMs: 65000,
|
|
69
|
+
llmMaxRetries: 4,
|
|
61
70
|
debugPort: 9666
|
|
62
71
|
}, null, 2));
|
|
63
72
|
|
|
@@ -254,6 +263,8 @@ async function testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli() {
|
|
|
254
263
|
assert.equal(stateAfterPrepare.last_prepare_args.baseurl, "https://api.example.com/v1");
|
|
255
264
|
assert.equal(stateAfterPrepare.last_prepare_args.apikey, "sk-test-key");
|
|
256
265
|
assert.equal(stateAfterPrepare.last_prepare_args.model, "gpt-4.1-mini");
|
|
266
|
+
assert.equal(stateAfterPrepare.last_prepare_args["llm-timeout-ms"], "65000");
|
|
267
|
+
assert.equal(stateAfterPrepare.last_prepare_args["llm-max-retries"], "4");
|
|
257
268
|
|
|
258
269
|
const started = await startBossChatRun({
|
|
259
270
|
workspaceRoot,
|
|
@@ -278,6 +289,8 @@ async function testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli() {
|
|
|
278
289
|
assert.equal(stateAfterStart.last_start_args.apikey, "sk-test-key");
|
|
279
290
|
assert.equal(stateAfterStart.last_start_args.model, "gpt-4.1-mini");
|
|
280
291
|
assert.equal(stateAfterStart.last_start_args.port, "9666");
|
|
292
|
+
assert.equal(stateAfterStart.last_start_args["llm-timeout-ms"], "65000");
|
|
293
|
+
assert.equal(stateAfterStart.last_start_args["llm-max-retries"], "4");
|
|
281
294
|
|
|
282
295
|
const startedAll = await startBossChatRun({
|
|
283
296
|
workspaceRoot,
|
|
@@ -897,41 +910,43 @@ function testCliShouldPinInstalledPackageVersionInGeneratedMcpConfig() {
|
|
|
897
910
|
assert.equal(launchConfig.args[0], "-y");
|
|
898
911
|
}
|
|
899
912
|
|
|
900
|
-
function
|
|
913
|
+
function testVendorBossChatCliShouldParseSharedLlmTransportArgs() {
|
|
914
|
+
const parsed = vendorCliTestables.parseArgs([
|
|
915
|
+
"start-run",
|
|
916
|
+
"--llm-timeout-ms",
|
|
917
|
+
"70000",
|
|
918
|
+
"--llm-max-retries",
|
|
919
|
+
"5",
|
|
920
|
+
]);
|
|
921
|
+
assert.equal(parsed.command, "start-run");
|
|
922
|
+
assert.equal(parsed.overrides.llm.timeoutMs, 70000);
|
|
923
|
+
assert.equal(parsed.overrides.llm.maxRetries, 5);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
function testBossChatLlmParserShouldAcceptMinimalDecisionJson() {
|
|
901
927
|
const parsed = parseLlmJson(
|
|
902
928
|
JSON.stringify({
|
|
903
929
|
passed: true,
|
|
904
|
-
reason: "命中标准",
|
|
905
|
-
summary: "命中",
|
|
906
|
-
evidence: [],
|
|
907
930
|
}),
|
|
908
|
-
{
|
|
909
|
-
evidenceCorpus: "南京大学 机器学习 项目经历",
|
|
910
|
-
},
|
|
911
931
|
);
|
|
912
|
-
assert.equal(parsed.
|
|
913
|
-
assert.equal(parsed.passed,
|
|
914
|
-
|
|
915
|
-
|
|
932
|
+
assert.equal(parsed.passed, true);
|
|
933
|
+
assert.equal(parsed.rawOutputText.includes('"passed":true'), true);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function testBossChatLlmParserShouldAcceptPlainPassFailText() {
|
|
937
|
+
const passed = parseLlmJson("PASS");
|
|
938
|
+
assert.equal(passed.passed, true);
|
|
939
|
+
const failed = parseLlmJson("false");
|
|
940
|
+
assert.equal(failed.passed, false);
|
|
916
941
|
}
|
|
917
942
|
|
|
918
|
-
function
|
|
943
|
+
function testBossChatLlmParserShouldAcceptDecisionField() {
|
|
919
944
|
const parsed = parseLlmJson(
|
|
920
945
|
JSON.stringify({
|
|
921
|
-
|
|
922
|
-
reason: "命中标准",
|
|
923
|
-
summary: "命中",
|
|
924
|
-
evidence: ["十年金融风控投研经验"],
|
|
946
|
+
decision: "fail",
|
|
925
947
|
}),
|
|
926
|
-
{
|
|
927
|
-
evidenceCorpus: "南京大学 机器学习 项目经历",
|
|
928
|
-
},
|
|
929
948
|
);
|
|
930
|
-
assert.equal(parsed.rawPassed, true);
|
|
931
949
|
assert.equal(parsed.passed, false);
|
|
932
|
-
assert.equal(parsed.evidenceGateDemoted, true);
|
|
933
|
-
assert.equal(parsed.evidenceRawCount, 1);
|
|
934
|
-
assert.equal(parsed.evidenceMatchedCount, 0);
|
|
935
950
|
}
|
|
936
951
|
|
|
937
952
|
async function testBossChatLlmTextChunkFallbackShouldWork() {
|
|
@@ -962,39 +977,21 @@ async function testBossChatLlmTextChunkFallbackShouldWork() {
|
|
|
962
977
|
if (payload.chunkIndex === 2) {
|
|
963
978
|
return {
|
|
964
979
|
passed: true,
|
|
965
|
-
|
|
966
|
-
reason: "命中分段证据",
|
|
967
|
-
summary: "命中",
|
|
968
|
-
evidence: ["PASS_MARKER_ABC"],
|
|
969
|
-
evidenceRawCount: 1,
|
|
970
|
-
evidenceMatchedCount: 1,
|
|
971
|
-
evidenceGateDemoted: false,
|
|
980
|
+
rawOutputText: '{"passed":true}',
|
|
972
981
|
chunkIndex: payload.chunkIndex,
|
|
973
982
|
chunkTotal: payload.chunkTotal,
|
|
974
983
|
};
|
|
975
984
|
}
|
|
976
985
|
return {
|
|
977
986
|
passed: false,
|
|
978
|
-
|
|
979
|
-
reason: "本段证据不足",
|
|
980
|
-
summary: "不足",
|
|
981
|
-
evidence: [],
|
|
982
|
-
evidenceRawCount: 0,
|
|
983
|
-
evidenceMatchedCount: 0,
|
|
984
|
-
evidenceGateDemoted: false,
|
|
987
|
+
rawOutputText: '{"passed":false}',
|
|
985
988
|
chunkIndex: payload.chunkIndex,
|
|
986
989
|
chunkTotal: payload.chunkTotal,
|
|
987
990
|
};
|
|
988
991
|
}
|
|
989
992
|
return {
|
|
990
993
|
passed: false,
|
|
991
|
-
|
|
992
|
-
reason: "unexpected",
|
|
993
|
-
summary: "unexpected",
|
|
994
|
-
evidence: [],
|
|
995
|
-
evidenceRawCount: 0,
|
|
996
|
-
evidenceMatchedCount: 0,
|
|
997
|
-
evidenceGateDemoted: false,
|
|
994
|
+
rawOutputText: '{"passed":false}',
|
|
998
995
|
chunkIndex: 1,
|
|
999
996
|
chunkTotal: 1,
|
|
1000
997
|
};
|
|
@@ -1115,6 +1112,70 @@ async function testBossChatLlmShouldApplyThinkingDefaultsAndOverrides() {
|
|
|
1115
1112
|
assert.deepEqual(responsesPayload.reasoning, { effort: "low" });
|
|
1116
1113
|
}
|
|
1117
1114
|
|
|
1115
|
+
async function testBossChatLlmShouldSendAllImageChunksInSingleRequest() {
|
|
1116
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-chat-image-chunks-"));
|
|
1117
|
+
const firstImage = path.join(tempDir, "chunk-1.png");
|
|
1118
|
+
const secondImage = path.join(tempDir, "chunk-2.png");
|
|
1119
|
+
fs.writeFileSync(
|
|
1120
|
+
firstImage,
|
|
1121
|
+
Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aN4QAAAAASUVORK5CYII=", "base64"),
|
|
1122
|
+
);
|
|
1123
|
+
fs.writeFileSync(
|
|
1124
|
+
secondImage,
|
|
1125
|
+
Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aN4QAAAAASUVORK5CYII=", "base64"),
|
|
1126
|
+
);
|
|
1127
|
+
|
|
1128
|
+
let completionPayload = null;
|
|
1129
|
+
const client = new LlmClient({
|
|
1130
|
+
baseUrl: "https://api.openai.com/v1",
|
|
1131
|
+
apiKey: "sk-test",
|
|
1132
|
+
model: "gpt-test",
|
|
1133
|
+
}, {
|
|
1134
|
+
fetchImpl: async (_url, options = {}) => {
|
|
1135
|
+
completionPayload = JSON.parse(String(options.body || "{}"));
|
|
1136
|
+
return {
|
|
1137
|
+
ok: true,
|
|
1138
|
+
status: 200,
|
|
1139
|
+
async json() {
|
|
1140
|
+
return {
|
|
1141
|
+
choices: [
|
|
1142
|
+
{
|
|
1143
|
+
message: {
|
|
1144
|
+
content: "{\"passed\":true}",
|
|
1145
|
+
},
|
|
1146
|
+
},
|
|
1147
|
+
],
|
|
1148
|
+
};
|
|
1149
|
+
},
|
|
1150
|
+
};
|
|
1151
|
+
},
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
try {
|
|
1155
|
+
const result = await client.evaluateResume({
|
|
1156
|
+
screeningCriteria: "有 AI 项目经验",
|
|
1157
|
+
candidate: {
|
|
1158
|
+
name: "候选人A",
|
|
1159
|
+
sourceJob: "算法工程师",
|
|
1160
|
+
resumeText: "",
|
|
1161
|
+
evidenceCorpus: "",
|
|
1162
|
+
},
|
|
1163
|
+
imagePaths: [firstImage, secondImage],
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
assert.equal(result.passed, true);
|
|
1167
|
+
assert.equal(result.evaluationMode, "image-multi-chunk");
|
|
1168
|
+
assert.equal(result.imageCount, 2);
|
|
1169
|
+
assert.equal(Array.isArray(completionPayload.messages?.[0]?.content), true);
|
|
1170
|
+
assert.equal(
|
|
1171
|
+
completionPayload.messages[0].content.filter((item) => item.type === "image_url").length,
|
|
1172
|
+
2,
|
|
1173
|
+
);
|
|
1174
|
+
} finally {
|
|
1175
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1118
1179
|
async function testBossChatAppShouldResetPrimaryChatLabelBeforeInitialPrime() {
|
|
1119
1180
|
const calls = [];
|
|
1120
1181
|
const page = {
|
|
@@ -1599,6 +1660,431 @@ async function testBossChatAppShouldWaitForCandidateListBeforePriming() {
|
|
|
1599
1660
|
assert.equal(summary.skipped, 1);
|
|
1600
1661
|
}
|
|
1601
1662
|
|
|
1663
|
+
function createProcessCustomerHarness({
|
|
1664
|
+
llmEvaluate,
|
|
1665
|
+
captureResume,
|
|
1666
|
+
tracker,
|
|
1667
|
+
pageOverrides = {},
|
|
1668
|
+
} = {}) {
|
|
1669
|
+
const recorded = [];
|
|
1670
|
+
const page = {
|
|
1671
|
+
async closeResumeModalDomOnce() {
|
|
1672
|
+
recorded.push("closeResumeModalDomOnce");
|
|
1673
|
+
return {
|
|
1674
|
+
closed: true,
|
|
1675
|
+
method: "dom",
|
|
1676
|
+
finalState: { scopeCount: 0, iframeCount: 0, closeCount: 0, topScopeClass: "" },
|
|
1677
|
+
};
|
|
1678
|
+
},
|
|
1679
|
+
async waitForConversationReady() {
|
|
1680
|
+
recorded.push("waitForConversationReady");
|
|
1681
|
+
return {
|
|
1682
|
+
hasOnlineResume: true,
|
|
1683
|
+
hasAskResume: true,
|
|
1684
|
+
hasAttachmentResume: false,
|
|
1685
|
+
attachmentResumeEnabled: false,
|
|
1686
|
+
};
|
|
1687
|
+
},
|
|
1688
|
+
async openOnlineResume() {
|
|
1689
|
+
recorded.push("openOnlineResume");
|
|
1690
|
+
return { clicked: true, detectedOpen: true, by: "dom" };
|
|
1691
|
+
},
|
|
1692
|
+
async getResumeRateLimitWarning() {
|
|
1693
|
+
return { hit: false, text: "" };
|
|
1694
|
+
},
|
|
1695
|
+
async getResumeModalState() {
|
|
1696
|
+
return { open: true, iframeCount: 1, scopeCount: 1, closeCount: 1 };
|
|
1697
|
+
},
|
|
1698
|
+
async waitForCandidateActivated() {
|
|
1699
|
+
recorded.push("waitForCandidateActivated");
|
|
1700
|
+
return { matched: true };
|
|
1701
|
+
},
|
|
1702
|
+
async activateCandidate() {
|
|
1703
|
+
recorded.push("activateCandidate");
|
|
1704
|
+
return { ok: true };
|
|
1705
|
+
},
|
|
1706
|
+
...pageOverrides,
|
|
1707
|
+
};
|
|
1708
|
+
const llmCalls = [];
|
|
1709
|
+
const llmClient = {
|
|
1710
|
+
async evaluateResume(payload) {
|
|
1711
|
+
llmCalls.push(payload);
|
|
1712
|
+
return llmEvaluate(payload);
|
|
1713
|
+
},
|
|
1714
|
+
};
|
|
1715
|
+
const resumeCaptureService = {
|
|
1716
|
+
async captureResume(payload) {
|
|
1717
|
+
recorded.push("captureResume");
|
|
1718
|
+
return captureResume(payload);
|
|
1719
|
+
},
|
|
1720
|
+
};
|
|
1721
|
+
const stateStore = {
|
|
1722
|
+
async record(_key, result) {
|
|
1723
|
+
recorded.push(`record:${result.decision}`);
|
|
1724
|
+
},
|
|
1725
|
+
};
|
|
1726
|
+
const app = new BossChatApp({
|
|
1727
|
+
page,
|
|
1728
|
+
llmClient,
|
|
1729
|
+
interaction: {
|
|
1730
|
+
async sleepRange() {},
|
|
1731
|
+
async clickRect() {},
|
|
1732
|
+
},
|
|
1733
|
+
resumeCaptureService,
|
|
1734
|
+
resumeNetworkTracker: tracker || null,
|
|
1735
|
+
stateStore,
|
|
1736
|
+
reportStore: { async write() { return ""; } },
|
|
1737
|
+
dryRun: true,
|
|
1738
|
+
artifactRootDir: os.tmpdir(),
|
|
1739
|
+
resumeOpenCooldownMs: 0,
|
|
1740
|
+
logger: { log() {} },
|
|
1741
|
+
});
|
|
1742
|
+
app.waitResumeOpenCooldown = async () => {};
|
|
1743
|
+
return { app, llmCalls, recorded };
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
async function testBossChatResumeTrackerShouldRetryInitialNetworkWait() {
|
|
1747
|
+
const tracker = new ResumeNetworkTracker({
|
|
1748
|
+
chromeClient: { Network: null },
|
|
1749
|
+
logger: { log() {} },
|
|
1750
|
+
});
|
|
1751
|
+
const waits = [];
|
|
1752
|
+
let callCount = 0;
|
|
1753
|
+
tracker.waitForNetworkResumeCandidateInfo = async (_candidate, timeoutMs) => {
|
|
1754
|
+
waits.push(timeoutMs);
|
|
1755
|
+
callCount += 1;
|
|
1756
|
+
if (callCount === 2) {
|
|
1757
|
+
return {
|
|
1758
|
+
candidateInfo: { resumeText: "network resume" },
|
|
1759
|
+
source: "geek_id_map",
|
|
1760
|
+
waitedMs: 80,
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
return null;
|
|
1764
|
+
};
|
|
1765
|
+
const result = await tracker.waitForResumeNetworkByMode({ customerId: "1001" });
|
|
1766
|
+
assert.deepEqual(waits, [NETWORK_RESUME_WAIT_MS, NETWORK_RESUME_RETRY_WAIT_MS]);
|
|
1767
|
+
assert.equal(result.acquisitionReason, "network_retry_hit");
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
async function testBossChatResumeTrackerShouldUseImageModeGraceWindow() {
|
|
1771
|
+
const tracker = new ResumeNetworkTracker({
|
|
1772
|
+
chromeClient: { Network: null },
|
|
1773
|
+
logger: { log() {} },
|
|
1774
|
+
});
|
|
1775
|
+
tracker.setResumeAcquisitionMode("image", "previous_image_fallback");
|
|
1776
|
+
const waits = [];
|
|
1777
|
+
tracker.waitForNetworkResumeCandidateInfo = async (_candidate, timeoutMs) => {
|
|
1778
|
+
waits.push(timeoutMs);
|
|
1779
|
+
return null;
|
|
1780
|
+
};
|
|
1781
|
+
const result = await tracker.waitForResumeNetworkByMode({ customerId: "1002" });
|
|
1782
|
+
assert.deepEqual(waits, [NETWORK_RESUME_IMAGE_MODE_GRACE_MS]);
|
|
1783
|
+
assert.equal(result.initialWaitMs >= 0, true);
|
|
1784
|
+
assert.equal(result.retryWaitMs, 0);
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
async function testBossChatAppShouldUseNetworkBeforeImageFallback() {
|
|
1788
|
+
const tracker = {
|
|
1789
|
+
resumeNetworkDiagnostics: [],
|
|
1790
|
+
getResumeAcquisitionState() {
|
|
1791
|
+
return { mode: "network", reason: "initial_network_hit" };
|
|
1792
|
+
},
|
|
1793
|
+
async waitForResumeNetworkByMode() {
|
|
1794
|
+
return {
|
|
1795
|
+
candidateInfo: {
|
|
1796
|
+
name: "候选人A",
|
|
1797
|
+
school: "清华大学",
|
|
1798
|
+
major: "计算机",
|
|
1799
|
+
company: "OpenAI",
|
|
1800
|
+
position: "工程师",
|
|
1801
|
+
resumeText: "清华大学 计算机 OpenAI",
|
|
1802
|
+
evidenceCorpus: "清华大学 计算机 OpenAI",
|
|
1803
|
+
},
|
|
1804
|
+
acquisitionReason: "initial_network_hit",
|
|
1805
|
+
initialWaitMs: 12,
|
|
1806
|
+
retryWaitMs: 0,
|
|
1807
|
+
};
|
|
1808
|
+
},
|
|
1809
|
+
async waitForLateNetworkResumeCandidateInfo() {
|
|
1810
|
+
throw new Error("late network retry should not run");
|
|
1811
|
+
},
|
|
1812
|
+
};
|
|
1813
|
+
const { app, llmCalls, recorded } = createProcessCustomerHarness({
|
|
1814
|
+
tracker,
|
|
1815
|
+
llmEvaluate: async () => ({
|
|
1816
|
+
passed: true,
|
|
1817
|
+
rawOutputText: '{"passed":true}',
|
|
1818
|
+
evaluationMode: "text",
|
|
1819
|
+
chunkIndex: 1,
|
|
1820
|
+
chunkTotal: 1,
|
|
1821
|
+
}),
|
|
1822
|
+
captureResume: async () => {
|
|
1823
|
+
throw new Error("image capture should not run");
|
|
1824
|
+
},
|
|
1825
|
+
});
|
|
1826
|
+
|
|
1827
|
+
const result = await app.processCustomer(
|
|
1828
|
+
{
|
|
1829
|
+
customerKey: "candidate-network",
|
|
1830
|
+
name: "候选人A",
|
|
1831
|
+
sourceJob: "算法工程师",
|
|
1832
|
+
domIndex: 0,
|
|
1833
|
+
customerId: "1001",
|
|
1834
|
+
textSnippet: "",
|
|
1835
|
+
},
|
|
1836
|
+
{ screeningCriteria: "有 AI 项目经验" },
|
|
1837
|
+
"run-network",
|
|
1838
|
+
{ skipCardClick: true },
|
|
1839
|
+
);
|
|
1840
|
+
|
|
1841
|
+
assert.equal(result.artifacts.resumeAcquisitionMode, "network");
|
|
1842
|
+
assert.equal(result.artifacts.resumeAcquisitionReason, "initial_network_hit");
|
|
1843
|
+
assert.equal(llmCalls.length, 1);
|
|
1844
|
+
assert.equal(llmCalls[0].candidate.resumeText.includes("清华大学"), true);
|
|
1845
|
+
assert.equal(Array.isArray(llmCalls[0].imagePaths), false);
|
|
1846
|
+
assert.equal(recorded.includes("captureResume"), false);
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
async function testBossChatAppShouldFallbackToImageAfterNetworkMiss() {
|
|
1850
|
+
const tracker = {
|
|
1851
|
+
resumeNetworkDiagnostics: [],
|
|
1852
|
+
setResumeAcquisitionMode(mode, reason) {
|
|
1853
|
+
this.state = { mode, reason };
|
|
1854
|
+
},
|
|
1855
|
+
getResumeAcquisitionState() {
|
|
1856
|
+
return this.state || { mode: "image", reason: "image_capture_success" };
|
|
1857
|
+
},
|
|
1858
|
+
async waitForResumeNetworkByMode() {
|
|
1859
|
+
return {
|
|
1860
|
+
candidateInfo: null,
|
|
1861
|
+
acquisitionReason: "",
|
|
1862
|
+
initialWaitMs: 10,
|
|
1863
|
+
retryWaitMs: 20,
|
|
1864
|
+
};
|
|
1865
|
+
},
|
|
1866
|
+
async waitForLateNetworkResumeCandidateInfo() {
|
|
1867
|
+
return {
|
|
1868
|
+
candidateInfo: null,
|
|
1869
|
+
acquisitionReason: "",
|
|
1870
|
+
lateRetryMs: 0,
|
|
1871
|
+
};
|
|
1872
|
+
},
|
|
1873
|
+
};
|
|
1874
|
+
const { app, llmCalls } = createProcessCustomerHarness({
|
|
1875
|
+
tracker,
|
|
1876
|
+
llmEvaluate: async () => ({
|
|
1877
|
+
passed: false,
|
|
1878
|
+
rawOutputText: '{"passed":false}',
|
|
1879
|
+
evaluationMode: "image-multi-chunk",
|
|
1880
|
+
imageCount: 2,
|
|
1881
|
+
chunkIndex: 1,
|
|
1882
|
+
chunkTotal: 1,
|
|
1883
|
+
}),
|
|
1884
|
+
captureResume: async ({ artifactDir }) => ({
|
|
1885
|
+
metadataFile: path.join(artifactDir, "chunks.json"),
|
|
1886
|
+
chunkDir: path.join(artifactDir, "chunks"),
|
|
1887
|
+
chunkCount: 2,
|
|
1888
|
+
modelImagePaths: [
|
|
1889
|
+
path.join(artifactDir, "chunks", "chunk_000.png"),
|
|
1890
|
+
path.join(artifactDir, "chunks", "chunk_001.png"),
|
|
1891
|
+
],
|
|
1892
|
+
stitchedImage: "",
|
|
1893
|
+
quality: { likelyBlank: false },
|
|
1894
|
+
}),
|
|
1895
|
+
});
|
|
1896
|
+
|
|
1897
|
+
const result = await app.processCustomer(
|
|
1898
|
+
{
|
|
1899
|
+
customerKey: "candidate-image",
|
|
1900
|
+
name: "候选人B",
|
|
1901
|
+
sourceJob: "算法工程师",
|
|
1902
|
+
domIndex: 0,
|
|
1903
|
+
customerId: "1002",
|
|
1904
|
+
textSnippet: "",
|
|
1905
|
+
},
|
|
1906
|
+
{ screeningCriteria: "有 AI 项目经验" },
|
|
1907
|
+
"run-image",
|
|
1908
|
+
{ skipCardClick: true },
|
|
1909
|
+
);
|
|
1910
|
+
|
|
1911
|
+
assert.equal(result.artifacts.resumeAcquisitionMode, "image_fallback");
|
|
1912
|
+
assert.equal(result.artifacts.resumeAcquisitionReason, "image_capture_success");
|
|
1913
|
+
assert.equal(Array.isArray(llmCalls[0].imagePaths), true);
|
|
1914
|
+
assert.equal(llmCalls[0].imagePaths.length, 2);
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
async function testBossChatAppShouldRetryLateNetworkBeforeDomFallback() {
|
|
1918
|
+
const tracker = {
|
|
1919
|
+
resumeNetworkDiagnostics: [],
|
|
1920
|
+
getResumeAcquisitionState() {
|
|
1921
|
+
return { mode: "network", reason: "late_network_hit" };
|
|
1922
|
+
},
|
|
1923
|
+
setResumeAcquisitionMode() {},
|
|
1924
|
+
async waitForResumeNetworkByMode() {
|
|
1925
|
+
return {
|
|
1926
|
+
candidateInfo: null,
|
|
1927
|
+
acquisitionReason: "",
|
|
1928
|
+
initialWaitMs: 10,
|
|
1929
|
+
retryWaitMs: 20,
|
|
1930
|
+
};
|
|
1931
|
+
},
|
|
1932
|
+
async waitForLateNetworkResumeCandidateInfo() {
|
|
1933
|
+
return {
|
|
1934
|
+
candidateInfo: {
|
|
1935
|
+
name: "候选人C",
|
|
1936
|
+
school: "上海交大",
|
|
1937
|
+
major: "软件工程",
|
|
1938
|
+
resumeText: "上海交大 软件工程",
|
|
1939
|
+
evidenceCorpus: "上海交大 软件工程",
|
|
1940
|
+
},
|
|
1941
|
+
acquisitionReason: "late_network_hit",
|
|
1942
|
+
lateRetryMs: 30,
|
|
1943
|
+
};
|
|
1944
|
+
},
|
|
1945
|
+
};
|
|
1946
|
+
let imageAttempt = 0;
|
|
1947
|
+
const { app, llmCalls } = createProcessCustomerHarness({
|
|
1948
|
+
tracker,
|
|
1949
|
+
llmEvaluate: async (payload) => {
|
|
1950
|
+
imageAttempt += 1;
|
|
1951
|
+
if (Array.isArray(payload.imagePaths) && payload.imagePaths.length > 0) {
|
|
1952
|
+
throw new Error("VISION_MODEL_FAILED");
|
|
1953
|
+
}
|
|
1954
|
+
return {
|
|
1955
|
+
passed: true,
|
|
1956
|
+
rawOutputText: '{"passed":true}',
|
|
1957
|
+
evaluationMode: "text",
|
|
1958
|
+
chunkIndex: 1,
|
|
1959
|
+
chunkTotal: 1,
|
|
1960
|
+
};
|
|
1961
|
+
},
|
|
1962
|
+
captureResume: async ({ artifactDir }) => ({
|
|
1963
|
+
metadataFile: path.join(artifactDir, "chunks.json"),
|
|
1964
|
+
chunkDir: path.join(artifactDir, "chunks"),
|
|
1965
|
+
chunkCount: 1,
|
|
1966
|
+
modelImagePaths: [path.join(artifactDir, "chunks", "chunk_000.png")],
|
|
1967
|
+
stitchedImage: "",
|
|
1968
|
+
quality: { likelyBlank: false },
|
|
1969
|
+
}),
|
|
1970
|
+
});
|
|
1971
|
+
|
|
1972
|
+
const result = await app.processCustomer(
|
|
1973
|
+
{
|
|
1974
|
+
customerKey: "candidate-late-network",
|
|
1975
|
+
name: "候选人C",
|
|
1976
|
+
sourceJob: "算法工程师",
|
|
1977
|
+
domIndex: 0,
|
|
1978
|
+
customerId: "1003",
|
|
1979
|
+
textSnippet: "",
|
|
1980
|
+
},
|
|
1981
|
+
{ screeningCriteria: "有 AI 项目经验" },
|
|
1982
|
+
"run-late-network",
|
|
1983
|
+
{ skipCardClick: true },
|
|
1984
|
+
);
|
|
1985
|
+
|
|
1986
|
+
assert.equal(imageAttempt >= 2, true);
|
|
1987
|
+
assert.equal(result.artifacts.resumeAcquisitionMode, "network");
|
|
1988
|
+
assert.equal(result.artifacts.resumeAcquisitionReason, "late_network_hit");
|
|
1989
|
+
assert.equal(llmCalls[llmCalls.length - 1].candidate.resumeText.includes("上海交大"), true);
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
async function testBossChatAppShouldUseDomOnlyAfterHigherPriorityPathsFail() {
|
|
1993
|
+
let domReadCount = 0;
|
|
1994
|
+
const tracker = {
|
|
1995
|
+
resumeNetworkDiagnostics: [],
|
|
1996
|
+
getResumeAcquisitionState() {
|
|
1997
|
+
return { mode: "image", reason: "image_capture_success" };
|
|
1998
|
+
},
|
|
1999
|
+
setResumeAcquisitionMode() {},
|
|
2000
|
+
async waitForResumeNetworkByMode() {
|
|
2001
|
+
return {
|
|
2002
|
+
candidateInfo: null,
|
|
2003
|
+
acquisitionReason: "",
|
|
2004
|
+
initialWaitMs: 10,
|
|
2005
|
+
retryWaitMs: 20,
|
|
2006
|
+
};
|
|
2007
|
+
},
|
|
2008
|
+
async waitForLateNetworkResumeCandidateInfo() {
|
|
2009
|
+
return {
|
|
2010
|
+
candidateInfo: null,
|
|
2011
|
+
acquisitionReason: "",
|
|
2012
|
+
lateRetryMs: 15,
|
|
2013
|
+
};
|
|
2014
|
+
},
|
|
2015
|
+
async waitForNetworkResumeCandidateInfo() {
|
|
2016
|
+
return null;
|
|
2017
|
+
},
|
|
2018
|
+
};
|
|
2019
|
+
const { app, recorded } = createProcessCustomerHarness({
|
|
2020
|
+
tracker,
|
|
2021
|
+
llmEvaluate: async (payload) => ({
|
|
2022
|
+
passed: false,
|
|
2023
|
+
rawOutputText: '{"passed":false}',
|
|
2024
|
+
evaluationMode: "text",
|
|
2025
|
+
chunkIndex: 1,
|
|
2026
|
+
chunkTotal: 1,
|
|
2027
|
+
imageCount: 0,
|
|
2028
|
+
}),
|
|
2029
|
+
captureResume: async () => {
|
|
2030
|
+
throw new Error("IMAGE_CAPTURE_FAILED");
|
|
2031
|
+
},
|
|
2032
|
+
pageOverrides: {
|
|
2033
|
+
async getResumeProfileFromDom() {
|
|
2034
|
+
domReadCount += 1;
|
|
2035
|
+
if (domReadCount === 1) {
|
|
2036
|
+
return {
|
|
2037
|
+
ok: true,
|
|
2038
|
+
name: "李同学",
|
|
2039
|
+
primarySchool: "北京大学",
|
|
2040
|
+
schools: ["北京大学"],
|
|
2041
|
+
major: "数学",
|
|
2042
|
+
majors: ["数学"],
|
|
2043
|
+
company: "",
|
|
2044
|
+
position: "",
|
|
2045
|
+
resumeText: "北京大学 数学",
|
|
2046
|
+
evidenceCorpus: "北京大学 数学",
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
return {
|
|
2050
|
+
ok: true,
|
|
2051
|
+
name: "候选人D",
|
|
2052
|
+
primarySchool: "浙江大学",
|
|
2053
|
+
schools: ["浙江大学"],
|
|
2054
|
+
major: "计算机",
|
|
2055
|
+
majors: ["计算机"],
|
|
2056
|
+
company: "",
|
|
2057
|
+
position: "",
|
|
2058
|
+
resumeText: "浙江大学 计算机",
|
|
2059
|
+
evidenceCorpus: "浙江大学 计算机",
|
|
2060
|
+
};
|
|
2061
|
+
},
|
|
2062
|
+
},
|
|
2063
|
+
});
|
|
2064
|
+
|
|
2065
|
+
const result = await app.processCustomer(
|
|
2066
|
+
{
|
|
2067
|
+
customerKey: "candidate-dom",
|
|
2068
|
+
name: "候选人D",
|
|
2069
|
+
school: "浙江大学",
|
|
2070
|
+
major: "计算机",
|
|
2071
|
+
sourceJob: "算法工程师",
|
|
2072
|
+
domIndex: 0,
|
|
2073
|
+
customerId: "1004",
|
|
2074
|
+
textSnippet: "",
|
|
2075
|
+
},
|
|
2076
|
+
{ screeningCriteria: "有 AI 项目经验" },
|
|
2077
|
+
"run-dom",
|
|
2078
|
+
{ skipCardClick: true },
|
|
2079
|
+
);
|
|
2080
|
+
|
|
2081
|
+
assert.equal(result.artifacts.resumeAcquisitionMode, "dom_fallback");
|
|
2082
|
+
assert.equal(result.artifacts.resumeAcquisitionReason, "dom_retry_hit");
|
|
2083
|
+
assert.equal(domReadCount, 2);
|
|
2084
|
+
assert.equal(recorded.includes("activateCandidate"), true);
|
|
2085
|
+
assert.equal(recorded.includes("openOnlineResume"), true);
|
|
2086
|
+
}
|
|
2087
|
+
|
|
1602
2088
|
async function testBossChatAppShouldPersistEvidenceArtifacts() {
|
|
1603
2089
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-chat-artifacts-"));
|
|
1604
2090
|
await mkdir(tempDir, { recursive: true });
|
|
@@ -1646,14 +2132,9 @@ async function testBossChatAppShouldPersistEvidenceArtifacts() {
|
|
|
1646
2132
|
async evaluateResume() {
|
|
1647
2133
|
return {
|
|
1648
2134
|
passed: false,
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
evidence: [],
|
|
1653
|
-
evidenceRawCount: 1,
|
|
1654
|
-
evidenceMatchedCount: 0,
|
|
1655
|
-
evidenceGateDemoted: true,
|
|
1656
|
-
evaluationMode: "text",
|
|
2135
|
+
rawOutputText: '{"passed":false}',
|
|
2136
|
+
evaluationMode: "image-multi-chunk",
|
|
2137
|
+
imageCount: 3,
|
|
1657
2138
|
chunkIndex: 1,
|
|
1658
2139
|
chunkTotal: 1,
|
|
1659
2140
|
};
|
|
@@ -1666,10 +2147,15 @@ async function testBossChatAppShouldPersistEvidenceArtifacts() {
|
|
|
1666
2147
|
const resumeCaptureService = {
|
|
1667
2148
|
async captureResume({ artifactDir }) {
|
|
1668
2149
|
return {
|
|
1669
|
-
stitchedImage: path.join(artifactDir, "resume.png"),
|
|
1670
2150
|
metadataFile: path.join(artifactDir, "chunks.json"),
|
|
1671
2151
|
chunkDir: path.join(artifactDir, "chunks"),
|
|
1672
2152
|
chunkCount: 1,
|
|
2153
|
+
modelImagePaths: [
|
|
2154
|
+
path.join(artifactDir, "chunks", "chunk_000.png"),
|
|
2155
|
+
path.join(artifactDir, "chunks", "chunk_001.png"),
|
|
2156
|
+
path.join(artifactDir, "chunks", "chunk_002.png"),
|
|
2157
|
+
],
|
|
2158
|
+
stitchedImage: "",
|
|
1673
2159
|
quality: { likelyBlank: false },
|
|
1674
2160
|
};
|
|
1675
2161
|
},
|
|
@@ -1710,16 +2196,111 @@ async function testBossChatAppShouldPersistEvidenceArtifacts() {
|
|
|
1710
2196
|
);
|
|
1711
2197
|
|
|
1712
2198
|
assert.equal(result.passed, false);
|
|
1713
|
-
assert.equal(result.artifacts.rawPassed, true);
|
|
1714
2199
|
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.
|
|
2200
|
+
assert.equal(result.reason, "LLM判定不通过");
|
|
2201
|
+
assert.equal(result.artifacts.evaluationMode, "image-multi-chunk");
|
|
2202
|
+
assert.equal(result.artifacts.evaluationImageCount, 3);
|
|
2203
|
+
assert.equal(result.artifacts.llmRawOutput, '{"passed":false}');
|
|
2204
|
+
assert.equal(Array.isArray(result.artifacts.modelImagePaths), true);
|
|
2205
|
+
assert.equal(result.artifacts.modelImagePaths.length, 3);
|
|
1719
2206
|
assert.equal(Array.isArray(records), true);
|
|
1720
2207
|
assert.equal(records.length, 1);
|
|
1721
2208
|
}
|
|
1722
2209
|
|
|
2210
|
+
async function testBossChatReportStoreShouldWriteReadableMarkdownAndCsv() {
|
|
2211
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-chat-report-store-"));
|
|
2212
|
+
const reportStore = new ReportStore(tempDir);
|
|
2213
|
+
const summary = {
|
|
2214
|
+
startedAt: "2026-04-17T10:00:00.000Z",
|
|
2215
|
+
finishedAt: "2026-04-17T10:01:05.000Z",
|
|
2216
|
+
dryRun: true,
|
|
2217
|
+
profile: {
|
|
2218
|
+
targetCount: 5,
|
|
2219
|
+
screeningCriteria: "有 AI 项目经验",
|
|
2220
|
+
},
|
|
2221
|
+
inspected: 2,
|
|
2222
|
+
passed: 1,
|
|
2223
|
+
requested: 0,
|
|
2224
|
+
skipped: 1,
|
|
2225
|
+
errors: 0,
|
|
2226
|
+
exhausted: false,
|
|
2227
|
+
stopped: false,
|
|
2228
|
+
stopReason: "",
|
|
2229
|
+
results: [
|
|
2230
|
+
{
|
|
2231
|
+
name: "候选人A",
|
|
2232
|
+
sourceJob: "算法工程师",
|
|
2233
|
+
decision: "passed",
|
|
2234
|
+
passed: true,
|
|
2235
|
+
requested: false,
|
|
2236
|
+
reason: "符合要求",
|
|
2237
|
+
error: "",
|
|
2238
|
+
artifacts: {
|
|
2239
|
+
resumeAcquisitionMode: "network",
|
|
2240
|
+
resumeAcquisitionReason: "initial_hit",
|
|
2241
|
+
textModelMs: 18234,
|
|
2242
|
+
initialNetworkWaitMs: 4200,
|
|
2243
|
+
evaluationMode: "text",
|
|
2244
|
+
llmRawOutput: '{"passed":true}',
|
|
2245
|
+
},
|
|
2246
|
+
},
|
|
2247
|
+
{
|
|
2248
|
+
name: "候选人B",
|
|
2249
|
+
sourceJob: "大模型算法",
|
|
2250
|
+
decision: "skipped",
|
|
2251
|
+
passed: false,
|
|
2252
|
+
requested: false,
|
|
2253
|
+
reason: "LLM判定不通过",
|
|
2254
|
+
error: "",
|
|
2255
|
+
artifacts: {
|
|
2256
|
+
resumeAcquisitionMode: "image_fallback",
|
|
2257
|
+
resumeAcquisitionReason: "late_network_miss",
|
|
2258
|
+
imageCaptureMs: 2300,
|
|
2259
|
+
imageModelMs: 19500,
|
|
2260
|
+
lateNetworkRetryMs: 3000,
|
|
2261
|
+
evaluationMode: "image-multi-chunk",
|
|
2262
|
+
evaluationImageCount: 3,
|
|
2263
|
+
llmRawOutput: '{"passed":false}',
|
|
2264
|
+
},
|
|
2265
|
+
},
|
|
2266
|
+
],
|
|
2267
|
+
reportPath: null,
|
|
2268
|
+
};
|
|
2269
|
+
|
|
2270
|
+
const jsonPath = await reportStore.write(summary);
|
|
2271
|
+
const markdownPath = summary.reportMarkdownPath;
|
|
2272
|
+
const csvPath = summary.reportCsvPath;
|
|
2273
|
+
const jsonContent = fs.readFileSync(jsonPath, "utf8");
|
|
2274
|
+
const markdownContent = fs.readFileSync(markdownPath, "utf8");
|
|
2275
|
+
const csvContent = fs.readFileSync(csvPath, "utf8");
|
|
2276
|
+
|
|
2277
|
+
assert.equal(path.extname(jsonPath), ".json");
|
|
2278
|
+
assert.equal(path.extname(markdownPath), ".md");
|
|
2279
|
+
assert.equal(path.extname(csvPath), ".csv");
|
|
2280
|
+
assert.equal(summary.reportPath, jsonPath);
|
|
2281
|
+
assert.equal(typeof summary.reportArtifacts, "object");
|
|
2282
|
+
assert.equal(summary.reportArtifacts.markdownPath, markdownPath);
|
|
2283
|
+
assert.equal(summary.reportArtifacts.csvPath, csvPath);
|
|
2284
|
+
|
|
2285
|
+
const parsedJson = JSON.parse(jsonContent);
|
|
2286
|
+
assert.equal(parsedJson.reportPath, jsonPath);
|
|
2287
|
+
assert.equal(parsedJson.reportMarkdownPath, markdownPath);
|
|
2288
|
+
assert.equal(parsedJson.reportCsvPath, csvPath);
|
|
2289
|
+
|
|
2290
|
+
assert.match(markdownContent, /# Boss Chat 运行报告/);
|
|
2291
|
+
assert.match(markdownContent, /Resume Acquisition 汇总/);
|
|
2292
|
+
assert.match(markdownContent, /Timing 汇总/);
|
|
2293
|
+
assert.match(markdownContent, /候选人A/);
|
|
2294
|
+
assert.match(markdownContent, /image_fallback/);
|
|
2295
|
+
assert.match(markdownContent, /图片模型 19500ms/);
|
|
2296
|
+
|
|
2297
|
+
assert.match(csvContent, /resume_acquisition_mode/);
|
|
2298
|
+
assert.match(csvContent, /initial_network_wait_ms/);
|
|
2299
|
+
assert.match(csvContent, /late_network_retry_ms/);
|
|
2300
|
+
assert.match(csvContent, /候选人B/);
|
|
2301
|
+
assert.match(csvContent, /image-multi-chunk/);
|
|
2302
|
+
}
|
|
2303
|
+
|
|
1723
2304
|
async function main() {
|
|
1724
2305
|
await testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli();
|
|
1725
2306
|
await testBossChatPrepareShouldRetryWhenChatPageIsNotReady();
|
|
@@ -1731,15 +2312,25 @@ async function main() {
|
|
|
1731
2312
|
await testVendorBossChatCliShouldWaitForHydratedChatShell();
|
|
1732
2313
|
await testVendorBossChatCliShouldRetryJobListDuringPromptRunProfile();
|
|
1733
2314
|
testCliShouldPinInstalledPackageVersionInGeneratedMcpConfig();
|
|
1734
|
-
|
|
1735
|
-
|
|
2315
|
+
testVendorBossChatCliShouldParseSharedLlmTransportArgs();
|
|
2316
|
+
testBossChatLlmParserShouldAcceptMinimalDecisionJson();
|
|
2317
|
+
testBossChatLlmParserShouldAcceptPlainPassFailText();
|
|
2318
|
+
testBossChatLlmParserShouldAcceptDecisionField();
|
|
1736
2319
|
await testBossChatLlmTextChunkFallbackShouldWork();
|
|
1737
2320
|
await testBossChatLlmShouldApplyThinkingDefaultsAndOverrides();
|
|
2321
|
+
await testBossChatLlmShouldSendAllImageChunksInSingleRequest();
|
|
1738
2322
|
await testBossChatAppShouldResetPrimaryChatLabelBeforeInitialPrime();
|
|
1739
2323
|
await testBossChatAppShouldCloseCandidateDetailDuringRunCleanup();
|
|
1740
2324
|
await testBossChatAppShouldRestoreListContextAfterRecovery();
|
|
1741
2325
|
await testBossChatAppShouldWaitForCandidateListBeforePriming();
|
|
2326
|
+
await testBossChatResumeTrackerShouldRetryInitialNetworkWait();
|
|
2327
|
+
await testBossChatResumeTrackerShouldUseImageModeGraceWindow();
|
|
2328
|
+
await testBossChatAppShouldUseNetworkBeforeImageFallback();
|
|
2329
|
+
await testBossChatAppShouldFallbackToImageAfterNetworkMiss();
|
|
2330
|
+
await testBossChatAppShouldRetryLateNetworkBeforeDomFallback();
|
|
2331
|
+
await testBossChatAppShouldUseDomOnlyAfterHigherPriorityPathsFail();
|
|
1742
2332
|
await testBossChatAppShouldPersistEvidenceArtifacts();
|
|
2333
|
+
await testBossChatReportStoreShouldWriteReadableMarkdownAndCsv();
|
|
1743
2334
|
console.log("boss-chat tests passed");
|
|
1744
2335
|
}
|
|
1745
2336
|
|