@reconcrap/boss-recommend-mcp 2.1.9 → 2.1.11
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 +16 -11
- package/package.json +1 -1
- package/skills/boss-chat/README.md +3 -1
- package/skills/boss-chat/SKILL.md +5 -2
- package/skills/boss-recommend-pipeline/SKILL.md +17 -8
- package/src/cli.js +38 -5
- package/src/core/run/index.js +32 -25
- package/src/index.js +376 -140
- package/src/recommend-mcp.js +323 -72
package/src/index.js
CHANGED
|
@@ -101,6 +101,7 @@ const TOOL_GET_SCHEDULED_RUN = "get_recommend_scheduled_run";
|
|
|
101
101
|
const TOOL_RUN_RECOMMEND = "run_recommend";
|
|
102
102
|
const TOOL_START_RUN = "start_recommend_pipeline_run";
|
|
103
103
|
const TOOL_GET_RUN = "get_recommend_pipeline_run";
|
|
104
|
+
const TOOL_LIST_RUNS = "list_recommend_pipeline_runs";
|
|
104
105
|
const TOOL_CANCEL_RUN = "cancel_recommend_pipeline_run";
|
|
105
106
|
const TOOL_PAUSE_RUN = "pause_recommend_pipeline_run";
|
|
106
107
|
const TOOL_RESUME_RUN = "resume_recommend_pipeline_run";
|
|
@@ -109,6 +110,7 @@ const TOOL_RUN_FEATURED_CALIBRATION = "run_featured_calibration";
|
|
|
109
110
|
const TOOL_GET_FEATURED_CALIBRATION_STATUS = "get_featured_calibration_status";
|
|
110
111
|
const TOOL_RUN_RECOMMEND_SELF_HEAL = "run_recommend_self_heal";
|
|
111
112
|
const TOOL_BOSS_CHAT_HEALTH_CHECK = "boss_chat_health_check";
|
|
113
|
+
const TOOL_BOSS_CHAT_LIST_JOBS = "list_boss_chat_jobs";
|
|
112
114
|
const TOOL_BOSS_CHAT_PREPARE_RUN = "prepare_boss_chat_run";
|
|
113
115
|
const TOOL_BOSS_CHAT_START_RUN = "start_boss_chat_run";
|
|
114
116
|
const TOOL_BOSS_CHAT_GET_RUN = "get_boss_chat_run";
|
|
@@ -371,6 +373,68 @@ function getRunArtifacts(runId) {
|
|
|
371
373
|
};
|
|
372
374
|
}
|
|
373
375
|
|
|
376
|
+
function isShutdownLikeError(error = {}) {
|
|
377
|
+
const text = normalizeText([
|
|
378
|
+
error?.code || "",
|
|
379
|
+
error?.message || error || ""
|
|
380
|
+
].join(" "));
|
|
381
|
+
return /socket hang up|ECONNREFUSED|ECONNRESET|WebSocket is not open|Target closed|Session closed|Connection closed|RUN_PROCESS_EXITED|DETACHED_WORKER|RUN_WORKER/i.test(text);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function buildCanceledResultFromExisting(existing = {}, errorPayload = null, message = "流水线已取消。") {
|
|
385
|
+
const previousResult = existing.result && typeof existing.result === "object" ? existing.result : {};
|
|
386
|
+
const previousError = previousResult.error || existing.error || errorPayload || null;
|
|
387
|
+
return {
|
|
388
|
+
...previousResult,
|
|
389
|
+
status: "CANCELED",
|
|
390
|
+
completion_reason: "canceled_by_user",
|
|
391
|
+
error: {
|
|
392
|
+
code: "PIPELINE_CANCELED",
|
|
393
|
+
message,
|
|
394
|
+
retryable: true,
|
|
395
|
+
shutdown_error: previousError || undefined
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function finalizeRawRunStateAsCanceled(runId, existing = {}, {
|
|
401
|
+
errorPayload = null,
|
|
402
|
+
message = "流水线已取消。"
|
|
403
|
+
} = {}) {
|
|
404
|
+
const normalizedRunId = normalizeText(runId);
|
|
405
|
+
if (!normalizedRunId) return null;
|
|
406
|
+
const now = new Date().toISOString();
|
|
407
|
+
const current = existing && typeof existing === "object" ? existing : {};
|
|
408
|
+
const result = buildCanceledResultFromExisting(current, errorPayload, message);
|
|
409
|
+
return writeRawRunState(normalizedRunId, {
|
|
410
|
+
...current,
|
|
411
|
+
run_id: normalizedRunId,
|
|
412
|
+
mode: current.mode || RUN_MODE_ASYNC,
|
|
413
|
+
state: RUN_STATE_CANCELED,
|
|
414
|
+
status: RUN_STATE_CANCELED,
|
|
415
|
+
stage: current.stage || RUN_STAGE_PREFLIGHT,
|
|
416
|
+
started_at: current.started_at || now,
|
|
417
|
+
updated_at: now,
|
|
418
|
+
heartbeat_at: now,
|
|
419
|
+
completed_at: current.completed_at || now,
|
|
420
|
+
pid: Number.isInteger(current.pid) && current.pid > 0 ? current.pid : process.pid,
|
|
421
|
+
progress: current.progress || {},
|
|
422
|
+
last_message: message,
|
|
423
|
+
context: current.context || {},
|
|
424
|
+
control: {
|
|
425
|
+
...(current.control || {}),
|
|
426
|
+
pause_requested: false,
|
|
427
|
+
pause_requested_at: null,
|
|
428
|
+
pause_requested_by: null,
|
|
429
|
+
cancel_requested: false
|
|
430
|
+
},
|
|
431
|
+
resume: current.resume || {},
|
|
432
|
+
artifacts: current.artifacts || undefined,
|
|
433
|
+
error: result.error,
|
|
434
|
+
result
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
374
438
|
function writeRawRunState(runId, payload) {
|
|
375
439
|
const artifacts = getRunArtifacts(runId);
|
|
376
440
|
fs.mkdirSync(path.dirname(artifacts.run_state_path), { recursive: true });
|
|
@@ -390,6 +454,110 @@ function readRawRunState(runId) {
|
|
|
390
454
|
}
|
|
391
455
|
}
|
|
392
456
|
|
|
457
|
+
function getRunSortTime(run = {}, fallbackMs = 0) {
|
|
458
|
+
for (const key of ["updated_at", "heartbeat_at", "completed_at", "started_at", "updatedAt", "completedAt", "startedAt"]) {
|
|
459
|
+
const ms = Date.parse(run?.[key] || "");
|
|
460
|
+
if (Number.isFinite(ms)) return ms;
|
|
461
|
+
}
|
|
462
|
+
return fallbackMs;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function compactRunForList(run = {}) {
|
|
466
|
+
const state = normalizeText(run.state || run.status);
|
|
467
|
+
const result = run.result && typeof run.result === "object" ? run.result : null;
|
|
468
|
+
const error = run.error || result?.error || null;
|
|
469
|
+
return {
|
|
470
|
+
run_id: normalizeText(run.run_id || run.runId),
|
|
471
|
+
state,
|
|
472
|
+
status: state,
|
|
473
|
+
stage: normalizeText(run.stage || run.phase),
|
|
474
|
+
mode: normalizeText(run.mode),
|
|
475
|
+
started_at: run.started_at || run.startedAt || null,
|
|
476
|
+
updated_at: run.updated_at || run.updatedAt || null,
|
|
477
|
+
heartbeat_at: run.heartbeat_at || null,
|
|
478
|
+
completed_at: run.completed_at || run.completedAt || null,
|
|
479
|
+
pid: Number.isInteger(run.pid) && run.pid > 0 ? run.pid : null,
|
|
480
|
+
progress: run.progress || {},
|
|
481
|
+
last_message: normalizeText(run.last_message || error?.message || ""),
|
|
482
|
+
control: {
|
|
483
|
+
pause_requested: run.control?.pause_requested === true,
|
|
484
|
+
cancel_requested: run.control?.cancel_requested === true
|
|
485
|
+
},
|
|
486
|
+
error: error ? {
|
|
487
|
+
code: normalizeText(error.code || ""),
|
|
488
|
+
message: normalizeText(error.message || error || "")
|
|
489
|
+
} : null,
|
|
490
|
+
result: result ? {
|
|
491
|
+
status: normalizeText(result.status || ""),
|
|
492
|
+
completion_reason: normalizeText(result.completion_reason || ""),
|
|
493
|
+
output_csv: normalizeText(result.output_csv || result.result?.output_csv || ""),
|
|
494
|
+
report_json: normalizeText(result.report_json || result.result?.report_json || ""),
|
|
495
|
+
checkpoint_path: normalizeText(result.checkpoint_path || result.result?.checkpoint_path || "")
|
|
496
|
+
} : null,
|
|
497
|
+
resume: {
|
|
498
|
+
checkpoint_path: normalizeText(run.resume?.checkpoint_path || ""),
|
|
499
|
+
output_csv: normalizeText(run.resume?.output_csv || ""),
|
|
500
|
+
worker_stdout_path: normalizeText(run.resume?.worker_stdout_path || ""),
|
|
501
|
+
worker_stderr_path: normalizeText(run.resume?.worker_stderr_path || "")
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function normalizeRunStateFilter(args = {}) {
|
|
507
|
+
const rawStates = Array.isArray(args.states)
|
|
508
|
+
? args.states
|
|
509
|
+
: args.state === undefined
|
|
510
|
+
? []
|
|
511
|
+
: [args.state];
|
|
512
|
+
return new Set(rawStates.map((item) => normalizeText(item)).filter(Boolean));
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function handleListRunsTool(args = {}) {
|
|
516
|
+
const limit = Math.max(1, Math.min(100, Number.parseInt(String(args.limit || 20), 10) || 20));
|
|
517
|
+
const stateFilter = normalizeRunStateFilter(args);
|
|
518
|
+
const runsDir = getRunsDir();
|
|
519
|
+
if (!fs.existsSync(runsDir)) {
|
|
520
|
+
return {
|
|
521
|
+
status: "OK",
|
|
522
|
+
runs: [],
|
|
523
|
+
latest_run: null,
|
|
524
|
+
count: 0,
|
|
525
|
+
total_matching: 0,
|
|
526
|
+
message: "No recommend run state directory exists yet."
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
const entries = fs.readdirSync(runsDir, { withFileTypes: true });
|
|
530
|
+
const runs = [];
|
|
531
|
+
for (const entry of entries) {
|
|
532
|
+
if (!entry.isFile() || !entry.name.endsWith(".json") || entry.name.endsWith(".checkpoint.json")) continue;
|
|
533
|
+
const filePath = path.join(runsDir, entry.name);
|
|
534
|
+
const runId = entry.name.replace(/\.json$/, "");
|
|
535
|
+
const raw = readRawRunState(runId);
|
|
536
|
+
if (!raw) continue;
|
|
537
|
+
const state = normalizeText(raw.state || raw.status);
|
|
538
|
+
if (stateFilter.size > 0 && !stateFilter.has(state)) continue;
|
|
539
|
+
const stat = fs.statSync(filePath);
|
|
540
|
+
runs.push({
|
|
541
|
+
sort_ms: getRunSortTime(raw, Number(stat.mtimeMs || 0)),
|
|
542
|
+
run: compactRunForList(raw)
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
runs.sort((a, b) => b.sort_ms - a.sort_ms);
|
|
546
|
+
const compacted = runs.slice(0, limit).map((item) => item.run);
|
|
547
|
+
return {
|
|
548
|
+
status: "OK",
|
|
549
|
+
runs: compacted,
|
|
550
|
+
latest_run: compacted[0] || null,
|
|
551
|
+
count: compacted.length,
|
|
552
|
+
total_matching: runs.length,
|
|
553
|
+
limit,
|
|
554
|
+
filters: {
|
|
555
|
+
states: Array.from(stateFilter)
|
|
556
|
+
},
|
|
557
|
+
message: "Use latest_run.run_id with get_recommend_pipeline_run, cancel_recommend_pipeline_run, pause_recommend_pipeline_run, or resume_recommend_pipeline_run. In Trae-CN, do not use terminal, PowerShell, CLI, or filesystem scraping to recover run status."
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
393
561
|
function patchRawRunState(runId, patch) {
|
|
394
562
|
const current = readRawRunState(runId);
|
|
395
563
|
if (!current) return null;
|
|
@@ -1128,113 +1296,9 @@ function createScheduleRunInputSchema() {
|
|
|
1128
1296
|
|
|
1129
1297
|
function createToolsSchema() {
|
|
1130
1298
|
return [
|
|
1131
|
-
{
|
|
1132
|
-
name: TOOL_LIST_RECOMMEND_JOBS,
|
|
1133
|
-
description: "CDP-only 读取 Boss 推荐页岗位下拉框,返回所有可用岗位完整名称,方便 cron/一次性任务提前填写 job 参数。不会启动筛选任务。",
|
|
1134
|
-
inputSchema: createListRecommendJobsInputSchema()
|
|
1135
|
-
},
|
|
1136
|
-
{
|
|
1137
|
-
name: TOOL_PREPARE_RUN,
|
|
1138
|
-
description: "只校验 Boss 推荐页流水线参数是否完整;不会启动筛选任务。若用户要现在运行,返回 READY/cron_ready=true 后必须继续调用本 MCP server 的 run_recommend 或 start_recommend_pipeline_run;只有定时/稍后运行才调用 schedule_recommend_pipeline_run。prepare 能返回结果就证明原生 MCP 可用,禁止改用 terminal/shell/run_command/CLI/manual JSON-RPC。",
|
|
1139
|
-
inputSchema: createRunInputSchema()
|
|
1140
|
-
},
|
|
1141
|
-
{
|
|
1142
|
-
name: TOOL_RUN_RECOMMEND,
|
|
1143
|
-
description: "立即通过原生 MCP 启动 Boss 推荐页筛选。它是 start_recommend_pipeline_run 的短别名,适合 Trae/Trae-CN 等代理在 prepare_recommend_pipeline_run 返回 READY 后继续正式运行;必须作为 MCP tool call 调用,禁止通过 terminal/shell/run_command/CLI/manual JSON-RPC 代替,也不要用 schedule_recommend_pipeline_run 冒充立即启动。",
|
|
1144
|
-
inputSchema: createRunInputSchema()
|
|
1145
|
-
},
|
|
1146
|
-
{
|
|
1147
|
-
name: TOOL_START_RUN,
|
|
1148
|
-
description: "立即通过原生 MCP 异步启动 Boss 推荐页流水线(含同步门禁预检);prepare_recommend_pipeline_run 返回 READY 后,如果用户要现在运行就调用本工具或 run_recommend。必须作为 MCP tool call 调用,禁止通过 terminal/shell/run_command/CLI/manual JSON-RPC 代替,也不要用 schedule_recommend_pipeline_run 冒充立即启动。",
|
|
1149
|
-
inputSchema: createRunInputSchema()
|
|
1150
|
-
},
|
|
1151
|
-
{
|
|
1152
|
-
name: TOOL_SCHEDULE_RUN,
|
|
1153
|
-
description: "只用于用户明确要求稍后/cron/定时启动的 package-owned Boss 推荐页定时任务。若用户要现在运行,必须调用 run_recommend 或 start_recommend_pipeline_run,不要用短延迟 schedule 冒充立即启动。schedule 会先校验 READY/cron_ready,再保存完整 payload,并由 detached scheduler 到点直接启动,不依赖 AI harness 自己拼 shell cron。",
|
|
1154
|
-
inputSchema: createScheduleRunInputSchema()
|
|
1155
|
-
},
|
|
1156
|
-
{
|
|
1157
|
-
name: TOOL_GET_SCHEDULED_RUN,
|
|
1158
|
-
description: "查询 package-owned 推荐页定时任务状态;返回 schedule_id、worker 状态、到点后启动的 run_id 与运行快照。",
|
|
1159
|
-
inputSchema: {
|
|
1160
|
-
type: "object",
|
|
1161
|
-
properties: {
|
|
1162
|
-
schedule_id: { type: "string" }
|
|
1163
|
-
},
|
|
1164
|
-
required: ["schedule_id"],
|
|
1165
|
-
additionalProperties: false
|
|
1166
|
-
}
|
|
1167
|
-
},
|
|
1168
|
-
{
|
|
1169
|
-
name: TOOL_GET_RUN,
|
|
1170
|
-
description: "按 run_id 查询异步/同步流水线运行状态快照。",
|
|
1171
|
-
inputSchema: {
|
|
1172
|
-
type: "object",
|
|
1173
|
-
properties: {
|
|
1174
|
-
run_id: { type: "string" }
|
|
1175
|
-
},
|
|
1176
|
-
required: ["run_id"],
|
|
1177
|
-
additionalProperties: false
|
|
1178
|
-
}
|
|
1179
|
-
},
|
|
1180
|
-
{
|
|
1181
|
-
name: TOOL_CANCEL_RUN,
|
|
1182
|
-
description: "取消指定 run_id 的运行中流水线。",
|
|
1183
|
-
inputSchema: {
|
|
1184
|
-
type: "object",
|
|
1185
|
-
properties: {
|
|
1186
|
-
run_id: { type: "string" }
|
|
1187
|
-
},
|
|
1188
|
-
required: ["run_id"],
|
|
1189
|
-
additionalProperties: false
|
|
1190
|
-
}
|
|
1191
|
-
},
|
|
1192
|
-
{
|
|
1193
|
-
name: TOOL_PAUSE_RUN,
|
|
1194
|
-
description: "请求暂停指定 run_id 的流水线;会在当前候选人处理完成后进入 paused。",
|
|
1195
|
-
inputSchema: {
|
|
1196
|
-
type: "object",
|
|
1197
|
-
properties: {
|
|
1198
|
-
run_id: { type: "string" }
|
|
1199
|
-
},
|
|
1200
|
-
required: ["run_id"],
|
|
1201
|
-
additionalProperties: false
|
|
1202
|
-
}
|
|
1203
|
-
},
|
|
1204
|
-
{
|
|
1205
|
-
name: TOOL_RESUME_RUN,
|
|
1206
|
-
description: "继续指定 run_id 的 paused 流水线;沿用原 CSV 与 checkpoint 续跑。",
|
|
1207
|
-
inputSchema: {
|
|
1208
|
-
type: "object",
|
|
1209
|
-
properties: {
|
|
1210
|
-
run_id: { type: "string" }
|
|
1211
|
-
},
|
|
1212
|
-
required: ["run_id"],
|
|
1213
|
-
additionalProperties: false
|
|
1214
|
-
}
|
|
1215
|
-
},
|
|
1216
|
-
{
|
|
1217
|
-
name: TOOL_RUN_FEATURED_CALIBRATION,
|
|
1218
|
-
description: "手动执行精选页收藏按钮校准。执行前请先在 Boss 推荐页切换到精选 tab 并打开任意候选人详情页。",
|
|
1219
|
-
inputSchema: createRunFeaturedCalibrationInputSchema()
|
|
1220
|
-
},
|
|
1221
|
-
{
|
|
1222
|
-
name: TOOL_GET_FEATURED_CALIBRATION_STATUS,
|
|
1223
|
-
description: "查询精选页收藏校准文件与校准脚本可用性。",
|
|
1224
|
-
inputSchema: {
|
|
1225
|
-
type: "object",
|
|
1226
|
-
properties: {},
|
|
1227
|
-
additionalProperties: false
|
|
1228
|
-
}
|
|
1229
|
-
},
|
|
1230
|
-
{
|
|
1231
|
-
name: TOOL_RUN_RECOMMEND_SELF_HEAL,
|
|
1232
|
-
description: "手动运维自愈工具:扫描 Boss recommend 相关 selector / network 规则漂移,并在确认后应用高置信度修复。",
|
|
1233
|
-
inputSchema: createRunRecommendSelfHealInputSchema()
|
|
1234
|
-
},
|
|
1235
1299
|
{
|
|
1236
1300
|
name: TOOL_BOSS_CHAT_HEALTH_CHECK,
|
|
1237
|
-
description: "
|
|
1301
|
+
description: "Boss 聊天页/chat-only 健康检查。chat-only、未读、全部聊天、求简历等任务必须先走 boss-chat 工具,不要调用 list_recommend_jobs 或 start_recommend_pipeline_run。",
|
|
1238
1302
|
inputSchema: {
|
|
1239
1303
|
type: "object",
|
|
1240
1304
|
properties: {
|
|
@@ -1263,19 +1327,24 @@ function createToolsSchema() {
|
|
|
1263
1327
|
additionalProperties: false
|
|
1264
1328
|
}
|
|
1265
1329
|
},
|
|
1330
|
+
{
|
|
1331
|
+
name: TOOL_BOSS_CHAT_LIST_JOBS,
|
|
1332
|
+
description: "只读读取 Boss 聊天页岗位列表;这是 chat-only 获取 job_options 的首选别名,等价于 prepare_boss_chat_run 的预备步骤但不会启动任务。聊天页/未读/全部聊天/求简历任务必须用本工具或 prepare_boss_chat_run,严禁用 list_recommend_jobs。",
|
|
1333
|
+
inputSchema: createBossChatStartInputSchema()
|
|
1334
|
+
},
|
|
1266
1335
|
{
|
|
1267
1336
|
name: TOOL_BOSS_CHAT_PREPARE_RUN,
|
|
1268
|
-
description: "预备一次 boss-chat 任务:只导航聊天页并返回岗位列表与待补字段,不会启动任务。用它先获取 job_options。",
|
|
1337
|
+
description: "预备一次 boss-chat/chat-only 任务:只导航聊天页并返回岗位列表与待补字段,不会启动任务。用它先获取 job_options;不要用 list_recommend_jobs。",
|
|
1269
1338
|
inputSchema: createBossChatStartInputSchema()
|
|
1270
1339
|
},
|
|
1271
1340
|
{
|
|
1272
1341
|
name: TOOL_BOSS_CHAT_START_RUN,
|
|
1273
|
-
description: "异步启动一次 boss-chat 任务。必须一次性提供 job、start_from、target_count、criteria;若用户选择扫到底/不限/全部候选人,必须字面传 target_count=\"all\"。",
|
|
1342
|
+
description: "异步启动一次 boss-chat/chat-only 任务。必须一次性提供 job、start_from、target_count、criteria;若用户选择扫到底/不限/全部候选人,必须字面传 target_count=\"all\"。严禁改用 start_recommend_pipeline_run。",
|
|
1274
1343
|
inputSchema: createBossChatStartInputSchema({ requireFullInput: true })
|
|
1275
1344
|
},
|
|
1276
1345
|
{
|
|
1277
1346
|
name: TOOL_BOSS_CHAT_GET_RUN,
|
|
1278
|
-
description: "查询 boss-chat run_id 的当前状态。",
|
|
1347
|
+
description: "查询 boss-chat run_id 的当前状态。chat-only 状态查询用本工具,不要用 get_recommend_pipeline_run。",
|
|
1279
1348
|
inputSchema: {
|
|
1280
1349
|
type: "object",
|
|
1281
1350
|
properties: {
|
|
@@ -1325,6 +1394,139 @@ function createToolsSchema() {
|
|
|
1325
1394
|
additionalProperties: false
|
|
1326
1395
|
}
|
|
1327
1396
|
},
|
|
1397
|
+
{
|
|
1398
|
+
name: TOOL_LIST_RECOMMEND_JOBS,
|
|
1399
|
+
description: "CDP-only 读取 Boss 推荐页岗位下拉框,返回所有可用岗位完整名称,方便 recommend/推荐页 cron/一次性任务提前填写 job 参数。不会启动筛选任务。chat-only、未读、全部聊天、求简历任务严禁调用本工具,必须用 list_boss_chat_jobs 或 prepare_boss_chat_run。",
|
|
1400
|
+
inputSchema: createListRecommendJobsInputSchema()
|
|
1401
|
+
},
|
|
1402
|
+
{
|
|
1403
|
+
name: TOOL_RUN_RECOMMEND,
|
|
1404
|
+
description: "立即通过原生 MCP 启动 Boss 推荐页筛选。它是 start_recommend_pipeline_run 的短别名;用户已经确认并要现在启动时,优先调用本工具,不需要先调用 prepare_recommend_pipeline_run。必须作为 MCP tool call 调用,禁止通过 terminal/shell/run_command/PowerShell/CLI/manual JSON-RPC 代替,也不要用 schedule_recommend_pipeline_run 冒充立即启动。",
|
|
1405
|
+
inputSchema: createRunInputSchema()
|
|
1406
|
+
},
|
|
1407
|
+
{
|
|
1408
|
+
name: TOOL_START_RUN,
|
|
1409
|
+
description: "立即通过原生 MCP 异步启动 Boss 推荐页流水线(含同步门禁预检)。用户已经确认并要现在启动时,优先调用本工具或 run_recommend,不需要先调用 prepare_recommend_pipeline_run。必须作为 MCP tool call 调用,禁止通过 terminal/shell/run_command/PowerShell/CLI/manual JSON-RPC 代替,也不要用 schedule_recommend_pipeline_run 冒充立即启动。",
|
|
1410
|
+
inputSchema: createRunInputSchema()
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
name: TOOL_PREPARE_RUN,
|
|
1414
|
+
description: "只校验 Boss 推荐页流水线参数是否完整;不会启动筛选任务。主要用于显式预检或稍后/cron/定时启动前校验。若用户要现在运行,READY/cron_ready=true 后必须继续调用本 MCP server 的 run_recommend 或 start_recommend_pipeline_run;prepare 能返回结果就证明原生 MCP 可用,禁止改用 terminal/shell/run_command/PowerShell/CLI/manual JSON-RPC,也禁止再次调用 prepare 试图启动。",
|
|
1415
|
+
inputSchema: createRunInputSchema()
|
|
1416
|
+
},
|
|
1417
|
+
{
|
|
1418
|
+
name: TOOL_SCHEDULE_RUN,
|
|
1419
|
+
description: "只用于用户明确要求稍后/cron/定时启动的 package-owned Boss 推荐页定时任务。若用户要现在运行,必须调用 run_recommend 或 start_recommend_pipeline_run,不要用短延迟 schedule 冒充立即启动。schedule 会先校验 READY/cron_ready,再保存完整 payload,并由 detached scheduler 到点直接启动,不依赖 AI harness 自己拼 shell cron。",
|
|
1420
|
+
inputSchema: createScheduleRunInputSchema()
|
|
1421
|
+
},
|
|
1422
|
+
{
|
|
1423
|
+
name: TOOL_GET_SCHEDULED_RUN,
|
|
1424
|
+
description: "查询 package-owned 推荐页定时任务状态;返回 schedule_id、worker 状态、到点后启动的 run_id 与运行快照。",
|
|
1425
|
+
inputSchema: {
|
|
1426
|
+
type: "object",
|
|
1427
|
+
properties: {
|
|
1428
|
+
schedule_id: { type: "string" }
|
|
1429
|
+
},
|
|
1430
|
+
required: ["schedule_id"],
|
|
1431
|
+
additionalProperties: false
|
|
1432
|
+
}
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
name: TOOL_GET_RUN,
|
|
1436
|
+
description: "按已知 run_id 查询异步/同步流水线运行状态快照。若忘记 run_id,请先调用 list_recommend_pipeline_runs 找 latest_run;在 Trae-CN 中禁止用 terminal/PowerShell/CLI/filesystem scraping 查看 run JSON。",
|
|
1437
|
+
inputSchema: {
|
|
1438
|
+
type: "object",
|
|
1439
|
+
properties: {
|
|
1440
|
+
run_id: { type: "string" }
|
|
1441
|
+
},
|
|
1442
|
+
required: ["run_id"],
|
|
1443
|
+
additionalProperties: false
|
|
1444
|
+
}
|
|
1445
|
+
},
|
|
1446
|
+
{
|
|
1447
|
+
name: TOOL_LIST_RUNS,
|
|
1448
|
+
description: "只读列出最近的 Boss 推荐页 run 状态摘要,并返回 latest_run。用于忘记 run_id 后恢复状态/取消/暂停;摘要不会包含大体积候选人 results。Trae-CN 中必须用本工具恢复 run_id,禁止用 terminal/PowerShell/CLI/Get-Content 读取 ~/.boss-recommend-mcp/runs。",
|
|
1449
|
+
inputSchema: {
|
|
1450
|
+
type: "object",
|
|
1451
|
+
properties: {
|
|
1452
|
+
limit: {
|
|
1453
|
+
type: "integer",
|
|
1454
|
+
minimum: 1,
|
|
1455
|
+
maximum: 100,
|
|
1456
|
+
description: "最多返回多少条最近 run;默认 20,最大 100。"
|
|
1457
|
+
},
|
|
1458
|
+
state: {
|
|
1459
|
+
type: "string",
|
|
1460
|
+
enum: ["queued", "running", "paused", "completed", "failed", "canceled"],
|
|
1461
|
+
description: "可选,只返回某个状态。"
|
|
1462
|
+
},
|
|
1463
|
+
states: {
|
|
1464
|
+
type: "array",
|
|
1465
|
+
items: {
|
|
1466
|
+
type: "string",
|
|
1467
|
+
enum: ["queued", "running", "paused", "completed", "failed", "canceled"]
|
|
1468
|
+
},
|
|
1469
|
+
description: "可选,只返回这些状态;与 state 同时传时取并集。"
|
|
1470
|
+
}
|
|
1471
|
+
},
|
|
1472
|
+
additionalProperties: false
|
|
1473
|
+
}
|
|
1474
|
+
},
|
|
1475
|
+
{
|
|
1476
|
+
name: TOOL_CANCEL_RUN,
|
|
1477
|
+
description: "取消指定 run_id 的运行中流水线。",
|
|
1478
|
+
inputSchema: {
|
|
1479
|
+
type: "object",
|
|
1480
|
+
properties: {
|
|
1481
|
+
run_id: { type: "string" }
|
|
1482
|
+
},
|
|
1483
|
+
required: ["run_id"],
|
|
1484
|
+
additionalProperties: false
|
|
1485
|
+
}
|
|
1486
|
+
},
|
|
1487
|
+
{
|
|
1488
|
+
name: TOOL_PAUSE_RUN,
|
|
1489
|
+
description: "请求暂停指定 run_id 的流水线;会在当前候选人处理完成后进入 paused。",
|
|
1490
|
+
inputSchema: {
|
|
1491
|
+
type: "object",
|
|
1492
|
+
properties: {
|
|
1493
|
+
run_id: { type: "string" }
|
|
1494
|
+
},
|
|
1495
|
+
required: ["run_id"],
|
|
1496
|
+
additionalProperties: false
|
|
1497
|
+
}
|
|
1498
|
+
},
|
|
1499
|
+
{
|
|
1500
|
+
name: TOOL_RESUME_RUN,
|
|
1501
|
+
description: "继续指定 run_id 的 paused 流水线;沿用原 CSV 与 checkpoint 续跑。",
|
|
1502
|
+
inputSchema: {
|
|
1503
|
+
type: "object",
|
|
1504
|
+
properties: {
|
|
1505
|
+
run_id: { type: "string" }
|
|
1506
|
+
},
|
|
1507
|
+
required: ["run_id"],
|
|
1508
|
+
additionalProperties: false
|
|
1509
|
+
}
|
|
1510
|
+
},
|
|
1511
|
+
{
|
|
1512
|
+
name: TOOL_RUN_FEATURED_CALIBRATION,
|
|
1513
|
+
description: "手动执行精选页收藏按钮校准。执行前请先在 Boss 推荐页切换到精选 tab 并打开任意候选人详情页。",
|
|
1514
|
+
inputSchema: createRunFeaturedCalibrationInputSchema()
|
|
1515
|
+
},
|
|
1516
|
+
{
|
|
1517
|
+
name: TOOL_GET_FEATURED_CALIBRATION_STATUS,
|
|
1518
|
+
description: "查询精选页收藏校准文件与校准脚本可用性。",
|
|
1519
|
+
inputSchema: {
|
|
1520
|
+
type: "object",
|
|
1521
|
+
properties: {},
|
|
1522
|
+
additionalProperties: false
|
|
1523
|
+
}
|
|
1524
|
+
},
|
|
1525
|
+
{
|
|
1526
|
+
name: TOOL_RUN_RECOMMEND_SELF_HEAL,
|
|
1527
|
+
description: "手动运维自愈工具:扫描 Boss recommend 相关 selector / network 规则漂移,并在确认后应用高置信度修复。",
|
|
1528
|
+
inputSchema: createRunRecommendSelfHealInputSchema()
|
|
1529
|
+
},
|
|
1328
1530
|
{
|
|
1329
1531
|
name: TOOL_RUN_RECRUIT_PIPELINE,
|
|
1330
1532
|
description: "兼容 Boss recruit 入口:默认 async;sync 模式会等待终态。所有浏览器动作走共享 CDP-only recruit service。",
|
|
@@ -1779,6 +1981,12 @@ function markDetachedWorkerFailed(runId, error, options = {}) {
|
|
|
1779
1981
|
...errorToDetachedWorkerPayload(error, options.message),
|
|
1780
1982
|
...(options.code ? { code: options.code } : {})
|
|
1781
1983
|
};
|
|
1984
|
+
if (existing.control?.cancel_requested === true && isShutdownLikeError(errorPayload)) {
|
|
1985
|
+
return finalizeRawRunStateAsCanceled(normalizedRunId, existing, {
|
|
1986
|
+
errorPayload,
|
|
1987
|
+
message: "流水线已取消;detached worker 在取消收尾时关闭了浏览器连接。"
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1782
1990
|
const previousResult = existing.result && typeof existing.result === "object" ? existing.result : {};
|
|
1783
1991
|
const result = {
|
|
1784
1992
|
...previousResult,
|
|
@@ -1876,7 +2084,8 @@ function buildWorkerLaunchFailedPayload(message) {
|
|
|
1876
2084
|
|
|
1877
2085
|
function finalizeCanceledRun(runId, snapshot) {
|
|
1878
2086
|
const canceledResult = {
|
|
1879
|
-
status: "
|
|
2087
|
+
status: "CANCELED",
|
|
2088
|
+
completion_reason: "canceled_by_user",
|
|
1880
2089
|
error: {
|
|
1881
2090
|
code: "PIPELINE_CANCELED",
|
|
1882
2091
|
message: "流水线已取消。",
|
|
@@ -2047,16 +2256,25 @@ async function executeTrackedPipeline({
|
|
|
2047
2256
|
}
|
|
2048
2257
|
);
|
|
2049
2258
|
} catch (error) {
|
|
2050
|
-
const canceled = Boolean(signal?.aborted)
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2259
|
+
const canceled = Boolean(signal?.aborted)
|
|
2260
|
+
|| error?.code === "PIPELINE_ABORTED"
|
|
2261
|
+
|| (isRunCancelRequested(runId) && isShutdownLikeError(error));
|
|
2262
|
+
if (canceled) {
|
|
2263
|
+
const canceledResult = {
|
|
2264
|
+
status: "CANCELED",
|
|
2265
|
+
completion_reason: "canceled_by_user",
|
|
2266
|
+
error: {
|
|
2267
|
+
code: "PIPELINE_CANCELED",
|
|
2268
|
+
message: "流水线已取消。",
|
|
2269
|
+
retryable: true,
|
|
2270
|
+
shutdown_error: isShutdownLikeError(error)
|
|
2271
|
+
? {
|
|
2272
|
+
code: error?.code || "SHUTDOWN_ERROR",
|
|
2273
|
+
message: error?.message || String(error)
|
|
2274
|
+
}
|
|
2275
|
+
: undefined
|
|
2276
|
+
}
|
|
2277
|
+
};
|
|
2060
2278
|
safeUpdateRunState(runId, {
|
|
2061
2279
|
mode,
|
|
2062
2280
|
state: RUN_STATE_CANCELED,
|
|
@@ -2105,21 +2323,35 @@ async function executeTrackedPipeline({
|
|
|
2105
2323
|
};
|
|
2106
2324
|
}
|
|
2107
2325
|
|
|
2108
|
-
const
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2326
|
+
const failedAfterCancel = result?.status === "FAILED"
|
|
2327
|
+
&& isRunCancelRequested(runId)
|
|
2328
|
+
&& isShutdownLikeError(result?.error || result);
|
|
2329
|
+
const terminalState = failedAfterCancel
|
|
2330
|
+
? RUN_STATE_CANCELED
|
|
2331
|
+
: result?.status === "FAILED"
|
|
2332
|
+
? RUN_STATE_FAILED
|
|
2333
|
+
: result?.status === "PAUSED"
|
|
2334
|
+
? (isRunCancelRequested(runId) ? RUN_STATE_CANCELED : RUN_STATE_PAUSED)
|
|
2335
|
+
: RUN_STATE_COMPLETED;
|
|
2113
2336
|
const outputCsv = getOutputCsvFromResult(result) || resumeConfig.output_csv;
|
|
2114
2337
|
const checkpointPath = normalizeText(result?.result?.checkpoint_path || resumeConfig.checkpoint_path);
|
|
2115
|
-
const canceledError = terminalState === RUN_STATE_CANCELED
|
|
2116
|
-
? {
|
|
2117
|
-
code: "PIPELINE_CANCELED",
|
|
2118
|
-
message: "流水线已取消。",
|
|
2119
|
-
retryable: true
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2338
|
+
const canceledError = terminalState === RUN_STATE_CANCELED
|
|
2339
|
+
? {
|
|
2340
|
+
code: "PIPELINE_CANCELED",
|
|
2341
|
+
message: "流水线已取消。",
|
|
2342
|
+
retryable: true,
|
|
2343
|
+
shutdown_error: failedAfterCancel ? (result?.error || null) : undefined
|
|
2344
|
+
}
|
|
2345
|
+
: null;
|
|
2346
|
+
const finalResult = failedAfterCancel
|
|
2347
|
+
? {
|
|
2348
|
+
...(result || {}),
|
|
2349
|
+
status: "CANCELED",
|
|
2350
|
+
completion_reason: "canceled_by_user",
|
|
2351
|
+
error: canceledError
|
|
2352
|
+
}
|
|
2353
|
+
: result || null;
|
|
2354
|
+
safeUpdateRunState(runId, {
|
|
2123
2355
|
mode,
|
|
2124
2356
|
state: terminalState,
|
|
2125
2357
|
stage: runtimeCallbacks.getLastStage(),
|
|
@@ -2147,13 +2379,13 @@ async function executeTrackedPipeline({
|
|
|
2147
2379
|
: terminalState === RUN_STATE_CANCELED
|
|
2148
2380
|
? canceledError
|
|
2149
2381
|
: null,
|
|
2150
|
-
result:
|
|
2151
|
-
});
|
|
2152
|
-
return {
|
|
2153
|
-
result,
|
|
2154
|
-
lastStage: runtimeCallbacks.getLastStage(),
|
|
2155
|
-
state: terminalState
|
|
2156
|
-
};
|
|
2382
|
+
result: finalResult
|
|
2383
|
+
});
|
|
2384
|
+
return {
|
|
2385
|
+
result: finalResult,
|
|
2386
|
+
lastStage: runtimeCallbacks.getLastStage(),
|
|
2387
|
+
state: terminalState
|
|
2388
|
+
};
|
|
2157
2389
|
}
|
|
2158
2390
|
|
|
2159
2391
|
function initializeRunStateOrThrow(runId, mode, workspaceRoot, args, pid = process.pid) {
|
|
@@ -2659,7 +2891,7 @@ async function handleRequest(message, workspaceRoot) {
|
|
|
2659
2891
|
}
|
|
2660
2892
|
}
|
|
2661
2893
|
|
|
2662
|
-
if ([TOOL_BOSS_CHAT_PREPARE_RUN, TOOL_BOSS_CHAT_START_RUN].includes(toolName)) {
|
|
2894
|
+
if ([TOOL_BOSS_CHAT_LIST_JOBS, TOOL_BOSS_CHAT_PREPARE_RUN, TOOL_BOSS_CHAT_START_RUN].includes(toolName)) {
|
|
2663
2895
|
const inputError = validateBossChatStartArgs(args);
|
|
2664
2896
|
if (inputError) {
|
|
2665
2897
|
return createJsonRpcError(id, -32602, inputError);
|
|
@@ -2699,6 +2931,8 @@ async function handleRequest(message, workspaceRoot) {
|
|
|
2699
2931
|
payload = await handleStartRunTool({ workspaceRoot, args });
|
|
2700
2932
|
} else if (toolName === TOOL_GET_RUN) {
|
|
2701
2933
|
payload = handleGetRunTool(args);
|
|
2934
|
+
} else if (toolName === TOOL_LIST_RUNS) {
|
|
2935
|
+
payload = handleListRunsTool(args);
|
|
2702
2936
|
} else if (toolName === TOOL_CANCEL_RUN) {
|
|
2703
2937
|
payload = handleCancelRunTool(args);
|
|
2704
2938
|
} else if (toolName === TOOL_PAUSE_RUN) {
|
|
@@ -2713,6 +2947,8 @@ async function handleRequest(message, workspaceRoot) {
|
|
|
2713
2947
|
payload = await handleRunRecommendSelfHealTool({ workspaceRoot, args });
|
|
2714
2948
|
} else if (toolName === TOOL_BOSS_CHAT_HEALTH_CHECK) {
|
|
2715
2949
|
payload = await handleBossChatHealthCheckTool(workspaceRoot, args);
|
|
2950
|
+
} else if (toolName === TOOL_BOSS_CHAT_LIST_JOBS) {
|
|
2951
|
+
payload = await handleBossChatPrepareRunTool({ workspaceRoot, args });
|
|
2716
2952
|
} else if (toolName === TOOL_BOSS_CHAT_PREPARE_RUN) {
|
|
2717
2953
|
payload = await handleBossChatPrepareRunTool({ workspaceRoot, args });
|
|
2718
2954
|
} else if (toolName === TOOL_BOSS_CHAT_START_RUN) {
|