@reconcrap/boss-recommend-mcp 1.3.38 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -33
- package/package.json +61 -9
- package/skills/boss-recommend-pipeline/SKILL.md +4 -0
- package/src/chat-mcp.js +1333 -0
- package/src/chat-runtime-config.js +559 -0
- package/src/cli.js +1095 -196
- package/src/core/browser/index.js +378 -0
- package/src/core/capture/index.js +298 -0
- package/src/core/cv-acquisition/index.js +219 -0
- package/src/core/greet-quota/index.js +54 -0
- package/src/core/infinite-list/index.js +459 -0
- package/src/core/reporting/legacy-csv.js +332 -0
- package/src/core/run/index.js +286 -0
- package/src/core/screening/index.js +1166 -0
- package/src/core/self-heal/index.js +848 -0
- package/src/domains/chat/cards.js +129 -0
- package/src/domains/chat/constants.js +183 -0
- package/src/domains/chat/detail.js +1369 -0
- package/src/domains/chat/index.js +7 -0
- package/src/domains/chat/jobs.js +334 -0
- package/src/domains/chat/page-guard.js +88 -0
- package/src/domains/chat/roots.js +56 -0
- package/src/domains/chat/run-service.js +1101 -0
- package/src/domains/recommend/actions.js +457 -0
- package/src/domains/recommend/cards.js +228 -0
- package/src/domains/recommend/constants.js +141 -0
- package/src/domains/recommend/detail.js +341 -0
- package/src/domains/recommend/filters.js +581 -0
- package/src/domains/recommend/index.js +10 -0
- package/src/domains/recommend/jobs.js +232 -0
- package/src/domains/recommend/refresh.js +204 -0
- package/src/domains/recommend/roots.js +78 -0
- package/src/domains/recommend/run-service.js +903 -0
- package/src/domains/recommend/scopes.js +245 -0
- package/src/domains/recruit/actions.js +277 -0
- package/src/domains/recruit/cards.js +67 -0
- package/src/domains/recruit/constants.js +130 -0
- package/src/domains/recruit/detail.js +414 -0
- package/src/domains/recruit/index.js +9 -0
- package/src/domains/recruit/instruction-parser.js +451 -0
- package/src/domains/recruit/refresh.js +40 -0
- package/src/domains/recruit/roots.js +68 -0
- package/src/domains/recruit/run-service.js +580 -0
- package/src/domains/recruit/search.js +1149 -0
- package/src/index.js +578 -419
- package/src/recommend-mcp.js +1257 -0
- package/src/recruit-mcp.js +1035 -0
- package/src/adapters.js +0 -3079
- package/src/boss-chat.js +0 -1037
- package/src/pipeline.js +0 -2249
- package/src/recommend-healing-config.js +0 -131
- package/src/recommend-healing-rules.json +0 -261
- package/src/self-heal.js +0 -2237
- package/src/test-adapters-runtime.js +0 -628
- package/src/test-boss-chat.js +0 -3196
- package/src/test-index-async.js +0 -498
- package/src/test-parser.js +0 -742
- package/src/test-pipeline.js +0 -2703
- package/src/test-run-state.js +0 -152
- package/src/test-self-heal.js +0 -224
- package/vendor/boss-chat-cli/README.md +0 -134
- package/vendor/boss-chat-cli/package.json +0 -53
- package/vendor/boss-chat-cli/src/app.js +0 -1501
- package/vendor/boss-chat-cli/src/browser/chat-page.js +0 -3562
- package/vendor/boss-chat-cli/src/cli.js +0 -1713
- package/vendor/boss-chat-cli/src/mcp/server.js +0 -149
- package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +0 -193
- package/vendor/boss-chat-cli/src/runtime/async-run-state.js +0 -260
- package/vendor/boss-chat-cli/src/runtime/interaction.js +0 -102
- package/vendor/boss-chat-cli/src/runtime/run-control.js +0 -102
- package/vendor/boss-chat-cli/src/services/chrome-client.js +0 -107
- package/vendor/boss-chat-cli/src/services/llm.js +0 -1292
- package/vendor/boss-chat-cli/src/services/llm.test.js +0 -326
- package/vendor/boss-chat-cli/src/services/profile-store.js +0 -173
- package/vendor/boss-chat-cli/src/services/report-store.js +0 -317
- package/vendor/boss-chat-cli/src/services/resume-capture.js +0 -469
- package/vendor/boss-chat-cli/src/services/resume-network.js +0 -727
- package/vendor/boss-chat-cli/src/services/state-store.js +0 -90
- package/vendor/boss-chat-cli/src/utils/customer-key.js +0 -82
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +0 -6927
- package/vendor/boss-recommend-screen-cli/scripts/capture-full-resume-canvas.cjs +0 -817
- package/vendor/boss-recommend-screen-cli/scripts/stitch_resume_chunks.py +0 -141
- package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +0 -2294
- package/vendor/boss-recommend-search-cli/src/cli.js +0 -1698
- package/vendor/boss-recommend-search-cli/src/test-job-selection.js +0 -211
package/src/chat-mcp.js
ADDED
|
@@ -0,0 +1,1333 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import {
|
|
5
|
+
assertNoForbiddenCdpCalls,
|
|
6
|
+
bringPageToFront,
|
|
7
|
+
connectToChromeTarget,
|
|
8
|
+
enableDomains,
|
|
9
|
+
sleep
|
|
10
|
+
} from "./core/browser/index.js";
|
|
11
|
+
import {
|
|
12
|
+
RUN_STATUS_CANCELING,
|
|
13
|
+
RUN_STATUS_CANCELED,
|
|
14
|
+
RUN_STATUS_COMPLETED,
|
|
15
|
+
RUN_STATUS_FAILED,
|
|
16
|
+
RUN_STATUS_PAUSED
|
|
17
|
+
} from "./core/run/index.js";
|
|
18
|
+
import {
|
|
19
|
+
buildLegacyScreenInputRows,
|
|
20
|
+
cloneReportInput,
|
|
21
|
+
writeLegacyScreenCsv
|
|
22
|
+
} from "./core/reporting/legacy-csv.js";
|
|
23
|
+
import {
|
|
24
|
+
buildChatSelfHealConfig,
|
|
25
|
+
HEALTH_STATUS,
|
|
26
|
+
resolveChatSelfHealRoots,
|
|
27
|
+
runSelfHealCheck
|
|
28
|
+
} from "./core/self-heal/index.js";
|
|
29
|
+
import {
|
|
30
|
+
CHAT_TARGET_URL,
|
|
31
|
+
closeChatResumeModal,
|
|
32
|
+
createChatRunService,
|
|
33
|
+
getChatRoots,
|
|
34
|
+
isForbiddenChatResumeTopLevelUrl,
|
|
35
|
+
readChatJobOptions,
|
|
36
|
+
runChatWorkflow
|
|
37
|
+
} from "./domains/chat/index.js";
|
|
38
|
+
import {
|
|
39
|
+
buildTargetCountCompatibilityHints,
|
|
40
|
+
getBossChatDataDir,
|
|
41
|
+
getBossChatTargetCountValue,
|
|
42
|
+
normalizeTargetCountInput,
|
|
43
|
+
resolveBossChatRuntimeLayout,
|
|
44
|
+
resolveBossScreeningConfig
|
|
45
|
+
} from "./chat-runtime-config.js";
|
|
46
|
+
|
|
47
|
+
const DEFAULT_CHAT_HOST = "127.0.0.1";
|
|
48
|
+
const DEFAULT_CHAT_PORT = 9222;
|
|
49
|
+
const DEFAULT_CHAT_POLL_AFTER_SEC = 10;
|
|
50
|
+
const DEFAULT_CHAT_GREETING_TEXT = "Hi同学,能麻烦发下简历吗?";
|
|
51
|
+
const CHAT_ALL_MAX_CANDIDATES = 100000;
|
|
52
|
+
const TARGET_COUNT_SEMANTICS = "target_count means candidates that pass screening; numeric targets scan until that many candidates pass or the list ends; all/全部/扫到底 scans to the end";
|
|
53
|
+
const RUN_MODE_ASYNC = "async";
|
|
54
|
+
|
|
55
|
+
const CHAT_REQUIRED_FIELDS = Object.freeze([
|
|
56
|
+
"job",
|
|
57
|
+
"start_from",
|
|
58
|
+
"target_count",
|
|
59
|
+
"criteria"
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
const TERMINAL_STATUSES = new Set([
|
|
63
|
+
RUN_STATUS_COMPLETED,
|
|
64
|
+
RUN_STATUS_FAILED,
|
|
65
|
+
RUN_STATUS_CANCELED
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
let chatWorkflowImpl = runChatWorkflow;
|
|
69
|
+
let chatConnectorImpl = connectChatChromeSession;
|
|
70
|
+
let chatJobReaderImpl = readChatJobOptionsFromSession;
|
|
71
|
+
let chatRunService = createChatRunService({
|
|
72
|
+
idPrefix: "mcp_chat",
|
|
73
|
+
workflow: (...args) => chatWorkflowImpl(...args)
|
|
74
|
+
});
|
|
75
|
+
const chatRunMeta = new Map();
|
|
76
|
+
|
|
77
|
+
function normalizeText(value) {
|
|
78
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parsePositiveInteger(raw, fallback) {
|
|
82
|
+
const parsed = Number.parseInt(String(raw || ""), 10);
|
|
83
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function parseNonNegativeInteger(raw, fallback) {
|
|
87
|
+
const parsed = Number.parseInt(String(raw ?? ""), 10);
|
|
88
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function methodSummary(methodLog = []) {
|
|
92
|
+
const summary = {};
|
|
93
|
+
for (const entry of methodLog || []) {
|
|
94
|
+
summary[entry.method] = (summary[entry.method] || 0) + 1;
|
|
95
|
+
}
|
|
96
|
+
return summary;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function clonePlain(value, fallback = null) {
|
|
100
|
+
try {
|
|
101
|
+
return value === undefined ? fallback : JSON.parse(JSON.stringify(value));
|
|
102
|
+
} catch {
|
|
103
|
+
return fallback;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function normalizeRunId(runId) {
|
|
108
|
+
const normalized = normalizeText(runId);
|
|
109
|
+
if (!normalized || normalized.includes("/") || normalized.includes("\\")) return "";
|
|
110
|
+
return normalized;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getChatRunsDir() {
|
|
114
|
+
return path.join(getBossChatDataDir(), "runs");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getChatRunArtifacts(runId) {
|
|
118
|
+
const normalized = normalizeRunId(runId);
|
|
119
|
+
if (!normalized) return null;
|
|
120
|
+
const runsDir = getChatRunsDir();
|
|
121
|
+
return {
|
|
122
|
+
runs_dir: runsDir,
|
|
123
|
+
run_state_path: path.join(runsDir, `${normalized}.json`),
|
|
124
|
+
checkpoint_path: path.join(runsDir, `${normalized}.checkpoint.json`),
|
|
125
|
+
output_csv: path.join(runsDir, `${normalized}.results.csv`),
|
|
126
|
+
report_json: path.join(runsDir, `${normalized}.report.json`)
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function ensureDirectory(dirPath) {
|
|
131
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function writeJsonAtomic(filePath, payload) {
|
|
135
|
+
ensureDirectory(path.dirname(filePath));
|
|
136
|
+
const tempPath = `${filePath}.tmp`;
|
|
137
|
+
fs.writeFileSync(tempPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
138
|
+
fs.renameSync(tempPath, filePath);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function readJsonFile(filePath) {
|
|
142
|
+
try {
|
|
143
|
+
if (!fs.existsSync(filePath)) return null;
|
|
144
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
145
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
146
|
+
} catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function selectedChatJobForCsv(meta = {}) {
|
|
152
|
+
const job = normalizeText(meta.normalized?.job || meta.args?.job || "");
|
|
153
|
+
return {
|
|
154
|
+
value: job,
|
|
155
|
+
title: job,
|
|
156
|
+
label: job
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function buildChatCsvInputRows(snapshot = {}, meta = {}) {
|
|
161
|
+
const normalized = meta.normalized || {};
|
|
162
|
+
const postAction = shouldRequestChatResume(meta.args)
|
|
163
|
+
? "request_cv"
|
|
164
|
+
: normalizeText(meta.args?.post_action || meta.args?.action || "") || "none";
|
|
165
|
+
const searchParams = {
|
|
166
|
+
job: normalized.job || meta.args?.job || "",
|
|
167
|
+
start_from: normalized.startFrom || meta.args?.start_from || "",
|
|
168
|
+
target_count: normalized.publicTargetCount ?? normalized.targetCount ?? snapshot.progress?.target_count ?? "",
|
|
169
|
+
detail_source: meta.args?.detail_source || snapshot.summary?.detail_source || snapshot.context?.detail_source || ""
|
|
170
|
+
};
|
|
171
|
+
return buildLegacyScreenInputRows({
|
|
172
|
+
instruction: meta.args?.instruction || "启动boss聊天任务",
|
|
173
|
+
selectedPage: "chat",
|
|
174
|
+
selectedJob: selectedChatJobForCsv(meta),
|
|
175
|
+
userSearchParams: cloneReportInput(searchParams, {}),
|
|
176
|
+
effectiveSearchParams: cloneReportInput(searchParams, {}),
|
|
177
|
+
screenParams: {
|
|
178
|
+
criteria: normalized.criteria || meta.args?.criteria || "",
|
|
179
|
+
target_count: searchParams.target_count,
|
|
180
|
+
post_action: postAction,
|
|
181
|
+
max_greet_count: meta.args?.max_greet_count ?? ""
|
|
182
|
+
},
|
|
183
|
+
followUp: meta.args?.follow_up || null,
|
|
184
|
+
extraRows: [
|
|
185
|
+
["chat_params.greeting_text", normalized.greetingText || meta.args?.greeting_text || meta.args?.greetingText || DEFAULT_CHAT_GREETING_TEXT],
|
|
186
|
+
["chat_params.profile", normalized.profile || meta.args?.profile || "default"]
|
|
187
|
+
]
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function writeChatLegacyCsvAtomic(filePath, rows = [], snapshot = {}, meta = {}) {
|
|
192
|
+
writeLegacyScreenCsv(filePath, {
|
|
193
|
+
inputRows: buildChatCsvInputRows(snapshot, meta),
|
|
194
|
+
results: rows
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function readChatRunState(runId) {
|
|
199
|
+
const artifacts = getChatRunArtifacts(runId);
|
|
200
|
+
if (!artifacts) return null;
|
|
201
|
+
return readJsonFile(artifacts.run_state_path);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function toIsoOrNull(value) {
|
|
205
|
+
const normalized = normalizeText(value);
|
|
206
|
+
return normalized || null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function secondsBetween(startedAt, endedAt) {
|
|
210
|
+
const startMs = Date.parse(startedAt || "");
|
|
211
|
+
const endMs = Date.parse(endedAt || "") || Date.now();
|
|
212
|
+
if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs < startMs) return null;
|
|
213
|
+
return Math.max(1, Math.round((endMs - startMs) / 1000));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function countPostActionResults(results = []) {
|
|
217
|
+
let requested = 0;
|
|
218
|
+
let requestSatisfied = 0;
|
|
219
|
+
let requestSkipped = 0;
|
|
220
|
+
for (const row of results || []) {
|
|
221
|
+
const action = row?.post_action || {};
|
|
222
|
+
if (action.requested) requestSatisfied += 1;
|
|
223
|
+
if (action.skipped) requestSkipped += 1;
|
|
224
|
+
if (action.requested && !action.skipped) requested += 1;
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
requested,
|
|
228
|
+
request_satisfied: requestSatisfied,
|
|
229
|
+
request_skipped: requestSkipped
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function normalizeLegacyProgress(progress = {}, summary = null) {
|
|
234
|
+
const countedRequests = countPostActionResults(Array.isArray(summary?.results) ? summary.results : []);
|
|
235
|
+
const processed = Number.isInteger(progress.processed)
|
|
236
|
+
? progress.processed
|
|
237
|
+
: Number.isInteger(summary?.processed)
|
|
238
|
+
? summary.processed
|
|
239
|
+
: 0;
|
|
240
|
+
const screened = Number.isInteger(progress.screened)
|
|
241
|
+
? progress.screened
|
|
242
|
+
: Number.isInteger(summary?.screened)
|
|
243
|
+
? summary.screened
|
|
244
|
+
: processed;
|
|
245
|
+
const passed = Number.isInteger(progress.passed)
|
|
246
|
+
? progress.passed
|
|
247
|
+
: Number.isInteger(summary?.passed)
|
|
248
|
+
? summary.passed
|
|
249
|
+
: 0;
|
|
250
|
+
const requested = Number.isInteger(progress.requested)
|
|
251
|
+
? progress.requested
|
|
252
|
+
: Number.isInteger(summary?.requested)
|
|
253
|
+
? summary.requested
|
|
254
|
+
: countedRequests.requested;
|
|
255
|
+
const requestSatisfied = Number.isInteger(progress.request_satisfied)
|
|
256
|
+
? progress.request_satisfied
|
|
257
|
+
: Number.isInteger(summary?.request_satisfied)
|
|
258
|
+
? summary.request_satisfied
|
|
259
|
+
: countedRequests.request_satisfied;
|
|
260
|
+
const requestSkipped = Number.isInteger(progress.request_skipped)
|
|
261
|
+
? progress.request_skipped
|
|
262
|
+
: Number.isInteger(summary?.request_skipped)
|
|
263
|
+
? summary.request_skipped
|
|
264
|
+
: countedRequests.request_skipped;
|
|
265
|
+
return {
|
|
266
|
+
...progress,
|
|
267
|
+
processed,
|
|
268
|
+
inspected: processed,
|
|
269
|
+
screened,
|
|
270
|
+
passed,
|
|
271
|
+
requested,
|
|
272
|
+
request_satisfied: requestSatisfied,
|
|
273
|
+
request_skipped: requestSkipped,
|
|
274
|
+
skipped: Number.isInteger(progress.skipped) ? progress.skipped : Math.max(processed - passed, 0),
|
|
275
|
+
greet_count: Number.isInteger(progress.greet_count) ? progress.greet_count : 0
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function completionReason(status) {
|
|
280
|
+
if (status === RUN_STATUS_COMPLETED) return "completed";
|
|
281
|
+
if (status === RUN_STATUS_CANCELED) return "canceled_by_user";
|
|
282
|
+
if (status === RUN_STATUS_FAILED) return "failed";
|
|
283
|
+
if (status === RUN_STATUS_PAUSED) return "paused";
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function getChatRunMeta(runId) {
|
|
288
|
+
return chatRunMeta.get(runId) || {};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function ensureChatRunArtifacts(snapshot) {
|
|
292
|
+
const artifacts = getChatRunArtifacts(snapshot?.runId || snapshot?.run_id);
|
|
293
|
+
if (!artifacts) return null;
|
|
294
|
+
|
|
295
|
+
const meta = getChatRunMeta(snapshot?.runId || snapshot?.run_id);
|
|
296
|
+
const checkpoint = snapshot?.checkpoint && typeof snapshot.checkpoint === "object"
|
|
297
|
+
? snapshot.checkpoint
|
|
298
|
+
: {};
|
|
299
|
+
writeJsonAtomic(artifacts.checkpoint_path, checkpoint);
|
|
300
|
+
if (meta) meta.checkpointPath = artifacts.checkpoint_path;
|
|
301
|
+
|
|
302
|
+
const summary = snapshot?.summary && typeof snapshot.summary === "object" ? snapshot.summary : null;
|
|
303
|
+
if (summary) {
|
|
304
|
+
const rows = Array.isArray(summary.results) ? summary.results : [];
|
|
305
|
+
writeChatLegacyCsvAtomic(artifacts.output_csv, rows, snapshot, meta);
|
|
306
|
+
writeJsonAtomic(artifacts.report_json, {
|
|
307
|
+
run_id: snapshot.runId || snapshot.run_id,
|
|
308
|
+
status: snapshot.status || snapshot.state,
|
|
309
|
+
phase: snapshot.phase || snapshot.stage,
|
|
310
|
+
progress: snapshot.progress || {},
|
|
311
|
+
context: snapshot.context || {},
|
|
312
|
+
checkpoint,
|
|
313
|
+
summary,
|
|
314
|
+
generated_at: new Date().toISOString()
|
|
315
|
+
});
|
|
316
|
+
if (meta) {
|
|
317
|
+
meta.outputCsvPath = artifacts.output_csv;
|
|
318
|
+
meta.reportJsonPath = artifacts.report_json;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return artifacts;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function buildLegacyChatResult(snapshot) {
|
|
326
|
+
if (!snapshot) return null;
|
|
327
|
+
const artifacts = ensureChatRunArtifacts(snapshot);
|
|
328
|
+
const meta = getChatRunMeta(snapshot.runId);
|
|
329
|
+
const summary = snapshot.summary && typeof snapshot.summary === "object" ? snapshot.summary : null;
|
|
330
|
+
const progress = normalizeLegacyProgress(snapshot.progress, summary);
|
|
331
|
+
return {
|
|
332
|
+
run_id: snapshot.runId,
|
|
333
|
+
state: snapshot.status,
|
|
334
|
+
status: snapshot.status,
|
|
335
|
+
completion_reason: completionReason(snapshot.status),
|
|
336
|
+
requested_count: progress.requested,
|
|
337
|
+
request_satisfied_count: progress.request_satisfied,
|
|
338
|
+
request_skipped_count: progress.request_skipped,
|
|
339
|
+
processed_count: progress.processed,
|
|
340
|
+
inspected_count: progress.processed,
|
|
341
|
+
screened_count: progress.screened,
|
|
342
|
+
passed_count: progress.passed,
|
|
343
|
+
skipped_count: progress.skipped,
|
|
344
|
+
detail_opened: progress.detail_opened || summary?.detail_opened || 0,
|
|
345
|
+
llm_screened: progress.llm_screened || summary?.llm_screened || 0,
|
|
346
|
+
output_csv: artifacts?.output_csv || meta.outputCsvPath || null,
|
|
347
|
+
report_json: artifacts?.report_json || meta.reportJsonPath || null,
|
|
348
|
+
checkpoint_path: artifacts?.checkpoint_path || meta.checkpointPath || null,
|
|
349
|
+
started_at: snapshot.startedAt,
|
|
350
|
+
completed_at: snapshot.completedAt || null,
|
|
351
|
+
duration_sec: secondsBetween(snapshot.startedAt, snapshot.completedAt),
|
|
352
|
+
error: snapshot.error || null,
|
|
353
|
+
results: Array.isArray(summary?.results) ? summary.results : []
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function normalizeRunSnapshot(snapshot) {
|
|
358
|
+
if (!snapshot) return null;
|
|
359
|
+
const meta = getChatRunMeta(snapshot.runId);
|
|
360
|
+
const summary = snapshot.summary && typeof snapshot.summary === "object" ? snapshot.summary : null;
|
|
361
|
+
const progress = normalizeLegacyProgress(snapshot.progress, summary);
|
|
362
|
+
const legacyResult = (
|
|
363
|
+
TERMINAL_STATUSES.has(snapshot.status)
|
|
364
|
+
|| snapshot.status === RUN_STATUS_PAUSED
|
|
365
|
+
) ? buildLegacyChatResult({ ...snapshot, progress }) : null;
|
|
366
|
+
const oldContext = {
|
|
367
|
+
workspace_root: meta.workspaceRoot || null,
|
|
368
|
+
profile: meta.normalized?.profile || meta.args?.profile || "default",
|
|
369
|
+
job: meta.normalized?.job || meta.args?.job || "",
|
|
370
|
+
start_from: meta.normalized?.startFrom || meta.args?.start_from || "",
|
|
371
|
+
criteria: meta.normalized?.criteria || meta.args?.criteria || "",
|
|
372
|
+
greeting_text: meta.normalized?.greetingText || meta.args?.greeting_text || meta.args?.greetingText || DEFAULT_CHAT_GREETING_TEXT,
|
|
373
|
+
target_count: meta.normalized?.publicTargetCount ?? null,
|
|
374
|
+
target_count_semantics: TARGET_COUNT_SEMANTICS
|
|
375
|
+
};
|
|
376
|
+
return {
|
|
377
|
+
...snapshot,
|
|
378
|
+
progress,
|
|
379
|
+
run_id: snapshot.runId,
|
|
380
|
+
mode: RUN_MODE_ASYNC,
|
|
381
|
+
state: snapshot.status,
|
|
382
|
+
stage: snapshot.phase,
|
|
383
|
+
started_at: snapshot.startedAt,
|
|
384
|
+
updated_at: snapshot.updatedAt,
|
|
385
|
+
completed_at: toIsoOrNull(snapshot.completedAt),
|
|
386
|
+
heartbeat_at: snapshot.updatedAt,
|
|
387
|
+
pid: process.pid || null,
|
|
388
|
+
last_message: snapshot.error?.message || snapshot.phase || null,
|
|
389
|
+
context: {
|
|
390
|
+
...(snapshot.context || {}),
|
|
391
|
+
...oldContext,
|
|
392
|
+
shared_run_context: snapshot.context || {}
|
|
393
|
+
},
|
|
394
|
+
control: {
|
|
395
|
+
pause_requested: snapshot.status === RUN_STATUS_PAUSED,
|
|
396
|
+
pause_requested_at: snapshot.status === RUN_STATUS_PAUSED ? snapshot.updatedAt : null,
|
|
397
|
+
pause_requested_by: snapshot.status === RUN_STATUS_PAUSED ? "pause_boss_chat_run" : null,
|
|
398
|
+
cancel_requested: snapshot.status === RUN_STATUS_CANCELING
|
|
399
|
+
},
|
|
400
|
+
resume: {
|
|
401
|
+
checkpoint_path: legacyResult?.checkpoint_path || null,
|
|
402
|
+
pause_control_path: getChatRunArtifacts(snapshot.runId)?.run_state_path || null,
|
|
403
|
+
output_csv: legacyResult?.output_csv || null,
|
|
404
|
+
resume_count: meta.resumeCount || 0,
|
|
405
|
+
last_resumed_at: meta.lastResumedAt || null,
|
|
406
|
+
last_paused_at: snapshot.status === RUN_STATUS_PAUSED ? snapshot.updatedAt : null
|
|
407
|
+
},
|
|
408
|
+
result: legacyResult,
|
|
409
|
+
artifacts: getChatRunArtifacts(snapshot.runId)
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function persistChatRunSnapshot(snapshot) {
|
|
414
|
+
const normalized = normalizeRunSnapshot(snapshot);
|
|
415
|
+
if (!normalized?.run_id) return normalized;
|
|
416
|
+
const artifacts = getChatRunArtifacts(normalized.run_id);
|
|
417
|
+
if (!artifacts) return normalized;
|
|
418
|
+
const payload = {
|
|
419
|
+
run_id: normalized.run_id,
|
|
420
|
+
mode: normalized.mode,
|
|
421
|
+
state: normalized.state,
|
|
422
|
+
status: normalized.status,
|
|
423
|
+
stage: normalized.stage,
|
|
424
|
+
started_at: normalized.started_at,
|
|
425
|
+
updated_at: normalized.updated_at,
|
|
426
|
+
heartbeat_at: normalized.heartbeat_at,
|
|
427
|
+
completed_at: normalized.completed_at,
|
|
428
|
+
pid: normalized.pid,
|
|
429
|
+
progress: normalized.progress,
|
|
430
|
+
last_message: normalized.last_message,
|
|
431
|
+
context: normalized.context,
|
|
432
|
+
control: normalized.control,
|
|
433
|
+
resume: normalized.resume,
|
|
434
|
+
error: normalized.error,
|
|
435
|
+
result: normalized.result,
|
|
436
|
+
summary: normalized.summary,
|
|
437
|
+
artifacts: normalized.artifacts
|
|
438
|
+
};
|
|
439
|
+
writeJsonAtomic(artifacts.run_state_path, payload);
|
|
440
|
+
return normalized;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function attachMethodEvidence(payload, runId) {
|
|
444
|
+
const meta = getChatRunMeta(runId);
|
|
445
|
+
assertNoForbiddenCdpCalls(meta.methodLog || []);
|
|
446
|
+
return {
|
|
447
|
+
...payload,
|
|
448
|
+
runtime_evaluate_used: false,
|
|
449
|
+
method_summary: methodSummary(meta.methodLog || []),
|
|
450
|
+
method_log: meta.methodLog || [],
|
|
451
|
+
chrome: meta.chrome || null
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function shouldNavigateToChat(url) {
|
|
456
|
+
const text = String(url || "");
|
|
457
|
+
return !text.includes("/web/chat/index")
|
|
458
|
+
|| text.includes("/web/chat/recommend")
|
|
459
|
+
|| text.includes("/web/chat/search");
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function isRecoverableChatTargetUrl(url) {
|
|
463
|
+
const text = String(url || "");
|
|
464
|
+
return text.includes("zhipin.com/web/chat")
|
|
465
|
+
|| isForbiddenChatResumeTopLevelUrl(text);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async function waitForHealthyChat(client, config, {
|
|
469
|
+
timeoutMs = 90000,
|
|
470
|
+
intervalMs = 1000
|
|
471
|
+
} = {}) {
|
|
472
|
+
const started = Date.now();
|
|
473
|
+
let lastCheck = null;
|
|
474
|
+
while (Date.now() - started <= timeoutMs) {
|
|
475
|
+
const roots = await resolveChatSelfHealRoots(client, config);
|
|
476
|
+
lastCheck = await runSelfHealCheck({
|
|
477
|
+
client,
|
|
478
|
+
domain: "chat",
|
|
479
|
+
roots: roots.roots,
|
|
480
|
+
selectorProbes: config.selectorProbes,
|
|
481
|
+
accessibilityProbes: config.accessibilityProbes
|
|
482
|
+
});
|
|
483
|
+
if (lastCheck.status === HEALTH_STATUS.HEALTHY) return lastCheck;
|
|
484
|
+
await sleep(intervalMs);
|
|
485
|
+
}
|
|
486
|
+
return lastCheck;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async function connectChatChromeSession({
|
|
490
|
+
host = DEFAULT_CHAT_HOST,
|
|
491
|
+
port = DEFAULT_CHAT_PORT,
|
|
492
|
+
targetUrlIncludes = CHAT_TARGET_URL,
|
|
493
|
+
allowNavigate = true,
|
|
494
|
+
slowLive = false
|
|
495
|
+
} = {}) {
|
|
496
|
+
let session;
|
|
497
|
+
try {
|
|
498
|
+
session = await connectToChromeTarget({
|
|
499
|
+
host,
|
|
500
|
+
port,
|
|
501
|
+
targetUrlIncludes
|
|
502
|
+
});
|
|
503
|
+
} catch (error) {
|
|
504
|
+
if (!allowNavigate) throw error;
|
|
505
|
+
session = await connectToChromeTarget({
|
|
506
|
+
host,
|
|
507
|
+
port,
|
|
508
|
+
targetPredicate: (target) => (
|
|
509
|
+
target?.type === "page"
|
|
510
|
+
&& isRecoverableChatTargetUrl(target?.url)
|
|
511
|
+
)
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const { client, target } = session;
|
|
516
|
+
await enableDomains(client, ["Page", "DOM", "Input", "Network", "Accessibility"]);
|
|
517
|
+
if (typeof client?.Network?.setCacheDisabled === "function") {
|
|
518
|
+
await client.Network.setCacheDisabled({ cacheDisabled: true });
|
|
519
|
+
}
|
|
520
|
+
await bringPageToFront(client);
|
|
521
|
+
|
|
522
|
+
const targetUrl = String(target?.url || "");
|
|
523
|
+
let navigation = {
|
|
524
|
+
navigated: false,
|
|
525
|
+
url: targetUrl
|
|
526
|
+
};
|
|
527
|
+
if (allowNavigate && shouldNavigateToChat(targetUrl)) {
|
|
528
|
+
await client.Page.navigate({ url: CHAT_TARGET_URL });
|
|
529
|
+
const settleMs = slowLive ? 10000 : 5000;
|
|
530
|
+
await sleep(settleMs);
|
|
531
|
+
navigation = {
|
|
532
|
+
navigated: true,
|
|
533
|
+
url: CHAT_TARGET_URL,
|
|
534
|
+
settle_ms: settleMs
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const selfHealConfig = buildChatSelfHealConfig();
|
|
539
|
+
const health = await waitForHealthyChat(client, selfHealConfig, {
|
|
540
|
+
timeoutMs: slowLive ? 180000 : 90000,
|
|
541
|
+
intervalMs: slowLive ? 1200 : 800
|
|
542
|
+
});
|
|
543
|
+
if (!health || health.status !== HEALTH_STATUS.HEALTHY) {
|
|
544
|
+
throw new Error(`Boss chat page is not healthy: ${health?.status || "missing"}`);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return {
|
|
548
|
+
...session,
|
|
549
|
+
navigation,
|
|
550
|
+
health
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async function readChatJobOptionsFromSession(session) {
|
|
555
|
+
const roots = await getChatRoots(session.client);
|
|
556
|
+
return readChatJobOptions(session.client, roots.rootNodes.top);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function normalizeChatStartInput(args = {}) {
|
|
560
|
+
const target = normalizeTargetCountInput(getBossChatTargetCountValue(args));
|
|
561
|
+
return {
|
|
562
|
+
profile: normalizeText(args.profile) || "default",
|
|
563
|
+
job: normalizeText(args.job),
|
|
564
|
+
startFrom: normalizeText(args.start_from).toLowerCase(),
|
|
565
|
+
criteria: normalizeText(args.criteria),
|
|
566
|
+
greetingText: normalizeText(args.greeting_text || args.greetingText || args.greeting),
|
|
567
|
+
target,
|
|
568
|
+
targetCount: target.targetCount,
|
|
569
|
+
publicTargetCount: target.publicValue,
|
|
570
|
+
host: normalizeText(args.host) || DEFAULT_CHAT_HOST,
|
|
571
|
+
port: parsePositiveInteger(args.port, DEFAULT_CHAT_PORT),
|
|
572
|
+
targetUrlIncludes: normalizeText(args.target_url_includes) || CHAT_TARGET_URL,
|
|
573
|
+
allowNavigate: args.allow_navigate !== false,
|
|
574
|
+
slowLive: args.slow_live === true
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function buildChatNextCallExample(args, missingFields, normalized) {
|
|
579
|
+
const example = {};
|
|
580
|
+
if (normalized.job) example.job = normalized.job;
|
|
581
|
+
if (normalized.startFrom) example.start_from = normalized.startFrom;
|
|
582
|
+
if (normalized.target.provided && !normalized.target.parseError) {
|
|
583
|
+
example.target_count = normalized.publicTargetCount ?? normalized.targetCount;
|
|
584
|
+
} else if (missingFields.includes("target_count")) {
|
|
585
|
+
example.target_count = "all";
|
|
586
|
+
}
|
|
587
|
+
if (normalized.criteria) example.criteria = normalized.criteria;
|
|
588
|
+
if (normalizeText(args.greeting_text || args.greetingText || args.greeting)) {
|
|
589
|
+
example.greeting_text = normalizeText(args.greeting_text || args.greetingText || args.greeting);
|
|
590
|
+
}
|
|
591
|
+
return Object.keys(example).length ? example : null;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function getMissingChatStartFields(args = {}, normalized = normalizeChatStartInput(args)) {
|
|
595
|
+
const missing = [];
|
|
596
|
+
if (!normalized.job) missing.push("job");
|
|
597
|
+
if (!["unread", "all"].includes(normalized.startFrom)) missing.push("start_from");
|
|
598
|
+
if (!normalized.target.provided || normalized.target.parseError) missing.push("target_count");
|
|
599
|
+
if (!normalized.criteria) missing.push("criteria");
|
|
600
|
+
return missing;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function buildTargetCountDiagnostics(args, missingFields, normalized) {
|
|
604
|
+
if (!missingFields.includes("target_count")) return {};
|
|
605
|
+
const hints = buildTargetCountCompatibilityHints({
|
|
606
|
+
argumentName: "target_count",
|
|
607
|
+
recommendedArgumentPatch: { target_count: "all" }
|
|
608
|
+
});
|
|
609
|
+
const received = getBossChatTargetCountValue(args);
|
|
610
|
+
const nextCallExample = {
|
|
611
|
+
...(normalizeText(args.job) ? { job: normalizeText(args.job) } : {}),
|
|
612
|
+
...(normalizeText(args.start_from) ? { start_from: normalizeText(args.start_from).toLowerCase() } : {}),
|
|
613
|
+
target_count: "all",
|
|
614
|
+
...(normalizeText(args.criteria) ? { criteria: normalizeText(args.criteria) } : {})
|
|
615
|
+
};
|
|
616
|
+
return {
|
|
617
|
+
...hints,
|
|
618
|
+
received_target_count: received,
|
|
619
|
+
target_count_parse_error: normalized.target.parseError || null,
|
|
620
|
+
next_call_example: nextCallExample
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function buildJobQuestionOptions(jobOptions = []) {
|
|
625
|
+
return (jobOptions || []).map((option) => ({
|
|
626
|
+
label: option.label,
|
|
627
|
+
value: option.value,
|
|
628
|
+
index: option.index,
|
|
629
|
+
active: option.active === true
|
|
630
|
+
}));
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function buildPendingChatQuestions({ args, missingFields, normalized, jobOptions = [] }) {
|
|
634
|
+
const diagnostics = buildTargetCountDiagnostics(args, missingFields, normalized);
|
|
635
|
+
return missingFields.map((field) => {
|
|
636
|
+
if (field === "job") {
|
|
637
|
+
return {
|
|
638
|
+
field,
|
|
639
|
+
question: "请提供 Boss chat 岗位,支持岗位名、编号或页面中的岗位 value。",
|
|
640
|
+
value: normalized.job || null,
|
|
641
|
+
options: buildJobQuestionOptions(jobOptions)
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
if (field === "start_from") {
|
|
645
|
+
return {
|
|
646
|
+
field,
|
|
647
|
+
question: "请确认 chat 起始范围。",
|
|
648
|
+
value: normalized.startFrom || null,
|
|
649
|
+
options: [
|
|
650
|
+
{ label: "未读", value: "unread" },
|
|
651
|
+
{ label: "全部", value: "all" }
|
|
652
|
+
]
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
if (field === "target_count") {
|
|
656
|
+
return {
|
|
657
|
+
field,
|
|
658
|
+
...diagnostics,
|
|
659
|
+
question: "请提供 target_count,使用正整数或 all(扫到底)。",
|
|
660
|
+
value: normalized.publicTargetCount ?? null,
|
|
661
|
+
options: Array.isArray(diagnostics.options) ? diagnostics.options : [],
|
|
662
|
+
parse_error: normalized.target.parseError || null
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
if (field === "criteria") {
|
|
666
|
+
return {
|
|
667
|
+
field,
|
|
668
|
+
question: "请提供自然语言筛选 criteria。",
|
|
669
|
+
value: normalized.criteria || null
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
field,
|
|
674
|
+
question: `请提供 ${field}。`,
|
|
675
|
+
value: null
|
|
676
|
+
};
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
async function buildNeedInputResponse({ args, missingFields, normalized }) {
|
|
681
|
+
const diagnostics = buildTargetCountDiagnostics(args, missingFields, normalized);
|
|
682
|
+
return {
|
|
683
|
+
status: "NEED_INPUT",
|
|
684
|
+
required_fields: CHAT_REQUIRED_FIELDS.slice(),
|
|
685
|
+
missing_fields: missingFields,
|
|
686
|
+
...diagnostics,
|
|
687
|
+
pending_questions: buildPendingChatQuestions({ args, missingFields, normalized }),
|
|
688
|
+
job_options: [],
|
|
689
|
+
error: {
|
|
690
|
+
code: "MISSING_REQUIRED_FIELDS",
|
|
691
|
+
message: "缺少必要字段。请补齐 job、start_from、target_count、criteria 后再启动 Boss chat CDP-only run。",
|
|
692
|
+
retryable: true
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function shouldRequestChatResume(args = {}) {
|
|
698
|
+
return (
|
|
699
|
+
args.request_cv === true
|
|
700
|
+
|| args.request_resume === true
|
|
701
|
+
|| args.ask_cv === true
|
|
702
|
+
|| args.execute_post_action === true
|
|
703
|
+
|| ["request_cv", "ask_cv", "request_resume", "求简历", "索要简历"].includes(normalizeText(args.post_action || args.action))
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function shouldUseChatLlm(args = {}, shouldRequestResume = false) {
|
|
708
|
+
if (args.use_llm === false) return false;
|
|
709
|
+
return (
|
|
710
|
+
args.use_llm === true
|
|
711
|
+
|| shouldRequestResume
|
|
712
|
+
|| parseNonNegativeInteger(args.detail_limit, 0) > 0
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function getRunOptions(args, normalized, session, { workspaceRoot = "" } = {}) {
|
|
717
|
+
const slowLive = args.slow_live === true;
|
|
718
|
+
const isAllTarget = normalized.publicTargetCount === "all";
|
|
719
|
+
const processedLimit = parsePositiveInteger(
|
|
720
|
+
args.max_candidates,
|
|
721
|
+
isAllTarget ? CHAT_ALL_MAX_CANDIDATES : CHAT_ALL_MAX_CANDIDATES
|
|
722
|
+
);
|
|
723
|
+
const shouldRequestResume = shouldRequestChatResume(args);
|
|
724
|
+
const useLlm = shouldUseChatLlm(args, shouldRequestResume);
|
|
725
|
+
const configResolution = useLlm ? resolveBossScreeningConfig(workspaceRoot) : { ok: false };
|
|
726
|
+
const configFile = configResolution.ok ? readJsonFile(configResolution.config_path) : null;
|
|
727
|
+
return {
|
|
728
|
+
client: session.client,
|
|
729
|
+
targetUrl: CHAT_TARGET_URL,
|
|
730
|
+
job: normalized.job,
|
|
731
|
+
startFrom: normalized.startFrom,
|
|
732
|
+
criteria: normalized.criteria,
|
|
733
|
+
maxCandidates: processedLimit,
|
|
734
|
+
targetPassCount: isAllTarget ? null : normalized.targetCount,
|
|
735
|
+
processUntilListEnd: isAllTarget,
|
|
736
|
+
detailLimit: parseNonNegativeInteger(args.detail_limit, useLlm || shouldRequestResume ? processedLimit : 0),
|
|
737
|
+
detailSource: normalizeText(args.detail_source) || "cascade",
|
|
738
|
+
closeResume: true,
|
|
739
|
+
requestResumeForPassed: shouldRequestResume,
|
|
740
|
+
dryRunRequestCv: args.dry_run === true || args.dry_run_request_cv === true,
|
|
741
|
+
greetingText: normalized.greetingText || DEFAULT_CHAT_GREETING_TEXT,
|
|
742
|
+
delayMs: parseNonNegativeInteger(args.delay_ms, 0),
|
|
743
|
+
cardTimeoutMs: slowLive ? 180000 : 90000,
|
|
744
|
+
readyTimeoutMs: slowLive ? 120000 : 60000,
|
|
745
|
+
onlineResumeButtonTimeoutMs: parsePositiveInteger(
|
|
746
|
+
args.online_resume_button_timeout_ms,
|
|
747
|
+
slowLive ? 30000 : 15000
|
|
748
|
+
),
|
|
749
|
+
resumeDomTimeoutMs: slowLive ? 120000 : 60000,
|
|
750
|
+
maxImagePages: parsePositiveInteger(args.max_image_pages, 8),
|
|
751
|
+
imageWheelDeltaY: parsePositiveInteger(args.image_wheel_delta_y, 650),
|
|
752
|
+
llmConfig: configResolution.ok ? {
|
|
753
|
+
...configResolution.config,
|
|
754
|
+
apiKey: configFile?.apiKey || ""
|
|
755
|
+
} : null,
|
|
756
|
+
llmTimeoutMs: parsePositiveInteger(args.llm_timeout_ms, slowLive ? 180000 : 120000),
|
|
757
|
+
llmImageLimit: parsePositiveInteger(args.llm_image_limit, 8),
|
|
758
|
+
llmImageDetail: normalizeText(args.llm_image_detail) || "high",
|
|
759
|
+
listMaxScrolls: parsePositiveInteger(args.list_max_scrolls, 200),
|
|
760
|
+
listStableSignatureLimit: parsePositiveInteger(args.list_stable_signature_limit, 2),
|
|
761
|
+
listWheelDeltaY: parsePositiveInteger(args.list_wheel_delta_y, 850),
|
|
762
|
+
listSettleMs: parsePositiveInteger(args.list_settle_ms, slowLive ? 1800 : 1200),
|
|
763
|
+
listFallbackPoint: null,
|
|
764
|
+
name: "mcp-boss-chat-run"
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
async function closeChatRunSession(runId) {
|
|
769
|
+
const meta = chatRunMeta.get(runId);
|
|
770
|
+
if (!meta || meta.closed) return;
|
|
771
|
+
try {
|
|
772
|
+
try {
|
|
773
|
+
if (meta.session?.client) {
|
|
774
|
+
await closeChatResumeModal(meta.session.client, { attemptsLimit: 2 });
|
|
775
|
+
}
|
|
776
|
+
} catch {
|
|
777
|
+
// Cleanup is best-effort once the run has settled.
|
|
778
|
+
}
|
|
779
|
+
assertNoForbiddenCdpCalls(meta.methodLog || []);
|
|
780
|
+
} finally {
|
|
781
|
+
meta.closed = true;
|
|
782
|
+
try {
|
|
783
|
+
await meta.session?.close?.();
|
|
784
|
+
} catch {
|
|
785
|
+
// Nothing actionable for the caller once the run has settled.
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
async function waitForChatRunTerminal(runId) {
|
|
791
|
+
while (true) {
|
|
792
|
+
try {
|
|
793
|
+
const snapshot = chatRunService.getChatRun(runId);
|
|
794
|
+
if (TERMINAL_STATUSES.has(snapshot.status)) return snapshot;
|
|
795
|
+
} catch {
|
|
796
|
+
return null;
|
|
797
|
+
}
|
|
798
|
+
await sleep(1000);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function trackChatRun(runId) {
|
|
803
|
+
waitForChatRunTerminal(runId)
|
|
804
|
+
.then((terminal) => {
|
|
805
|
+
if (terminal) persistChatRunSnapshot(terminal);
|
|
806
|
+
})
|
|
807
|
+
.catch(() => null)
|
|
808
|
+
.finally(() => {
|
|
809
|
+
closeChatRunSession(runId).catch(() => {});
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
async function startBossChatRunInternal(args = {}, { workspaceRoot = "" } = {}) {
|
|
814
|
+
const normalized = normalizeChatStartInput(args);
|
|
815
|
+
const missingFields = getMissingChatStartFields(args, normalized);
|
|
816
|
+
if (missingFields.length) {
|
|
817
|
+
return buildNeedInputResponse({
|
|
818
|
+
args,
|
|
819
|
+
missingFields,
|
|
820
|
+
normalized
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const shouldRequestResume = shouldRequestChatResume(args);
|
|
825
|
+
const useLlm = shouldUseChatLlm(args, shouldRequestResume);
|
|
826
|
+
const configResolution = useLlm ? resolveBossScreeningConfig(workspaceRoot) : null;
|
|
827
|
+
if (useLlm && !configResolution?.ok) {
|
|
828
|
+
return {
|
|
829
|
+
status: "FAILED",
|
|
830
|
+
error: {
|
|
831
|
+
code: "SCREEN_CONFIG_ERROR",
|
|
832
|
+
message: configResolution?.error?.message || "screening-config.json is required for chat LLM screening",
|
|
833
|
+
retryable: true
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
let session;
|
|
839
|
+
try {
|
|
840
|
+
session = await chatConnectorImpl({
|
|
841
|
+
host: normalized.host,
|
|
842
|
+
port: normalized.port,
|
|
843
|
+
targetUrlIncludes: normalized.targetUrlIncludes,
|
|
844
|
+
allowNavigate: normalized.allowNavigate,
|
|
845
|
+
slowLive: normalized.slowLive
|
|
846
|
+
});
|
|
847
|
+
} catch (error) {
|
|
848
|
+
return {
|
|
849
|
+
status: "FAILED",
|
|
850
|
+
error: {
|
|
851
|
+
code: "BOSS_CHAT_PAGE_NOT_READY",
|
|
852
|
+
message: error?.message || "Boss chat page is not ready",
|
|
853
|
+
retryable: true
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
let started;
|
|
859
|
+
try {
|
|
860
|
+
started = chatRunService.startChatRun(getRunOptions(args, normalized, session, { workspaceRoot }));
|
|
861
|
+
} catch (error) {
|
|
862
|
+
await session.close?.();
|
|
863
|
+
return {
|
|
864
|
+
status: "FAILED",
|
|
865
|
+
error: {
|
|
866
|
+
code: "CHAT_RUN_START_FAILED",
|
|
867
|
+
message: error?.message || "Failed to start Boss chat run",
|
|
868
|
+
retryable: true
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
chatRunMeta.set(started.runId, {
|
|
874
|
+
session,
|
|
875
|
+
methodLog: session.methodLog || [],
|
|
876
|
+
workspaceRoot: normalizeText(workspaceRoot) || process.cwd(),
|
|
877
|
+
args: clonePlain(args, {}),
|
|
878
|
+
normalized,
|
|
879
|
+
chrome: {
|
|
880
|
+
host: normalized.host,
|
|
881
|
+
port: normalized.port,
|
|
882
|
+
target_url: session.navigation?.url || session.target?.url || CHAT_TARGET_URL,
|
|
883
|
+
target_id: session.target?.id || null
|
|
884
|
+
},
|
|
885
|
+
health: session.health || null
|
|
886
|
+
});
|
|
887
|
+
trackChatRun(started.runId);
|
|
888
|
+
const persistedStarted = persistChatRunSnapshot(started);
|
|
889
|
+
|
|
890
|
+
return {
|
|
891
|
+
status: "ACCEPTED",
|
|
892
|
+
run_id: persistedStarted.run_id,
|
|
893
|
+
state: persistedStarted.state,
|
|
894
|
+
run: persistedStarted,
|
|
895
|
+
poll_after_sec: DEFAULT_CHAT_POLL_AFTER_SEC,
|
|
896
|
+
message: shouldRequestResume
|
|
897
|
+
? "Boss chat run started through the shared CDP-only chat service. Passed candidates will follow the configured request-CV sequence."
|
|
898
|
+
: "Boss chat run started through the shared CDP-only chat service.",
|
|
899
|
+
target_count_semantics: TARGET_COUNT_SEMANTICS
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
export async function prepareBossChatRunTool({ workspaceRoot = "", args = {} } = {}) {
|
|
904
|
+
const normalized = normalizeChatStartInput(args);
|
|
905
|
+
let session;
|
|
906
|
+
try {
|
|
907
|
+
session = await chatConnectorImpl({
|
|
908
|
+
host: normalized.host,
|
|
909
|
+
port: normalized.port,
|
|
910
|
+
targetUrlIncludes: normalized.targetUrlIncludes,
|
|
911
|
+
allowNavigate: normalized.allowNavigate,
|
|
912
|
+
slowLive: normalized.slowLive
|
|
913
|
+
});
|
|
914
|
+
} catch (error) {
|
|
915
|
+
return {
|
|
916
|
+
status: "FAILED",
|
|
917
|
+
stage: "chat_run_setup",
|
|
918
|
+
error: {
|
|
919
|
+
code: "BOSS_CHAT_PAGE_NOT_READY",
|
|
920
|
+
message: error?.message || "Boss chat page is not ready",
|
|
921
|
+
retryable: true
|
|
922
|
+
},
|
|
923
|
+
runtime_evaluate_used: false,
|
|
924
|
+
method_summary: {},
|
|
925
|
+
method_log: [],
|
|
926
|
+
chrome: {
|
|
927
|
+
host: normalized.host,
|
|
928
|
+
port: normalized.port,
|
|
929
|
+
target_url: CHAT_TARGET_URL
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
try {
|
|
935
|
+
const jobs = await chatJobReaderImpl(session, {
|
|
936
|
+
workspaceRoot: normalizeText(workspaceRoot) || process.cwd(),
|
|
937
|
+
args: clonePlain(args, {}),
|
|
938
|
+
normalized
|
|
939
|
+
});
|
|
940
|
+
const jobOptions = Array.isArray(jobs?.job_options) ? jobs.job_options : [];
|
|
941
|
+
const missingFields = getMissingChatStartFields(args, normalized);
|
|
942
|
+
const diagnostics = buildTargetCountDiagnostics(args, missingFields, normalized);
|
|
943
|
+
const nextCallExample = buildChatNextCallExample(args, missingFields, normalized);
|
|
944
|
+
const selectedJob = jobOptions.find((option) => {
|
|
945
|
+
const job = normalizeText(normalized.job).toLowerCase();
|
|
946
|
+
if (!job) return option.active === true;
|
|
947
|
+
return [option.value, option.label, option.title]
|
|
948
|
+
.map((value) => normalizeText(value).toLowerCase())
|
|
949
|
+
.includes(job);
|
|
950
|
+
}) || null;
|
|
951
|
+
|
|
952
|
+
assertNoForbiddenCdpCalls(session.methodLog || []);
|
|
953
|
+
return {
|
|
954
|
+
status: missingFields.length ? "NEED_INPUT" : "READY",
|
|
955
|
+
stage: "chat_run_setup",
|
|
956
|
+
page_url: session.navigation?.url || session.target?.url || CHAT_TARGET_URL,
|
|
957
|
+
required_fields: CHAT_REQUIRED_FIELDS.slice(),
|
|
958
|
+
missing_fields: missingFields,
|
|
959
|
+
job_options: jobOptions,
|
|
960
|
+
selected_job: selectedJob,
|
|
961
|
+
selected_job_label: jobs?.selected_label || selectedJob?.label || "",
|
|
962
|
+
job_options_source: jobs?.source || "",
|
|
963
|
+
job_options_selector: jobs?.selector || "",
|
|
964
|
+
pending_questions: buildPendingChatQuestions({
|
|
965
|
+
args,
|
|
966
|
+
missingFields,
|
|
967
|
+
normalized,
|
|
968
|
+
jobOptions
|
|
969
|
+
}),
|
|
970
|
+
...diagnostics,
|
|
971
|
+
...(nextCallExample ? { next_call_example: nextCallExample } : {}),
|
|
972
|
+
message: missingFields.length
|
|
973
|
+
? "已通过 CDP-only 读取 Boss 聊天页岗位列表,请补齐 job / start_from / target_count / criteria。"
|
|
974
|
+
: "Boss chat CDP-only preflight is ready. Use start_boss_chat_run to start screening.",
|
|
975
|
+
runtime_evaluate_used: false,
|
|
976
|
+
method_summary: methodSummary(session.methodLog || []),
|
|
977
|
+
method_log: session.methodLog || [],
|
|
978
|
+
chrome: {
|
|
979
|
+
host: normalized.host,
|
|
980
|
+
port: normalized.port,
|
|
981
|
+
target_url: session.navigation?.url || session.target?.url || CHAT_TARGET_URL,
|
|
982
|
+
target_id: session.target?.id || null
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
} catch (error) {
|
|
986
|
+
return {
|
|
987
|
+
status: "FAILED",
|
|
988
|
+
stage: "chat_run_setup",
|
|
989
|
+
error: {
|
|
990
|
+
code: "BOSS_CHAT_PREPARE_FAILED",
|
|
991
|
+
message: error?.message || "Boss chat CDP-only prepare failed",
|
|
992
|
+
retryable: true
|
|
993
|
+
},
|
|
994
|
+
runtime_evaluate_used: false,
|
|
995
|
+
method_summary: methodSummary(session.methodLog || []),
|
|
996
|
+
method_log: session.methodLog || [],
|
|
997
|
+
chrome: {
|
|
998
|
+
host: normalized.host,
|
|
999
|
+
port: normalized.port,
|
|
1000
|
+
target_url: session.navigation?.url || session.target?.url || CHAT_TARGET_URL,
|
|
1001
|
+
target_id: session.target?.id || null
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
} finally {
|
|
1005
|
+
try {
|
|
1006
|
+
assertNoForbiddenCdpCalls(session.methodLog || []);
|
|
1007
|
+
} finally {
|
|
1008
|
+
await session.close?.();
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
export async function bossChatHealthCheckTool({ workspaceRoot = "", args = {} } = {}) {
|
|
1014
|
+
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|
|
1015
|
+
const runtimeLayout = resolveBossChatRuntimeLayout(workspaceRoot);
|
|
1016
|
+
const host = normalizeText(args.host) || DEFAULT_CHAT_HOST;
|
|
1017
|
+
const port = parsePositiveInteger(args.port, configResolution.ok ? configResolution.config.debugPort : DEFAULT_CHAT_PORT);
|
|
1018
|
+
const targetUrlIncludes = normalizeText(args.target_url_includes) || CHAT_TARGET_URL;
|
|
1019
|
+
const allowNavigate = args.allow_navigate !== false;
|
|
1020
|
+
const slowLive = args.slow_live === true;
|
|
1021
|
+
const basePayload = {
|
|
1022
|
+
server: "boss-chat",
|
|
1023
|
+
mode: "cdp-only",
|
|
1024
|
+
cdp_only: true,
|
|
1025
|
+
cli_dir: null,
|
|
1026
|
+
cli_path: null,
|
|
1027
|
+
config_path: configResolution.config_path || null,
|
|
1028
|
+
config_dir: configResolution.config_dir || null,
|
|
1029
|
+
debug_port: port,
|
|
1030
|
+
shared_llm_config: configResolution.ok === true,
|
|
1031
|
+
data_dir: runtimeLayout.data_dir,
|
|
1032
|
+
data_dir_source: runtimeLayout.data_dir_source,
|
|
1033
|
+
legacy_workspace_dir: runtimeLayout.legacy_workspace_dir,
|
|
1034
|
+
migration_source_dir: runtimeLayout.migration_source_dir,
|
|
1035
|
+
migration_pending: runtimeLayout.migration_pending
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
if (!configResolution.ok) {
|
|
1039
|
+
return {
|
|
1040
|
+
status: "FAILED",
|
|
1041
|
+
...basePayload,
|
|
1042
|
+
error: configResolution.error,
|
|
1043
|
+
runtime_evaluate_used: false,
|
|
1044
|
+
method_summary: {},
|
|
1045
|
+
method_log: [],
|
|
1046
|
+
chrome: {
|
|
1047
|
+
host,
|
|
1048
|
+
port,
|
|
1049
|
+
target_url: targetUrlIncludes
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
let session;
|
|
1055
|
+
try {
|
|
1056
|
+
session = await chatConnectorImpl({
|
|
1057
|
+
host,
|
|
1058
|
+
port,
|
|
1059
|
+
targetUrlIncludes,
|
|
1060
|
+
allowNavigate,
|
|
1061
|
+
slowLive
|
|
1062
|
+
});
|
|
1063
|
+
assertNoForbiddenCdpCalls(session.methodLog || []);
|
|
1064
|
+
return {
|
|
1065
|
+
status: "OK",
|
|
1066
|
+
...basePayload,
|
|
1067
|
+
page_url: session.navigation?.url || session.target?.url || CHAT_TARGET_URL,
|
|
1068
|
+
health: session.health || null,
|
|
1069
|
+
runtime_evaluate_used: false,
|
|
1070
|
+
method_summary: methodSummary(session.methodLog || []),
|
|
1071
|
+
method_log: session.methodLog || [],
|
|
1072
|
+
chrome: {
|
|
1073
|
+
host,
|
|
1074
|
+
port,
|
|
1075
|
+
target_url: session.navigation?.url || session.target?.url || CHAT_TARGET_URL,
|
|
1076
|
+
target_id: session.target?.id || null
|
|
1077
|
+
},
|
|
1078
|
+
message: "Boss chat CDP-only health check passed with shared self-heal probes."
|
|
1079
|
+
};
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
return {
|
|
1082
|
+
status: "FAILED",
|
|
1083
|
+
...basePayload,
|
|
1084
|
+
error: {
|
|
1085
|
+
code: "BOSS_CHAT_PAGE_NOT_READY",
|
|
1086
|
+
message: error?.message || "Boss chat page is not ready",
|
|
1087
|
+
retryable: true
|
|
1088
|
+
},
|
|
1089
|
+
runtime_evaluate_used: false,
|
|
1090
|
+
method_summary: methodSummary(session?.methodLog || []),
|
|
1091
|
+
method_log: session?.methodLog || [],
|
|
1092
|
+
chrome: {
|
|
1093
|
+
host,
|
|
1094
|
+
port,
|
|
1095
|
+
target_url: session?.navigation?.url || session?.target?.url || targetUrlIncludes,
|
|
1096
|
+
target_id: session?.target?.id || null
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
} finally {
|
|
1100
|
+
if (session?.methodLog) assertNoForbiddenCdpCalls(session.methodLog);
|
|
1101
|
+
await session?.close?.();
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
export async function startBossChatRunTool({ workspaceRoot = "", args = {} } = {}) {
|
|
1106
|
+
const started = await startBossChatRunInternal(args, { workspaceRoot });
|
|
1107
|
+
if (started.status !== "ACCEPTED") return started;
|
|
1108
|
+
return attachMethodEvidence(started, started.run_id);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
export function getBossChatRunTool({ args = {} } = {}) {
|
|
1112
|
+
const runId = normalizeRunId(args.run_id || args.runId);
|
|
1113
|
+
if (!runId) {
|
|
1114
|
+
return {
|
|
1115
|
+
status: "FAILED",
|
|
1116
|
+
error: {
|
|
1117
|
+
code: "INVALID_RUN_ID",
|
|
1118
|
+
message: "run_id is required",
|
|
1119
|
+
retryable: false
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
try {
|
|
1124
|
+
const run = chatRunService.getChatRun(runId);
|
|
1125
|
+
const normalizedRun = persistChatRunSnapshot(run);
|
|
1126
|
+
return attachMethodEvidence({
|
|
1127
|
+
status: "RUN_STATUS",
|
|
1128
|
+
run: normalizedRun
|
|
1129
|
+
}, runId);
|
|
1130
|
+
} catch {
|
|
1131
|
+
const persisted = readChatRunState(runId);
|
|
1132
|
+
if (persisted) {
|
|
1133
|
+
return {
|
|
1134
|
+
status: "RUN_STATUS",
|
|
1135
|
+
run: persisted,
|
|
1136
|
+
persistence: {
|
|
1137
|
+
source: "disk",
|
|
1138
|
+
active_control_available: false
|
|
1139
|
+
},
|
|
1140
|
+
runtime_evaluate_used: false,
|
|
1141
|
+
method_summary: {},
|
|
1142
|
+
method_log: [],
|
|
1143
|
+
chrome: null
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
return {
|
|
1147
|
+
status: "FAILED",
|
|
1148
|
+
error: {
|
|
1149
|
+
code: "RUN_NOT_FOUND",
|
|
1150
|
+
message: `No Boss chat run found for run_id=${runId}`,
|
|
1151
|
+
retryable: false
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
export function pauseBossChatRunTool({ args = {} } = {}) {
|
|
1158
|
+
const runId = normalizeRunId(args.run_id || args.runId);
|
|
1159
|
+
try {
|
|
1160
|
+
const before = chatRunService.getChatRun(runId);
|
|
1161
|
+
if (TERMINAL_STATUSES.has(before.status)) {
|
|
1162
|
+
const normalizedBefore = persistChatRunSnapshot(before);
|
|
1163
|
+
return attachMethodEvidence({
|
|
1164
|
+
status: "PAUSE_IGNORED",
|
|
1165
|
+
run: normalizedBefore,
|
|
1166
|
+
message: "目标任务已结束,无需暂停。"
|
|
1167
|
+
}, runId);
|
|
1168
|
+
}
|
|
1169
|
+
if (before.status === RUN_STATUS_PAUSED) {
|
|
1170
|
+
const normalizedBefore = persistChatRunSnapshot(before);
|
|
1171
|
+
return attachMethodEvidence({
|
|
1172
|
+
status: "PAUSE_IGNORED",
|
|
1173
|
+
run: normalizedBefore,
|
|
1174
|
+
message: "目标任务已经处于 paused 状态。"
|
|
1175
|
+
}, runId);
|
|
1176
|
+
}
|
|
1177
|
+
const run = chatRunService.pauseChatRun(runId);
|
|
1178
|
+
const normalizedRun = persistChatRunSnapshot(run);
|
|
1179
|
+
return attachMethodEvidence({
|
|
1180
|
+
status: "PAUSE_REQUESTED",
|
|
1181
|
+
run: normalizedRun,
|
|
1182
|
+
message: "暂停请求已接收,将在当前候选人处理完成后进入 paused。"
|
|
1183
|
+
}, runId);
|
|
1184
|
+
} catch {
|
|
1185
|
+
const persisted = readChatRunState(runId);
|
|
1186
|
+
if (persisted && TERMINAL_STATUSES.has(persisted.state)) {
|
|
1187
|
+
return {
|
|
1188
|
+
status: "PAUSE_IGNORED",
|
|
1189
|
+
run: persisted,
|
|
1190
|
+
message: "目标任务已结束,无需暂停。",
|
|
1191
|
+
runtime_evaluate_used: false,
|
|
1192
|
+
method_summary: {},
|
|
1193
|
+
method_log: [],
|
|
1194
|
+
chrome: null
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
return getBossChatRunTool({ args });
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
export function resumeBossChatRunTool({ args = {} } = {}) {
|
|
1202
|
+
const runId = normalizeRunId(args.run_id || args.runId);
|
|
1203
|
+
try {
|
|
1204
|
+
const before = chatRunService.getChatRun(runId);
|
|
1205
|
+
if (TERMINAL_STATUSES.has(before.status)) {
|
|
1206
|
+
const normalizedBefore = persistChatRunSnapshot(before);
|
|
1207
|
+
return attachMethodEvidence({
|
|
1208
|
+
status: "FAILED",
|
|
1209
|
+
error: {
|
|
1210
|
+
code: "RUN_ALREADY_TERMINATED",
|
|
1211
|
+
message: "目标任务已结束,无法继续。",
|
|
1212
|
+
retryable: false
|
|
1213
|
+
},
|
|
1214
|
+
run: normalizedBefore
|
|
1215
|
+
}, runId);
|
|
1216
|
+
}
|
|
1217
|
+
if (before.status !== RUN_STATUS_PAUSED) {
|
|
1218
|
+
const normalizedBefore = persistChatRunSnapshot(before);
|
|
1219
|
+
return attachMethodEvidence({
|
|
1220
|
+
status: "FAILED",
|
|
1221
|
+
error: {
|
|
1222
|
+
code: "RUN_NOT_PAUSED",
|
|
1223
|
+
message: "仅 paused 状态的 run 才能继续。",
|
|
1224
|
+
retryable: true
|
|
1225
|
+
},
|
|
1226
|
+
run: normalizedBefore
|
|
1227
|
+
}, runId);
|
|
1228
|
+
}
|
|
1229
|
+
const run = chatRunService.resumeChatRun(runId);
|
|
1230
|
+
const meta = getChatRunMeta(runId);
|
|
1231
|
+
if (meta) {
|
|
1232
|
+
meta.resumeCount = (meta.resumeCount || 0) + 1;
|
|
1233
|
+
meta.lastResumedAt = new Date().toISOString();
|
|
1234
|
+
}
|
|
1235
|
+
const normalizedRun = persistChatRunSnapshot(run);
|
|
1236
|
+
return attachMethodEvidence({
|
|
1237
|
+
status: "RESUME_REQUESTED",
|
|
1238
|
+
run: normalizedRun,
|
|
1239
|
+
poll_after_sec: DEFAULT_CHAT_POLL_AFTER_SEC,
|
|
1240
|
+
message: "已恢复 Boss chat run,请使用 get_boss_chat_run 按需轮询。"
|
|
1241
|
+
}, runId);
|
|
1242
|
+
} catch {
|
|
1243
|
+
const persisted = readChatRunState(runId);
|
|
1244
|
+
if (persisted) {
|
|
1245
|
+
return {
|
|
1246
|
+
status: "FAILED",
|
|
1247
|
+
error: {
|
|
1248
|
+
code: TERMINAL_STATUSES.has(persisted.state) ? "RUN_ALREADY_TERMINATED" : "RUN_NOT_ACTIVE",
|
|
1249
|
+
message: TERMINAL_STATUSES.has(persisted.state)
|
|
1250
|
+
? "目标任务已结束,无法继续。"
|
|
1251
|
+
: "该 run 只有磁盘快照,没有当前进程内的活动 CDP 会话,无法安全继续。",
|
|
1252
|
+
retryable: !TERMINAL_STATUSES.has(persisted.state)
|
|
1253
|
+
},
|
|
1254
|
+
run: persisted,
|
|
1255
|
+
persistence: {
|
|
1256
|
+
source: "disk",
|
|
1257
|
+
active_control_available: false
|
|
1258
|
+
},
|
|
1259
|
+
runtime_evaluate_used: false,
|
|
1260
|
+
method_summary: {},
|
|
1261
|
+
method_log: [],
|
|
1262
|
+
chrome: null
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
return getBossChatRunTool({ args });
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
export function cancelBossChatRunTool({ args = {} } = {}) {
|
|
1270
|
+
const runId = normalizeRunId(args.run_id || args.runId);
|
|
1271
|
+
try {
|
|
1272
|
+
const before = chatRunService.getChatRun(runId);
|
|
1273
|
+
if (TERMINAL_STATUSES.has(before.status)) {
|
|
1274
|
+
const normalizedBefore = persistChatRunSnapshot(before);
|
|
1275
|
+
return attachMethodEvidence({
|
|
1276
|
+
status: "CANCEL_IGNORED",
|
|
1277
|
+
run: normalizedBefore,
|
|
1278
|
+
message: "目标任务已结束,无需取消。"
|
|
1279
|
+
}, runId);
|
|
1280
|
+
}
|
|
1281
|
+
const run = chatRunService.cancelChatRun(runId);
|
|
1282
|
+
const normalizedRun = persistChatRunSnapshot(run);
|
|
1283
|
+
return attachMethodEvidence({
|
|
1284
|
+
status: "CANCEL_REQUESTED",
|
|
1285
|
+
run: normalizedRun,
|
|
1286
|
+
message: "已收到取消请求,将在当前候选人处理完成后安全停止。"
|
|
1287
|
+
}, runId);
|
|
1288
|
+
} catch {
|
|
1289
|
+
const persisted = readChatRunState(runId);
|
|
1290
|
+
if (persisted && TERMINAL_STATUSES.has(persisted.state)) {
|
|
1291
|
+
return {
|
|
1292
|
+
status: "CANCEL_IGNORED",
|
|
1293
|
+
run: persisted,
|
|
1294
|
+
message: "目标任务已结束,无需取消。",
|
|
1295
|
+
runtime_evaluate_used: false,
|
|
1296
|
+
method_summary: {},
|
|
1297
|
+
method_log: [],
|
|
1298
|
+
chrome: null
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
return getBossChatRunTool({ args });
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
export function __setChatMcpConnectorForTests(nextConnector) {
|
|
1306
|
+
chatConnectorImpl = typeof nextConnector === "function" ? nextConnector : connectChatChromeSession;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
export function __setChatMcpJobReaderForTests(nextReader) {
|
|
1310
|
+
chatJobReaderImpl = typeof nextReader === "function" ? nextReader : readChatJobOptionsFromSession;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
export function __setChatMcpWorkflowForTests(nextWorkflow) {
|
|
1314
|
+
chatWorkflowImpl = typeof nextWorkflow === "function" ? nextWorkflow : runChatWorkflow;
|
|
1315
|
+
chatRunService = createChatRunService({
|
|
1316
|
+
idPrefix: "mcp_chat",
|
|
1317
|
+
workflow: (...args) => chatWorkflowImpl(...args)
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
export function __resetChatMcpStateForTests() {
|
|
1322
|
+
for (const meta of chatRunMeta.values()) {
|
|
1323
|
+
try {
|
|
1324
|
+
meta.session?.close?.();
|
|
1325
|
+
} catch {
|
|
1326
|
+
// Best-effort test cleanup.
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
chatRunMeta.clear();
|
|
1330
|
+
__setChatMcpConnectorForTests(null);
|
|
1331
|
+
__setChatMcpJobReaderForTests(null);
|
|
1332
|
+
__setChatMcpWorkflowForTests(null);
|
|
1333
|
+
}
|