@reconcrap/boss-recommend-mcp 1.1.11 → 1.1.12
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/package.json
CHANGED
|
@@ -91,6 +91,234 @@ function parseBoolean(value) {
|
|
|
91
91
|
return null;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
function normalizeCliOptionToken(rawToken) {
|
|
95
|
+
const token = String(rawToken || "").trim();
|
|
96
|
+
if (!token) {
|
|
97
|
+
return { token: "", inlineValue: null };
|
|
98
|
+
}
|
|
99
|
+
const normalizedDashes = token.replace(/^[\u2010-\u2015\u2212\uFE58\uFE63\uFF0D]+/, "--");
|
|
100
|
+
const eqIndex = normalizedDashes.indexOf("=");
|
|
101
|
+
if (normalizedDashes.startsWith("--") && eqIndex > 2) {
|
|
102
|
+
return {
|
|
103
|
+
token: normalizedDashes.slice(0, eqIndex),
|
|
104
|
+
inlineValue: normalizedDashes.slice(eqIndex + 1)
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return { token: normalizedDashes, inlineValue: null };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseFavoriteActionValue(rawValue) {
|
|
111
|
+
if (rawValue === null || rawValue === undefined) return null;
|
|
112
|
+
if (typeof rawValue === "boolean") return rawValue ? "add" : "del";
|
|
113
|
+
if (typeof rawValue === "number") {
|
|
114
|
+
if (rawValue === 1) return "add";
|
|
115
|
+
if (rawValue === 0) return "del";
|
|
116
|
+
}
|
|
117
|
+
const normalized = normalizeText(rawValue).toLowerCase();
|
|
118
|
+
if (!normalized) return null;
|
|
119
|
+
if (
|
|
120
|
+
["1", "add", "favorite", "collect", "interested", "true", "yes", "on"].includes(normalized)
|
|
121
|
+
|| /(?:^|[_\W])(add|favorite|collect|interest(?:ed)?)(?:$|[_\W])/.test(normalized)
|
|
122
|
+
) {
|
|
123
|
+
return "add";
|
|
124
|
+
}
|
|
125
|
+
if (
|
|
126
|
+
["0", "del", "delete", "remove", "cancel", "unfavorite", "uncollect", "false", "no", "off"].includes(normalized)
|
|
127
|
+
|| /(?:^|[_\W])(del|delete|remove|cancel|unfavorite|uncollect|uninterest)(?:$|[_\W])/.test(normalized)
|
|
128
|
+
) {
|
|
129
|
+
return "del";
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function parseFavoriteActionFromObject(payload, visited = new Set()) {
|
|
135
|
+
if (!payload || typeof payload !== "object") return null;
|
|
136
|
+
if (visited.has(payload)) return null;
|
|
137
|
+
visited.add(payload);
|
|
138
|
+
|
|
139
|
+
if (Array.isArray(payload)) {
|
|
140
|
+
for (const item of payload) {
|
|
141
|
+
const action = parseFavoriteActionFromObject(item, visited);
|
|
142
|
+
if (action) return action;
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const keys = Object.keys(payload);
|
|
148
|
+
const strongSignalKey = (key) => /p3|status|state|favorite|collect|interested|markstatus|isfavorite|iscollect/.test(key);
|
|
149
|
+
const weakSignalKey = (key) => /action|op|operation|type|mode|mark|interest/.test(key);
|
|
150
|
+
for (const key of keys) {
|
|
151
|
+
const value = payload[key];
|
|
152
|
+
const normalizedKey = normalizeText(key).toLowerCase();
|
|
153
|
+
if (strongSignalKey(normalizedKey)) {
|
|
154
|
+
const action = parseFavoriteActionValue(value);
|
|
155
|
+
if (action) return action;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const key of keys) {
|
|
159
|
+
const value = payload[key];
|
|
160
|
+
const normalizedKey = normalizeText(key).toLowerCase();
|
|
161
|
+
if (weakSignalKey(normalizedKey)) {
|
|
162
|
+
const action = parseFavoriteActionValue(value);
|
|
163
|
+
if (action) return action;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (const key of keys) {
|
|
168
|
+
const value = payload[key];
|
|
169
|
+
if (value && typeof value === "object") {
|
|
170
|
+
const action = parseFavoriteActionFromObject(value, visited);
|
|
171
|
+
if (action) return action;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function parseFavoriteActionFromPostData(rawPostData) {
|
|
178
|
+
const postData = normalizeText(rawPostData);
|
|
179
|
+
if (!postData) return null;
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
const parsed = JSON.parse(postData);
|
|
183
|
+
const action = parseFavoriteActionFromObject(parsed);
|
|
184
|
+
if (action) return action;
|
|
185
|
+
} catch {}
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const params = new URLSearchParams(postData);
|
|
189
|
+
const strongEntries = [];
|
|
190
|
+
const weakEntries = [];
|
|
191
|
+
for (const [key, value] of params.entries()) {
|
|
192
|
+
const normalizedKey = normalizeText(key).toLowerCase();
|
|
193
|
+
if (/p3|status|state|favorite|collect|interested|markstatus|isfavorite|iscollect/.test(normalizedKey)) {
|
|
194
|
+
strongEntries.push(value);
|
|
195
|
+
} else if (/action|op|operation|type|mode|mark|interest/.test(normalizedKey)) {
|
|
196
|
+
weakEntries.push(value);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
for (const value of strongEntries) {
|
|
200
|
+
const action = parseFavoriteActionValue(value);
|
|
201
|
+
if (action) return action;
|
|
202
|
+
}
|
|
203
|
+
for (const value of weakEntries) {
|
|
204
|
+
const action = parseFavoriteActionValue(value);
|
|
205
|
+
if (action) return action;
|
|
206
|
+
}
|
|
207
|
+
} catch {}
|
|
208
|
+
|
|
209
|
+
const fallback = parseFavoriteActionValue(postData);
|
|
210
|
+
if (fallback) return fallback;
|
|
211
|
+
|
|
212
|
+
if (/star-interest-click/i.test(postData)) {
|
|
213
|
+
if (/(?:^|[?&"'\s])p3(?:["'\s:=]){1,3}1(?:$|[&"'\s,}])/i.test(postData)) return "add";
|
|
214
|
+
if (/(?:^|[?&"'\s])p3(?:["'\s:=]){1,3}0(?:$|[&"'\s,}])/i.test(postData)) return "del";
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function parseFavoriteActionFromRequest(url, postData = "") {
|
|
220
|
+
const normalizedUrl = normalizeText(url).toLowerCase();
|
|
221
|
+
if (!normalizedUrl) return null;
|
|
222
|
+
|
|
223
|
+
if (/\/add(?:\/|$)|[?&](?:action|op|operation|type)=add\b|[?&](?:status|p3|favorite|collect|interested)=1\b/i.test(normalizedUrl)) {
|
|
224
|
+
return "add";
|
|
225
|
+
}
|
|
226
|
+
if (/\/del(?:\/|$)|[?&](?:action|op|operation|type)=del\b|[?&](?:status|p3|favorite|collect|interested)=0\b/i.test(normalizedUrl)) {
|
|
227
|
+
return "del";
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return parseFavoriteActionFromPostData(postData);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function parseFavoriteActionFromActionLog(postData = "") {
|
|
234
|
+
const raw = normalizeText(postData);
|
|
235
|
+
if (!raw) return null;
|
|
236
|
+
try {
|
|
237
|
+
const payload = JSON.parse(raw);
|
|
238
|
+
if (normalizeText(payload?.action).toLowerCase() !== "star-interest-click") return null;
|
|
239
|
+
return parseFavoriteActionValue(payload?.p3);
|
|
240
|
+
} catch {}
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const params = new URLSearchParams(raw);
|
|
244
|
+
const actionName = normalizeText(params.get("action")).toLowerCase();
|
|
245
|
+
if (actionName !== "star-interest-click") return null;
|
|
246
|
+
return parseFavoriteActionValue(params.get("p3"));
|
|
247
|
+
} catch {}
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function parseFavoriteActionFromKnownRequest(url, postData = "") {
|
|
252
|
+
const normalizedUrl = normalizeText(url).toLowerCase();
|
|
253
|
+
if (!normalizedUrl) return null;
|
|
254
|
+
|
|
255
|
+
if (normalizedUrl.includes("usermark")) {
|
|
256
|
+
if (/\/add(?:\/|$)|[?&](?:action|op|operation|type)=add\b/i.test(normalizedUrl)) {
|
|
257
|
+
return "add";
|
|
258
|
+
}
|
|
259
|
+
if (/\/del(?:\/|$)|[?&](?:action|op|operation|type)=del\b/i.test(normalizedUrl)) {
|
|
260
|
+
return "del";
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (normalizedUrl.includes("actionlog/common.json")) {
|
|
266
|
+
return parseFavoriteActionFromActionLog(postData);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function parseFavoriteActionFromWsPayload(payload, depth = 0) {
|
|
273
|
+
if (depth > 3 || payload === null || payload === undefined) return null;
|
|
274
|
+
|
|
275
|
+
if (typeof payload === "object") {
|
|
276
|
+
if (normalizeText(payload?.action).toLowerCase() === "star-interest-click") {
|
|
277
|
+
const strictAction = parseFavoriteActionValue(payload?.p3);
|
|
278
|
+
if (strictAction) return strictAction;
|
|
279
|
+
}
|
|
280
|
+
const nestedCandidates = [
|
|
281
|
+
payload.data,
|
|
282
|
+
payload.payload,
|
|
283
|
+
payload.body,
|
|
284
|
+
payload.message,
|
|
285
|
+
payload.msg
|
|
286
|
+
];
|
|
287
|
+
for (const nested of nestedCandidates) {
|
|
288
|
+
const action = parseFavoriteActionFromWsPayload(nested, depth + 1);
|
|
289
|
+
if (action) return action;
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const text = normalizeText(payload);
|
|
295
|
+
if (!text) return null;
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
const parsed = JSON.parse(text);
|
|
299
|
+
const action = parseFavoriteActionFromWsPayload(parsed, depth + 1);
|
|
300
|
+
if (action) return action;
|
|
301
|
+
} catch {}
|
|
302
|
+
|
|
303
|
+
const actionFromActionLog = parseFavoriteActionFromActionLog(text);
|
|
304
|
+
if (actionFromActionLog) return actionFromActionLog;
|
|
305
|
+
if (/usermark/i.test(text)) {
|
|
306
|
+
if (/\/add(?:\/|$)|[?&](?:action|op|operation|type)=add\b/i.test(text)) return "add";
|
|
307
|
+
if (/\/del(?:\/|$)|[?&](?:action|op|operation|type)=del\b/i.test(text)) return "del";
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function isRecoverablePostActionError(error, action) {
|
|
314
|
+
const normalizedAction = normalizeText(action).toLowerCase();
|
|
315
|
+
const normalizedCode = normalizeText(error?.code).toUpperCase();
|
|
316
|
+
if (!normalizedAction || !normalizedCode) return false;
|
|
317
|
+
if (normalizedAction === "favorite" && normalizedCode === "FAVORITE_BUTTON_FAILED") return true;
|
|
318
|
+
if (normalizedAction === "greet" && normalizedCode === "GREET_BUTTON_FAILED") return true;
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
|
|
94
322
|
function loadCalibrationPosition(filePath) {
|
|
95
323
|
try {
|
|
96
324
|
const resolved = path.resolve(String(filePath || ""));
|
|
@@ -162,69 +390,71 @@ function parseArgs(argv) {
|
|
|
162
390
|
};
|
|
163
391
|
|
|
164
392
|
for (let index = 0; index < argv.length; index += 1) {
|
|
165
|
-
const
|
|
393
|
+
const normalizedToken = normalizeCliOptionToken(argv[index]);
|
|
394
|
+
const token = normalizedToken.token;
|
|
166
395
|
const next = argv[index + 1];
|
|
167
|
-
|
|
168
|
-
|
|
396
|
+
const inlineValue = normalizedToken.inlineValue;
|
|
397
|
+
if ((token === "--baseurl" || token === "--base-url") && (inlineValue || next)) {
|
|
398
|
+
parsed.baseUrl = inlineValue || next;
|
|
169
399
|
parsed.__provided.baseUrl = true;
|
|
170
|
-
index += 1;
|
|
171
|
-
} else if (token === "--apikey" && next) {
|
|
172
|
-
parsed.apiKey = next;
|
|
400
|
+
if (!inlineValue) index += 1;
|
|
401
|
+
} else if ((token === "--apikey" || token === "--api-key") && (inlineValue || next)) {
|
|
402
|
+
parsed.apiKey = inlineValue || next;
|
|
173
403
|
parsed.__provided.apiKey = true;
|
|
174
|
-
index += 1;
|
|
175
|
-
} else if (token === "--model" && next) {
|
|
176
|
-
parsed.model = next;
|
|
404
|
+
if (!inlineValue) index += 1;
|
|
405
|
+
} else if (token === "--model" && (inlineValue || next)) {
|
|
406
|
+
parsed.model = inlineValue || next;
|
|
177
407
|
parsed.__provided.model = true;
|
|
178
|
-
index += 1;
|
|
179
|
-
} else if (token === "--openai-organization" && next) {
|
|
180
|
-
parsed.openaiOrganization = next;
|
|
181
|
-
index += 1;
|
|
182
|
-
} else if (token === "--openai-project" && next) {
|
|
183
|
-
parsed.openaiProject = next;
|
|
184
|
-
index += 1;
|
|
185
|
-
} else if (token === "--criteria" && next) {
|
|
186
|
-
parsed.criteria = next;
|
|
408
|
+
if (!inlineValue) index += 1;
|
|
409
|
+
} else if (token === "--openai-organization" && (inlineValue || next)) {
|
|
410
|
+
parsed.openaiOrganization = inlineValue || next;
|
|
411
|
+
if (!inlineValue) index += 1;
|
|
412
|
+
} else if (token === "--openai-project" && (inlineValue || next)) {
|
|
413
|
+
parsed.openaiProject = inlineValue || next;
|
|
414
|
+
if (!inlineValue) index += 1;
|
|
415
|
+
} else if (token === "--criteria" && (inlineValue || next)) {
|
|
416
|
+
parsed.criteria = inlineValue || next;
|
|
187
417
|
parsed.__provided.criteria = true;
|
|
188
|
-
index += 1;
|
|
189
|
-
} else if (token === "--targetCount" && next) {
|
|
190
|
-
parsed.targetCount = parsePositiveInteger(next);
|
|
418
|
+
if (!inlineValue) index += 1;
|
|
419
|
+
} else if ((token === "--targetCount" || token === "--target-count") && (inlineValue || next)) {
|
|
420
|
+
parsed.targetCount = parsePositiveInteger(inlineValue || next);
|
|
191
421
|
parsed.__provided.targetCount = true;
|
|
192
|
-
index += 1;
|
|
193
|
-
} else if (token === "--max-greet-count" && next) {
|
|
194
|
-
parsed.maxGreetCount = parsePositiveInteger(next);
|
|
422
|
+
if (!inlineValue) index += 1;
|
|
423
|
+
} else if ((token === "--max-greet-count" || token === "--maxGreetCount") && (inlineValue || next)) {
|
|
424
|
+
parsed.maxGreetCount = parsePositiveInteger(inlineValue || next);
|
|
195
425
|
parsed.__provided.maxGreetCount = true;
|
|
196
|
-
index += 1;
|
|
197
|
-
} else if (token === "--page-scope" && next) {
|
|
198
|
-
parsed.pageScope = normalizePageScope(next) || "recommend";
|
|
426
|
+
if (!inlineValue) index += 1;
|
|
427
|
+
} else if ((token === "--page-scope" || token === "--pageScope" || token === "--page_scope") && (inlineValue || next)) {
|
|
428
|
+
parsed.pageScope = normalizePageScope(inlineValue || next) || "recommend";
|
|
199
429
|
parsed.__provided.pageScope = true;
|
|
200
|
-
index += 1;
|
|
201
|
-
} else if (token === "--calibration" && next) {
|
|
202
|
-
parsed.calibrationPath = path.resolve(next);
|
|
430
|
+
if (!inlineValue) index += 1;
|
|
431
|
+
} else if ((token === "--calibration" || token === "--calibration-path") && (inlineValue || next)) {
|
|
432
|
+
parsed.calibrationPath = path.resolve(inlineValue || next);
|
|
203
433
|
parsed.__provided.calibrationPath = true;
|
|
204
|
-
index += 1;
|
|
205
|
-
} else if (token === "--port" && next) {
|
|
206
|
-
parsed.port = parsePositiveInteger(next) || DEFAULT_PORT;
|
|
434
|
+
if (!inlineValue) index += 1;
|
|
435
|
+
} else if (token === "--port" && (inlineValue || next)) {
|
|
436
|
+
parsed.port = parsePositiveInteger(inlineValue || next) || DEFAULT_PORT;
|
|
207
437
|
parsed.__provided.port = true;
|
|
208
|
-
index += 1;
|
|
209
|
-
} else if (token === "--output" && next) {
|
|
210
|
-
parsed.output = path.resolve(next);
|
|
211
|
-
index += 1;
|
|
212
|
-
} else if (token === "--checkpoint-path" && next) {
|
|
213
|
-
parsed.checkpointPath = path.resolve(next);
|
|
214
|
-
index += 1;
|
|
215
|
-
} else if (token === "--pause-control-path" && next) {
|
|
216
|
-
parsed.pauseControlPath = path.resolve(next);
|
|
217
|
-
index += 1;
|
|
438
|
+
if (!inlineValue) index += 1;
|
|
439
|
+
} else if (token === "--output" && (inlineValue || next)) {
|
|
440
|
+
parsed.output = path.resolve(inlineValue || next);
|
|
441
|
+
if (!inlineValue) index += 1;
|
|
442
|
+
} else if (token === "--checkpoint-path" && (inlineValue || next)) {
|
|
443
|
+
parsed.checkpointPath = path.resolve(inlineValue || next);
|
|
444
|
+
if (!inlineValue) index += 1;
|
|
445
|
+
} else if (token === "--pause-control-path" && (inlineValue || next)) {
|
|
446
|
+
parsed.pauseControlPath = path.resolve(inlineValue || next);
|
|
447
|
+
if (!inlineValue) index += 1;
|
|
218
448
|
} else if (token === "--resume") {
|
|
219
449
|
parsed.resume = true;
|
|
220
|
-
} else if (token === "--post-action" && next) {
|
|
221
|
-
parsed.postAction = normalizePostAction(next);
|
|
450
|
+
} else if ((token === "--post-action" || token === "--postAction") && (inlineValue || next)) {
|
|
451
|
+
parsed.postAction = normalizePostAction(inlineValue || next);
|
|
222
452
|
parsed.__provided.postAction = true;
|
|
223
|
-
index += 1;
|
|
224
|
-
} else if (token === "--post-action-confirmed" && next) {
|
|
225
|
-
parsed.postActionConfirmed = parseBoolean(next);
|
|
453
|
+
if (!inlineValue) index += 1;
|
|
454
|
+
} else if ((token === "--post-action-confirmed" || token === "--postActionConfirmed") && (inlineValue || next)) {
|
|
455
|
+
parsed.postActionConfirmed = parseBoolean(inlineValue || next);
|
|
226
456
|
parsed.__provided.postActionConfirmed = true;
|
|
227
|
-
index += 1;
|
|
457
|
+
if (!inlineValue) index += 1;
|
|
228
458
|
} else if (token === "--help" || token === "-h") {
|
|
229
459
|
parsed.help = true;
|
|
230
460
|
}
|
|
@@ -1546,6 +1776,8 @@ class RecommendScreenCli {
|
|
|
1546
1776
|
this.latestResumeNetworkPayload = null;
|
|
1547
1777
|
this.favoriteActionEvents = [];
|
|
1548
1778
|
this.favoriteClickPendingSince = 0;
|
|
1779
|
+
this.favoriteNetworkTraces = [];
|
|
1780
|
+
this.webSocketByRequestId = new Map();
|
|
1549
1781
|
this.resumeSourceStats = {
|
|
1550
1782
|
network: 0,
|
|
1551
1783
|
image_fallback: 0
|
|
@@ -1640,6 +1872,7 @@ class RecommendScreenCli {
|
|
|
1640
1872
|
|
|
1641
1873
|
markFavoriteClickPending() {
|
|
1642
1874
|
this.favoriteClickPendingSince = Date.now();
|
|
1875
|
+
this.favoriteNetworkTraces = [];
|
|
1643
1876
|
}
|
|
1644
1877
|
|
|
1645
1878
|
consumeFavoriteActionResult(since = 0) {
|
|
@@ -1656,6 +1889,30 @@ class RecommendScreenCli {
|
|
|
1656
1889
|
return matched.action || null;
|
|
1657
1890
|
}
|
|
1658
1891
|
|
|
1892
|
+
recordFavoriteNetworkTrace(entry) {
|
|
1893
|
+
const trace = {
|
|
1894
|
+
ts: Date.now(),
|
|
1895
|
+
...entry
|
|
1896
|
+
};
|
|
1897
|
+
this.favoriteNetworkTraces.push(trace);
|
|
1898
|
+
if (this.favoriteNetworkTraces.length > 60) {
|
|
1899
|
+
this.favoriteNetworkTraces = this.favoriteNetworkTraces.slice(-60);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
summarizeFavoriteNetworkTrace(since = 0) {
|
|
1904
|
+
const timestamp = Number.isFinite(since) ? since : 0;
|
|
1905
|
+
return this.favoriteNetworkTraces
|
|
1906
|
+
.filter((item) => Number(item?.ts || 0) >= timestamp)
|
|
1907
|
+
.slice(-12)
|
|
1908
|
+
.map((item) => {
|
|
1909
|
+
if (item.kind === "ws") {
|
|
1910
|
+
return `[ws:${item.direction}] ${item.url || "unknown"} payload=${item.payload || ""}`;
|
|
1911
|
+
}
|
|
1912
|
+
return `[http] ${item.method || "GET"} ${item.url || ""} body=${item.postData || ""}`;
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1659
1916
|
cacheResumeNetworkPayload(payload, fallbackGeekId = null) {
|
|
1660
1917
|
if (!payload || typeof payload !== "object") return;
|
|
1661
1918
|
const geekDetail = payload.geekDetail || payload;
|
|
@@ -1734,31 +1991,55 @@ class RecommendScreenCli {
|
|
|
1734
1991
|
|
|
1735
1992
|
if (this.favoriteClickPendingSince <= 0) return;
|
|
1736
1993
|
const requestTs = Date.now();
|
|
1737
|
-
if (requestTs < this.favoriteClickPendingSince
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1994
|
+
if (requestTs < this.favoriteClickPendingSince) return;
|
|
1995
|
+
const method = normalizeText(params?.request?.method || "").toUpperCase() || "GET";
|
|
1996
|
+
const postData = params?.request?.postData || "";
|
|
1997
|
+
this.recordFavoriteNetworkTrace({
|
|
1998
|
+
ts: requestTs,
|
|
1999
|
+
kind: "http",
|
|
2000
|
+
method,
|
|
2001
|
+
url: url.slice(0, 240),
|
|
2002
|
+
postData: normalizeText(postData).slice(0, 200)
|
|
2003
|
+
});
|
|
2004
|
+
const action = parseFavoriteActionFromKnownRequest(url, postData);
|
|
2005
|
+
if (!action) return;
|
|
2006
|
+
const source = url.includes("userMark")
|
|
2007
|
+
? "userMark"
|
|
2008
|
+
: url.includes("actionLog/common.json")
|
|
2009
|
+
? "actionLog"
|
|
2010
|
+
: "favorite";
|
|
2011
|
+
this.favoriteActionEvents.push({ action, ts: requestTs, source, url });
|
|
2012
|
+
}
|
|
1750
2013
|
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
2014
|
+
handleNetworkWebSocketCreated(params) {
|
|
2015
|
+
const requestId = normalizeText(params?.requestId || "");
|
|
2016
|
+
if (!requestId) return;
|
|
2017
|
+
const url = normalizeText(params?.url || "");
|
|
2018
|
+
this.webSocketByRequestId.set(requestId, url || "");
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
handleNetworkWebSocketFrame(params, direction = "sent") {
|
|
2022
|
+
if (this.favoriteClickPendingSince <= 0) return;
|
|
2023
|
+
const ts = Date.now();
|
|
2024
|
+
if (ts < this.favoriteClickPendingSince) return;
|
|
2025
|
+
const requestId = normalizeText(params?.requestId || "");
|
|
2026
|
+
const payloadData = normalizeText(params?.response?.payloadData || "");
|
|
2027
|
+
const wsUrl = this.webSocketByRequestId.get(requestId) || "";
|
|
2028
|
+
this.recordFavoriteNetworkTrace({
|
|
2029
|
+
ts,
|
|
2030
|
+
kind: "ws",
|
|
2031
|
+
direction,
|
|
2032
|
+
url: wsUrl ? wsUrl.slice(0, 240) : requestId ? `ws:${requestId}` : "ws",
|
|
2033
|
+
payload: payloadData.slice(0, 200)
|
|
2034
|
+
});
|
|
2035
|
+
const action = parseFavoriteActionFromWsPayload(payloadData);
|
|
2036
|
+
if (!action) return;
|
|
2037
|
+
this.favoriteActionEvents.push({
|
|
2038
|
+
action,
|
|
2039
|
+
ts,
|
|
2040
|
+
source: `websocket_${direction}`,
|
|
2041
|
+
url: wsUrl || (requestId ? `ws:${requestId}` : "websocket")
|
|
2042
|
+
});
|
|
1762
2043
|
}
|
|
1763
2044
|
|
|
1764
2045
|
async handleNetworkLoadingFinished(params) {
|
|
@@ -1921,6 +2202,27 @@ class RecommendScreenCli {
|
|
|
1921
2202
|
} catch {}
|
|
1922
2203
|
});
|
|
1923
2204
|
}
|
|
2205
|
+
if (typeof this.Network.webSocketCreated === "function") {
|
|
2206
|
+
this.Network.webSocketCreated((params) => {
|
|
2207
|
+
try {
|
|
2208
|
+
this.handleNetworkWebSocketCreated(params);
|
|
2209
|
+
} catch {}
|
|
2210
|
+
});
|
|
2211
|
+
}
|
|
2212
|
+
if (typeof this.Network.webSocketFrameSent === "function") {
|
|
2213
|
+
this.Network.webSocketFrameSent((params) => {
|
|
2214
|
+
try {
|
|
2215
|
+
this.handleNetworkWebSocketFrame(params, "sent");
|
|
2216
|
+
} catch {}
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
2219
|
+
if (typeof this.Network.webSocketFrameReceived === "function") {
|
|
2220
|
+
this.Network.webSocketFrameReceived((params) => {
|
|
2221
|
+
try {
|
|
2222
|
+
this.handleNetworkWebSocketFrame(params, "received");
|
|
2223
|
+
} catch {}
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
1924
2226
|
if (typeof this.Network.loadingFinished === "function") {
|
|
1925
2227
|
this.Network.loadingFinished((params) => {
|
|
1926
2228
|
this.handleNetworkLoadingFinished(params).catch(() => {});
|
|
@@ -2444,7 +2746,8 @@ class RecommendScreenCli {
|
|
|
2444
2746
|
async favoriteCandidate(options = {}) {
|
|
2445
2747
|
if (this.args.pageScope === "featured") {
|
|
2446
2748
|
if (options.alreadyInterested === true) {
|
|
2447
|
-
|
|
2749
|
+
log("[FAVORITE] network profile indicates alreadyInterested=true,跳过点击以避免误取消收藏。");
|
|
2750
|
+
return { actionTaken: "already_favorited", source: "network_profile" };
|
|
2448
2751
|
}
|
|
2449
2752
|
if (!this.featuredCalibration?.position) {
|
|
2450
2753
|
throw this.buildError(
|
|
@@ -2459,6 +2762,7 @@ class RecommendScreenCli {
|
|
|
2459
2762
|
|
|
2460
2763
|
const base = this.featuredCalibration.position;
|
|
2461
2764
|
const maxClicks = 5;
|
|
2765
|
+
let detectedAlreadyFavoritedByNetwork = false;
|
|
2462
2766
|
for (let clickIndex = 0; clickIndex < maxClicks; clickIndex += 1) {
|
|
2463
2767
|
const clickStartedAt = Date.now();
|
|
2464
2768
|
this.markFavoriteClickPending();
|
|
@@ -2474,22 +2778,38 @@ class RecommendScreenCli {
|
|
|
2474
2778
|
}
|
|
2475
2779
|
|
|
2476
2780
|
let sawDel = false;
|
|
2477
|
-
for (let index = 0; index <
|
|
2781
|
+
for (let index = 0; index < 14; index += 1) {
|
|
2478
2782
|
await sleep(humanDelay(260, 80));
|
|
2479
2783
|
const networkAction = this.consumeFavoriteActionResult(clickStartedAt);
|
|
2480
2784
|
if (networkAction === "add") {
|
|
2481
|
-
return
|
|
2785
|
+
return detectedAlreadyFavoritedByNetwork
|
|
2786
|
+
? { actionTaken: "already_favorited", re_favorited: true }
|
|
2787
|
+
: { actionTaken: "favorite" };
|
|
2482
2788
|
}
|
|
2483
2789
|
if (networkAction === "del") {
|
|
2790
|
+
detectedAlreadyFavoritedByNetwork = true;
|
|
2791
|
+
log("[FAVORITE] 检测到 network=del,推断该人选原本已收藏,继续点击恢复为收藏状态。");
|
|
2484
2792
|
sawDel = true;
|
|
2485
2793
|
break;
|
|
2486
2794
|
}
|
|
2487
2795
|
}
|
|
2488
2796
|
if (!sawDel && clickIndex === maxClicks - 1) {
|
|
2797
|
+
const traceSummary = this.summarizeFavoriteNetworkTrace(clickStartedAt);
|
|
2798
|
+
if (traceSummary.length > 0) {
|
|
2799
|
+
log(`[FAVORITE_NETWORK_TRACE] ${traceSummary.join(" | ")}`);
|
|
2800
|
+
} else {
|
|
2801
|
+
log("[FAVORITE_NETWORK_TRACE] 点击后未捕获到可识别的 HTTP/WS 网络信号。");
|
|
2802
|
+
}
|
|
2489
2803
|
break;
|
|
2490
2804
|
}
|
|
2491
2805
|
}
|
|
2492
2806
|
|
|
2807
|
+
if (detectedAlreadyFavoritedByNetwork) {
|
|
2808
|
+
throw this.buildError(
|
|
2809
|
+
"FAVORITE_BUTTON_FAILED",
|
|
2810
|
+
"精选页检测到 network del(原本已收藏),但后续未检测到 network add(恢复收藏)成功信号。"
|
|
2811
|
+
);
|
|
2812
|
+
}
|
|
2493
2813
|
throw this.buildError("FAVORITE_BUTTON_FAILED", "精选页收藏未检测到 network add 成功信号。");
|
|
2494
2814
|
}
|
|
2495
2815
|
|
|
@@ -2700,6 +3020,10 @@ class RecommendScreenCli {
|
|
|
2700
3020
|
if (!this.args.baseUrl || !this.args.apiKey || !this.args.model) {
|
|
2701
3021
|
throw this.buildError("SCREEN_CONFIG_ERROR", "Missing baseUrl/apiKey/model", false);
|
|
2702
3022
|
}
|
|
3023
|
+
log(
|
|
3024
|
+
`[ARGS] page_scope=${this.args.pageScope} target_count=${this.args.targetCount ?? "none"} ` +
|
|
3025
|
+
`post_action=${this.args.postAction || "unset"} port=${this.args.port}`
|
|
3026
|
+
);
|
|
2703
3027
|
|
|
2704
3028
|
if (!(this.args.postActionConfirmed === true && this.args.postAction)) {
|
|
2705
3029
|
this.args.postAction = await promptPostAction();
|
|
@@ -2888,23 +3212,41 @@ class RecommendScreenCli {
|
|
|
2888
3212
|
effectiveAction = "favorite";
|
|
2889
3213
|
this.greetLimitFallbackCount += 1;
|
|
2890
3214
|
}
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
:
|
|
3215
|
+
let actionResult = { actionTaken: "none" };
|
|
3216
|
+
try {
|
|
3217
|
+
actionResult = effectiveAction === "favorite"
|
|
3218
|
+
? await this.favoriteCandidate({
|
|
3219
|
+
alreadyInterested: networkCandidateInfo?.alreadyInterested === true
|
|
3220
|
+
})
|
|
3221
|
+
: effectiveAction === "greet"
|
|
3222
|
+
? await this.greetCandidate()
|
|
3223
|
+
: { actionTaken: "none" };
|
|
3224
|
+
} catch (postActionError) {
|
|
3225
|
+
if (!isRecoverablePostActionError(postActionError, effectiveAction)) {
|
|
3226
|
+
throw postActionError;
|
|
3227
|
+
}
|
|
3228
|
+
log(`[POST_ACTION_WARN] ${effectiveAction} 失败,继续写入通过候选人: ${postActionError.message || postActionError}`);
|
|
3229
|
+
actionResult = {
|
|
3230
|
+
actionTaken: `${effectiveAction}_failed`,
|
|
3231
|
+
errorCode: postActionError.code || "POST_ACTION_FAILED",
|
|
3232
|
+
errorMessage: normalizeText(postActionError.message || "post action failed")
|
|
3233
|
+
};
|
|
3234
|
+
}
|
|
2898
3235
|
if (actionResult.actionTaken === "greet") {
|
|
2899
3236
|
this.greetCount += 1;
|
|
2900
3237
|
}
|
|
3238
|
+
const screeningReason = normalizeText(screening.reason || screening.summary || "");
|
|
3239
|
+
const actionErrorMessage = normalizeText(actionResult.errorMessage || "");
|
|
3240
|
+
const mergedReason = actionErrorMessage
|
|
3241
|
+
? `${screeningReason}${screeningReason ? " | " : ""}[${effectiveAction}失败] ${actionErrorMessage}`
|
|
3242
|
+
: screeningReason;
|
|
2901
3243
|
this.passedCandidates.push({
|
|
2902
3244
|
name: candidateProfile.name,
|
|
2903
3245
|
school: candidateProfile.school,
|
|
2904
3246
|
major: candidateProfile.major,
|
|
2905
3247
|
company: candidateProfile.company,
|
|
2906
3248
|
position: candidateProfile.position,
|
|
2907
|
-
reason:
|
|
3249
|
+
reason: mergedReason,
|
|
2908
3250
|
action: actionResult.actionTaken,
|
|
2909
3251
|
geekId: nextCandidate.geek_id,
|
|
2910
3252
|
summary: screening.summary,
|
|
@@ -3060,8 +3402,13 @@ if (require.main === module) {
|
|
|
3060
3402
|
__testables: {
|
|
3061
3403
|
MAX_CONSECUTIVE_RESUME_CAPTURE_FAILURES,
|
|
3062
3404
|
RESUME_CAPTURE_MAX_ATTEMPTS,
|
|
3063
|
-
RESUME_CAPTURE_WAIT_MS
|
|
3405
|
+
RESUME_CAPTURE_WAIT_MS,
|
|
3406
|
+
parseFavoriteActionFromPostData,
|
|
3407
|
+
parseFavoriteActionFromRequest,
|
|
3408
|
+
parseFavoriteActionFromKnownRequest,
|
|
3409
|
+
parseFavoriteActionFromActionLog,
|
|
3410
|
+
parseFavoriteActionFromWsPayload,
|
|
3411
|
+
isRecoverablePostActionError
|
|
3064
3412
|
}
|
|
3065
3413
|
};
|
|
3066
3414
|
}
|
|
3067
|
-
|
|
@@ -4,7 +4,7 @@ const os = require("node:os");
|
|
|
4
4
|
const path = require("node:path");
|
|
5
5
|
const sharp = require("sharp");
|
|
6
6
|
|
|
7
|
-
const { RecommendScreenCli, __testables } = require("./boss-recommend-screen-cli.cjs");
|
|
7
|
+
const { RecommendScreenCli, parseArgs, __testables } = require("./boss-recommend-screen-cli.cjs");
|
|
8
8
|
const { __testables: captureTestables } = require("./scripts/capture-full-resume-canvas.cjs");
|
|
9
9
|
|
|
10
10
|
class FakeRecommendScreenCli extends RecommendScreenCli {
|
|
@@ -434,6 +434,62 @@ async function testFeaturedFavoriteShouldNotUseDomFallback() {
|
|
|
434
434
|
assert.equal(evaluateCalls, 0);
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
+
async function testFeaturedFavoriteShouldSkipClickWhenAlreadyInterested() {
|
|
438
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-featured-favorite-already-"));
|
|
439
|
+
const args = createArgs(tempDir);
|
|
440
|
+
args.pageScope = "featured";
|
|
441
|
+
const calibrationPath = path.join(tempDir, "favorite-calibration.json");
|
|
442
|
+
fs.writeFileSync(calibrationPath, JSON.stringify({
|
|
443
|
+
favoritePosition: {
|
|
444
|
+
pageX: 120,
|
|
445
|
+
pageY: 220,
|
|
446
|
+
canvasX: 0,
|
|
447
|
+
canvasY: 0
|
|
448
|
+
}
|
|
449
|
+
}, null, 2));
|
|
450
|
+
args.calibrationPath = calibrationPath;
|
|
451
|
+
const cli = new RecommendScreenCli(args);
|
|
452
|
+
let clickCalls = 0;
|
|
453
|
+
cli.simulateHumanClick = async () => {
|
|
454
|
+
clickCalls += 1;
|
|
455
|
+
};
|
|
456
|
+
const result = await cli.favoriteCandidate({ alreadyInterested: true });
|
|
457
|
+
assert.equal(result.actionTaken, "already_favorited");
|
|
458
|
+
assert.equal(result.source, "network_profile");
|
|
459
|
+
assert.equal(clickCalls, 0);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async function testFeaturedFavoriteShouldRecognizeAlreadyFavoritedByDelThenAdd() {
|
|
463
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-featured-favorite-del-add-"));
|
|
464
|
+
const args = createArgs(tempDir);
|
|
465
|
+
args.pageScope = "featured";
|
|
466
|
+
const calibrationPath = path.join(tempDir, "favorite-calibration.json");
|
|
467
|
+
fs.writeFileSync(calibrationPath, JSON.stringify({
|
|
468
|
+
favoritePosition: {
|
|
469
|
+
pageX: 120,
|
|
470
|
+
pageY: 220,
|
|
471
|
+
canvasX: 0,
|
|
472
|
+
canvasY: 0
|
|
473
|
+
}
|
|
474
|
+
}, null, 2));
|
|
475
|
+
args.calibrationPath = calibrationPath;
|
|
476
|
+
const cli = new RecommendScreenCli(args);
|
|
477
|
+
let clickCalls = 0;
|
|
478
|
+
cli.simulateHumanClick = async () => {
|
|
479
|
+
clickCalls += 1;
|
|
480
|
+
cli.favoriteActionEvents.push({
|
|
481
|
+
action: clickCalls === 1 ? "del" : "add",
|
|
482
|
+
ts: Date.now(),
|
|
483
|
+
source: "test",
|
|
484
|
+
url: clickCalls === 1 ? "userMark/del" : "userMark/add"
|
|
485
|
+
});
|
|
486
|
+
};
|
|
487
|
+
const result = await cli.favoriteCandidate();
|
|
488
|
+
assert.equal(result.actionTaken, "already_favorited");
|
|
489
|
+
assert.equal(result.re_favorited, true);
|
|
490
|
+
assert.equal(clickCalls, 2);
|
|
491
|
+
}
|
|
492
|
+
|
|
437
493
|
async function testFeaturedFavoriteWithoutCalibrationShouldFail() {
|
|
438
494
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-featured-favorite-missing-cal-"));
|
|
439
495
|
const args = createArgs(tempDir);
|
|
@@ -449,6 +505,91 @@ async function testFeaturedFavoriteWithoutCalibrationShouldFail() {
|
|
|
449
505
|
);
|
|
450
506
|
}
|
|
451
507
|
|
|
508
|
+
function testFavoriteActionParserShouldSupportBodySignals() {
|
|
509
|
+
const addFromJson = __testables.parseFavoriteActionFromPostData(JSON.stringify({
|
|
510
|
+
action: "star-interest-click",
|
|
511
|
+
p3: 1
|
|
512
|
+
}));
|
|
513
|
+
const delFromForm = __testables.parseFavoriteActionFromPostData("action=star-interest-click&p3=0");
|
|
514
|
+
assert.equal(addFromJson, "add");
|
|
515
|
+
assert.equal(delFromForm, "del");
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function testFavoriteActionParserShouldSupportFallbackRequestShape() {
|
|
519
|
+
const action = __testables.parseFavoriteActionFromRequest(
|
|
520
|
+
"https://www.zhipin.com/wapi/zpgeek/favorite/operate",
|
|
521
|
+
JSON.stringify({ op: "add", geekId: "abc" })
|
|
522
|
+
);
|
|
523
|
+
assert.equal(action, "add");
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function testFavoriteActionParserShouldSupportWebSocketPayload() {
|
|
527
|
+
const addFromWsJson = __testables.parseFavoriteActionFromWsPayload(JSON.stringify({
|
|
528
|
+
action: "star-interest-click",
|
|
529
|
+
p3: 1
|
|
530
|
+
}));
|
|
531
|
+
const delFromWsForm = __testables.parseFavoriteActionFromWsPayload("action=star-interest-click&p3=0");
|
|
532
|
+
assert.equal(addFromWsJson, "add");
|
|
533
|
+
assert.equal(delFromWsForm, "del");
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function testFavoriteActionParserShouldOnlyTrustKnownRequestShapes() {
|
|
537
|
+
const unknown = __testables.parseFavoriteActionFromKnownRequest(
|
|
538
|
+
"https://www.zhipin.com/wapi/other/metrics",
|
|
539
|
+
JSON.stringify({ action: "add", p3: 1 })
|
|
540
|
+
);
|
|
541
|
+
const actionLog = __testables.parseFavoriteActionFromKnownRequest(
|
|
542
|
+
"https://www.zhipin.com/wapi/zplog/actionLog/common.json",
|
|
543
|
+
JSON.stringify({ action: "star-interest-click", p3: 1 })
|
|
544
|
+
);
|
|
545
|
+
const userMark = __testables.parseFavoriteActionFromKnownRequest(
|
|
546
|
+
"https://www.zhipin.com/wapi/zpgeek/userMark/add",
|
|
547
|
+
""
|
|
548
|
+
);
|
|
549
|
+
assert.equal(unknown, null);
|
|
550
|
+
assert.equal(actionLog, "add");
|
|
551
|
+
assert.equal(userMark, "add");
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async function testFeaturedPostActionFailureShouldStillRecordPassedCandidate() {
|
|
555
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-featured-action-failure-"));
|
|
556
|
+
const args = createArgs(tempDir);
|
|
557
|
+
args.pageScope = "featured";
|
|
558
|
+
args.postAction = "favorite";
|
|
559
|
+
const candidate = { key: "featured-fav-fail", geek_id: "featured-fav-fail", name: "featured candidate" };
|
|
560
|
+
const cli = new FakeRecommendScreenCli(args, {
|
|
561
|
+
candidates: [candidate]
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
cli.waitForNetworkResumeCandidateInfo = async () => ({
|
|
565
|
+
name: "featured candidate",
|
|
566
|
+
school: "测试大学",
|
|
567
|
+
major: "人工智能",
|
|
568
|
+
company: "测试公司",
|
|
569
|
+
position: "算法工程师",
|
|
570
|
+
resumeText: "满足测试标准"
|
|
571
|
+
});
|
|
572
|
+
cli.callTextModel = async () => ({
|
|
573
|
+
passed: true,
|
|
574
|
+
reason: "通过",
|
|
575
|
+
summary: "通过"
|
|
576
|
+
});
|
|
577
|
+
cli.favoriteCandidate = async () => {
|
|
578
|
+
const error = new Error("精选页收藏未检测到 network add 成功信号。");
|
|
579
|
+
error.code = "FAVORITE_BUTTON_FAILED";
|
|
580
|
+
throw error;
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
const result = await cli.run();
|
|
584
|
+
assert.equal(result.status, "COMPLETED");
|
|
585
|
+
assert.equal(result.result.processed_count, 1);
|
|
586
|
+
assert.equal(result.result.passed_count, 1);
|
|
587
|
+
assert.equal(result.result.skipped_count, 0);
|
|
588
|
+
assert.equal(cli.passedCandidates.length, 1);
|
|
589
|
+
assert.equal(cli.passedCandidates[0].action, "favorite_failed");
|
|
590
|
+
assert.match(cli.passedCandidates[0].reason, /\[favorite失败]/);
|
|
591
|
+
}
|
|
592
|
+
|
|
452
593
|
async function testStitchWithSharpShouldComposeExpectedImage() {
|
|
453
594
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-sharp-stitch-"));
|
|
454
595
|
const chunkA = path.join(tempDir, "chunk_000.png");
|
|
@@ -540,6 +681,27 @@ function testStitchWithAvailablePythonShouldFailWhenScriptMissing() {
|
|
|
540
681
|
assert.equal(result.attempts[0].command, "python3");
|
|
541
682
|
}
|
|
542
683
|
|
|
684
|
+
function testParseArgsShouldSupportFeaturedAliasesAndInlinePort() {
|
|
685
|
+
const parsed = parseArgs([
|
|
686
|
+
"--criteria", "test criteria",
|
|
687
|
+
"--baseurl", "https://example.com/v1",
|
|
688
|
+
"--apikey", "key",
|
|
689
|
+
"--model", "test-model",
|
|
690
|
+
"--target-count", "3",
|
|
691
|
+
"--pageScope", "featured",
|
|
692
|
+
"--port=9222",
|
|
693
|
+
"--postAction", "favorite",
|
|
694
|
+
"--postActionConfirmed", "true"
|
|
695
|
+
]);
|
|
696
|
+
assert.equal(parsed.pageScope, "featured");
|
|
697
|
+
assert.equal(parsed.port, 9222);
|
|
698
|
+
assert.equal(parsed.targetCount, 3);
|
|
699
|
+
assert.equal(parsed.postAction, "favorite");
|
|
700
|
+
assert.equal(parsed.postActionConfirmed, true);
|
|
701
|
+
assert.equal(parsed.__provided.pageScope, true);
|
|
702
|
+
assert.equal(parsed.__provided.port, true);
|
|
703
|
+
}
|
|
704
|
+
|
|
543
705
|
async function main() {
|
|
544
706
|
testShouldAbortResumeProbeEarly();
|
|
545
707
|
await testSingleResumeCaptureFailureIsSkipped();
|
|
@@ -551,10 +713,18 @@ async function main() {
|
|
|
551
713
|
await testNetworkMissShouldFallbackToImageCapture();
|
|
552
714
|
await testFeaturedNetworkMissShouldSkipWithoutImageCapture();
|
|
553
715
|
await testFeaturedFavoriteShouldNotUseDomFallback();
|
|
716
|
+
await testFeaturedFavoriteShouldSkipClickWhenAlreadyInterested();
|
|
717
|
+
await testFeaturedFavoriteShouldRecognizeAlreadyFavoritedByDelThenAdd();
|
|
554
718
|
await testFeaturedFavoriteWithoutCalibrationShouldFail();
|
|
719
|
+
testFavoriteActionParserShouldSupportBodySignals();
|
|
720
|
+
testFavoriteActionParserShouldSupportFallbackRequestShape();
|
|
721
|
+
testFavoriteActionParserShouldSupportWebSocketPayload();
|
|
722
|
+
testFavoriteActionParserShouldOnlyTrustKnownRequestShapes();
|
|
723
|
+
await testFeaturedPostActionFailureShouldStillRecordPassedCandidate();
|
|
555
724
|
await testStitchWithSharpShouldComposeExpectedImage();
|
|
556
725
|
testStitchWithAvailablePythonShouldFallbackToPython();
|
|
557
726
|
testStitchWithAvailablePythonShouldFailWhenScriptMissing();
|
|
727
|
+
testParseArgsShouldSupportFeaturedAliasesAndInlinePort();
|
|
558
728
|
console.log("recoverable resume failure tests passed");
|
|
559
729
|
}
|
|
560
730
|
|