@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/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: "CDP-only 检查 Boss chat 页面、自愈 probes、共享 screening-config.json chat runtime 目录是否可用。",
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: "FAILED",
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) || error?.code === "PIPELINE_ABORTED";
2051
- if (canceled) {
2052
- const canceledResult = {
2053
- status: "FAILED",
2054
- error: {
2055
- code: "PIPELINE_CANCELED",
2056
- message: "流水线已取消。",
2057
- retryable: true
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 terminalState = result?.status === "FAILED"
2109
- ? RUN_STATE_FAILED
2110
- : result?.status === "PAUSED"
2111
- ? (isRunCancelRequested(runId) ? RUN_STATE_CANCELED : RUN_STATE_PAUSED)
2112
- : RUN_STATE_COMPLETED;
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
- : null;
2122
- safeUpdateRunState(runId, {
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: result || null
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) {