@reconcrap/boss-recommend-mcp 2.1.21 → 2.1.22
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 +5 -2
- package/bin/boss-recommend-mcp.js +4 -4
- package/config/screening-config.example.json +33 -33
- package/package.json +8 -8
- package/scripts/install-macos.sh +280 -280
- package/scripts/postinstall.cjs +44 -44
- package/skills/boss-chat/README.md +42 -42
- package/skills/boss-chat/SKILL.md +106 -106
- package/skills/boss-recommend-pipeline/README.md +13 -13
- package/skills/boss-recommend-pipeline/SKILL.md +219 -214
- package/skills/boss-recruit-pipeline/README.md +19 -19
- package/skills/boss-recruit-pipeline/SKILL.md +89 -89
- package/src/chat-mcp.js +127 -127
- package/src/chat-runtime-config.js +775 -775
- package/src/cli.js +573 -573
- package/src/core/boss-cards/index.js +199 -199
- package/src/core/browser/index.js +2419 -2415
- package/src/core/capture/index.js +1201 -1201
- package/src/core/cv-acquisition/index.js +238 -238
- package/src/core/cv-capture-target/index.js +299 -299
- package/src/core/greet-quota/index.js +71 -71
- package/src/core/infinite-list/index.js +1326 -1326
- package/src/core/reporting/legacy-csv.js +334 -332
- package/src/core/run/index.js +32 -32
- package/src/core/run/timing.js +33 -33
- package/src/core/screening/index.js +2135 -2135
- package/src/core/self-heal/index.js +973 -973
- package/src/core/self-heal/viewport.js +564 -564
- package/src/detached-worker.js +99 -99
- package/src/domains/chat/cards.js +137 -137
- package/src/domains/chat/constants.js +9 -9
- package/src/domains/chat/detail.js +113 -113
- package/src/domains/chat/index.js +7 -7
- package/src/domains/chat/jobs.js +620 -620
- package/src/domains/chat/page-guard.js +122 -122
- package/src/domains/chat/roots.js +56 -56
- package/src/domains/chat/run-service.js +571 -571
- package/src/domains/common/account-rights-panel.js +314 -314
- package/src/domains/common/recovery-settle.js +159 -159
- package/src/domains/recommend/actions.js +472 -472
- package/src/domains/recommend/cards.js +243 -243
- package/src/domains/recommend/colleague-contact.js +333 -333
- package/src/domains/recommend/constants.js +228 -159
- package/src/domains/recommend/detail.js +650 -650
- package/src/domains/recommend/filters.js +748 -377
- package/src/domains/recommend/index.js +4 -3
- package/src/domains/recommend/jobs.js +542 -542
- package/src/domains/recommend/location.js +736 -0
- package/src/domains/recommend/refresh.js +504 -361
- package/src/domains/recommend/roots.js +80 -80
- package/src/domains/recommend/run-service.js +987 -854
- package/src/domains/recommend/scopes.js +246 -246
- package/src/domains/recruit/actions.js +277 -277
- package/src/domains/recruit/cards.js +74 -74
- package/src/domains/recruit/constants.js +236 -236
- package/src/domains/recruit/detail.js +588 -588
- package/src/domains/recruit/index.js +9 -9
- package/src/domains/recruit/instruction-parser.js +866 -866
- package/src/domains/recruit/refresh.js +45 -45
- package/src/domains/recruit/roots.js +68 -68
- package/src/domains/recruit/run-service.js +1620 -1620
- package/src/domains/recruit/search.js +3229 -3229
- package/src/index.js +13 -0
- package/src/parser.js +376 -8
- package/src/recommend-mcp.js +929 -915
- package/src/recommend-scheduler.js +496 -496
- package/src/recruit-mcp.js +2121 -2121
package/src/recommend-mcp.js
CHANGED
|
@@ -44,21 +44,21 @@ import {
|
|
|
44
44
|
parseRecommendInstruction
|
|
45
45
|
} from "./parser.js";
|
|
46
46
|
import { getRunsDir } from "./run-state.js";
|
|
47
|
-
import {
|
|
48
|
-
resolveBossConfiguredOutputDir,
|
|
49
|
-
resolveHumanBehaviorForRun,
|
|
50
|
-
resolveBossScreeningConfig
|
|
51
|
-
} from "./chat-runtime-config.js";
|
|
47
|
+
import {
|
|
48
|
+
resolveBossConfiguredOutputDir,
|
|
49
|
+
resolveHumanBehaviorForRun,
|
|
50
|
+
resolveBossScreeningConfig
|
|
51
|
+
} from "./chat-runtime-config.js";
|
|
52
52
|
import { DEFAULT_MAX_IMAGE_PAGES } from "./core/cv-acquisition/index.js";
|
|
53
53
|
|
|
54
54
|
const DEFAULT_RECOMMEND_HOST = "127.0.0.1";
|
|
55
|
-
const DEFAULT_RECOMMEND_PORT = 9222;
|
|
56
|
-
const DEFAULT_RECOMMEND_POLL_AFTER_SEC = 10;
|
|
57
|
-
const STATUS_METHOD_LOG_TAIL_LIMIT = 25;
|
|
58
|
-
const TARGET_COUNT_SEMANTICS = "target_count means candidates that pass screening; scan continues until that many candidates pass or the list ends";
|
|
59
|
-
const RUN_MODE_ASYNC = "async";
|
|
60
|
-
const REST_LEVEL_OPTIONS = ["low", "medium", "high"];
|
|
61
|
-
const REST_LEVEL_SET = new Set(REST_LEVEL_OPTIONS);
|
|
55
|
+
const DEFAULT_RECOMMEND_PORT = 9222;
|
|
56
|
+
const DEFAULT_RECOMMEND_POLL_AFTER_SEC = 10;
|
|
57
|
+
const STATUS_METHOD_LOG_TAIL_LIMIT = 25;
|
|
58
|
+
const TARGET_COUNT_SEMANTICS = "target_count means candidates that pass screening; scan continues until that many candidates pass or the list ends";
|
|
59
|
+
const RUN_MODE_ASYNC = "async";
|
|
60
|
+
const REST_LEVEL_OPTIONS = ["low", "medium", "high"];
|
|
61
|
+
const REST_LEVEL_SET = new Set(REST_LEVEL_OPTIONS);
|
|
62
62
|
|
|
63
63
|
const TERMINAL_STATUSES = new Set([
|
|
64
64
|
RUN_STATUS_COMPLETED,
|
|
@@ -76,188 +76,188 @@ let recommendRunService = createRecommendRunService({
|
|
|
76
76
|
});
|
|
77
77
|
const recommendRunMeta = new Map();
|
|
78
78
|
|
|
79
|
-
function normalizeText(value) {
|
|
80
|
-
return String(value || "").replace(/\s+/g, " ").trim();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function collectRecommendRouteText(args = {}) {
|
|
84
|
-
return normalizeText([
|
|
85
|
-
args.instruction,
|
|
86
|
-
args.criteria,
|
|
87
|
-
args.target_url_includes,
|
|
88
|
-
args.page_scope,
|
|
89
|
-
args.confirmation?.page_value,
|
|
90
|
-
args.confirmation?.job_value,
|
|
91
|
-
args.overrides?.criteria,
|
|
92
|
-
args.overrides?.page_scope,
|
|
93
|
-
args.overrides?.job,
|
|
94
|
-
args.overrides?.target_url_includes
|
|
95
|
-
].filter((value) => value !== undefined && value !== null).join(" "));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function findRouteSignals(text, patterns = []) {
|
|
99
|
-
const signals = [];
|
|
100
|
-
for (const { label, pattern } of patterns) {
|
|
101
|
-
if (pattern.test(text)) signals.push(label);
|
|
102
|
-
}
|
|
103
|
-
return signals;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function detectExplicitNonRecommendScope(args = {}) {
|
|
107
|
-
const fields = [
|
|
108
|
-
["page_scope", args.page_scope],
|
|
109
|
-
["confirmation.page_value", args.confirmation?.page_value],
|
|
110
|
-
["overrides.page_scope", args.overrides?.page_scope],
|
|
111
|
-
["target_url_includes", args.target_url_includes],
|
|
112
|
-
["overrides.target_url_includes", args.overrides?.target_url_includes]
|
|
113
|
-
];
|
|
114
|
-
for (const [field, value] of fields) {
|
|
115
|
-
const normalized = normalizeText(value).toLowerCase();
|
|
116
|
-
if (!normalized) continue;
|
|
117
|
-
if (/\/web\/chat\/index|chat\/index/.test(normalized) || ["chat", "chat-page", "boss-chat"].includes(normalized)) {
|
|
118
|
-
return {
|
|
119
|
-
domain: "chat",
|
|
120
|
-
signals: [`${field}:chat`],
|
|
121
|
-
text: normalized
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
if (/\/web\/chat\/search|chat\/search/.test(normalized) || ["search", "search-page", "recruit", "recruit-page", "boss-recruit"].includes(normalized)) {
|
|
125
|
-
return {
|
|
126
|
-
domain: "search",
|
|
127
|
-
signals: [`${field}:search`],
|
|
128
|
-
text: normalized
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function detectRecruitShapedRecommendArgs(args = {}) {
|
|
136
|
-
const checks = [
|
|
137
|
-
["keyword", args.keyword],
|
|
138
|
-
["search_keyword", args.search_keyword],
|
|
139
|
-
["city", args.city],
|
|
140
|
-
["confirmation.keyword_value", args.confirmation?.keyword_value],
|
|
141
|
-
["confirmation.keyword_confirmed", args.confirmation?.keyword_confirmed],
|
|
142
|
-
["confirmation.search_params_confirmed", args.confirmation?.search_params_confirmed],
|
|
143
|
-
["overrides.keyword", args.overrides?.keyword],
|
|
144
|
-
["overrides.search_keyword", args.overrides?.search_keyword],
|
|
145
|
-
["overrides.city", args.overrides?.city],
|
|
146
|
-
["overrides.filter_recent_viewed", args.overrides?.filter_recent_viewed],
|
|
147
|
-
["overrides.schools", args.overrides?.schools],
|
|
148
|
-
["overrides.school_tags", args.overrides?.school_tags]
|
|
149
|
-
];
|
|
150
|
-
const signals = [];
|
|
151
|
-
for (const [field, value] of checks) {
|
|
152
|
-
if (value === undefined || value === null) continue;
|
|
153
|
-
if (Array.isArray(value) && value.length === 0) continue;
|
|
154
|
-
if (typeof value === "string" && !normalizeText(value)) continue;
|
|
155
|
-
signals.push(field);
|
|
156
|
-
}
|
|
157
|
-
if (!signals.length) return null;
|
|
158
|
-
return {
|
|
159
|
-
domain: "search",
|
|
160
|
-
signals: signals.map((field) => `${field}:recruit_arg`),
|
|
161
|
-
text: signals.join(" ")
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function detectNonRecommendRoute(args = {}) {
|
|
166
|
-
const explicitRoute = detectExplicitNonRecommendScope(args);
|
|
167
|
-
if (explicitRoute) return explicitRoute;
|
|
168
|
-
const recruitArgRoute = detectRecruitShapedRecommendArgs(args);
|
|
169
|
-
if (recruitArgRoute) return recruitArgRoute;
|
|
170
|
-
|
|
171
|
-
const text = collectRecommendRouteText(args);
|
|
172
|
-
if (!text) return null;
|
|
173
|
-
const chatSignals = findRouteSignals(text, [
|
|
174
|
-
{ label: "chat-only", pattern: /\bchat[-\s]?only\b/i },
|
|
175
|
-
{ label: "boss-chat", pattern: /\bboss[-\s]?chat\b/i },
|
|
176
|
-
{ label: "chat page", pattern: /\bchat\s+page\b/i },
|
|
177
|
-
{ label: "chat/index", pattern: /(?:\/web\/chat\/index|chat\/index)/i },
|
|
178
|
-
{ label: "聊天页", pattern: /聊天页/ },
|
|
179
|
-
{ label: "聊天列表", pattern: /聊天列表/ },
|
|
180
|
-
{ label: "未读", pattern: /未读/ },
|
|
181
|
-
{ label: "全部聊天", pattern: /全部聊天|所有聊天/ },
|
|
182
|
-
{ label: "求简历", pattern: /求简历|索要简历|要简历|在线简历/ }
|
|
183
|
-
]);
|
|
184
|
-
if (chatSignals.length) {
|
|
185
|
-
return {
|
|
186
|
-
domain: "chat",
|
|
187
|
-
signals: chatSignals,
|
|
188
|
-
text
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
const searchSignals = findRouteSignals(text, [
|
|
192
|
-
{ label: "search-only", pattern: /\bsearch[-\s]?only\b/i },
|
|
193
|
-
{ label: "search page", pattern: /\bsearch\s+page\b/i },
|
|
194
|
-
{ label: "search keyword", pattern: /搜索关键词|关键词|keyword/i },
|
|
195
|
-
{ label: "recruit pipeline", pattern: /\brecruit\s+pipeline\b/i },
|
|
196
|
-
{ label: "chat/search", pattern: /(?:\/web\/chat\/search|chat\/search)/i },
|
|
197
|
-
{ label: "搜索页", pattern: /搜索页/ },
|
|
198
|
-
{ label: "招聘搜索", pattern: /招聘搜索/ }
|
|
199
|
-
]);
|
|
200
|
-
if (searchSignals.length) {
|
|
201
|
-
return {
|
|
202
|
-
domain: "search",
|
|
203
|
-
signals: searchSignals,
|
|
204
|
-
text
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
return null;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function buildWrongRecommendRouteResponse(route) {
|
|
211
|
-
if (route?.domain === "chat") {
|
|
212
|
-
return {
|
|
213
|
-
status: "FAILED",
|
|
214
|
-
route_guard: true,
|
|
215
|
-
error: {
|
|
216
|
-
code: "WRONG_BOSS_TOOL_FOR_CHAT",
|
|
217
|
-
message: "This request is explicitly chat-only/chat-page work. In Trae-CN split toolsets, call boss-chat/boss_chat_health_check, then boss-chat/list_boss_chat_jobs or boss-chat/prepare_boss_chat_run, then boss-chat/start_boss_chat_run. Do not call boss-recommend tools.",
|
|
218
|
-
retryable: false
|
|
219
|
-
},
|
|
220
|
-
detected_domain: "chat",
|
|
221
|
-
detected_signals: route.signals || [],
|
|
222
|
-
wrong_server: "boss-recommend",
|
|
223
|
-
recommended_server: "boss-chat",
|
|
224
|
-
recommended_tool_sequence: [
|
|
225
|
-
"boss-chat/boss_chat_health_check",
|
|
226
|
-
"boss-chat/list_boss_chat_jobs",
|
|
227
|
-
"boss-chat/prepare_boss_chat_run",
|
|
228
|
-
"boss-chat/start_boss_chat_run"
|
|
229
|
-
]
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
if (route?.domain === "search") {
|
|
233
|
-
return {
|
|
234
|
-
status: "FAILED",
|
|
235
|
-
route_guard: true,
|
|
236
|
-
error: {
|
|
237
|
-
code: "WRONG_BOSS_TOOL_FOR_SEARCH",
|
|
238
|
-
message: "This request is explicitly search/recruit-page work. In Trae-CN split toolsets, call boss-recruit/run_recruit_pipeline or boss-recruit/start_recruit_pipeline_run. Do not call boss-recommend/run_recommend or boss-recommend/start_recommend_pipeline_run.",
|
|
239
|
-
retryable: false
|
|
240
|
-
},
|
|
241
|
-
detected_domain: "search",
|
|
242
|
-
detected_signals: route.signals || [],
|
|
243
|
-
wrong_server: "boss-recommend",
|
|
244
|
-
recommended_server: "boss-recruit",
|
|
245
|
-
recommended_tool_sequence: [
|
|
246
|
-
"boss-recruit/run_recruit_pipeline",
|
|
247
|
-
"boss-recruit/start_recruit_pipeline_run"
|
|
248
|
-
]
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
return null;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function guardRecommendRoute(args = {}) {
|
|
255
|
-
return buildWrongRecommendRouteResponse(detectNonRecommendRoute(args));
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function parsePositiveInteger(raw, fallback) {
|
|
259
|
-
const parsed = Number.parseInt(String(raw || ""), 10);
|
|
260
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
79
|
+
function normalizeText(value) {
|
|
80
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function collectRecommendRouteText(args = {}) {
|
|
84
|
+
return normalizeText([
|
|
85
|
+
args.instruction,
|
|
86
|
+
args.criteria,
|
|
87
|
+
args.target_url_includes,
|
|
88
|
+
args.page_scope,
|
|
89
|
+
args.confirmation?.page_value,
|
|
90
|
+
args.confirmation?.job_value,
|
|
91
|
+
args.overrides?.criteria,
|
|
92
|
+
args.overrides?.page_scope,
|
|
93
|
+
args.overrides?.job,
|
|
94
|
+
args.overrides?.target_url_includes
|
|
95
|
+
].filter((value) => value !== undefined && value !== null).join(" "));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function findRouteSignals(text, patterns = []) {
|
|
99
|
+
const signals = [];
|
|
100
|
+
for (const { label, pattern } of patterns) {
|
|
101
|
+
if (pattern.test(text)) signals.push(label);
|
|
102
|
+
}
|
|
103
|
+
return signals;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function detectExplicitNonRecommendScope(args = {}) {
|
|
107
|
+
const fields = [
|
|
108
|
+
["page_scope", args.page_scope],
|
|
109
|
+
["confirmation.page_value", args.confirmation?.page_value],
|
|
110
|
+
["overrides.page_scope", args.overrides?.page_scope],
|
|
111
|
+
["target_url_includes", args.target_url_includes],
|
|
112
|
+
["overrides.target_url_includes", args.overrides?.target_url_includes]
|
|
113
|
+
];
|
|
114
|
+
for (const [field, value] of fields) {
|
|
115
|
+
const normalized = normalizeText(value).toLowerCase();
|
|
116
|
+
if (!normalized) continue;
|
|
117
|
+
if (/\/web\/chat\/index|chat\/index/.test(normalized) || ["chat", "chat-page", "boss-chat"].includes(normalized)) {
|
|
118
|
+
return {
|
|
119
|
+
domain: "chat",
|
|
120
|
+
signals: [`${field}:chat`],
|
|
121
|
+
text: normalized
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (/\/web\/chat\/search|chat\/search/.test(normalized) || ["search", "search-page", "recruit", "recruit-page", "boss-recruit"].includes(normalized)) {
|
|
125
|
+
return {
|
|
126
|
+
domain: "search",
|
|
127
|
+
signals: [`${field}:search`],
|
|
128
|
+
text: normalized
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function detectRecruitShapedRecommendArgs(args = {}) {
|
|
136
|
+
const checks = [
|
|
137
|
+
["keyword", args.keyword],
|
|
138
|
+
["search_keyword", args.search_keyword],
|
|
139
|
+
["city", args.city],
|
|
140
|
+
["confirmation.keyword_value", args.confirmation?.keyword_value],
|
|
141
|
+
["confirmation.keyword_confirmed", args.confirmation?.keyword_confirmed],
|
|
142
|
+
["confirmation.search_params_confirmed", args.confirmation?.search_params_confirmed],
|
|
143
|
+
["overrides.keyword", args.overrides?.keyword],
|
|
144
|
+
["overrides.search_keyword", args.overrides?.search_keyword],
|
|
145
|
+
["overrides.city", args.overrides?.city],
|
|
146
|
+
["overrides.filter_recent_viewed", args.overrides?.filter_recent_viewed],
|
|
147
|
+
["overrides.schools", args.overrides?.schools],
|
|
148
|
+
["overrides.school_tags", args.overrides?.school_tags]
|
|
149
|
+
];
|
|
150
|
+
const signals = [];
|
|
151
|
+
for (const [field, value] of checks) {
|
|
152
|
+
if (value === undefined || value === null) continue;
|
|
153
|
+
if (Array.isArray(value) && value.length === 0) continue;
|
|
154
|
+
if (typeof value === "string" && !normalizeText(value)) continue;
|
|
155
|
+
signals.push(field);
|
|
156
|
+
}
|
|
157
|
+
if (!signals.length) return null;
|
|
158
|
+
return {
|
|
159
|
+
domain: "search",
|
|
160
|
+
signals: signals.map((field) => `${field}:recruit_arg`),
|
|
161
|
+
text: signals.join(" ")
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function detectNonRecommendRoute(args = {}) {
|
|
166
|
+
const explicitRoute = detectExplicitNonRecommendScope(args);
|
|
167
|
+
if (explicitRoute) return explicitRoute;
|
|
168
|
+
const recruitArgRoute = detectRecruitShapedRecommendArgs(args);
|
|
169
|
+
if (recruitArgRoute) return recruitArgRoute;
|
|
170
|
+
|
|
171
|
+
const text = collectRecommendRouteText(args);
|
|
172
|
+
if (!text) return null;
|
|
173
|
+
const chatSignals = findRouteSignals(text, [
|
|
174
|
+
{ label: "chat-only", pattern: /\bchat[-\s]?only\b/i },
|
|
175
|
+
{ label: "boss-chat", pattern: /\bboss[-\s]?chat\b/i },
|
|
176
|
+
{ label: "chat page", pattern: /\bchat\s+page\b/i },
|
|
177
|
+
{ label: "chat/index", pattern: /(?:\/web\/chat\/index|chat\/index)/i },
|
|
178
|
+
{ label: "聊天页", pattern: /聊天页/ },
|
|
179
|
+
{ label: "聊天列表", pattern: /聊天列表/ },
|
|
180
|
+
{ label: "未读", pattern: /未读/ },
|
|
181
|
+
{ label: "全部聊天", pattern: /全部聊天|所有聊天/ },
|
|
182
|
+
{ label: "求简历", pattern: /求简历|索要简历|要简历|在线简历/ }
|
|
183
|
+
]);
|
|
184
|
+
if (chatSignals.length) {
|
|
185
|
+
return {
|
|
186
|
+
domain: "chat",
|
|
187
|
+
signals: chatSignals,
|
|
188
|
+
text
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const searchSignals = findRouteSignals(text, [
|
|
192
|
+
{ label: "search-only", pattern: /\bsearch[-\s]?only\b/i },
|
|
193
|
+
{ label: "search page", pattern: /\bsearch\s+page\b/i },
|
|
194
|
+
{ label: "search keyword", pattern: /搜索关键词|关键词|keyword/i },
|
|
195
|
+
{ label: "recruit pipeline", pattern: /\brecruit\s+pipeline\b/i },
|
|
196
|
+
{ label: "chat/search", pattern: /(?:\/web\/chat\/search|chat\/search)/i },
|
|
197
|
+
{ label: "搜索页", pattern: /搜索页/ },
|
|
198
|
+
{ label: "招聘搜索", pattern: /招聘搜索/ }
|
|
199
|
+
]);
|
|
200
|
+
if (searchSignals.length) {
|
|
201
|
+
return {
|
|
202
|
+
domain: "search",
|
|
203
|
+
signals: searchSignals,
|
|
204
|
+
text
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function buildWrongRecommendRouteResponse(route) {
|
|
211
|
+
if (route?.domain === "chat") {
|
|
212
|
+
return {
|
|
213
|
+
status: "FAILED",
|
|
214
|
+
route_guard: true,
|
|
215
|
+
error: {
|
|
216
|
+
code: "WRONG_BOSS_TOOL_FOR_CHAT",
|
|
217
|
+
message: "This request is explicitly chat-only/chat-page work. In Trae-CN split toolsets, call boss-chat/boss_chat_health_check, then boss-chat/list_boss_chat_jobs or boss-chat/prepare_boss_chat_run, then boss-chat/start_boss_chat_run. Do not call boss-recommend tools.",
|
|
218
|
+
retryable: false
|
|
219
|
+
},
|
|
220
|
+
detected_domain: "chat",
|
|
221
|
+
detected_signals: route.signals || [],
|
|
222
|
+
wrong_server: "boss-recommend",
|
|
223
|
+
recommended_server: "boss-chat",
|
|
224
|
+
recommended_tool_sequence: [
|
|
225
|
+
"boss-chat/boss_chat_health_check",
|
|
226
|
+
"boss-chat/list_boss_chat_jobs",
|
|
227
|
+
"boss-chat/prepare_boss_chat_run",
|
|
228
|
+
"boss-chat/start_boss_chat_run"
|
|
229
|
+
]
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
if (route?.domain === "search") {
|
|
233
|
+
return {
|
|
234
|
+
status: "FAILED",
|
|
235
|
+
route_guard: true,
|
|
236
|
+
error: {
|
|
237
|
+
code: "WRONG_BOSS_TOOL_FOR_SEARCH",
|
|
238
|
+
message: "This request is explicitly search/recruit-page work. In Trae-CN split toolsets, call boss-recruit/run_recruit_pipeline or boss-recruit/start_recruit_pipeline_run. Do not call boss-recommend/run_recommend or boss-recommend/start_recommend_pipeline_run.",
|
|
239
|
+
retryable: false
|
|
240
|
+
},
|
|
241
|
+
detected_domain: "search",
|
|
242
|
+
detected_signals: route.signals || [],
|
|
243
|
+
wrong_server: "boss-recommend",
|
|
244
|
+
recommended_server: "boss-recruit",
|
|
245
|
+
recommended_tool_sequence: [
|
|
246
|
+
"boss-recruit/run_recruit_pipeline",
|
|
247
|
+
"boss-recruit/start_recruit_pipeline_run"
|
|
248
|
+
]
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function guardRecommendRoute(args = {}) {
|
|
255
|
+
return buildWrongRecommendRouteResponse(detectNonRecommendRoute(args));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function parsePositiveInteger(raw, fallback) {
|
|
259
|
+
const parsed = Number.parseInt(String(raw || ""), 10);
|
|
260
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
function parseNonNegativeInteger(raw, fallback) {
|
|
@@ -303,23 +303,23 @@ function resolveRecommendDetailLimit(args = {}, normalized = {}) {
|
|
|
303
303
|
return requested;
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
function methodSummary(methodLog = []) {
|
|
306
|
+
function methodSummary(methodLog = []) {
|
|
307
307
|
const summary = {};
|
|
308
308
|
for (const entry of methodLog || []) {
|
|
309
309
|
summary[entry.method] = (summary[entry.method] || 0) + 1;
|
|
310
310
|
}
|
|
311
|
-
return summary;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function compactMethodLogForStatus(methodLog = []) {
|
|
315
|
-
if (!Array.isArray(methodLog)) return [];
|
|
316
|
-
return methodLog.slice(-STATUS_METHOD_LOG_TAIL_LIMIT).map((entry) => ({
|
|
317
|
-
method: normalizeText(entry?.method || entry || ""),
|
|
318
|
-
at: normalizeText(entry?.at || "")
|
|
319
|
-
}));
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function clonePlain(value, fallback = null) {
|
|
311
|
+
return summary;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function compactMethodLogForStatus(methodLog = []) {
|
|
315
|
+
if (!Array.isArray(methodLog)) return [];
|
|
316
|
+
return methodLog.slice(-STATUS_METHOD_LOG_TAIL_LIMIT).map((entry) => ({
|
|
317
|
+
method: normalizeText(entry?.method || entry || ""),
|
|
318
|
+
at: normalizeText(entry?.at || "")
|
|
319
|
+
}));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function clonePlain(value, fallback = null) {
|
|
323
323
|
try {
|
|
324
324
|
return value === undefined ? fallback : JSON.parse(JSON.stringify(value));
|
|
325
325
|
} catch {
|
|
@@ -382,10 +382,12 @@ function readJsonFile(filePath) {
|
|
|
382
382
|
|
|
383
383
|
function recommendSearchParamsForCsv(searchParams = {}) {
|
|
384
384
|
return {
|
|
385
|
-
school_tag: Object.prototype.hasOwnProperty.call(searchParams, "school_tag") ? searchParams.school_tag : "不限",
|
|
386
|
-
degree: Object.prototype.hasOwnProperty.call(searchParams, "degree") ? searchParams.degree : "不限",
|
|
387
|
-
gender: Object.prototype.hasOwnProperty.call(searchParams, "gender") ? searchParams.gender : "不限",
|
|
388
|
-
recent_not_view: Object.prototype.hasOwnProperty.call(searchParams, "recent_not_view") ? searchParams.recent_not_view : "不限"
|
|
385
|
+
school_tag: Object.prototype.hasOwnProperty.call(searchParams, "school_tag") ? searchParams.school_tag : "不限",
|
|
386
|
+
degree: Object.prototype.hasOwnProperty.call(searchParams, "degree") ? searchParams.degree : "不限",
|
|
387
|
+
gender: Object.prototype.hasOwnProperty.call(searchParams, "gender") ? searchParams.gender : "不限",
|
|
388
|
+
recent_not_view: Object.prototype.hasOwnProperty.call(searchParams, "recent_not_view") ? searchParams.recent_not_view : "不限",
|
|
389
|
+
current_city_only: Object.prototype.hasOwnProperty.call(searchParams, "current_city_only") ? searchParams.current_city_only : false,
|
|
390
|
+
activity_level: Object.prototype.hasOwnProperty.call(searchParams, "activity_level") ? searchParams.activity_level : "不限"
|
|
389
391
|
};
|
|
390
392
|
}
|
|
391
393
|
|
|
@@ -424,10 +426,12 @@ function selectedRecommendJobForCsv(meta = {}, snapshot = {}) {
|
|
|
424
426
|
function buildRecommendCsvInputRows(snapshot = {}, meta = {}) {
|
|
425
427
|
const { context, confirmation, overrides, followUp, shared } = getSnapshotRequestContext(snapshot);
|
|
426
428
|
const searchParams = recommendSearchParamsForCsv(meta.parsed?.searchParams || {
|
|
427
|
-
school_tag: overrides.school_tag ?? confirmation.school_tag_value,
|
|
428
|
-
degree: overrides.degree ?? confirmation.degree_value,
|
|
429
|
-
gender: overrides.gender ?? confirmation.gender_value,
|
|
430
|
-
recent_not_view: overrides.recent_not_view ?? confirmation.recent_not_view_value
|
|
429
|
+
school_tag: overrides.school_tag ?? confirmation.school_tag_value,
|
|
430
|
+
degree: overrides.degree ?? confirmation.degree_value,
|
|
431
|
+
gender: overrides.gender ?? confirmation.gender_value,
|
|
432
|
+
recent_not_view: overrides.recent_not_view ?? confirmation.recent_not_view_value,
|
|
433
|
+
current_city_only: overrides.current_city_only ?? false,
|
|
434
|
+
activity_level: overrides.activity_level ?? "不限"
|
|
431
435
|
});
|
|
432
436
|
const parsedScreenParams = meta.parsed?.screenParams || {};
|
|
433
437
|
const screenParams = {
|
|
@@ -486,7 +490,7 @@ function secondsBetween(startedAt, endedAt) {
|
|
|
486
490
|
return Math.max(1, Math.round((endMs - startMs) / 1000));
|
|
487
491
|
}
|
|
488
492
|
|
|
489
|
-
function normalizeLegacyProgress(progress = {}, summary = null) {
|
|
493
|
+
function normalizeLegacyProgress(progress = {}, summary = null) {
|
|
490
494
|
const processed = Number.isInteger(progress.processed)
|
|
491
495
|
? progress.processed
|
|
492
496
|
: Number.isInteger(summary?.processed)
|
|
@@ -511,146 +515,146 @@ function normalizeLegacyProgress(progress = {}, summary = null) {
|
|
|
511
515
|
skipped: Number.isInteger(progress.skipped) ? progress.skipped : Math.max(processed - passed, 0),
|
|
512
516
|
greet_count: Number.isInteger(progress.greet_count) ? progress.greet_count : 0,
|
|
513
517
|
post_action_clicked: Number.isInteger(progress.post_action_clicked) ? progress.post_action_clicked : 0
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
const STATUS_COUNT_KEYS = [
|
|
518
|
-
"processed",
|
|
519
|
-
"screened",
|
|
520
|
-
"detail_opened",
|
|
521
|
-
"passed",
|
|
522
|
-
"skipped",
|
|
523
|
-
"llm_screened",
|
|
524
|
-
"greet_count",
|
|
525
|
-
"post_action_clicked",
|
|
526
|
-
"image_capture_failed",
|
|
527
|
-
"detail_open_failed",
|
|
528
|
-
"transient_recovered",
|
|
529
|
-
"colleague_contact_checked",
|
|
530
|
-
"recent_colleague_contact_skipped",
|
|
531
|
-
"colleague_contact_panel_missing",
|
|
532
|
-
"context_recoveries",
|
|
533
|
-
"human_rest_count",
|
|
534
|
-
"human_rest_ms",
|
|
535
|
-
"card_count",
|
|
536
|
-
"refresh_rounds"
|
|
537
|
-
];
|
|
538
|
-
|
|
539
|
-
function compactPositiveInteger(value, fallback = null) {
|
|
540
|
-
return Number.isInteger(value) && value >= 0 ? value : fallback;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
function compactSmallRecord(value, fallback = null) {
|
|
544
|
-
const record = plainRecord(value);
|
|
545
|
-
if (!Object.keys(record).length) return fallback;
|
|
546
|
-
return clonePlain(record, fallback);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
function compactRecommendSummaryForStatus(summary) {
|
|
550
|
-
if (!summary || typeof summary !== "object" || Array.isArray(summary)) return null;
|
|
551
|
-
const compact = {
|
|
552
|
-
domain: normalizeText(summary.domain || "recommend") || "recommend"
|
|
553
|
-
};
|
|
554
|
-
for (const key of STATUS_COUNT_KEYS) {
|
|
555
|
-
if (Number.isInteger(summary[key])) compact[key] = summary[key];
|
|
556
|
-
}
|
|
557
|
-
if (summary.target_url) compact.target_url = normalizeText(summary.target_url);
|
|
558
|
-
if (summary.list_end_reason) compact.list_end_reason = normalizeText(summary.list_end_reason);
|
|
559
|
-
if (Array.isArray(summary.results)) {
|
|
560
|
-
compact.results_count = summary.results.length;
|
|
561
|
-
} else if (Number.isInteger(summary.results_count)) {
|
|
562
|
-
compact.results_count = summary.results_count;
|
|
563
|
-
}
|
|
564
|
-
if (Array.isArray(summary.refresh_attempts)) {
|
|
565
|
-
compact.refresh_attempt_count = summary.refresh_attempts.length;
|
|
566
|
-
}
|
|
567
|
-
if (Array.isArray(summary.context_recoveries)) {
|
|
568
|
-
compact.context_recovery_count = summary.context_recoveries.length;
|
|
569
|
-
} else if (Number.isInteger(summary.context_recoveries)) {
|
|
570
|
-
compact.context_recoveries = summary.context_recoveries;
|
|
571
|
-
}
|
|
572
|
-
if (summary.job_selection) compact.job_selection = compactSmallRecord(summary.job_selection);
|
|
573
|
-
if (summary.page_scope) compact.page_scope = compactSmallRecord(summary.page_scope);
|
|
574
|
-
if (summary.filter) compact.filter = compactSmallRecord(summary.filter);
|
|
575
|
-
if (summary.candidate_list) {
|
|
576
|
-
const candidateList = plainRecord(summary.candidate_list);
|
|
577
|
-
compact.candidate_list = {
|
|
578
|
-
total_count: compactPositiveInteger(candidateList.total_count ?? candidateList.card_count, null),
|
|
579
|
-
visible_count: compactPositiveInteger(candidateList.visible_count, null),
|
|
580
|
-
list_end_reason: normalizeText(candidateList.list_end_reason || "")
|
|
581
|
-
};
|
|
582
|
-
}
|
|
583
|
-
if (summary.viewport_health?.stats) {
|
|
584
|
-
compact.viewport_health = {
|
|
585
|
-
stats: clonePlain(summary.viewport_health.stats, {})
|
|
586
|
-
};
|
|
587
|
-
}
|
|
588
|
-
if (summary.human_behavior) {
|
|
589
|
-
compact.human_behavior = {
|
|
590
|
-
enabled: summary.human_behavior.enabled === true,
|
|
591
|
-
profile: normalizeText(summary.human_behavior.profile || ""),
|
|
592
|
-
restLevel: normalizeText(summary.human_behavior.restLevel || summary.human_behavior.rest_level || "")
|
|
593
|
-
};
|
|
594
|
-
}
|
|
595
|
-
if (summary.human_rest) {
|
|
596
|
-
const humanRest = plainRecord(summary.human_rest);
|
|
597
|
-
compact.human_rest = {
|
|
598
|
-
enabled: humanRest.enabled === true,
|
|
599
|
-
restLevel: normalizeText(humanRest.restLevel || humanRest.rest_level || ""),
|
|
600
|
-
rest_count: compactPositiveInteger(humanRest.rest_count ?? humanRest.count, null),
|
|
601
|
-
total_pause_ms: compactPositiveInteger(humanRest.total_pause_ms ?? humanRest.pause_ms, null)
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
return compact;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
function compactRecommendCheckpointForStatus(checkpoint) {
|
|
608
|
-
if (!checkpoint || typeof checkpoint !== "object" || Array.isArray(checkpoint)) return {};
|
|
609
|
-
const compact = {};
|
|
610
|
-
if (checkpoint.updatedAt || checkpoint.updated_at) {
|
|
611
|
-
compact.updatedAt = normalizeText(checkpoint.updatedAt || checkpoint.updated_at);
|
|
612
|
-
}
|
|
613
|
-
if (Array.isArray(checkpoint.results)) {
|
|
614
|
-
compact.results_count = checkpoint.results.length;
|
|
615
|
-
} else if (Number.isInteger(checkpoint.results_count)) {
|
|
616
|
-
compact.results_count = checkpoint.results_count;
|
|
617
|
-
}
|
|
618
|
-
for (const key of STATUS_COUNT_KEYS) {
|
|
619
|
-
if (Number.isInteger(checkpoint[key])) compact[key] = checkpoint[key];
|
|
620
|
-
}
|
|
621
|
-
return compact;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
function compactRecommendResultForStatus(result) {
|
|
625
|
-
if (!result || typeof result !== "object" || Array.isArray(result)) return result || null;
|
|
626
|
-
const compact = {
|
|
627
|
-
...result
|
|
628
|
-
};
|
|
629
|
-
if (Array.isArray(result.results)) {
|
|
630
|
-
compact.results_count = result.results.length;
|
|
631
|
-
compact.results_available = result.results.length > 0;
|
|
632
|
-
} else if (Number.isInteger(result.results_count)) {
|
|
633
|
-
compact.results_count = result.results_count;
|
|
634
|
-
compact.results_available = result.results_available === true || result.results_count > 0;
|
|
635
|
-
}
|
|
636
|
-
delete compact.results;
|
|
637
|
-
return compact;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
export function compactRecommendRunForStatus(run) {
|
|
641
|
-
if (!run || typeof run !== "object" || Array.isArray(run)) return run || null;
|
|
642
|
-
const compact = {
|
|
643
|
-
...run
|
|
644
|
-
};
|
|
645
|
-
if (compact.result) compact.result = compactRecommendResultForStatus(compact.result);
|
|
646
|
-
if (compact.summary) compact.summary = compactRecommendSummaryForStatus(compact.summary);
|
|
647
|
-
if (compact.checkpoint) compact.checkpoint = compactRecommendCheckpointForStatus(compact.checkpoint);
|
|
648
|
-
return compact;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
function completionReason(status) {
|
|
652
|
-
if (status === RUN_STATUS_COMPLETED) return "completed";
|
|
653
|
-
if (status === RUN_STATUS_CANCELED) return "canceled_by_user";
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const STATUS_COUNT_KEYS = [
|
|
522
|
+
"processed",
|
|
523
|
+
"screened",
|
|
524
|
+
"detail_opened",
|
|
525
|
+
"passed",
|
|
526
|
+
"skipped",
|
|
527
|
+
"llm_screened",
|
|
528
|
+
"greet_count",
|
|
529
|
+
"post_action_clicked",
|
|
530
|
+
"image_capture_failed",
|
|
531
|
+
"detail_open_failed",
|
|
532
|
+
"transient_recovered",
|
|
533
|
+
"colleague_contact_checked",
|
|
534
|
+
"recent_colleague_contact_skipped",
|
|
535
|
+
"colleague_contact_panel_missing",
|
|
536
|
+
"context_recoveries",
|
|
537
|
+
"human_rest_count",
|
|
538
|
+
"human_rest_ms",
|
|
539
|
+
"card_count",
|
|
540
|
+
"refresh_rounds"
|
|
541
|
+
];
|
|
542
|
+
|
|
543
|
+
function compactPositiveInteger(value, fallback = null) {
|
|
544
|
+
return Number.isInteger(value) && value >= 0 ? value : fallback;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function compactSmallRecord(value, fallback = null) {
|
|
548
|
+
const record = plainRecord(value);
|
|
549
|
+
if (!Object.keys(record).length) return fallback;
|
|
550
|
+
return clonePlain(record, fallback);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function compactRecommendSummaryForStatus(summary) {
|
|
554
|
+
if (!summary || typeof summary !== "object" || Array.isArray(summary)) return null;
|
|
555
|
+
const compact = {
|
|
556
|
+
domain: normalizeText(summary.domain || "recommend") || "recommend"
|
|
557
|
+
};
|
|
558
|
+
for (const key of STATUS_COUNT_KEYS) {
|
|
559
|
+
if (Number.isInteger(summary[key])) compact[key] = summary[key];
|
|
560
|
+
}
|
|
561
|
+
if (summary.target_url) compact.target_url = normalizeText(summary.target_url);
|
|
562
|
+
if (summary.list_end_reason) compact.list_end_reason = normalizeText(summary.list_end_reason);
|
|
563
|
+
if (Array.isArray(summary.results)) {
|
|
564
|
+
compact.results_count = summary.results.length;
|
|
565
|
+
} else if (Number.isInteger(summary.results_count)) {
|
|
566
|
+
compact.results_count = summary.results_count;
|
|
567
|
+
}
|
|
568
|
+
if (Array.isArray(summary.refresh_attempts)) {
|
|
569
|
+
compact.refresh_attempt_count = summary.refresh_attempts.length;
|
|
570
|
+
}
|
|
571
|
+
if (Array.isArray(summary.context_recoveries)) {
|
|
572
|
+
compact.context_recovery_count = summary.context_recoveries.length;
|
|
573
|
+
} else if (Number.isInteger(summary.context_recoveries)) {
|
|
574
|
+
compact.context_recoveries = summary.context_recoveries;
|
|
575
|
+
}
|
|
576
|
+
if (summary.job_selection) compact.job_selection = compactSmallRecord(summary.job_selection);
|
|
577
|
+
if (summary.page_scope) compact.page_scope = compactSmallRecord(summary.page_scope);
|
|
578
|
+
if (summary.filter) compact.filter = compactSmallRecord(summary.filter);
|
|
579
|
+
if (summary.candidate_list) {
|
|
580
|
+
const candidateList = plainRecord(summary.candidate_list);
|
|
581
|
+
compact.candidate_list = {
|
|
582
|
+
total_count: compactPositiveInteger(candidateList.total_count ?? candidateList.card_count, null),
|
|
583
|
+
visible_count: compactPositiveInteger(candidateList.visible_count, null),
|
|
584
|
+
list_end_reason: normalizeText(candidateList.list_end_reason || "")
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
if (summary.viewport_health?.stats) {
|
|
588
|
+
compact.viewport_health = {
|
|
589
|
+
stats: clonePlain(summary.viewport_health.stats, {})
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
if (summary.human_behavior) {
|
|
593
|
+
compact.human_behavior = {
|
|
594
|
+
enabled: summary.human_behavior.enabled === true,
|
|
595
|
+
profile: normalizeText(summary.human_behavior.profile || ""),
|
|
596
|
+
restLevel: normalizeText(summary.human_behavior.restLevel || summary.human_behavior.rest_level || "")
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
if (summary.human_rest) {
|
|
600
|
+
const humanRest = plainRecord(summary.human_rest);
|
|
601
|
+
compact.human_rest = {
|
|
602
|
+
enabled: humanRest.enabled === true,
|
|
603
|
+
restLevel: normalizeText(humanRest.restLevel || humanRest.rest_level || ""),
|
|
604
|
+
rest_count: compactPositiveInteger(humanRest.rest_count ?? humanRest.count, null),
|
|
605
|
+
total_pause_ms: compactPositiveInteger(humanRest.total_pause_ms ?? humanRest.pause_ms, null)
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
return compact;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function compactRecommendCheckpointForStatus(checkpoint) {
|
|
612
|
+
if (!checkpoint || typeof checkpoint !== "object" || Array.isArray(checkpoint)) return {};
|
|
613
|
+
const compact = {};
|
|
614
|
+
if (checkpoint.updatedAt || checkpoint.updated_at) {
|
|
615
|
+
compact.updatedAt = normalizeText(checkpoint.updatedAt || checkpoint.updated_at);
|
|
616
|
+
}
|
|
617
|
+
if (Array.isArray(checkpoint.results)) {
|
|
618
|
+
compact.results_count = checkpoint.results.length;
|
|
619
|
+
} else if (Number.isInteger(checkpoint.results_count)) {
|
|
620
|
+
compact.results_count = checkpoint.results_count;
|
|
621
|
+
}
|
|
622
|
+
for (const key of STATUS_COUNT_KEYS) {
|
|
623
|
+
if (Number.isInteger(checkpoint[key])) compact[key] = checkpoint[key];
|
|
624
|
+
}
|
|
625
|
+
return compact;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function compactRecommendResultForStatus(result) {
|
|
629
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) return result || null;
|
|
630
|
+
const compact = {
|
|
631
|
+
...result
|
|
632
|
+
};
|
|
633
|
+
if (Array.isArray(result.results)) {
|
|
634
|
+
compact.results_count = result.results.length;
|
|
635
|
+
compact.results_available = result.results.length > 0;
|
|
636
|
+
} else if (Number.isInteger(result.results_count)) {
|
|
637
|
+
compact.results_count = result.results_count;
|
|
638
|
+
compact.results_available = result.results_available === true || result.results_count > 0;
|
|
639
|
+
}
|
|
640
|
+
delete compact.results;
|
|
641
|
+
return compact;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
export function compactRecommendRunForStatus(run) {
|
|
645
|
+
if (!run || typeof run !== "object" || Array.isArray(run)) return run || null;
|
|
646
|
+
const compact = {
|
|
647
|
+
...run
|
|
648
|
+
};
|
|
649
|
+
if (compact.result) compact.result = compactRecommendResultForStatus(compact.result);
|
|
650
|
+
if (compact.summary) compact.summary = compactRecommendSummaryForStatus(compact.summary);
|
|
651
|
+
if (compact.checkpoint) compact.checkpoint = compactRecommendCheckpointForStatus(compact.checkpoint);
|
|
652
|
+
return compact;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function completionReason(status) {
|
|
656
|
+
if (status === RUN_STATUS_COMPLETED) return "completed";
|
|
657
|
+
if (status === RUN_STATUS_CANCELED) return "canceled_by_user";
|
|
654
658
|
if (status === RUN_STATUS_FAILED) return "failed";
|
|
655
659
|
if (status === RUN_STATUS_PAUSED) return "paused";
|
|
656
660
|
return null;
|
|
@@ -663,9 +667,9 @@ function normalizeErrorText(error = {}) {
|
|
|
663
667
|
].join(" "));
|
|
664
668
|
}
|
|
665
669
|
|
|
666
|
-
function classifyRecommendRecovery(error = {}) {
|
|
667
|
-
const text = normalizeErrorText(error);
|
|
668
|
-
if (!text) return null;
|
|
670
|
+
function classifyRecommendRecovery(error = {}) {
|
|
671
|
+
const text = normalizeErrorText(error);
|
|
672
|
+
if (!text) return null;
|
|
669
673
|
if (/BOSS_LOGIN_REQUIRED/i.test(text)) return "login_required";
|
|
670
674
|
if (/Could not find node with given id|No node with given id|Node is detached|Cannot find node|DETAIL_STALE_NODE|IMAGE_CAPTURE_STALE_NODE/i.test(text)) {
|
|
671
675
|
return "transient_stale_dom";
|
|
@@ -676,13 +680,13 @@ function classifyRecommendRecovery(error = {}) {
|
|
|
676
680
|
if (/(?:aborted|abort|timeout|timed out|fetch failed|socket|network|ECONNRESET|ETIMEDOUT|EAI_AGAIN)/i.test(text)) {
|
|
677
681
|
return "transient_network_or_llm";
|
|
678
682
|
}
|
|
679
|
-
return null;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
function isCancelShutdownError(error = {}) {
|
|
683
|
-
const text = normalizeErrorText(error);
|
|
684
|
-
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);
|
|
685
|
-
}
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function isCancelShutdownError(error = {}) {
|
|
687
|
+
const text = normalizeErrorText(error);
|
|
688
|
+
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);
|
|
689
|
+
}
|
|
686
690
|
|
|
687
691
|
function buildConstrainedAgentRecovery(snapshot = {}, meta = {}, artifacts = null) {
|
|
688
692
|
const error = snapshot?.error || snapshot?.result?.error || null;
|
|
@@ -828,14 +832,14 @@ function buildLegacyRecommendResult(snapshot) {
|
|
|
828
832
|
effective_scope: meta.normalized?.pageScope || meta.parsed?.page_scope || "recommend"
|
|
829
833
|
},
|
|
830
834
|
search_params: clonePlain(meta.parsed?.searchParams || {}, {}),
|
|
831
|
-
screen_params: clonePlain(meta.parsed?.screenParams || {}, {}),
|
|
832
|
-
target_count_semantics: TARGET_COUNT_SEMANTICS,
|
|
833
|
-
error: snapshot.error || null,
|
|
834
|
-
recovery: buildConstrainedAgentRecovery(snapshot, meta, artifacts),
|
|
835
|
-
results_count: resultRows.length,
|
|
836
|
-
results_available: resultRows.length > 0
|
|
837
|
-
};
|
|
838
|
-
}
|
|
835
|
+
screen_params: clonePlain(meta.parsed?.screenParams || {}, {}),
|
|
836
|
+
target_count_semantics: TARGET_COUNT_SEMANTICS,
|
|
837
|
+
error: snapshot.error || null,
|
|
838
|
+
recovery: buildConstrainedAgentRecovery(snapshot, meta, artifacts),
|
|
839
|
+
results_count: resultRows.length,
|
|
840
|
+
results_available: resultRows.length > 0
|
|
841
|
+
};
|
|
842
|
+
}
|
|
839
843
|
|
|
840
844
|
function normalizeRunSnapshot(snapshot) {
|
|
841
845
|
if (!snapshot) return null;
|
|
@@ -858,12 +862,12 @@ function normalizeRunSnapshot(snapshot) {
|
|
|
858
862
|
follow_up: clonePlain(metaArgs.follow_up ?? snapshotContext.follow_up ?? null, null),
|
|
859
863
|
target_count_semantics: TARGET_COUNT_SEMANTICS
|
|
860
864
|
};
|
|
861
|
-
return {
|
|
862
|
-
...snapshot,
|
|
863
|
-
checkpoint: compactRecommendCheckpointForStatus(snapshot.checkpoint),
|
|
864
|
-
summary: compactRecommendSummaryForStatus(summary),
|
|
865
|
-
progress,
|
|
866
|
-
run_id: snapshot.runId,
|
|
865
|
+
return {
|
|
866
|
+
...snapshot,
|
|
867
|
+
checkpoint: compactRecommendCheckpointForStatus(snapshot.checkpoint),
|
|
868
|
+
summary: compactRecommendSummaryForStatus(summary),
|
|
869
|
+
progress,
|
|
870
|
+
run_id: snapshot.runId,
|
|
867
871
|
mode: RUN_MODE_ASYNC,
|
|
868
872
|
state: snapshot.status,
|
|
869
873
|
stage: snapshot.phase,
|
|
@@ -900,31 +904,31 @@ function normalizeRunSnapshot(snapshot) {
|
|
|
900
904
|
};
|
|
901
905
|
}
|
|
902
906
|
|
|
903
|
-
function mergePersistedControlRequest(normalized, existing) {
|
|
904
|
-
const control = {
|
|
905
|
-
...(normalized?.control || {})
|
|
906
|
-
};
|
|
907
|
-
const existingControl = plainRecord(existing?.control);
|
|
908
|
-
if (!normalized) return control;
|
|
909
|
-
if (TERMINAL_STATUSES.has(normalized.state)) {
|
|
910
|
-
if (
|
|
911
|
-
normalized.state === RUN_STATUS_FAILED
|
|
912
|
-
&& existingControl.cancel_requested === true
|
|
913
|
-
&& isCancelShutdownError(normalized.error || normalized.result?.error || "")
|
|
914
|
-
) {
|
|
915
|
-
return {
|
|
916
|
-
...control,
|
|
917
|
-
pause_requested: true,
|
|
918
|
-
pause_requested_at: existingControl.pause_requested_at || control.pause_requested_at || new Date().toISOString(),
|
|
919
|
-
pause_requested_by: existingControl.pause_requested_by || control.pause_requested_by || "cancel_recommend_pipeline_run",
|
|
920
|
-
cancel_requested: true
|
|
921
|
-
};
|
|
922
|
-
}
|
|
923
|
-
return control;
|
|
924
|
-
}
|
|
925
|
-
if (existingControl.cancel_requested === true) {
|
|
926
|
-
return {
|
|
927
|
-
...control,
|
|
907
|
+
function mergePersistedControlRequest(normalized, existing) {
|
|
908
|
+
const control = {
|
|
909
|
+
...(normalized?.control || {})
|
|
910
|
+
};
|
|
911
|
+
const existingControl = plainRecord(existing?.control);
|
|
912
|
+
if (!normalized) return control;
|
|
913
|
+
if (TERMINAL_STATUSES.has(normalized.state)) {
|
|
914
|
+
if (
|
|
915
|
+
normalized.state === RUN_STATUS_FAILED
|
|
916
|
+
&& existingControl.cancel_requested === true
|
|
917
|
+
&& isCancelShutdownError(normalized.error || normalized.result?.error || "")
|
|
918
|
+
) {
|
|
919
|
+
return {
|
|
920
|
+
...control,
|
|
921
|
+
pause_requested: true,
|
|
922
|
+
pause_requested_at: existingControl.pause_requested_at || control.pause_requested_at || new Date().toISOString(),
|
|
923
|
+
pause_requested_by: existingControl.pause_requested_by || control.pause_requested_by || "cancel_recommend_pipeline_run",
|
|
924
|
+
cancel_requested: true
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
return control;
|
|
928
|
+
}
|
|
929
|
+
if (existingControl.cancel_requested === true) {
|
|
930
|
+
return {
|
|
931
|
+
...control,
|
|
928
932
|
pause_requested: true,
|
|
929
933
|
pause_requested_at: existingControl.pause_requested_at || control.pause_requested_at || new Date().toISOString(),
|
|
930
934
|
pause_requested_by: existingControl.pause_requested_by || control.pause_requested_by || "cancel_recommend_pipeline_run",
|
|
@@ -947,81 +951,81 @@ function mergePersistedControlRequest(normalized, existing) {
|
|
|
947
951
|
pause_requested_by: null,
|
|
948
952
|
cancel_requested: false
|
|
949
953
|
};
|
|
950
|
-
}
|
|
951
|
-
return control;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
function cancelErrorFromShutdown(shutdownError = null) {
|
|
955
|
-
return {
|
|
956
|
-
code: "PIPELINE_CANCELED",
|
|
957
|
-
message: "流水线已取消。",
|
|
958
|
-
retryable: true,
|
|
959
|
-
shutdown_error: shutdownError || undefined
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
function coerceCanceledTerminalSnapshot(normalized, existing) {
|
|
964
|
-
const existingControl = plainRecord(existing?.control);
|
|
965
|
-
const shutdownError = normalized?.error || normalized?.result?.error || null;
|
|
966
|
-
const shouldWrapCanceledShutdown = (
|
|
967
|
-
normalized
|
|
968
|
-
&& (
|
|
969
|
-
(
|
|
970
|
-
normalized.state === RUN_STATUS_FAILED
|
|
971
|
-
&& existingControl.cancel_requested === true
|
|
972
|
-
)
|
|
973
|
-
|| normalized.state === RUN_STATUS_CANCELED
|
|
974
|
-
)
|
|
975
|
-
&& isCancelShutdownError(shutdownError || "")
|
|
976
|
-
);
|
|
977
|
-
if (
|
|
978
|
-
!shouldWrapCanceledShutdown
|
|
979
|
-
) {
|
|
980
|
-
return normalized;
|
|
981
|
-
}
|
|
982
|
-
const canceledError = cancelErrorFromShutdown(shutdownError);
|
|
983
|
-
return {
|
|
984
|
-
...normalized,
|
|
985
|
-
state: RUN_STATUS_CANCELED,
|
|
986
|
-
status: RUN_STATUS_CANCELED,
|
|
987
|
-
last_message: "流水线已取消;取消收尾时浏览器连接已关闭。",
|
|
988
|
-
control: {
|
|
989
|
-
pause_requested: false,
|
|
990
|
-
pause_requested_at: null,
|
|
991
|
-
pause_requested_by: null,
|
|
992
|
-
cancel_requested: false
|
|
993
|
-
},
|
|
994
|
-
error: canceledError,
|
|
995
|
-
result: normalized.result ? {
|
|
996
|
-
...normalized.result,
|
|
997
|
-
status: "CANCELED",
|
|
998
|
-
completion_reason: "canceled_by_user",
|
|
999
|
-
error: canceledError
|
|
1000
|
-
} : {
|
|
1001
|
-
status: "CANCELED",
|
|
1002
|
-
completion_reason: "canceled_by_user",
|
|
1003
|
-
error: canceledError,
|
|
1004
|
-
run_id: normalized.run_id,
|
|
1005
|
-
processed_count: normalized.progress?.processed || 0,
|
|
1006
|
-
screened_count: normalized.progress?.screened || normalized.progress?.processed || 0,
|
|
1007
|
-
passed_count: normalized.progress?.passed || 0
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
function persistRecommendRunSnapshot(snapshot, {
|
|
1013
|
-
persistActiveCheckpoint = false
|
|
1014
|
-
} = {}) {
|
|
1015
|
-
let normalized = normalizeRunSnapshot(snapshot);
|
|
1016
|
-
if (!normalized?.run_id) return normalized;
|
|
1017
|
-
const artifacts = getRecommendRunArtifacts(normalized.run_id);
|
|
1018
|
-
if (!artifacts) return normalized;
|
|
1019
|
-
const existing = readJsonFile(artifacts.run_state_path);
|
|
1020
|
-
normalized.control = mergePersistedControlRequest(normalized, existing);
|
|
1021
|
-
normalized = coerceCanceledTerminalSnapshot(normalized, existing);
|
|
1022
|
-
if (persistActiveCheckpoint) {
|
|
1023
|
-
persistRecommendCheckpointSnapshot(snapshot);
|
|
1024
|
-
}
|
|
954
|
+
}
|
|
955
|
+
return control;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function cancelErrorFromShutdown(shutdownError = null) {
|
|
959
|
+
return {
|
|
960
|
+
code: "PIPELINE_CANCELED",
|
|
961
|
+
message: "流水线已取消。",
|
|
962
|
+
retryable: true,
|
|
963
|
+
shutdown_error: shutdownError || undefined
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function coerceCanceledTerminalSnapshot(normalized, existing) {
|
|
968
|
+
const existingControl = plainRecord(existing?.control);
|
|
969
|
+
const shutdownError = normalized?.error || normalized?.result?.error || null;
|
|
970
|
+
const shouldWrapCanceledShutdown = (
|
|
971
|
+
normalized
|
|
972
|
+
&& (
|
|
973
|
+
(
|
|
974
|
+
normalized.state === RUN_STATUS_FAILED
|
|
975
|
+
&& existingControl.cancel_requested === true
|
|
976
|
+
)
|
|
977
|
+
|| normalized.state === RUN_STATUS_CANCELED
|
|
978
|
+
)
|
|
979
|
+
&& isCancelShutdownError(shutdownError || "")
|
|
980
|
+
);
|
|
981
|
+
if (
|
|
982
|
+
!shouldWrapCanceledShutdown
|
|
983
|
+
) {
|
|
984
|
+
return normalized;
|
|
985
|
+
}
|
|
986
|
+
const canceledError = cancelErrorFromShutdown(shutdownError);
|
|
987
|
+
return {
|
|
988
|
+
...normalized,
|
|
989
|
+
state: RUN_STATUS_CANCELED,
|
|
990
|
+
status: RUN_STATUS_CANCELED,
|
|
991
|
+
last_message: "流水线已取消;取消收尾时浏览器连接已关闭。",
|
|
992
|
+
control: {
|
|
993
|
+
pause_requested: false,
|
|
994
|
+
pause_requested_at: null,
|
|
995
|
+
pause_requested_by: null,
|
|
996
|
+
cancel_requested: false
|
|
997
|
+
},
|
|
998
|
+
error: canceledError,
|
|
999
|
+
result: normalized.result ? {
|
|
1000
|
+
...normalized.result,
|
|
1001
|
+
status: "CANCELED",
|
|
1002
|
+
completion_reason: "canceled_by_user",
|
|
1003
|
+
error: canceledError
|
|
1004
|
+
} : {
|
|
1005
|
+
status: "CANCELED",
|
|
1006
|
+
completion_reason: "canceled_by_user",
|
|
1007
|
+
error: canceledError,
|
|
1008
|
+
run_id: normalized.run_id,
|
|
1009
|
+
processed_count: normalized.progress?.processed || 0,
|
|
1010
|
+
screened_count: normalized.progress?.screened || normalized.progress?.processed || 0,
|
|
1011
|
+
passed_count: normalized.progress?.passed || 0
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
function persistRecommendRunSnapshot(snapshot, {
|
|
1017
|
+
persistActiveCheckpoint = false
|
|
1018
|
+
} = {}) {
|
|
1019
|
+
let normalized = normalizeRunSnapshot(snapshot);
|
|
1020
|
+
if (!normalized?.run_id) return normalized;
|
|
1021
|
+
const artifacts = getRecommendRunArtifacts(normalized.run_id);
|
|
1022
|
+
if (!artifacts) return normalized;
|
|
1023
|
+
const existing = readJsonFile(artifacts.run_state_path);
|
|
1024
|
+
normalized.control = mergePersistedControlRequest(normalized, existing);
|
|
1025
|
+
normalized = coerceCanceledTerminalSnapshot(normalized, existing);
|
|
1026
|
+
if (persistActiveCheckpoint) {
|
|
1027
|
+
persistRecommendCheckpointSnapshot(snapshot);
|
|
1028
|
+
}
|
|
1025
1029
|
const payload = {
|
|
1026
1030
|
run_id: normalized.run_id,
|
|
1027
1031
|
mode: normalized.mode,
|
|
@@ -1044,59 +1048,59 @@ function persistRecommendRunSnapshot(snapshot, {
|
|
|
1044
1048
|
summary: normalized.summary,
|
|
1045
1049
|
artifacts: normalized.artifacts
|
|
1046
1050
|
};
|
|
1047
|
-
writeJsonAtomic(artifacts.run_state_path, payload);
|
|
1048
|
-
return normalized;
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
function patchPersistedRecommendRunControl(runId, controlPatch = {}, {
|
|
1052
|
-
message = ""
|
|
1053
|
-
} = {}) {
|
|
1054
|
-
const artifacts = getRecommendRunArtifacts(runId);
|
|
1055
|
-
if (!artifacts) return null;
|
|
1056
|
-
const current = readJsonFile(artifacts.run_state_path);
|
|
1057
|
-
const state = normalizeText(current?.state || current?.status || "");
|
|
1058
|
-
if (!current || TERMINAL_STATUSES.has(state)) return null;
|
|
1059
|
-
const now = new Date().toISOString();
|
|
1060
|
-
const patched = {
|
|
1061
|
-
...current,
|
|
1062
|
-
updated_at: now,
|
|
1063
|
-
heartbeat_at: current.heartbeat_at || now,
|
|
1064
|
-
last_message: message || current.last_message || "",
|
|
1065
|
-
control: {
|
|
1066
|
-
...(current.control || {}),
|
|
1067
|
-
...controlPatch
|
|
1068
|
-
}
|
|
1069
|
-
};
|
|
1070
|
-
writeJsonAtomic(artifacts.run_state_path, patched);
|
|
1071
|
-
return patched;
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
function reconcilePersistedRecommendRunIfNeeded(persisted) {
|
|
1075
|
-
if (!persisted || typeof persisted !== "object") return persisted;
|
|
1076
|
-
const persistedState = normalizeText(persisted.state || persisted.status);
|
|
1077
|
-
if (TERMINAL_STATUSES.has(persistedState)) return persisted;
|
|
1051
|
+
writeJsonAtomic(artifacts.run_state_path, payload);
|
|
1052
|
+
return normalized;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
function patchPersistedRecommendRunControl(runId, controlPatch = {}, {
|
|
1056
|
+
message = ""
|
|
1057
|
+
} = {}) {
|
|
1058
|
+
const artifacts = getRecommendRunArtifacts(runId);
|
|
1059
|
+
if (!artifacts) return null;
|
|
1060
|
+
const current = readJsonFile(artifacts.run_state_path);
|
|
1061
|
+
const state = normalizeText(current?.state || current?.status || "");
|
|
1062
|
+
if (!current || TERMINAL_STATUSES.has(state)) return null;
|
|
1063
|
+
const now = new Date().toISOString();
|
|
1064
|
+
const patched = {
|
|
1065
|
+
...current,
|
|
1066
|
+
updated_at: now,
|
|
1067
|
+
heartbeat_at: current.heartbeat_at || now,
|
|
1068
|
+
last_message: message || current.last_message || "",
|
|
1069
|
+
control: {
|
|
1070
|
+
...(current.control || {}),
|
|
1071
|
+
...controlPatch
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
writeJsonAtomic(artifacts.run_state_path, patched);
|
|
1075
|
+
return patched;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function reconcilePersistedRecommendRunIfNeeded(persisted) {
|
|
1079
|
+
if (!persisted || typeof persisted !== "object") return persisted;
|
|
1080
|
+
const persistedState = normalizeText(persisted.state || persisted.status);
|
|
1081
|
+
if (TERMINAL_STATUSES.has(persistedState)) return persisted;
|
|
1078
1082
|
if (isProcessAlive(persisted.pid)) return persisted;
|
|
1079
1083
|
|
|
1080
|
-
const runId = normalizeRunId(persisted.run_id || persisted.runId);
|
|
1081
|
-
const artifacts = getRecommendRunArtifacts(runId);
|
|
1082
|
-
const checkpoint = artifacts?.checkpoint_path ? readJsonFile(artifacts.checkpoint_path) : null;
|
|
1083
|
-
const now = new Date().toISOString();
|
|
1084
|
-
const cancelRequested = persisted.control?.cancel_requested === true;
|
|
1085
|
-
const processExitedError = {
|
|
1086
|
-
code: "RUN_PROCESS_EXITED",
|
|
1087
|
-
message: `检测到推荐任务进程已退出(pid=${persisted.pid || "unknown"})。`,
|
|
1088
|
-
retryable: true
|
|
1089
|
-
};
|
|
1090
|
-
const error = cancelRequested
|
|
1091
|
-
? cancelErrorFromShutdown(processExitedError)
|
|
1092
|
-
: {
|
|
1093
|
-
...processExitedError,
|
|
1094
|
-
message: `检测到推荐任务进程已退出(pid=${persisted.pid || "unknown"}),已自动标记为失败。`
|
|
1095
|
-
};
|
|
1096
|
-
return persistRecommendRunSnapshot({
|
|
1084
|
+
const runId = normalizeRunId(persisted.run_id || persisted.runId);
|
|
1085
|
+
const artifacts = getRecommendRunArtifacts(runId);
|
|
1086
|
+
const checkpoint = artifacts?.checkpoint_path ? readJsonFile(artifacts.checkpoint_path) : null;
|
|
1087
|
+
const now = new Date().toISOString();
|
|
1088
|
+
const cancelRequested = persisted.control?.cancel_requested === true;
|
|
1089
|
+
const processExitedError = {
|
|
1090
|
+
code: "RUN_PROCESS_EXITED",
|
|
1091
|
+
message: `检测到推荐任务进程已退出(pid=${persisted.pid || "unknown"})。`,
|
|
1092
|
+
retryable: true
|
|
1093
|
+
};
|
|
1094
|
+
const error = cancelRequested
|
|
1095
|
+
? cancelErrorFromShutdown(processExitedError)
|
|
1096
|
+
: {
|
|
1097
|
+
...processExitedError,
|
|
1098
|
+
message: `检测到推荐任务进程已退出(pid=${persisted.pid || "unknown"}),已自动标记为失败。`
|
|
1099
|
+
};
|
|
1100
|
+
return persistRecommendRunSnapshot({
|
|
1097
1101
|
runId,
|
|
1098
1102
|
name: persisted.name || runId,
|
|
1099
|
-
status: cancelRequested ? RUN_STATUS_CANCELED : RUN_STATUS_FAILED,
|
|
1103
|
+
status: cancelRequested ? RUN_STATUS_CANCELED : RUN_STATUS_FAILED,
|
|
1100
1104
|
phase: persisted.stage || persisted.phase || "recommend:orphaned",
|
|
1101
1105
|
progress: persisted.progress || {},
|
|
1102
1106
|
context: persisted.context || {},
|
|
@@ -1116,19 +1120,19 @@ function persistRecommendLifecycleSnapshot(snapshot, event = {}) {
|
|
|
1116
1120
|
});
|
|
1117
1121
|
}
|
|
1118
1122
|
|
|
1119
|
-
function attachMethodEvidence(payload, runId) {
|
|
1120
|
-
const meta = getRecommendRunMeta(runId);
|
|
1121
|
-
const methodLog = meta.methodLog || [];
|
|
1122
|
-
assertNoForbiddenCdpCalls(methodLog);
|
|
1123
|
-
return {
|
|
1124
|
-
...payload,
|
|
1125
|
-
runtime_evaluate_used: false,
|
|
1126
|
-
method_summary: methodSummary(methodLog),
|
|
1127
|
-
method_log: compactMethodLogForStatus(methodLog),
|
|
1128
|
-
method_log_total: Array.isArray(methodLog) ? methodLog.length : 0,
|
|
1129
|
-
chrome: meta.chrome || null
|
|
1130
|
-
};
|
|
1131
|
-
}
|
|
1123
|
+
function attachMethodEvidence(payload, runId) {
|
|
1124
|
+
const meta = getRecommendRunMeta(runId);
|
|
1125
|
+
const methodLog = meta.methodLog || [];
|
|
1126
|
+
assertNoForbiddenCdpCalls(methodLog);
|
|
1127
|
+
return {
|
|
1128
|
+
...payload,
|
|
1129
|
+
runtime_evaluate_used: false,
|
|
1130
|
+
method_summary: methodSummary(methodLog),
|
|
1131
|
+
method_log: compactMethodLogForStatus(methodLog),
|
|
1132
|
+
method_log_total: Array.isArray(methodLog) ? methodLog.length : 0,
|
|
1133
|
+
chrome: meta.chrome || null
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1132
1136
|
|
|
1133
1137
|
function compactRecommendJobListOption(option, index) {
|
|
1134
1138
|
const label = normalizeText(option?.label);
|
|
@@ -1178,17 +1182,17 @@ async function readRecommendJobOptionsFromSession(session) {
|
|
|
1178
1182
|
};
|
|
1179
1183
|
}
|
|
1180
1184
|
|
|
1181
|
-
export async function listRecommendJobsTool({ workspaceRoot = "", args = {} } = {}) {
|
|
1182
|
-
const routeGuard = guardRecommendRoute(args);
|
|
1183
|
-
if (routeGuard) {
|
|
1184
|
-
return {
|
|
1185
|
-
...routeGuard,
|
|
1186
|
-
stage: "recommend_job_list",
|
|
1187
|
-
message: "list_recommend_jobs is recommend-page only. For chat-only job lists, call list_boss_chat_jobs or prepare_boss_chat_run."
|
|
1188
|
-
};
|
|
1189
|
-
}
|
|
1190
|
-
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|
|
1191
|
-
const host = normalizeText(args.host) || DEFAULT_RECOMMEND_HOST;
|
|
1185
|
+
export async function listRecommendJobsTool({ workspaceRoot = "", args = {} } = {}) {
|
|
1186
|
+
const routeGuard = guardRecommendRoute(args);
|
|
1187
|
+
if (routeGuard) {
|
|
1188
|
+
return {
|
|
1189
|
+
...routeGuard,
|
|
1190
|
+
stage: "recommend_job_list",
|
|
1191
|
+
message: "list_recommend_jobs is recommend-page only. For chat-only job lists, call list_boss_chat_jobs or prepare_boss_chat_run."
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|
|
1195
|
+
const host = normalizeText(args.host) || DEFAULT_RECOMMEND_HOST;
|
|
1192
1196
|
const port = parsePositiveInteger(
|
|
1193
1197
|
args.port,
|
|
1194
1198
|
configResolution.ok ? configResolution.config.debugPort : DEFAULT_RECOMMEND_PORT
|
|
@@ -1462,146 +1466,146 @@ async function connectRecommendChromeSession({
|
|
|
1462
1466
|
};
|
|
1463
1467
|
}
|
|
1464
1468
|
|
|
1465
|
-
function parseRecommendPipelineRequest(args = {}) {
|
|
1466
|
-
return parseRecommendInstruction({
|
|
1467
|
-
instruction: args.instruction,
|
|
1469
|
+
function parseRecommendPipelineRequest(args = {}) {
|
|
1470
|
+
return parseRecommendInstruction({
|
|
1471
|
+
instruction: args.instruction,
|
|
1468
1472
|
confirmation: args.confirmation,
|
|
1469
1473
|
overrides: args.overrides
|
|
1470
|
-
});
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
function readOwn(source, keys = []) {
|
|
1474
|
-
if (!source || typeof source !== "object" || Array.isArray(source)) return undefined;
|
|
1475
|
-
for (const key of keys) {
|
|
1476
|
-
if (Object.prototype.hasOwnProperty.call(source, key)) return source[key];
|
|
1477
|
-
}
|
|
1478
|
-
return undefined;
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
function getExplicitRestLevel(args = {}) {
|
|
1482
|
-
const behavior = readOwn(args, ["human_behavior", "humanBehavior"]);
|
|
1483
|
-
const raw = readOwn(behavior, ["restLevel", "rest_level"]);
|
|
1484
|
-
const normalized = normalizeText(raw).toLowerCase();
|
|
1485
|
-
return {
|
|
1486
|
-
raw: raw ?? null,
|
|
1487
|
-
restLevel: REST_LEVEL_SET.has(normalized) ? normalized : null,
|
|
1488
|
-
valid: REST_LEVEL_SET.has(normalized),
|
|
1489
|
-
missing: raw === undefined || raw === null || normalizeText(raw) === ""
|
|
1490
|
-
};
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
function buildReviewScreenParams(parsed) {
|
|
1494
|
-
return {
|
|
1495
|
-
...(parsed.screenParams || {}),
|
|
1496
|
-
criteria: parsed.screenParams?.criteria || null,
|
|
1497
|
-
criteria_normalized: parsed.criteria_normalized || null,
|
|
1498
|
-
target_count: parsed.screenParams?.target_count ?? parsed.proposed_target_count ?? null,
|
|
1499
|
-
post_action: parsed.screenParams?.post_action || parsed.proposed_post_action || null,
|
|
1500
|
-
max_greet_count: parsed.screenParams?.max_greet_count ?? parsed.proposed_max_greet_count ?? null
|
|
1501
|
-
};
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1504
|
-
function buildReviewPageScope(parsed) {
|
|
1505
|
-
return parsed.page_scope || parsed.proposed_page_scope || "recommend";
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
function buildReviewJob(args = {}) {
|
|
1509
|
-
return normalizeText(args.confirmation?.job_value || args.overrides?.job || "") || null;
|
|
1510
|
-
}
|
|
1511
|
-
|
|
1512
|
-
function buildScheduleReview(args = {}) {
|
|
1513
|
-
const scheduleRunAt = normalizeText(args.schedule_run_at || args.scheduleRunAt || args.run_at || args.runAt);
|
|
1514
|
-
const scheduleDelayMinutes = args.schedule_delay_minutes ?? args.scheduleDelayMinutes;
|
|
1515
|
-
const scheduleDelaySeconds = args.schedule_delay_seconds ?? args.scheduleDelaySeconds;
|
|
1516
|
-
if (!scheduleRunAt && scheduleDelayMinutes === undefined && scheduleDelaySeconds === undefined) return null;
|
|
1517
|
-
return {
|
|
1518
|
-
schedule_run_at: scheduleRunAt || null,
|
|
1519
|
-
schedule_delay_minutes: scheduleDelayMinutes ?? null,
|
|
1520
|
-
schedule_delay_seconds: scheduleDelaySeconds ?? null
|
|
1521
|
-
};
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
function buildRequiredConfirmations(parsed, args = {}) {
|
|
1525
|
-
const required = [];
|
|
1526
|
-
if (parsed.needs_page_confirmation) required.push("page_scope");
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
function readOwn(source, keys = []) {
|
|
1478
|
+
if (!source || typeof source !== "object" || Array.isArray(source)) return undefined;
|
|
1479
|
+
for (const key of keys) {
|
|
1480
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) return source[key];
|
|
1481
|
+
}
|
|
1482
|
+
return undefined;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
function getExplicitRestLevel(args = {}) {
|
|
1486
|
+
const behavior = readOwn(args, ["human_behavior", "humanBehavior"]);
|
|
1487
|
+
const raw = readOwn(behavior, ["restLevel", "rest_level"]);
|
|
1488
|
+
const normalized = normalizeText(raw).toLowerCase();
|
|
1489
|
+
return {
|
|
1490
|
+
raw: raw ?? null,
|
|
1491
|
+
restLevel: REST_LEVEL_SET.has(normalized) ? normalized : null,
|
|
1492
|
+
valid: REST_LEVEL_SET.has(normalized),
|
|
1493
|
+
missing: raw === undefined || raw === null || normalizeText(raw) === ""
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
function buildReviewScreenParams(parsed) {
|
|
1498
|
+
return {
|
|
1499
|
+
...(parsed.screenParams || {}),
|
|
1500
|
+
criteria: parsed.screenParams?.criteria || null,
|
|
1501
|
+
criteria_normalized: parsed.criteria_normalized || null,
|
|
1502
|
+
target_count: parsed.screenParams?.target_count ?? parsed.proposed_target_count ?? null,
|
|
1503
|
+
post_action: parsed.screenParams?.post_action || parsed.proposed_post_action || null,
|
|
1504
|
+
max_greet_count: parsed.screenParams?.max_greet_count ?? parsed.proposed_max_greet_count ?? null
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
function buildReviewPageScope(parsed) {
|
|
1509
|
+
return parsed.page_scope || parsed.proposed_page_scope || "recommend";
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
function buildReviewJob(args = {}) {
|
|
1513
|
+
return normalizeText(args.confirmation?.job_value || args.overrides?.job || "") || null;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
function buildScheduleReview(args = {}) {
|
|
1517
|
+
const scheduleRunAt = normalizeText(args.schedule_run_at || args.scheduleRunAt || args.run_at || args.runAt);
|
|
1518
|
+
const scheduleDelayMinutes = args.schedule_delay_minutes ?? args.scheduleDelayMinutes;
|
|
1519
|
+
const scheduleDelaySeconds = args.schedule_delay_seconds ?? args.scheduleDelaySeconds;
|
|
1520
|
+
if (!scheduleRunAt && scheduleDelayMinutes === undefined && scheduleDelaySeconds === undefined) return null;
|
|
1521
|
+
return {
|
|
1522
|
+
schedule_run_at: scheduleRunAt || null,
|
|
1523
|
+
schedule_delay_minutes: scheduleDelayMinutes ?? null,
|
|
1524
|
+
schedule_delay_seconds: scheduleDelaySeconds ?? null
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
function buildRequiredConfirmations(parsed, args = {}) {
|
|
1529
|
+
const required = [];
|
|
1530
|
+
if (parsed.needs_page_confirmation) required.push("page_scope");
|
|
1527
1531
|
if (parsed.needs_filters_confirmation) required.push("filters");
|
|
1528
1532
|
if (parsed.needs_school_tag_confirmation) required.push("school_tag");
|
|
1529
1533
|
if (parsed.needs_degree_confirmation) required.push("degree");
|
|
1530
1534
|
if (parsed.needs_gender_confirmation) required.push("gender");
|
|
1531
1535
|
if (parsed.needs_recent_not_view_confirmation) required.push("recent_not_view");
|
|
1532
|
-
if (parsed.needs_criteria_confirmation) required.push("criteria");
|
|
1533
|
-
if (parsed.needs_target_count_confirmation) required.push("target_count");
|
|
1534
|
-
if (parsed.needs_post_action_confirmation) required.push("post_action");
|
|
1535
|
-
if (parsed.needs_skip_recent_colleague_contacted_confirmation) required.push("skip_recent_colleague_contacted");
|
|
1536
|
-
if ((parsed.suspicious_fields || []).length) required.push("suspicious_fields");
|
|
1537
|
-
|
|
1538
|
-
const confirmation = args.confirmation || {};
|
|
1539
|
-
const jobValue = normalizeText(confirmation.job_value || args.overrides?.job || "");
|
|
1540
|
-
if (!jobValue) required.push("job");
|
|
1541
|
-
const restLevel = getExplicitRestLevel(args);
|
|
1542
|
-
if (!restLevel.valid) required.push("rest_level");
|
|
1543
|
-
const blocksFinalReview = required.some((field) => field !== "rest_level");
|
|
1544
|
-
if (confirmation.final_confirmed !== true && !blocksFinalReview) required.push("final_review");
|
|
1545
|
-
return Array.from(new Set(required));
|
|
1546
|
-
}
|
|
1536
|
+
if (parsed.needs_criteria_confirmation) required.push("criteria");
|
|
1537
|
+
if (parsed.needs_target_count_confirmation) required.push("target_count");
|
|
1538
|
+
if (parsed.needs_post_action_confirmation) required.push("post_action");
|
|
1539
|
+
if (parsed.needs_skip_recent_colleague_contacted_confirmation) required.push("skip_recent_colleague_contacted");
|
|
1540
|
+
if ((parsed.suspicious_fields || []).length) required.push("suspicious_fields");
|
|
1547
1541
|
|
|
1548
|
-
|
|
1549
|
-
const
|
|
1542
|
+
const confirmation = args.confirmation || {};
|
|
1543
|
+
const jobValue = normalizeText(confirmation.job_value || args.overrides?.job || "");
|
|
1544
|
+
if (!jobValue) required.push("job");
|
|
1545
|
+
const restLevel = getExplicitRestLevel(args);
|
|
1546
|
+
if (!restLevel.valid) required.push("rest_level");
|
|
1547
|
+
const blocksFinalReview = required.some((field) => field !== "rest_level");
|
|
1548
|
+
if (confirmation.final_confirmed !== true && !blocksFinalReview) required.push("final_review");
|
|
1549
|
+
return Array.from(new Set(required));
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
function buildJobPendingQuestion(args = {}) {
|
|
1553
|
+
const value = normalizeText(args.confirmation?.job_value || args.overrides?.job || "");
|
|
1550
1554
|
return {
|
|
1551
1555
|
field: "job",
|
|
1552
1556
|
question: "请确认推荐页岗位。CDP-only rewrite 会先切换到该岗位,再按所选页面范围执行筛选。",
|
|
1553
1557
|
value: value || null
|
|
1554
|
-
};
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
function buildRestLevelPendingQuestion(args = {}) {
|
|
1558
|
-
const restLevel = getExplicitRestLevel(args);
|
|
1559
|
-
return {
|
|
1560
|
-
field: "rest_level",
|
|
1561
|
-
question: restLevel.missing
|
|
1562
|
-
? "请确认本次运行休息强度 rest_level。"
|
|
1563
|
-
: "rest_level 只能是 low / medium / high,请重新确认本次运行休息强度。",
|
|
1564
|
-
value: restLevel.restLevel || restLevel.raw || null,
|
|
1565
|
-
options: REST_LEVEL_OPTIONS.map((value) => ({
|
|
1566
|
-
label: value,
|
|
1567
|
-
value
|
|
1568
|
-
}))
|
|
1569
|
-
};
|
|
1570
|
-
}
|
|
1571
|
-
|
|
1572
|
-
function buildSuspiciousFieldsQuestion(parsed) {
|
|
1573
|
-
return {
|
|
1574
|
-
field: "suspicious_fields",
|
|
1575
|
-
question: "检测到需要修正或明确确认的异常字段,请先修正后再启动。",
|
|
1576
|
-
value: parsed.suspicious_fields || []
|
|
1577
|
-
};
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1580
|
-
function buildFinalReviewQuestion(parsed, args = {}) {
|
|
1581
|
-
const restLevel = getExplicitRestLevel(args);
|
|
1582
|
-
return {
|
|
1583
|
-
field: "final_review",
|
|
1584
|
-
question: "请最终确认本次推荐页筛选参数无误;确认后设置 final_confirmed=true 即可启动或创建定时任务。",
|
|
1585
|
-
value: {
|
|
1586
|
-
page_scope: buildReviewPageScope(parsed),
|
|
1587
|
-
job: buildReviewJob(args),
|
|
1588
|
-
search_params: parsed.searchParams,
|
|
1589
|
-
screen_params: buildReviewScreenParams(parsed),
|
|
1590
|
-
human_behavior: {
|
|
1591
|
-
restLevel: restLevel.restLevel || null
|
|
1592
|
-
},
|
|
1593
|
-
schedule: buildScheduleReview(args)
|
|
1594
|
-
}
|
|
1595
|
-
};
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
function buildNeedInputResponse(parsed, args = {}) {
|
|
1599
|
-
return {
|
|
1600
|
-
status: "NEED_INPUT",
|
|
1601
|
-
missing_fields: parsed.missing_fields,
|
|
1602
|
-
required_confirmations: buildRequiredConfirmations(parsed, args),
|
|
1603
|
-
search_params: parsed.searchParams,
|
|
1604
|
-
screen_params: parsed.screenParams,
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
function buildRestLevelPendingQuestion(args = {}) {
|
|
1562
|
+
const restLevel = getExplicitRestLevel(args);
|
|
1563
|
+
return {
|
|
1564
|
+
field: "rest_level",
|
|
1565
|
+
question: restLevel.missing
|
|
1566
|
+
? "请确认本次运行休息强度 rest_level。"
|
|
1567
|
+
: "rest_level 只能是 low / medium / high,请重新确认本次运行休息强度。",
|
|
1568
|
+
value: restLevel.restLevel || restLevel.raw || null,
|
|
1569
|
+
options: REST_LEVEL_OPTIONS.map((value) => ({
|
|
1570
|
+
label: value,
|
|
1571
|
+
value
|
|
1572
|
+
}))
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
function buildSuspiciousFieldsQuestion(parsed) {
|
|
1577
|
+
return {
|
|
1578
|
+
field: "suspicious_fields",
|
|
1579
|
+
question: "检测到需要修正或明确确认的异常字段,请先修正后再启动。",
|
|
1580
|
+
value: parsed.suspicious_fields || []
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
function buildFinalReviewQuestion(parsed, args = {}) {
|
|
1585
|
+
const restLevel = getExplicitRestLevel(args);
|
|
1586
|
+
return {
|
|
1587
|
+
field: "final_review",
|
|
1588
|
+
question: "请最终确认本次推荐页筛选参数无误;确认后设置 final_confirmed=true 即可启动或创建定时任务。",
|
|
1589
|
+
value: {
|
|
1590
|
+
page_scope: buildReviewPageScope(parsed),
|
|
1591
|
+
job: buildReviewJob(args),
|
|
1592
|
+
search_params: parsed.searchParams,
|
|
1593
|
+
screen_params: buildReviewScreenParams(parsed),
|
|
1594
|
+
human_behavior: {
|
|
1595
|
+
restLevel: restLevel.restLevel || null
|
|
1596
|
+
},
|
|
1597
|
+
schedule: buildScheduleReview(args)
|
|
1598
|
+
}
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
function buildNeedInputResponse(parsed, args = {}) {
|
|
1603
|
+
return {
|
|
1604
|
+
status: "NEED_INPUT",
|
|
1605
|
+
missing_fields: parsed.missing_fields,
|
|
1606
|
+
required_confirmations: buildRequiredConfirmations(parsed, args),
|
|
1607
|
+
search_params: parsed.searchParams,
|
|
1608
|
+
screen_params: parsed.screenParams,
|
|
1605
1609
|
pending_questions: parsed.pending_questions,
|
|
1606
1610
|
review: parsed.review,
|
|
1607
1611
|
error: {
|
|
@@ -1612,36 +1616,36 @@ function buildNeedInputResponse(parsed, args = {}) {
|
|
|
1612
1616
|
};
|
|
1613
1617
|
}
|
|
1614
1618
|
|
|
1615
|
-
function buildNeedConfirmationResponse(parsed, args, requiredConfirmations) {
|
|
1616
|
-
const pending = [...(parsed.pending_questions || [])];
|
|
1617
|
-
if (requiredConfirmations.includes("suspicious_fields") && !pending.some((item) => item.field === "suspicious_fields")) {
|
|
1618
|
-
pending.push(buildSuspiciousFieldsQuestion(parsed));
|
|
1619
|
-
}
|
|
1620
|
-
if (requiredConfirmations.includes("job") && !pending.some((item) => item.field === "job")) {
|
|
1621
|
-
pending.push(buildJobPendingQuestion(args));
|
|
1622
|
-
}
|
|
1623
|
-
if (requiredConfirmations.includes("rest_level") && !pending.some((item) => item.field === "rest_level")) {
|
|
1624
|
-
pending.push(buildRestLevelPendingQuestion(args));
|
|
1625
|
-
}
|
|
1626
|
-
if (requiredConfirmations.includes("final_review") && !pending.some((item) => item.field === "final_review")) {
|
|
1627
|
-
pending.push(buildFinalReviewQuestion(parsed, args));
|
|
1628
|
-
}
|
|
1629
|
-
return {
|
|
1630
|
-
status: "NEED_CONFIRMATION",
|
|
1631
|
-
required_confirmations: requiredConfirmations,
|
|
1632
|
-
page_scope: buildReviewPageScope(parsed),
|
|
1633
|
-
search_params: parsed.searchParams,
|
|
1634
|
-
screen_params: buildReviewScreenParams(parsed),
|
|
1635
|
-
pending_questions: pending,
|
|
1636
|
-
review: {
|
|
1637
|
-
...(parsed.review || {}),
|
|
1619
|
+
function buildNeedConfirmationResponse(parsed, args, requiredConfirmations) {
|
|
1620
|
+
const pending = [...(parsed.pending_questions || [])];
|
|
1621
|
+
if (requiredConfirmations.includes("suspicious_fields") && !pending.some((item) => item.field === "suspicious_fields")) {
|
|
1622
|
+
pending.push(buildSuspiciousFieldsQuestion(parsed));
|
|
1623
|
+
}
|
|
1624
|
+
if (requiredConfirmations.includes("job") && !pending.some((item) => item.field === "job")) {
|
|
1625
|
+
pending.push(buildJobPendingQuestion(args));
|
|
1626
|
+
}
|
|
1627
|
+
if (requiredConfirmations.includes("rest_level") && !pending.some((item) => item.field === "rest_level")) {
|
|
1628
|
+
pending.push(buildRestLevelPendingQuestion(args));
|
|
1629
|
+
}
|
|
1630
|
+
if (requiredConfirmations.includes("final_review") && !pending.some((item) => item.field === "final_review")) {
|
|
1631
|
+
pending.push(buildFinalReviewQuestion(parsed, args));
|
|
1632
|
+
}
|
|
1633
|
+
return {
|
|
1634
|
+
status: "NEED_CONFIRMATION",
|
|
1635
|
+
required_confirmations: requiredConfirmations,
|
|
1636
|
+
page_scope: buildReviewPageScope(parsed),
|
|
1637
|
+
search_params: parsed.searchParams,
|
|
1638
|
+
screen_params: buildReviewScreenParams(parsed),
|
|
1639
|
+
pending_questions: pending,
|
|
1640
|
+
review: {
|
|
1641
|
+
...(parsed.review || {}),
|
|
1638
1642
|
required_confirmations: requiredConfirmations
|
|
1639
1643
|
}
|
|
1640
1644
|
};
|
|
1641
1645
|
}
|
|
1642
|
-
|
|
1643
|
-
function evaluateRecommendPipelineGate(parsed, args = {}) {
|
|
1644
|
-
if (parsed.missing_fields?.length) return buildNeedInputResponse(parsed, args);
|
|
1646
|
+
|
|
1647
|
+
function evaluateRecommendPipelineGate(parsed, args = {}) {
|
|
1648
|
+
if (parsed.missing_fields?.length) return buildNeedInputResponse(parsed, args);
|
|
1645
1649
|
const requiredConfirmations = buildRequiredConfirmations(parsed, args);
|
|
1646
1650
|
if (requiredConfirmations.length) {
|
|
1647
1651
|
return buildNeedConfirmationResponse(parsed, args, requiredConfirmations);
|
|
@@ -1679,7 +1683,13 @@ function buildRecommendFilter(parsed, args = {}) {
|
|
|
1679
1683
|
return { enabled: false };
|
|
1680
1684
|
}
|
|
1681
1685
|
|
|
1682
|
-
const groups = [
|
|
1686
|
+
const groups = [{
|
|
1687
|
+
group: "activity",
|
|
1688
|
+
labels: [parsed.searchParams?.activity_level || "不限"],
|
|
1689
|
+
selectAllLabels: false,
|
|
1690
|
+
allowUnlimited: true,
|
|
1691
|
+
verifySticky: true
|
|
1692
|
+
}];
|
|
1683
1693
|
const recentNotView = withoutUnlimited(parsed.searchParams?.recent_not_view);
|
|
1684
1694
|
if (recentNotView.length) {
|
|
1685
1695
|
groups.push({
|
|
@@ -1716,8 +1726,12 @@ function buildRecommendFilter(parsed, args = {}) {
|
|
|
1716
1726
|
});
|
|
1717
1727
|
}
|
|
1718
1728
|
|
|
1719
|
-
return
|
|
1720
|
-
|
|
1729
|
+
return {
|
|
1730
|
+
enabled: true,
|
|
1731
|
+
currentCityOnly: parsed.searchParams?.current_city_only === true,
|
|
1732
|
+
filterGroups: groups
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1721
1735
|
|
|
1722
1736
|
function normalizeRecommendStartInput(args = {}, parsed, configResolution = null) {
|
|
1723
1737
|
const confirmation = args.confirmation || {};
|
|
@@ -1740,24 +1754,24 @@ function normalizeRecommendStartInput(args = {}, parsed, configResolution = null
|
|
|
1740
1754
|
targetCount,
|
|
1741
1755
|
job: normalizeText(confirmation.job_value || overrides.job || ""),
|
|
1742
1756
|
pageScope: parsed.page_scope || "recommend",
|
|
1743
|
-
filter: buildRecommendFilter(parsed, args),
|
|
1744
|
-
postAction: parsed.screenParams?.post_action || "none",
|
|
1745
|
-
maxGreetCount: Number.isInteger(parsed.screenParams?.max_greet_count)
|
|
1746
|
-
? parsed.screenParams.max_greet_count
|
|
1747
|
-
: null,
|
|
1748
|
-
skipRecentColleagueContacted: parsed.screenParams?.skip_recent_colleague_contacted !== false,
|
|
1749
|
-
colleagueContactWindowDays: 14,
|
|
1750
|
-
screeningMode: normalizeScreeningModeArg(args)
|
|
1751
|
-
};
|
|
1752
|
-
}
|
|
1757
|
+
filter: buildRecommendFilter(parsed, args),
|
|
1758
|
+
postAction: parsed.screenParams?.post_action || "none",
|
|
1759
|
+
maxGreetCount: Number.isInteger(parsed.screenParams?.max_greet_count)
|
|
1760
|
+
? parsed.screenParams.max_greet_count
|
|
1761
|
+
: null,
|
|
1762
|
+
skipRecentColleagueContacted: parsed.screenParams?.skip_recent_colleague_contacted !== false,
|
|
1763
|
+
colleagueContactWindowDays: 14,
|
|
1764
|
+
screeningMode: normalizeScreeningModeArg(args)
|
|
1765
|
+
};
|
|
1766
|
+
}
|
|
1753
1767
|
|
|
1754
|
-
function getRunOptions(args, parsed, normalized, session, configResolution = null) {
|
|
1755
|
-
const slowLive = args.slow_live === true;
|
|
1756
|
-
const executePostAction = args.dry_run_post_action === true
|
|
1757
|
-
? false
|
|
1758
|
-
: args.execute_post_action !== false;
|
|
1759
|
-
const humanBehavior = resolveHumanBehaviorForRun(args, configResolution?.config || {});
|
|
1760
|
-
return {
|
|
1768
|
+
function getRunOptions(args, parsed, normalized, session, configResolution = null) {
|
|
1769
|
+
const slowLive = args.slow_live === true;
|
|
1770
|
+
const executePostAction = args.dry_run_post_action === true
|
|
1771
|
+
? false
|
|
1772
|
+
: args.execute_post_action !== false;
|
|
1773
|
+
const humanBehavior = resolveHumanBehaviorForRun(args, configResolution?.config || {});
|
|
1774
|
+
return {
|
|
1761
1775
|
client: session.client,
|
|
1762
1776
|
targetUrl: RECOMMEND_TARGET_URL,
|
|
1763
1777
|
criteria: normalized.criteria,
|
|
@@ -1787,8 +1801,8 @@ function getRunOptions(args, parsed, normalized, session, configResolution = nul
|
|
|
1787
1801
|
executePostAction,
|
|
1788
1802
|
actionTimeoutMs: parsePositiveInteger(args.action_timeout_ms, slowLive ? 12000 : 8000),
|
|
1789
1803
|
actionIntervalMs: parsePositiveInteger(args.action_interval_ms, 500),
|
|
1790
|
-
actionAfterClickDelayMs: parseNonNegativeInteger(args.action_after_click_delay_ms, slowLive ? 1200 : 900),
|
|
1791
|
-
screeningMode: normalized.screeningMode,
|
|
1804
|
+
actionAfterClickDelayMs: parseNonNegativeInteger(args.action_after_click_delay_ms, slowLive ? 1200 : 900),
|
|
1805
|
+
screeningMode: normalized.screeningMode,
|
|
1792
1806
|
llmConfig: normalized.screeningMode === "llm" && configResolution?.ok ? {
|
|
1793
1807
|
...configResolution.config
|
|
1794
1808
|
} : null,
|
|
@@ -1800,23 +1814,23 @@ function getRunOptions(args, parsed, normalized, session, configResolution = nul
|
|
|
1800
1814
|
args.llm_image_limit,
|
|
1801
1815
|
parsePositiveInteger(configResolution?.config?.llmImageLimit || configResolution?.config?.imageLimit, 8)
|
|
1802
1816
|
),
|
|
1803
|
-
llmImageDetail: normalizeText(
|
|
1804
|
-
args.llm_image_detail || configResolution?.config?.llmImageDetail || configResolution?.config?.imageDetail
|
|
1805
|
-
) || "low",
|
|
1806
|
-
imageOutputDir: resolveBossConfiguredOutputDir("", getRunsDir()),
|
|
1807
|
-
humanRestEnabled: humanBehavior.restEnabled,
|
|
1808
|
-
humanBehavior,
|
|
1809
|
-
skipRecentColleagueContacted: normalized.skipRecentColleagueContacted,
|
|
1810
|
-
colleagueContactWindowDays: normalized.colleagueContactWindowDays,
|
|
1811
|
-
name: "mcp-recommend-pipeline-run",
|
|
1812
|
-
parsed
|
|
1813
|
-
};
|
|
1814
|
-
}
|
|
1817
|
+
llmImageDetail: normalizeText(
|
|
1818
|
+
args.llm_image_detail || configResolution?.config?.llmImageDetail || configResolution?.config?.imageDetail
|
|
1819
|
+
) || "low",
|
|
1820
|
+
imageOutputDir: resolveBossConfiguredOutputDir("", getRunsDir()),
|
|
1821
|
+
humanRestEnabled: humanBehavior.restEnabled,
|
|
1822
|
+
humanBehavior,
|
|
1823
|
+
skipRecentColleagueContacted: normalized.skipRecentColleagueContacted,
|
|
1824
|
+
colleagueContactWindowDays: normalized.colleagueContactWindowDays,
|
|
1825
|
+
name: "mcp-recommend-pipeline-run",
|
|
1826
|
+
parsed
|
|
1827
|
+
};
|
|
1828
|
+
}
|
|
1815
1829
|
|
|
1816
|
-
function prepareRecommendPipelineStart(args = {}, { workspaceRoot = "" } = {}) {
|
|
1817
|
-
const routeGuard = guardRecommendRoute(args);
|
|
1818
|
-
if (routeGuard) return { response: routeGuard };
|
|
1819
|
-
const parsed = parseRecommendPipelineRequest(args);
|
|
1830
|
+
function prepareRecommendPipelineStart(args = {}, { workspaceRoot = "" } = {}) {
|
|
1831
|
+
const routeGuard = guardRecommendRoute(args);
|
|
1832
|
+
if (routeGuard) return { response: routeGuard };
|
|
1833
|
+
const parsed = parseRecommendPipelineRequest(args);
|
|
1820
1834
|
const gate = evaluateRecommendPipelineGate(parsed, args);
|
|
1821
1835
|
if (gate) return { response: gate };
|
|
1822
1836
|
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|
|
@@ -2002,72 +2016,72 @@ async function startRecommendPipelineRunInternal(args = {}, { workspaceRoot = ""
|
|
|
2002
2016
|
};
|
|
2003
2017
|
}
|
|
2004
2018
|
|
|
2005
|
-
export function prepareRecommendPipelineRunTool({ workspaceRoot = "", args = {} } = {}) {
|
|
2006
|
-
const prepared = prepareRecommendPipelineStart(args, { workspaceRoot });
|
|
2007
|
-
if (prepared.response) {
|
|
2008
|
-
return {
|
|
2009
|
-
...prepared.response,
|
|
2010
|
-
cron_ready: false
|
|
2011
|
-
};
|
|
2012
|
-
}
|
|
2013
|
-
const { parsed, normalized } = prepared;
|
|
2014
|
-
const nextAction = {
|
|
2015
|
-
immediate_run: {
|
|
2016
|
-
recommended_next_tool: "start_recommend_pipeline_run",
|
|
2017
|
-
alternate_next_tool: "run_recommend",
|
|
2018
|
-
same_arguments: true,
|
|
2019
|
-
native_mcp_required: true
|
|
2020
|
-
},
|
|
2021
|
-
scheduled_run: {
|
|
2022
|
-
recommended_next_tool: "schedule_recommend_pipeline_run",
|
|
2023
|
-
same_arguments: true,
|
|
2024
|
-
requires_schedule_field: true
|
|
2025
|
-
},
|
|
2026
|
-
do_not_call_prepare_again: true,
|
|
2027
|
-
do_not_use_cli_fallback_when_mcp_tools_available: true,
|
|
2028
|
-
do_not_use_terminal_or_shell: true,
|
|
2029
|
-
forbidden_fallbacks: [
|
|
2030
|
-
"run_command",
|
|
2031
|
-
"terminal",
|
|
2032
|
-
"shell",
|
|
2033
|
-
"CLI detached fallback",
|
|
2034
|
-
"manual JSON-RPC over shell",
|
|
2035
|
-
"npx --stdio"
|
|
2036
|
-
]
|
|
2037
|
-
};
|
|
2038
|
-
const agentGuidance = {
|
|
2039
|
-
host_has_native_mcp: true,
|
|
2040
|
-
native_mcp_required_after_prepare: true,
|
|
2041
|
-
proof: "This prepare_recommend_pipeline_run response was returned by the MCP server, so the host can call MCP tools from this server.",
|
|
2042
|
-
immediate_run: {
|
|
2043
|
-
tool: nextAction.immediate_run.recommended_next_tool,
|
|
2044
|
-
alternate_tool: nextAction.immediate_run.alternate_next_tool,
|
|
2045
|
-
same_arguments: true
|
|
2046
|
-
},
|
|
2047
|
-
scheduled_run: {
|
|
2048
|
-
tool: nextAction.scheduled_run.recommended_next_tool,
|
|
2049
|
-
same_arguments: true
|
|
2050
|
-
},
|
|
2051
|
-
trae_cn: {
|
|
2052
|
-
next_step: "Call boss-recommend/start_recommend_pipeline_run or boss-recommend/run_recommend through the native MCP tool interface.",
|
|
2053
|
-
never_use_terminal_fallback_after_prepare: true
|
|
2054
|
-
},
|
|
2055
|
-
forbidden_when_mcp_tools_are_available: nextAction.forbidden_fallbacks
|
|
2056
|
-
};
|
|
2057
|
-
return {
|
|
2058
|
-
status: "READY",
|
|
2059
|
-
cron_ready: true,
|
|
2060
|
-
prepared_only: true,
|
|
2061
|
-
run_started: false,
|
|
2062
|
-
recommended_next_tool: nextAction.immediate_run.recommended_next_tool,
|
|
2063
|
-
alternate_next_tool: nextAction.immediate_run.alternate_next_tool,
|
|
2064
|
-
next_action: nextAction,
|
|
2065
|
-
agent_guidance: agentGuidance,
|
|
2066
|
-
message: "READY only means the payload passed validation; prepare_recommend_pipeline_run did not start a run. This response proves the MCP server is available. To start now, call the native MCP tool start_recommend_pipeline_run or run_recommend with the same arguments. Do not call prepare_recommend_pipeline_run again, and do not use terminal, shell, CLI detached fallback, or manual JSON-RPC when MCP tools are available.",
|
|
2067
|
-
review: parsed.review,
|
|
2068
|
-
post_action: {
|
|
2069
|
-
requested: normalized.postAction,
|
|
2070
|
-
execute_post_action: args.dry_run_post_action === true ? false : args.execute_post_action !== false,
|
|
2019
|
+
export function prepareRecommendPipelineRunTool({ workspaceRoot = "", args = {} } = {}) {
|
|
2020
|
+
const prepared = prepareRecommendPipelineStart(args, { workspaceRoot });
|
|
2021
|
+
if (prepared.response) {
|
|
2022
|
+
return {
|
|
2023
|
+
...prepared.response,
|
|
2024
|
+
cron_ready: false
|
|
2025
|
+
};
|
|
2026
|
+
}
|
|
2027
|
+
const { parsed, normalized } = prepared;
|
|
2028
|
+
const nextAction = {
|
|
2029
|
+
immediate_run: {
|
|
2030
|
+
recommended_next_tool: "start_recommend_pipeline_run",
|
|
2031
|
+
alternate_next_tool: "run_recommend",
|
|
2032
|
+
same_arguments: true,
|
|
2033
|
+
native_mcp_required: true
|
|
2034
|
+
},
|
|
2035
|
+
scheduled_run: {
|
|
2036
|
+
recommended_next_tool: "schedule_recommend_pipeline_run",
|
|
2037
|
+
same_arguments: true,
|
|
2038
|
+
requires_schedule_field: true
|
|
2039
|
+
},
|
|
2040
|
+
do_not_call_prepare_again: true,
|
|
2041
|
+
do_not_use_cli_fallback_when_mcp_tools_available: true,
|
|
2042
|
+
do_not_use_terminal_or_shell: true,
|
|
2043
|
+
forbidden_fallbacks: [
|
|
2044
|
+
"run_command",
|
|
2045
|
+
"terminal",
|
|
2046
|
+
"shell",
|
|
2047
|
+
"CLI detached fallback",
|
|
2048
|
+
"manual JSON-RPC over shell",
|
|
2049
|
+
"npx --stdio"
|
|
2050
|
+
]
|
|
2051
|
+
};
|
|
2052
|
+
const agentGuidance = {
|
|
2053
|
+
host_has_native_mcp: true,
|
|
2054
|
+
native_mcp_required_after_prepare: true,
|
|
2055
|
+
proof: "This prepare_recommend_pipeline_run response was returned by the MCP server, so the host can call MCP tools from this server.",
|
|
2056
|
+
immediate_run: {
|
|
2057
|
+
tool: nextAction.immediate_run.recommended_next_tool,
|
|
2058
|
+
alternate_tool: nextAction.immediate_run.alternate_next_tool,
|
|
2059
|
+
same_arguments: true
|
|
2060
|
+
},
|
|
2061
|
+
scheduled_run: {
|
|
2062
|
+
tool: nextAction.scheduled_run.recommended_next_tool,
|
|
2063
|
+
same_arguments: true
|
|
2064
|
+
},
|
|
2065
|
+
trae_cn: {
|
|
2066
|
+
next_step: "Call boss-recommend/start_recommend_pipeline_run or boss-recommend/run_recommend through the native MCP tool interface.",
|
|
2067
|
+
never_use_terminal_fallback_after_prepare: true
|
|
2068
|
+
},
|
|
2069
|
+
forbidden_when_mcp_tools_are_available: nextAction.forbidden_fallbacks
|
|
2070
|
+
};
|
|
2071
|
+
return {
|
|
2072
|
+
status: "READY",
|
|
2073
|
+
cron_ready: true,
|
|
2074
|
+
prepared_only: true,
|
|
2075
|
+
run_started: false,
|
|
2076
|
+
recommended_next_tool: nextAction.immediate_run.recommended_next_tool,
|
|
2077
|
+
alternate_next_tool: nextAction.immediate_run.alternate_next_tool,
|
|
2078
|
+
next_action: nextAction,
|
|
2079
|
+
agent_guidance: agentGuidance,
|
|
2080
|
+
message: "READY only means the payload passed validation; prepare_recommend_pipeline_run did not start a run. This response proves the MCP server is available. To start now, call the native MCP tool start_recommend_pipeline_run or run_recommend with the same arguments. Do not call prepare_recommend_pipeline_run again, and do not use terminal, shell, CLI detached fallback, or manual JSON-RPC when MCP tools are available.",
|
|
2081
|
+
review: parsed.review,
|
|
2082
|
+
post_action: {
|
|
2083
|
+
requested: normalized.postAction,
|
|
2084
|
+
execute_post_action: args.dry_run_post_action === true ? false : args.execute_post_action !== false,
|
|
2071
2085
|
max_greet_count: normalized.maxGreetCount
|
|
2072
2086
|
},
|
|
2073
2087
|
target_count_semantics: TARGET_COUNT_SEMANTICS
|
|
@@ -2093,19 +2107,19 @@ export function getRecommendPipelineRunTool({ args = {} } = {}) {
|
|
|
2093
2107
|
};
|
|
2094
2108
|
}
|
|
2095
2109
|
try {
|
|
2096
|
-
const run = recommendRunService.getRecommendRun(runId);
|
|
2097
|
-
const normalizedRun = persistRecommendRunSnapshot(run);
|
|
2098
|
-
return attachMethodEvidence({
|
|
2099
|
-
status: "RUN_STATUS",
|
|
2100
|
-
run: compactRecommendRunForStatus(normalizedRun)
|
|
2101
|
-
}, runId);
|
|
2102
|
-
} catch {
|
|
2103
|
-
const persisted = readRecommendRunState(runId);
|
|
2104
|
-
if (persisted) {
|
|
2105
|
-
const reconciled = compactRecommendRunForStatus(reconcilePersistedRecommendRunIfNeeded(persisted));
|
|
2106
|
-
return {
|
|
2107
|
-
status: "RUN_STATUS",
|
|
2108
|
-
run: reconciled,
|
|
2110
|
+
const run = recommendRunService.getRecommendRun(runId);
|
|
2111
|
+
const normalizedRun = persistRecommendRunSnapshot(run);
|
|
2112
|
+
return attachMethodEvidence({
|
|
2113
|
+
status: "RUN_STATUS",
|
|
2114
|
+
run: compactRecommendRunForStatus(normalizedRun)
|
|
2115
|
+
}, runId);
|
|
2116
|
+
} catch {
|
|
2117
|
+
const persisted = readRecommendRunState(runId);
|
|
2118
|
+
if (persisted) {
|
|
2119
|
+
const reconciled = compactRecommendRunForStatus(reconcilePersistedRecommendRunIfNeeded(persisted));
|
|
2120
|
+
return {
|
|
2121
|
+
status: "RUN_STATUS",
|
|
2122
|
+
run: reconciled,
|
|
2109
2123
|
persistence: {
|
|
2110
2124
|
source: "disk",
|
|
2111
2125
|
active_control_available: false,
|
|
@@ -2133,36 +2147,36 @@ export function pauseRecommendPipelineRunTool({ args = {} } = {}) {
|
|
|
2133
2147
|
try {
|
|
2134
2148
|
const before = recommendRunService.getRecommendRun(runId);
|
|
2135
2149
|
if (TERMINAL_STATUSES.has(before.status)) {
|
|
2136
|
-
const normalizedBefore = persistRecommendRunSnapshot(before);
|
|
2137
|
-
return attachMethodEvidence({
|
|
2138
|
-
status: "PAUSE_IGNORED",
|
|
2139
|
-
run: compactRecommendRunForStatus(normalizedBefore),
|
|
2140
|
-
message: "目标任务已结束,无需暂停。"
|
|
2141
|
-
}, runId);
|
|
2150
|
+
const normalizedBefore = persistRecommendRunSnapshot(before);
|
|
2151
|
+
return attachMethodEvidence({
|
|
2152
|
+
status: "PAUSE_IGNORED",
|
|
2153
|
+
run: compactRecommendRunForStatus(normalizedBefore),
|
|
2154
|
+
message: "目标任务已结束,无需暂停。"
|
|
2155
|
+
}, runId);
|
|
2142
2156
|
}
|
|
2143
2157
|
if (before.status === RUN_STATUS_PAUSED) {
|
|
2144
|
-
const normalizedBefore = persistRecommendRunSnapshot(before);
|
|
2145
|
-
return attachMethodEvidence({
|
|
2146
|
-
status: "PAUSE_IGNORED",
|
|
2147
|
-
run: compactRecommendRunForStatus(normalizedBefore),
|
|
2148
|
-
message: "目标任务已经处于 paused 状态。"
|
|
2149
|
-
}, runId);
|
|
2158
|
+
const normalizedBefore = persistRecommendRunSnapshot(before);
|
|
2159
|
+
return attachMethodEvidence({
|
|
2160
|
+
status: "PAUSE_IGNORED",
|
|
2161
|
+
run: compactRecommendRunForStatus(normalizedBefore),
|
|
2162
|
+
message: "目标任务已经处于 paused 状态。"
|
|
2163
|
+
}, runId);
|
|
2150
2164
|
}
|
|
2151
2165
|
const run = recommendRunService.pauseRecommendRun(runId);
|
|
2152
|
-
const normalizedRun = persistRecommendRunSnapshot(run);
|
|
2153
|
-
return attachMethodEvidence({
|
|
2154
|
-
status: "PAUSE_REQUESTED",
|
|
2155
|
-
run: compactRecommendRunForStatus(normalizedRun),
|
|
2156
|
-
message: "暂停请求已接收,将在当前候选人处理完成后进入 paused。"
|
|
2157
|
-
}, runId);
|
|
2166
|
+
const normalizedRun = persistRecommendRunSnapshot(run);
|
|
2167
|
+
return attachMethodEvidence({
|
|
2168
|
+
status: "PAUSE_REQUESTED",
|
|
2169
|
+
run: compactRecommendRunForStatus(normalizedRun),
|
|
2170
|
+
message: "暂停请求已接收,将在当前候选人处理完成后进入 paused。"
|
|
2171
|
+
}, runId);
|
|
2158
2172
|
} catch {
|
|
2159
2173
|
const persisted = readRecommendRunState(runId);
|
|
2160
2174
|
if (persisted && TERMINAL_STATUSES.has(persisted.state)) {
|
|
2161
|
-
return {
|
|
2162
|
-
status: "PAUSE_IGNORED",
|
|
2163
|
-
run: compactRecommendRunForStatus(persisted),
|
|
2164
|
-
message: "目标任务已结束,无需暂停。",
|
|
2165
|
-
runtime_evaluate_used: false,
|
|
2175
|
+
return {
|
|
2176
|
+
status: "PAUSE_IGNORED",
|
|
2177
|
+
run: compactRecommendRunForStatus(persisted),
|
|
2178
|
+
message: "目标任务已结束,无需暂停。",
|
|
2179
|
+
runtime_evaluate_used: false,
|
|
2166
2180
|
method_summary: {},
|
|
2167
2181
|
method_log: [],
|
|
2168
2182
|
chrome: null
|
|
@@ -2177,28 +2191,28 @@ export function resumeRecommendPipelineRunTool({ args = {} } = {}) {
|
|
|
2177
2191
|
try {
|
|
2178
2192
|
const before = recommendRunService.getRecommendRun(runId);
|
|
2179
2193
|
if (TERMINAL_STATUSES.has(before.status)) {
|
|
2180
|
-
const normalizedBefore = persistRecommendRunSnapshot(before);
|
|
2181
|
-
return attachMethodEvidence({
|
|
2182
|
-
status: "FAILED",
|
|
2194
|
+
const normalizedBefore = persistRecommendRunSnapshot(before);
|
|
2195
|
+
return attachMethodEvidence({
|
|
2196
|
+
status: "FAILED",
|
|
2183
2197
|
error: {
|
|
2184
2198
|
code: "RUN_ALREADY_TERMINATED",
|
|
2185
2199
|
message: "目标任务已结束,无法继续。",
|
|
2186
2200
|
retryable: false
|
|
2187
2201
|
},
|
|
2188
|
-
run: compactRecommendRunForStatus(normalizedBefore)
|
|
2189
|
-
}, runId);
|
|
2202
|
+
run: compactRecommendRunForStatus(normalizedBefore)
|
|
2203
|
+
}, runId);
|
|
2190
2204
|
}
|
|
2191
2205
|
if (before.status !== RUN_STATUS_PAUSED) {
|
|
2192
|
-
const normalizedBefore = persistRecommendRunSnapshot(before);
|
|
2193
|
-
return attachMethodEvidence({
|
|
2194
|
-
status: "FAILED",
|
|
2206
|
+
const normalizedBefore = persistRecommendRunSnapshot(before);
|
|
2207
|
+
return attachMethodEvidence({
|
|
2208
|
+
status: "FAILED",
|
|
2195
2209
|
error: {
|
|
2196
2210
|
code: "RUN_NOT_PAUSED",
|
|
2197
2211
|
message: "仅 paused 状态的 run 才能继续。",
|
|
2198
2212
|
retryable: true
|
|
2199
2213
|
},
|
|
2200
|
-
run: compactRecommendRunForStatus(normalizedBefore)
|
|
2201
|
-
}, runId);
|
|
2214
|
+
run: compactRecommendRunForStatus(normalizedBefore)
|
|
2215
|
+
}, runId);
|
|
2202
2216
|
}
|
|
2203
2217
|
const run = recommendRunService.resumeRecommendRun(runId);
|
|
2204
2218
|
const meta = getRecommendRunMeta(runId);
|
|
@@ -2206,12 +2220,12 @@ export function resumeRecommendPipelineRunTool({ args = {} } = {}) {
|
|
|
2206
2220
|
meta.resumeCount = (meta.resumeCount || 0) + 1;
|
|
2207
2221
|
meta.lastResumedAt = new Date().toISOString();
|
|
2208
2222
|
}
|
|
2209
|
-
const normalizedRun = persistRecommendRunSnapshot(run);
|
|
2210
|
-
return attachMethodEvidence({
|
|
2211
|
-
status: "RESUME_REQUESTED",
|
|
2212
|
-
run: compactRecommendRunForStatus(normalizedRun),
|
|
2213
|
-
poll_after_sec: DEFAULT_RECOMMEND_POLL_AFTER_SEC,
|
|
2214
|
-
message: "已恢复 Recommend run,请使用 get_recommend_pipeline_run 按需轮询。"
|
|
2223
|
+
const normalizedRun = persistRecommendRunSnapshot(run);
|
|
2224
|
+
return attachMethodEvidence({
|
|
2225
|
+
status: "RESUME_REQUESTED",
|
|
2226
|
+
run: compactRecommendRunForStatus(normalizedRun),
|
|
2227
|
+
poll_after_sec: DEFAULT_RECOMMEND_POLL_AFTER_SEC,
|
|
2228
|
+
message: "已恢复 Recommend run,请使用 get_recommend_pipeline_run 按需轮询。"
|
|
2215
2229
|
}, runId);
|
|
2216
2230
|
} catch {
|
|
2217
2231
|
const persisted = readRecommendRunState(runId);
|
|
@@ -2225,9 +2239,9 @@ export function resumeRecommendPipelineRunTool({ args = {} } = {}) {
|
|
|
2225
2239
|
: "该 run 只有磁盘快照,没有当前进程内的活动 CDP 会话,无法安全继续。",
|
|
2226
2240
|
retryable: !TERMINAL_STATUSES.has(persisted.state)
|
|
2227
2241
|
},
|
|
2228
|
-
run: compactRecommendRunForStatus(persisted),
|
|
2229
|
-
persistence: {
|
|
2230
|
-
source: "disk",
|
|
2242
|
+
run: compactRecommendRunForStatus(persisted),
|
|
2243
|
+
persistence: {
|
|
2244
|
+
source: "disk",
|
|
2231
2245
|
active_control_available: false
|
|
2232
2246
|
},
|
|
2233
2247
|
runtime_evaluate_used: false,
|
|
@@ -2245,61 +2259,61 @@ export function cancelRecommendPipelineRunTool({ args = {} } = {}) {
|
|
|
2245
2259
|
try {
|
|
2246
2260
|
const before = recommendRunService.getRecommendRun(runId);
|
|
2247
2261
|
if (TERMINAL_STATUSES.has(before.status)) {
|
|
2248
|
-
const normalizedBefore = persistRecommendRunSnapshot(before);
|
|
2249
|
-
return attachMethodEvidence({
|
|
2250
|
-
status: "CANCEL_IGNORED",
|
|
2251
|
-
run: compactRecommendRunForStatus(normalizedBefore),
|
|
2252
|
-
message: "目标任务已结束,无需取消。"
|
|
2253
|
-
}, runId);
|
|
2262
|
+
const normalizedBefore = persistRecommendRunSnapshot(before);
|
|
2263
|
+
return attachMethodEvidence({
|
|
2264
|
+
status: "CANCEL_IGNORED",
|
|
2265
|
+
run: compactRecommendRunForStatus(normalizedBefore),
|
|
2266
|
+
message: "目标任务已结束,无需取消。"
|
|
2267
|
+
}, runId);
|
|
2254
2268
|
}
|
|
2255
2269
|
const run = recommendRunService.cancelRecommendRun(runId);
|
|
2256
|
-
const normalizedRun = persistRecommendRunSnapshot(run);
|
|
2257
|
-
return attachMethodEvidence({
|
|
2258
|
-
status: "CANCEL_REQUESTED",
|
|
2259
|
-
run: compactRecommendRunForStatus(normalizedRun),
|
|
2260
|
-
message: "已收到取消请求,将在当前候选人处理完成后安全停止。"
|
|
2261
|
-
}, runId);
|
|
2270
|
+
const normalizedRun = persistRecommendRunSnapshot(run);
|
|
2271
|
+
return attachMethodEvidence({
|
|
2272
|
+
status: "CANCEL_REQUESTED",
|
|
2273
|
+
run: compactRecommendRunForStatus(normalizedRun),
|
|
2274
|
+
message: "已收到取消请求,将在当前候选人处理完成后安全停止。"
|
|
2275
|
+
}, runId);
|
|
2262
2276
|
} catch {
|
|
2263
2277
|
const persisted = readRecommendRunState(runId);
|
|
2264
|
-
if (persisted && TERMINAL_STATUSES.has(persisted.state)) {
|
|
2265
|
-
return {
|
|
2266
|
-
status: "CANCEL_IGNORED",
|
|
2267
|
-
run: compactRecommendRunForStatus(persisted),
|
|
2268
|
-
message: "目标任务已结束,无需取消。",
|
|
2269
|
-
runtime_evaluate_used: false,
|
|
2278
|
+
if (persisted && TERMINAL_STATUSES.has(persisted.state)) {
|
|
2279
|
+
return {
|
|
2280
|
+
status: "CANCEL_IGNORED",
|
|
2281
|
+
run: compactRecommendRunForStatus(persisted),
|
|
2282
|
+
message: "目标任务已结束,无需取消。",
|
|
2283
|
+
runtime_evaluate_used: false,
|
|
2270
2284
|
method_summary: {},
|
|
2271
2285
|
method_log: [],
|
|
2272
|
-
chrome: null
|
|
2273
|
-
};
|
|
2274
|
-
}
|
|
2275
|
-
const cancelMessage = "已收到取消请求,将由 detached worker 在下一个安全边界停止。";
|
|
2276
|
-
const patched = patchPersistedRecommendRunControl(runId, {
|
|
2277
|
-
pause_requested: true,
|
|
2278
|
-
pause_requested_at: new Date().toISOString(),
|
|
2279
|
-
pause_requested_by: "cancel_recommend_pipeline_run",
|
|
2280
|
-
cancel_requested: true
|
|
2281
|
-
}, {
|
|
2282
|
-
message: cancelMessage
|
|
2283
|
-
});
|
|
2284
|
-
if (patched) {
|
|
2285
|
-
return {
|
|
2286
|
-
status: "CANCEL_REQUESTED",
|
|
2287
|
-
run: compactRecommendRunForStatus(patched),
|
|
2288
|
-
message: cancelMessage,
|
|
2289
|
-
persistence: {
|
|
2290
|
-
source: "disk",
|
|
2291
|
-
active_control_available: false,
|
|
2292
|
-
detached_control_requested: true
|
|
2293
|
-
},
|
|
2294
|
-
runtime_evaluate_used: false,
|
|
2295
|
-
method_summary: {},
|
|
2296
|
-
method_log: [],
|
|
2297
|
-
chrome: null
|
|
2298
|
-
};
|
|
2299
|
-
}
|
|
2300
|
-
return getRecommendPipelineRunTool({ args });
|
|
2301
|
-
}
|
|
2302
|
-
}
|
|
2286
|
+
chrome: null
|
|
2287
|
+
};
|
|
2288
|
+
}
|
|
2289
|
+
const cancelMessage = "已收到取消请求,将由 detached worker 在下一个安全边界停止。";
|
|
2290
|
+
const patched = patchPersistedRecommendRunControl(runId, {
|
|
2291
|
+
pause_requested: true,
|
|
2292
|
+
pause_requested_at: new Date().toISOString(),
|
|
2293
|
+
pause_requested_by: "cancel_recommend_pipeline_run",
|
|
2294
|
+
cancel_requested: true
|
|
2295
|
+
}, {
|
|
2296
|
+
message: cancelMessage
|
|
2297
|
+
});
|
|
2298
|
+
if (patched) {
|
|
2299
|
+
return {
|
|
2300
|
+
status: "CANCEL_REQUESTED",
|
|
2301
|
+
run: compactRecommendRunForStatus(patched),
|
|
2302
|
+
message: cancelMessage,
|
|
2303
|
+
persistence: {
|
|
2304
|
+
source: "disk",
|
|
2305
|
+
active_control_available: false,
|
|
2306
|
+
detached_control_requested: true
|
|
2307
|
+
},
|
|
2308
|
+
runtime_evaluate_used: false,
|
|
2309
|
+
method_summary: {},
|
|
2310
|
+
method_log: [],
|
|
2311
|
+
chrome: null
|
|
2312
|
+
};
|
|
2313
|
+
}
|
|
2314
|
+
return getRecommendPipelineRunTool({ args });
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2303
2317
|
|
|
2304
2318
|
export function getRecommendMcpHealthSnapshot(runId) {
|
|
2305
2319
|
const meta = getRecommendRunMeta(runId);
|