@reconcrap/boss-recommend-mcp 2.1.21 → 2.1.23
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 +2 -1
- 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 +2416 -2385
- 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 +1315 -1306
- package/src/core/reporting/legacy-csv.js +334 -332
- package/src/core/run/index.js +44 -27
- 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 +655 -646
- 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 +575 -351
- package/src/domains/recommend/roots.js +80 -80
- package/src/domains/recommend/run-service.js +1616 -878
- 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 +121 -4
- package/src/parser.js +376 -8
- package/src/recommend-mcp.js +1174 -914
- package/src/recommend-scheduler.js +535 -469
- package/src/recruit-mcp.js +2121 -2121
package/src/recommend-mcp.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
|
-
import {
|
|
5
|
-
assertNoForbiddenCdpCalls,
|
|
6
|
-
bringPageToFront,
|
|
7
|
-
connectToChromeTargetOrOpen,
|
|
8
|
-
createBossLoginRequiredError,
|
|
9
|
-
detectBossLoginState,
|
|
10
|
-
enableDomains,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
4
|
+
import {
|
|
5
|
+
assertNoForbiddenCdpCalls,
|
|
6
|
+
bringPageToFront,
|
|
7
|
+
connectToChromeTargetOrOpen,
|
|
8
|
+
createBossLoginRequiredError,
|
|
9
|
+
detectBossLoginState,
|
|
10
|
+
enableDomains,
|
|
11
|
+
FORBIDDEN_CDP_METHODS,
|
|
12
|
+
getMainFrameUrl,
|
|
13
|
+
isBossLoginUrl,
|
|
14
|
+
waitForMainFrameUrl,
|
|
15
|
+
sleep
|
|
16
|
+
} from "./core/browser/index.js";
|
|
16
17
|
import {
|
|
17
18
|
RUN_STATUS_CANCELING,
|
|
18
19
|
RUN_STATUS_CANCELED,
|
|
@@ -44,21 +45,21 @@ import {
|
|
|
44
45
|
parseRecommendInstruction
|
|
45
46
|
} from "./parser.js";
|
|
46
47
|
import { getRunsDir } from "./run-state.js";
|
|
47
|
-
import {
|
|
48
|
-
resolveBossConfiguredOutputDir,
|
|
49
|
-
resolveHumanBehaviorForRun,
|
|
50
|
-
resolveBossScreeningConfig
|
|
51
|
-
} from "./chat-runtime-config.js";
|
|
48
|
+
import {
|
|
49
|
+
resolveBossConfiguredOutputDir,
|
|
50
|
+
resolveHumanBehaviorForRun,
|
|
51
|
+
resolveBossScreeningConfig
|
|
52
|
+
} from "./chat-runtime-config.js";
|
|
52
53
|
import { DEFAULT_MAX_IMAGE_PAGES } from "./core/cv-acquisition/index.js";
|
|
53
54
|
|
|
54
55
|
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);
|
|
56
|
+
const DEFAULT_RECOMMEND_PORT = 9222;
|
|
57
|
+
const DEFAULT_RECOMMEND_POLL_AFTER_SEC = 10;
|
|
58
|
+
const STATUS_METHOD_LOG_TAIL_LIMIT = 25;
|
|
59
|
+
const TARGET_COUNT_SEMANTICS = "target_count means candidates that pass screening; scan continues until that many candidates pass or the list ends";
|
|
60
|
+
const RUN_MODE_ASYNC = "async";
|
|
61
|
+
const REST_LEVEL_OPTIONS = ["low", "medium", "high"];
|
|
62
|
+
const REST_LEVEL_SET = new Set(REST_LEVEL_OPTIONS);
|
|
62
63
|
|
|
63
64
|
const TERMINAL_STATUSES = new Set([
|
|
64
65
|
RUN_STATUS_COMPLETED,
|
|
@@ -76,188 +77,188 @@ let recommendRunService = createRecommendRunService({
|
|
|
76
77
|
});
|
|
77
78
|
const recommendRunMeta = new Map();
|
|
78
79
|
|
|
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;
|
|
80
|
+
function normalizeText(value) {
|
|
81
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function collectRecommendRouteText(args = {}) {
|
|
85
|
+
return normalizeText([
|
|
86
|
+
args.instruction,
|
|
87
|
+
args.criteria,
|
|
88
|
+
args.target_url_includes,
|
|
89
|
+
args.page_scope,
|
|
90
|
+
args.confirmation?.page_value,
|
|
91
|
+
args.confirmation?.job_value,
|
|
92
|
+
args.overrides?.criteria,
|
|
93
|
+
args.overrides?.page_scope,
|
|
94
|
+
args.overrides?.job,
|
|
95
|
+
args.overrides?.target_url_includes
|
|
96
|
+
].filter((value) => value !== undefined && value !== null).join(" "));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function findRouteSignals(text, patterns = []) {
|
|
100
|
+
const signals = [];
|
|
101
|
+
for (const { label, pattern } of patterns) {
|
|
102
|
+
if (pattern.test(text)) signals.push(label);
|
|
103
|
+
}
|
|
104
|
+
return signals;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function detectExplicitNonRecommendScope(args = {}) {
|
|
108
|
+
const fields = [
|
|
109
|
+
["page_scope", args.page_scope],
|
|
110
|
+
["confirmation.page_value", args.confirmation?.page_value],
|
|
111
|
+
["overrides.page_scope", args.overrides?.page_scope],
|
|
112
|
+
["target_url_includes", args.target_url_includes],
|
|
113
|
+
["overrides.target_url_includes", args.overrides?.target_url_includes]
|
|
114
|
+
];
|
|
115
|
+
for (const [field, value] of fields) {
|
|
116
|
+
const normalized = normalizeText(value).toLowerCase();
|
|
117
|
+
if (!normalized) continue;
|
|
118
|
+
if (/\/web\/chat\/index|chat\/index/.test(normalized) || ["chat", "chat-page", "boss-chat"].includes(normalized)) {
|
|
119
|
+
return {
|
|
120
|
+
domain: "chat",
|
|
121
|
+
signals: [`${field}:chat`],
|
|
122
|
+
text: normalized
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (/\/web\/chat\/search|chat\/search/.test(normalized) || ["search", "search-page", "recruit", "recruit-page", "boss-recruit"].includes(normalized)) {
|
|
126
|
+
return {
|
|
127
|
+
domain: "search",
|
|
128
|
+
signals: [`${field}:search`],
|
|
129
|
+
text: normalized
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function detectRecruitShapedRecommendArgs(args = {}) {
|
|
137
|
+
const checks = [
|
|
138
|
+
["keyword", args.keyword],
|
|
139
|
+
["search_keyword", args.search_keyword],
|
|
140
|
+
["city", args.city],
|
|
141
|
+
["confirmation.keyword_value", args.confirmation?.keyword_value],
|
|
142
|
+
["confirmation.keyword_confirmed", args.confirmation?.keyword_confirmed],
|
|
143
|
+
["confirmation.search_params_confirmed", args.confirmation?.search_params_confirmed],
|
|
144
|
+
["overrides.keyword", args.overrides?.keyword],
|
|
145
|
+
["overrides.search_keyword", args.overrides?.search_keyword],
|
|
146
|
+
["overrides.city", args.overrides?.city],
|
|
147
|
+
["overrides.filter_recent_viewed", args.overrides?.filter_recent_viewed],
|
|
148
|
+
["overrides.schools", args.overrides?.schools],
|
|
149
|
+
["overrides.school_tags", args.overrides?.school_tags]
|
|
150
|
+
];
|
|
151
|
+
const signals = [];
|
|
152
|
+
for (const [field, value] of checks) {
|
|
153
|
+
if (value === undefined || value === null) continue;
|
|
154
|
+
if (Array.isArray(value) && value.length === 0) continue;
|
|
155
|
+
if (typeof value === "string" && !normalizeText(value)) continue;
|
|
156
|
+
signals.push(field);
|
|
157
|
+
}
|
|
158
|
+
if (!signals.length) return null;
|
|
159
|
+
return {
|
|
160
|
+
domain: "search",
|
|
161
|
+
signals: signals.map((field) => `${field}:recruit_arg`),
|
|
162
|
+
text: signals.join(" ")
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function detectNonRecommendRoute(args = {}) {
|
|
167
|
+
const explicitRoute = detectExplicitNonRecommendScope(args);
|
|
168
|
+
if (explicitRoute) return explicitRoute;
|
|
169
|
+
const recruitArgRoute = detectRecruitShapedRecommendArgs(args);
|
|
170
|
+
if (recruitArgRoute) return recruitArgRoute;
|
|
171
|
+
|
|
172
|
+
const text = collectRecommendRouteText(args);
|
|
173
|
+
if (!text) return null;
|
|
174
|
+
const chatSignals = findRouteSignals(text, [
|
|
175
|
+
{ label: "chat-only", pattern: /\bchat[-\s]?only\b/i },
|
|
176
|
+
{ label: "boss-chat", pattern: /\bboss[-\s]?chat\b/i },
|
|
177
|
+
{ label: "chat page", pattern: /\bchat\s+page\b/i },
|
|
178
|
+
{ label: "chat/index", pattern: /(?:\/web\/chat\/index|chat\/index)/i },
|
|
179
|
+
{ label: "聊天页", pattern: /聊天页/ },
|
|
180
|
+
{ label: "聊天列表", pattern: /聊天列表/ },
|
|
181
|
+
{ label: "未读", pattern: /未读/ },
|
|
182
|
+
{ label: "全部聊天", pattern: /全部聊天|所有聊天/ },
|
|
183
|
+
{ label: "求简历", pattern: /求简历|索要简历|要简历|在线简历/ }
|
|
184
|
+
]);
|
|
185
|
+
if (chatSignals.length) {
|
|
186
|
+
return {
|
|
187
|
+
domain: "chat",
|
|
188
|
+
signals: chatSignals,
|
|
189
|
+
text
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const searchSignals = findRouteSignals(text, [
|
|
193
|
+
{ label: "search-only", pattern: /\bsearch[-\s]?only\b/i },
|
|
194
|
+
{ label: "search page", pattern: /\bsearch\s+page\b/i },
|
|
195
|
+
{ label: "search keyword", pattern: /搜索关键词|关键词|keyword/i },
|
|
196
|
+
{ label: "recruit pipeline", pattern: /\brecruit\s+pipeline\b/i },
|
|
197
|
+
{ label: "chat/search", pattern: /(?:\/web\/chat\/search|chat\/search)/i },
|
|
198
|
+
{ label: "搜索页", pattern: /搜索页/ },
|
|
199
|
+
{ label: "招聘搜索", pattern: /招聘搜索/ }
|
|
200
|
+
]);
|
|
201
|
+
if (searchSignals.length) {
|
|
202
|
+
return {
|
|
203
|
+
domain: "search",
|
|
204
|
+
signals: searchSignals,
|
|
205
|
+
text
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function buildWrongRecommendRouteResponse(route) {
|
|
212
|
+
if (route?.domain === "chat") {
|
|
213
|
+
return {
|
|
214
|
+
status: "FAILED",
|
|
215
|
+
route_guard: true,
|
|
216
|
+
error: {
|
|
217
|
+
code: "WRONG_BOSS_TOOL_FOR_CHAT",
|
|
218
|
+
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.",
|
|
219
|
+
retryable: false
|
|
220
|
+
},
|
|
221
|
+
detected_domain: "chat",
|
|
222
|
+
detected_signals: route.signals || [],
|
|
223
|
+
wrong_server: "boss-recommend",
|
|
224
|
+
recommended_server: "boss-chat",
|
|
225
|
+
recommended_tool_sequence: [
|
|
226
|
+
"boss-chat/boss_chat_health_check",
|
|
227
|
+
"boss-chat/list_boss_chat_jobs",
|
|
228
|
+
"boss-chat/prepare_boss_chat_run",
|
|
229
|
+
"boss-chat/start_boss_chat_run"
|
|
230
|
+
]
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
if (route?.domain === "search") {
|
|
234
|
+
return {
|
|
235
|
+
status: "FAILED",
|
|
236
|
+
route_guard: true,
|
|
237
|
+
error: {
|
|
238
|
+
code: "WRONG_BOSS_TOOL_FOR_SEARCH",
|
|
239
|
+
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.",
|
|
240
|
+
retryable: false
|
|
241
|
+
},
|
|
242
|
+
detected_domain: "search",
|
|
243
|
+
detected_signals: route.signals || [],
|
|
244
|
+
wrong_server: "boss-recommend",
|
|
245
|
+
recommended_server: "boss-recruit",
|
|
246
|
+
recommended_tool_sequence: [
|
|
247
|
+
"boss-recruit/run_recruit_pipeline",
|
|
248
|
+
"boss-recruit/start_recruit_pipeline_run"
|
|
249
|
+
]
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function guardRecommendRoute(args = {}) {
|
|
256
|
+
return buildWrongRecommendRouteResponse(detectNonRecommendRoute(args));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function parsePositiveInteger(raw, fallback) {
|
|
260
|
+
const parsed = Number.parseInt(String(raw || ""), 10);
|
|
261
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
261
262
|
}
|
|
262
263
|
|
|
263
264
|
function parseNonNegativeInteger(raw, fallback) {
|
|
@@ -277,13 +278,22 @@ function normalizeScreeningModeArg(args = {}) {
|
|
|
277
278
|
: "llm";
|
|
278
279
|
}
|
|
279
280
|
|
|
280
|
-
function collectRecommendDebugTestOptions(args = {}, normalized = {}) {
|
|
281
|
-
const reasons = [];
|
|
281
|
+
function collectRecommendDebugTestOptions(args = {}, normalized = {}) {
|
|
282
|
+
const reasons = [];
|
|
282
283
|
if (normalizeScreeningModeArg(args) === "deterministic") reasons.push("deterministic_screening");
|
|
283
284
|
if (args.allow_card_only_screening === true) reasons.push("allow_card_only_screening");
|
|
284
285
|
if (parseNonNegativeInteger(args.detail_limit, null) === 0) reasons.push("detail_limit=0");
|
|
285
286
|
if (args.no_filter === true) reasons.push("no_filter");
|
|
286
|
-
if (args.filter_enabled === false) reasons.push("filter_enabled=false");
|
|
287
|
+
if (args.filter_enabled === false) reasons.push("filter_enabled=false");
|
|
288
|
+
if (args.debug_force_list_end_after_processed != null) {
|
|
289
|
+
reasons.push("debug_force_list_end_after_processed");
|
|
290
|
+
}
|
|
291
|
+
if (args.debug_force_context_recovery_after_processed != null) {
|
|
292
|
+
reasons.push("debug_force_context_recovery_after_processed");
|
|
293
|
+
}
|
|
294
|
+
if (args.debug_force_cdp_reconnect_after_processed != null) {
|
|
295
|
+
reasons.push("debug_force_cdp_reconnect_after_processed");
|
|
296
|
+
}
|
|
287
297
|
if (args.dry_run_post_action === true) reasons.push("dry_run_post_action");
|
|
288
298
|
if (args.execute_post_action === false && normalized.postAction && normalized.postAction !== "none") {
|
|
289
299
|
reasons.push("execute_post_action=false");
|
|
@@ -304,22 +314,40 @@ function resolveRecommendDetailLimit(args = {}, normalized = {}) {
|
|
|
304
314
|
}
|
|
305
315
|
|
|
306
316
|
function methodSummary(methodLog = []) {
|
|
307
|
-
const summary = {};
|
|
308
|
-
for (const entry of methodLog || []) {
|
|
309
|
-
|
|
310
|
-
|
|
317
|
+
const summary = {};
|
|
318
|
+
for (const entry of methodLog || []) {
|
|
319
|
+
const method = compactCdpMethodName(entry?.method || entry || "");
|
|
320
|
+
if (!method) continue;
|
|
321
|
+
summary[method] = (summary[method] || 0) + 1;
|
|
322
|
+
}
|
|
311
323
|
return summary;
|
|
312
324
|
}
|
|
313
325
|
|
|
326
|
+
function compactCdpMethodName(value) {
|
|
327
|
+
const method = normalizeText(value);
|
|
328
|
+
return /^[A-Za-z][A-Za-z0-9_]*\.[A-Za-z][A-Za-z0-9_]*(?::retry_after_reconnect)?$/.test(method)
|
|
329
|
+
? method
|
|
330
|
+
: "";
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function compactCdpMethodTimestamp(value) {
|
|
334
|
+
const timestamp = normalizeText(value);
|
|
335
|
+
const parsed = Date.parse(timestamp);
|
|
336
|
+
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : "";
|
|
337
|
+
}
|
|
338
|
+
|
|
314
339
|
function compactMethodLogForStatus(methodLog = []) {
|
|
315
340
|
if (!Array.isArray(methodLog)) return [];
|
|
316
|
-
return methodLog
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
341
|
+
return methodLog
|
|
342
|
+
.map((entry) => ({
|
|
343
|
+
method: compactCdpMethodName(entry?.method || entry || ""),
|
|
344
|
+
at: compactCdpMethodTimestamp(entry?.at || "")
|
|
345
|
+
}))
|
|
346
|
+
.filter((entry) => Boolean(entry.method))
|
|
347
|
+
.slice(-STATUS_METHOD_LOG_TAIL_LIMIT);
|
|
320
348
|
}
|
|
321
|
-
|
|
322
|
-
function clonePlain(value, fallback = null) {
|
|
349
|
+
|
|
350
|
+
function clonePlain(value, fallback = null) {
|
|
323
351
|
try {
|
|
324
352
|
return value === undefined ? fallback : JSON.parse(JSON.stringify(value));
|
|
325
353
|
} catch {
|
|
@@ -331,10 +359,130 @@ function plainRecord(value) {
|
|
|
331
359
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
332
360
|
}
|
|
333
361
|
|
|
334
|
-
function nonEmptyRecord(value) {
|
|
335
|
-
const record = plainRecord(value);
|
|
336
|
-
return Object.keys(record).length ? record : null;
|
|
337
|
-
}
|
|
362
|
+
function nonEmptyRecord(value) {
|
|
363
|
+
const record = plainRecord(value);
|
|
364
|
+
return Object.keys(record).length ? record : null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function compactMethodSummaryForStatus(value = {}) {
|
|
368
|
+
const compact = {};
|
|
369
|
+
for (const [rawMethod, rawCount] of Object.entries(plainRecord(value))) {
|
|
370
|
+
const method = compactCdpMethodName(rawMethod);
|
|
371
|
+
const count = Number(rawCount);
|
|
372
|
+
if (!method || !Number.isInteger(count) || count < 1) continue;
|
|
373
|
+
compact[method] = count;
|
|
374
|
+
}
|
|
375
|
+
return compact;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function compactChromeEvidenceUrl(value) {
|
|
379
|
+
const text = normalizeText(value);
|
|
380
|
+
if (!text) return null;
|
|
381
|
+
try {
|
|
382
|
+
const parsed = new URL(text);
|
|
383
|
+
parsed.username = "";
|
|
384
|
+
parsed.password = "";
|
|
385
|
+
parsed.search = "";
|
|
386
|
+
parsed.hash = "";
|
|
387
|
+
return parsed.toString();
|
|
388
|
+
} catch {
|
|
389
|
+
return text.split(/[?#]/, 1)[0] || null;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function compactChromeAutoLaunchEvidence(value = {}) {
|
|
394
|
+
const source = plainRecord(value);
|
|
395
|
+
if (!Object.keys(source).length) return null;
|
|
396
|
+
const compact = {};
|
|
397
|
+
const booleanKeys = [
|
|
398
|
+
"launched",
|
|
399
|
+
"reused",
|
|
400
|
+
"guard_checked",
|
|
401
|
+
"required_flags_ok",
|
|
402
|
+
"replaced",
|
|
403
|
+
"target_created",
|
|
404
|
+
"fallback_target",
|
|
405
|
+
"fallback_any_page"
|
|
406
|
+
];
|
|
407
|
+
for (const key of booleanKeys) {
|
|
408
|
+
if (typeof source[key] === "boolean") compact[key] = source[key];
|
|
409
|
+
}
|
|
410
|
+
for (const key of ["port", "target_count", "command_line_args_count"]) {
|
|
411
|
+
if (Number.isInteger(source[key]) && source[key] >= 0) compact[key] = source[key];
|
|
412
|
+
}
|
|
413
|
+
for (const key of ["close_method", "command_line_source"]) {
|
|
414
|
+
const text = normalizeText(source[key]);
|
|
415
|
+
if (text) compact[key] = text;
|
|
416
|
+
}
|
|
417
|
+
return Object.keys(compact).length ? compact : null;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function compactChromeEvidence(value = {}) {
|
|
421
|
+
const source = plainRecord(value);
|
|
422
|
+
if (!Object.keys(source).length) return null;
|
|
423
|
+
const compact = {};
|
|
424
|
+
const host = normalizeText(source.host);
|
|
425
|
+
const targetId = normalizeText(source.target_id);
|
|
426
|
+
const targetUrl = compactChromeEvidenceUrl(source.target_url);
|
|
427
|
+
if (host) compact.host = host;
|
|
428
|
+
if (Number.isInteger(source.port) && source.port > 0) compact.port = source.port;
|
|
429
|
+
if (targetUrl) compact.target_url = targetUrl;
|
|
430
|
+
if (targetId) compact.target_id = targetId;
|
|
431
|
+
const autoLaunch = compactChromeAutoLaunchEvidence(source.auto_launch);
|
|
432
|
+
if (autoLaunch) compact.auto_launch = autoLaunch;
|
|
433
|
+
return Object.keys(compact).length ? compact : null;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function methodEvidenceFlags(methodSummaryValue = {}) {
|
|
437
|
+
const methods = Object.keys(methodSummaryValue);
|
|
438
|
+
return {
|
|
439
|
+
runtime_evaluate_used: methods.some((method) => method.split(".", 1)[0] === "Runtime"),
|
|
440
|
+
script_injection_used: methods.some((method) => (
|
|
441
|
+
FORBIDDEN_CDP_METHODS.has(method.replace(/:retry_after_reconnect$/, ""))
|
|
442
|
+
))
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function buildPersistedCdpEvidence({ meta = {}, persisted = {} } = {}) {
|
|
447
|
+
const hasLiveMethodLog = Array.isArray(meta.methodLog);
|
|
448
|
+
const persistedMethodLog = compactMethodLogForStatus(persisted.method_log);
|
|
449
|
+
const methodLog = hasLiveMethodLog
|
|
450
|
+
? compactMethodLogForStatus(meta.methodLog)
|
|
451
|
+
: persistedMethodLog;
|
|
452
|
+
const persistedSummary = compactMethodSummaryForStatus(persisted.method_summary);
|
|
453
|
+
const summary = hasLiveMethodLog
|
|
454
|
+
? methodSummary(meta.methodLog)
|
|
455
|
+
: Object.keys(persistedSummary).length
|
|
456
|
+
? persistedSummary
|
|
457
|
+
: methodSummary(methodLog);
|
|
458
|
+
const liveTotal = hasLiveMethodLog ? meta.methodLog.length : null;
|
|
459
|
+
const persistedTotal = Number(persisted.method_log_total);
|
|
460
|
+
const methodLogTotal = Number.isInteger(liveTotal)
|
|
461
|
+
? liveTotal
|
|
462
|
+
: Number.isInteger(persistedTotal) && persistedTotal >= methodLog.length
|
|
463
|
+
? persistedTotal
|
|
464
|
+
: methodLog.length;
|
|
465
|
+
const inferredFlags = methodEvidenceFlags(summary);
|
|
466
|
+
return {
|
|
467
|
+
runtime_evaluate_used: hasLiveMethodLog
|
|
468
|
+
? inferredFlags.runtime_evaluate_used
|
|
469
|
+
: persisted.runtime_evaluate_used === true || inferredFlags.runtime_evaluate_used,
|
|
470
|
+
script_injection_used: hasLiveMethodLog
|
|
471
|
+
? inferredFlags.script_injection_used
|
|
472
|
+
: persisted.script_injection_used === true || inferredFlags.script_injection_used,
|
|
473
|
+
method_summary: summary,
|
|
474
|
+
method_log: methodLog,
|
|
475
|
+
method_log_total: methodLogTotal,
|
|
476
|
+
chrome: compactChromeEvidence(meta.chrome || persisted.chrome)
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function attachPersistedCdpEvidence(payload, persisted = {}) {
|
|
481
|
+
return {
|
|
482
|
+
...payload,
|
|
483
|
+
...buildPersistedCdpEvidence({ persisted })
|
|
484
|
+
};
|
|
485
|
+
}
|
|
338
486
|
|
|
339
487
|
function normalizeRunId(runId) {
|
|
340
488
|
const normalized = normalizeText(runId);
|
|
@@ -382,10 +530,12 @@ function readJsonFile(filePath) {
|
|
|
382
530
|
|
|
383
531
|
function recommendSearchParamsForCsv(searchParams = {}) {
|
|
384
532
|
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 : "不限"
|
|
533
|
+
school_tag: Object.prototype.hasOwnProperty.call(searchParams, "school_tag") ? searchParams.school_tag : "不限",
|
|
534
|
+
degree: Object.prototype.hasOwnProperty.call(searchParams, "degree") ? searchParams.degree : "不限",
|
|
535
|
+
gender: Object.prototype.hasOwnProperty.call(searchParams, "gender") ? searchParams.gender : "不限",
|
|
536
|
+
recent_not_view: Object.prototype.hasOwnProperty.call(searchParams, "recent_not_view") ? searchParams.recent_not_view : "不限",
|
|
537
|
+
current_city_only: Object.prototype.hasOwnProperty.call(searchParams, "current_city_only") ? searchParams.current_city_only : false,
|
|
538
|
+
activity_level: Object.prototype.hasOwnProperty.call(searchParams, "activity_level") ? searchParams.activity_level : "不限"
|
|
389
539
|
};
|
|
390
540
|
}
|
|
391
541
|
|
|
@@ -424,10 +574,12 @@ function selectedRecommendJobForCsv(meta = {}, snapshot = {}) {
|
|
|
424
574
|
function buildRecommendCsvInputRows(snapshot = {}, meta = {}) {
|
|
425
575
|
const { context, confirmation, overrides, followUp, shared } = getSnapshotRequestContext(snapshot);
|
|
426
576
|
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
|
|
577
|
+
school_tag: overrides.school_tag ?? confirmation.school_tag_value,
|
|
578
|
+
degree: overrides.degree ?? confirmation.degree_value,
|
|
579
|
+
gender: overrides.gender ?? confirmation.gender_value,
|
|
580
|
+
recent_not_view: overrides.recent_not_view ?? confirmation.recent_not_view_value,
|
|
581
|
+
current_city_only: overrides.current_city_only ?? false,
|
|
582
|
+
activity_level: overrides.activity_level ?? "不限"
|
|
431
583
|
});
|
|
432
584
|
const parsedScreenParams = meta.parsed?.screenParams || {};
|
|
433
585
|
const screenParams = {
|
|
@@ -486,7 +638,7 @@ function secondsBetween(startedAt, endedAt) {
|
|
|
486
638
|
return Math.max(1, Math.round((endMs - startMs) / 1000));
|
|
487
639
|
}
|
|
488
640
|
|
|
489
|
-
function normalizeLegacyProgress(progress = {}, summary = null) {
|
|
641
|
+
function normalizeLegacyProgress(progress = {}, summary = null) {
|
|
490
642
|
const processed = Number.isInteger(progress.processed)
|
|
491
643
|
? progress.processed
|
|
492
644
|
: Number.isInteger(summary?.processed)
|
|
@@ -502,155 +654,254 @@ function normalizeLegacyProgress(progress = {}, summary = null) {
|
|
|
502
654
|
: Number.isInteger(summary?.passed)
|
|
503
655
|
? summary.passed
|
|
504
656
|
: 0;
|
|
505
|
-
|
|
506
|
-
...progress,
|
|
657
|
+
const normalized = {
|
|
658
|
+
...progress,
|
|
507
659
|
processed,
|
|
508
660
|
inspected: processed,
|
|
509
661
|
screened,
|
|
510
662
|
passed,
|
|
511
663
|
skipped: Number.isInteger(progress.skipped) ? progress.skipped : Math.max(processed - passed, 0),
|
|
512
|
-
greet_count: Number.isInteger(progress.greet_count) ? progress.greet_count : 0,
|
|
513
|
-
post_action_clicked: Number.isInteger(progress.post_action_clicked) ? progress.post_action_clicked : 0
|
|
664
|
+
greet_count: Number.isInteger(progress.greet_count) ? progress.greet_count : 0,
|
|
665
|
+
post_action_clicked: Number.isInteger(progress.post_action_clicked) ? progress.post_action_clicked : 0
|
|
514
666
|
};
|
|
667
|
+
if (Object.prototype.hasOwnProperty.call(normalized, "last_list_read_stale_diagnostic")) {
|
|
668
|
+
normalized.last_list_read_stale_diagnostic = compactListReadStaleDiagnosticForStatus(
|
|
669
|
+
normalized.last_list_read_stale_diagnostic
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
if (Object.prototype.hasOwnProperty.call(normalized, "list_read_stale_diagnostics")) {
|
|
673
|
+
normalized.list_read_stale_diagnostics = compactListReadStaleDiagnosticsForStatus(
|
|
674
|
+
normalized.list_read_stale_diagnostics
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
return normalized;
|
|
515
678
|
}
|
|
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
|
-
|
|
679
|
+
|
|
680
|
+
const STATUS_COUNT_KEYS = [
|
|
681
|
+
"processed",
|
|
682
|
+
"screened",
|
|
683
|
+
"detail_opened",
|
|
684
|
+
"passed",
|
|
685
|
+
"skipped",
|
|
686
|
+
"llm_screened",
|
|
687
|
+
"greet_count",
|
|
688
|
+
"post_action_clicked",
|
|
689
|
+
"image_capture_failed",
|
|
690
|
+
"detail_open_failed",
|
|
691
|
+
"transient_recovered",
|
|
692
|
+
"colleague_contact_checked",
|
|
693
|
+
"recent_colleague_contact_skipped",
|
|
694
|
+
"colleague_contact_panel_missing",
|
|
695
|
+
"context_recoveries",
|
|
696
|
+
"human_rest_count",
|
|
697
|
+
"human_rest_ms",
|
|
698
|
+
"card_count",
|
|
699
|
+
"refresh_rounds"
|
|
700
|
+
];
|
|
701
|
+
|
|
702
|
+
function compactPositiveInteger(value, fallback = null) {
|
|
703
|
+
return Number.isInteger(value) && value >= 0 ? value : fallback;
|
|
704
|
+
}
|
|
705
|
+
|
|
543
706
|
function compactSmallRecord(value, fallback = null) {
|
|
544
|
-
const record = plainRecord(value);
|
|
545
|
-
if (!Object.keys(record).length) return fallback;
|
|
707
|
+
const record = plainRecord(value);
|
|
708
|
+
if (!Object.keys(record).length) return fallback;
|
|
546
709
|
return clonePlain(record, fallback);
|
|
547
710
|
}
|
|
548
711
|
|
|
549
|
-
function
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
if (
|
|
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;
|
|
712
|
+
function compactListReadStaleDiagnosticForStatus(value) {
|
|
713
|
+
const source = plainRecord(value);
|
|
714
|
+
if (!Object.keys(source).length) return null;
|
|
715
|
+
const compact = {};
|
|
716
|
+
for (const key of ["code", "message", "phase", "cdp_method", "cdp_at", "recovery_mode"]) {
|
|
717
|
+
const text = normalizeText(source[key] || "");
|
|
718
|
+
if (text) compact[key] = text.slice(0, key === "message" ? 500 : 200);
|
|
571
719
|
}
|
|
572
|
-
|
|
573
|
-
|
|
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
|
-
};
|
|
720
|
+
for (const key of ["cdp_node_id", "cdp_backend_node_id", "attempt"]) {
|
|
721
|
+
if (Number.isInteger(source[key]) && source[key] >= 0) compact[key] = source[key];
|
|
582
722
|
}
|
|
583
|
-
|
|
584
|
-
compact
|
|
585
|
-
stats: clonePlain(summary.viewport_health.stats, {})
|
|
586
|
-
};
|
|
723
|
+
for (const key of ["exhausted", "recovery_applied", "recovered"]) {
|
|
724
|
+
if (typeof source[key] === "boolean") compact[key] = source[key];
|
|
587
725
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
profile: normalizeText(summary.human_behavior.profile || ""),
|
|
592
|
-
restLevel: normalizeText(summary.human_behavior.restLevel || summary.human_behavior.rest_level || "")
|
|
593
|
-
};
|
|
726
|
+
for (const key of ["at", "recovery_applied_at", "recovered_at"]) {
|
|
727
|
+
const text = normalizeText(source[key] || "");
|
|
728
|
+
if (text && Number.isFinite(Date.parse(text))) compact[key] = new Date(text).toISOString();
|
|
594
729
|
}
|
|
595
|
-
if (
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
rest_count: compactPositiveInteger(humanRest.rest_count ?? humanRest.count, null),
|
|
601
|
-
total_pause_ms: compactPositiveInteger(humanRest.total_pause_ms ?? humanRest.pause_ms, null)
|
|
602
|
-
};
|
|
730
|
+
if (Array.isArray(source.cdp_param_keys)) {
|
|
731
|
+
compact.cdp_param_keys = source.cdp_param_keys
|
|
732
|
+
.map((key) => normalizeText(key))
|
|
733
|
+
.filter((key) => /^[A-Za-z][A-Za-z0-9_]*$/.test(key))
|
|
734
|
+
.slice(0, 20);
|
|
603
735
|
}
|
|
604
|
-
return compact;
|
|
736
|
+
return Object.keys(compact).length ? compact : null;
|
|
605
737
|
}
|
|
606
738
|
|
|
607
|
-
function
|
|
608
|
-
if (!
|
|
739
|
+
function compactListReadStaleDiagnosticsForStatus(value) {
|
|
740
|
+
if (!Array.isArray(value)) return [];
|
|
741
|
+
return value
|
|
742
|
+
.map((item) => compactListReadStaleDiagnosticForStatus(item))
|
|
743
|
+
.filter(Boolean)
|
|
744
|
+
.slice(-12);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function compactListReadStaleCheckpointEvent(value) {
|
|
748
|
+
const source = plainRecord(value);
|
|
749
|
+
if (!Object.keys(source).length) return null;
|
|
609
750
|
const compact = {};
|
|
610
|
-
|
|
611
|
-
|
|
751
|
+
const diagnostic = compactListReadStaleDiagnosticForStatus(source.diagnostic || source.trigger);
|
|
752
|
+
if (diagnostic) compact.diagnostic = diagnostic;
|
|
753
|
+
const recentDiagnostics = compactListReadStaleDiagnosticsForStatus(source.recent_diagnostics);
|
|
754
|
+
if (recentDiagnostics.length) compact.recent_diagnostics = recentDiagnostics;
|
|
755
|
+
for (const key of ["recovery_count", "recovery_applied_count"]) {
|
|
756
|
+
if (Number.isInteger(source[key]) && source[key] >= 0) compact[key] = source[key];
|
|
612
757
|
}
|
|
613
|
-
if (
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
compact.results_count = checkpoint.results_count;
|
|
758
|
+
if (source.recovery_error) {
|
|
759
|
+
const recoveryError = compactListReadStaleDiagnosticForStatus(source.recovery_error);
|
|
760
|
+
if (recoveryError) compact.recovery_error = recoveryError;
|
|
617
761
|
}
|
|
618
|
-
|
|
619
|
-
|
|
762
|
+
if (source.candidate_list) {
|
|
763
|
+
const candidateList = plainRecord(source.candidate_list);
|
|
764
|
+
compact.candidate_list = {};
|
|
765
|
+
for (const key of ["seen_count", "queued_count", "processed_count", "read_error_count", "scroll_count"]) {
|
|
766
|
+
if (Number.isInteger(candidateList[key]) && candidateList[key] >= 0) {
|
|
767
|
+
compact.candidate_list[key] = candidateList[key];
|
|
768
|
+
}
|
|
769
|
+
}
|
|
620
770
|
}
|
|
621
|
-
return compact;
|
|
771
|
+
return Object.keys(compact).length ? compact : null;
|
|
622
772
|
}
|
|
623
|
-
|
|
624
|
-
function
|
|
625
|
-
if (!
|
|
626
|
-
const compact = {
|
|
627
|
-
|
|
628
|
-
};
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
773
|
+
|
|
774
|
+
function compactRecommendSummaryForStatus(summary) {
|
|
775
|
+
if (!summary || typeof summary !== "object" || Array.isArray(summary)) return null;
|
|
776
|
+
const compact = {
|
|
777
|
+
domain: normalizeText(summary.domain || "recommend") || "recommend"
|
|
778
|
+
};
|
|
779
|
+
for (const key of STATUS_COUNT_KEYS) {
|
|
780
|
+
if (Number.isInteger(summary[key])) compact[key] = summary[key];
|
|
781
|
+
}
|
|
782
|
+
if (summary.target_url) compact.target_url = normalizeText(summary.target_url);
|
|
783
|
+
if (summary.list_end_reason) compact.list_end_reason = normalizeText(summary.list_end_reason);
|
|
784
|
+
if (Array.isArray(summary.results)) {
|
|
785
|
+
compact.results_count = summary.results.length;
|
|
786
|
+
} else if (Number.isInteger(summary.results_count)) {
|
|
787
|
+
compact.results_count = summary.results_count;
|
|
788
|
+
}
|
|
789
|
+
if (Array.isArray(summary.refresh_attempts)) {
|
|
790
|
+
compact.refresh_attempt_count = summary.refresh_attempts.length;
|
|
791
|
+
}
|
|
792
|
+
if (Array.isArray(summary.context_recoveries)) {
|
|
793
|
+
compact.context_recovery_count = summary.context_recoveries.length;
|
|
794
|
+
} else if (Number.isInteger(summary.context_recoveries)) {
|
|
795
|
+
compact.context_recoveries = summary.context_recoveries;
|
|
796
|
+
}
|
|
797
|
+
if (summary.job_selection) compact.job_selection = compactSmallRecord(summary.job_selection);
|
|
798
|
+
if (summary.page_scope) compact.page_scope = compactSmallRecord(summary.page_scope);
|
|
799
|
+
if (summary.filter) compact.filter = compactSmallRecord(summary.filter);
|
|
800
|
+
if (summary.candidate_list) {
|
|
801
|
+
const candidateList = plainRecord(summary.candidate_list);
|
|
802
|
+
compact.candidate_list = {
|
|
803
|
+
total_count: compactPositiveInteger(candidateList.total_count ?? candidateList.card_count, null),
|
|
804
|
+
visible_count: compactPositiveInteger(candidateList.visible_count, null),
|
|
805
|
+
list_end_reason: normalizeText(candidateList.list_end_reason || "")
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
if (summary.viewport_health?.stats) {
|
|
809
|
+
compact.viewport_health = {
|
|
810
|
+
stats: clonePlain(summary.viewport_health.stats, {})
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
if (summary.human_behavior) {
|
|
814
|
+
compact.human_behavior = {
|
|
815
|
+
enabled: summary.human_behavior.enabled === true,
|
|
816
|
+
profile: normalizeText(summary.human_behavior.profile || ""),
|
|
817
|
+
restLevel: normalizeText(summary.human_behavior.restLevel || summary.human_behavior.rest_level || "")
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
if (summary.human_rest) {
|
|
821
|
+
const humanRest = plainRecord(summary.human_rest);
|
|
822
|
+
compact.human_rest = {
|
|
823
|
+
enabled: humanRest.enabled === true,
|
|
824
|
+
restLevel: normalizeText(humanRest.restLevel || humanRest.rest_level || ""),
|
|
825
|
+
rest_count: compactPositiveInteger(humanRest.rest_count ?? humanRest.count, null),
|
|
826
|
+
total_pause_ms: compactPositiveInteger(humanRest.total_pause_ms ?? humanRest.pause_ms, null)
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
for (const key of [
|
|
830
|
+
"list_read_stale_recovery_attempts",
|
|
831
|
+
"list_read_stale_recovery_applied",
|
|
832
|
+
"list_read_stale_recoveries"
|
|
833
|
+
]) {
|
|
834
|
+
if (Number.isInteger(summary[key]) && summary[key] >= 0) compact[key] = summary[key];
|
|
635
835
|
}
|
|
636
|
-
|
|
836
|
+
if (summary.last_list_read_recovery_mode) {
|
|
837
|
+
compact.last_list_read_recovery_mode = normalizeText(summary.last_list_read_recovery_mode).slice(0, 100);
|
|
838
|
+
}
|
|
839
|
+
const lastStaleDiagnostic = compactListReadStaleDiagnosticForStatus(
|
|
840
|
+
summary.last_list_read_stale_diagnostic
|
|
841
|
+
);
|
|
842
|
+
if (lastStaleDiagnostic) compact.last_list_read_stale_diagnostic = lastStaleDiagnostic;
|
|
843
|
+
const staleDiagnostics = compactListReadStaleDiagnosticsForStatus(summary.list_read_stale_diagnostics);
|
|
844
|
+
if (staleDiagnostics.length) compact.list_read_stale_diagnostics = staleDiagnostics;
|
|
637
845
|
return compact;
|
|
638
846
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
if (!
|
|
642
|
-
const compact = {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
if (
|
|
647
|
-
|
|
847
|
+
|
|
848
|
+
function compactRecommendCheckpointForStatus(checkpoint) {
|
|
849
|
+
if (!checkpoint || typeof checkpoint !== "object" || Array.isArray(checkpoint)) return {};
|
|
850
|
+
const compact = {};
|
|
851
|
+
if (checkpoint.updatedAt || checkpoint.updated_at) {
|
|
852
|
+
compact.updatedAt = normalizeText(checkpoint.updatedAt || checkpoint.updated_at);
|
|
853
|
+
}
|
|
854
|
+
if (Array.isArray(checkpoint.results)) {
|
|
855
|
+
compact.results_count = checkpoint.results.length;
|
|
856
|
+
} else if (Number.isInteger(checkpoint.results_count)) {
|
|
857
|
+
compact.results_count = checkpoint.results_count;
|
|
858
|
+
}
|
|
859
|
+
for (const key of STATUS_COUNT_KEYS) {
|
|
860
|
+
if (Number.isInteger(checkpoint[key])) compact[key] = checkpoint[key];
|
|
861
|
+
}
|
|
862
|
+
for (const key of [
|
|
863
|
+
"list_read_stale_recovery",
|
|
864
|
+
"list_read_stale_recovery_applied",
|
|
865
|
+
"list_read_stale_recovered",
|
|
866
|
+
"list_read_stale_recovery_exhausted",
|
|
867
|
+
"list_read_stale_recovery_failed"
|
|
868
|
+
]) {
|
|
869
|
+
const event = compactListReadStaleCheckpointEvent(checkpoint[key]);
|
|
870
|
+
if (event) compact[key] = event;
|
|
871
|
+
}
|
|
648
872
|
return compact;
|
|
649
873
|
}
|
|
650
|
-
|
|
651
|
-
function
|
|
652
|
-
if (
|
|
653
|
-
|
|
874
|
+
|
|
875
|
+
function compactRecommendResultForStatus(result) {
|
|
876
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) return result || null;
|
|
877
|
+
const compact = {
|
|
878
|
+
...result
|
|
879
|
+
};
|
|
880
|
+
if (Array.isArray(result.results)) {
|
|
881
|
+
compact.results_count = result.results.length;
|
|
882
|
+
compact.results_available = result.results.length > 0;
|
|
883
|
+
} else if (Number.isInteger(result.results_count)) {
|
|
884
|
+
compact.results_count = result.results_count;
|
|
885
|
+
compact.results_available = result.results_available === true || result.results_count > 0;
|
|
886
|
+
}
|
|
887
|
+
delete compact.results;
|
|
888
|
+
return compact;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
export function compactRecommendRunForStatus(run) {
|
|
892
|
+
if (!run || typeof run !== "object" || Array.isArray(run)) return run || null;
|
|
893
|
+
const compact = {
|
|
894
|
+
...run
|
|
895
|
+
};
|
|
896
|
+
if (compact.result) compact.result = compactRecommendResultForStatus(compact.result);
|
|
897
|
+
if (compact.summary) compact.summary = compactRecommendSummaryForStatus(compact.summary);
|
|
898
|
+
if (compact.checkpoint) compact.checkpoint = compactRecommendCheckpointForStatus(compact.checkpoint);
|
|
899
|
+
return compact;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
function completionReason(status) {
|
|
903
|
+
if (status === RUN_STATUS_COMPLETED) return "completed";
|
|
904
|
+
if (status === RUN_STATUS_CANCELED) return "canceled_by_user";
|
|
654
905
|
if (status === RUN_STATUS_FAILED) return "failed";
|
|
655
906
|
if (status === RUN_STATUS_PAUSED) return "paused";
|
|
656
907
|
return null;
|
|
@@ -663,9 +914,9 @@ function normalizeErrorText(error = {}) {
|
|
|
663
914
|
].join(" "));
|
|
664
915
|
}
|
|
665
916
|
|
|
666
|
-
function classifyRecommendRecovery(error = {}) {
|
|
667
|
-
const text = normalizeErrorText(error);
|
|
668
|
-
if (!text) return null;
|
|
917
|
+
function classifyRecommendRecovery(error = {}) {
|
|
918
|
+
const text = normalizeErrorText(error);
|
|
919
|
+
if (!text) return null;
|
|
669
920
|
if (/BOSS_LOGIN_REQUIRED/i.test(text)) return "login_required";
|
|
670
921
|
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
922
|
return "transient_stale_dom";
|
|
@@ -676,13 +927,13 @@ function classifyRecommendRecovery(error = {}) {
|
|
|
676
927
|
if (/(?:aborted|abort|timeout|timed out|fetch failed|socket|network|ECONNRESET|ETIMEDOUT|EAI_AGAIN)/i.test(text)) {
|
|
677
928
|
return "transient_network_or_llm";
|
|
678
929
|
}
|
|
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
|
-
}
|
|
930
|
+
return null;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function isCancelShutdownError(error = {}) {
|
|
934
|
+
const text = normalizeErrorText(error);
|
|
935
|
+
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);
|
|
936
|
+
}
|
|
686
937
|
|
|
687
938
|
function buildConstrainedAgentRecovery(snapshot = {}, meta = {}, artifacts = null) {
|
|
688
939
|
const error = snapshot?.error || snapshot?.result?.error || null;
|
|
@@ -828,14 +1079,14 @@ function buildLegacyRecommendResult(snapshot) {
|
|
|
828
1079
|
effective_scope: meta.normalized?.pageScope || meta.parsed?.page_scope || "recommend"
|
|
829
1080
|
},
|
|
830
1081
|
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
|
-
}
|
|
1082
|
+
screen_params: clonePlain(meta.parsed?.screenParams || {}, {}),
|
|
1083
|
+
target_count_semantics: TARGET_COUNT_SEMANTICS,
|
|
1084
|
+
error: snapshot.error || null,
|
|
1085
|
+
recovery: buildConstrainedAgentRecovery(snapshot, meta, artifacts),
|
|
1086
|
+
results_count: resultRows.length,
|
|
1087
|
+
results_available: resultRows.length > 0
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
839
1090
|
|
|
840
1091
|
function normalizeRunSnapshot(snapshot) {
|
|
841
1092
|
if (!snapshot) return null;
|
|
@@ -858,12 +1109,12 @@ function normalizeRunSnapshot(snapshot) {
|
|
|
858
1109
|
follow_up: clonePlain(metaArgs.follow_up ?? snapshotContext.follow_up ?? null, null),
|
|
859
1110
|
target_count_semantics: TARGET_COUNT_SEMANTICS
|
|
860
1111
|
};
|
|
861
|
-
return {
|
|
862
|
-
...snapshot,
|
|
863
|
-
checkpoint: compactRecommendCheckpointForStatus(snapshot.checkpoint),
|
|
864
|
-
summary: compactRecommendSummaryForStatus(summary),
|
|
865
|
-
progress,
|
|
866
|
-
run_id: snapshot.runId,
|
|
1112
|
+
return {
|
|
1113
|
+
...snapshot,
|
|
1114
|
+
checkpoint: compactRecommendCheckpointForStatus(snapshot.checkpoint),
|
|
1115
|
+
summary: compactRecommendSummaryForStatus(summary),
|
|
1116
|
+
progress,
|
|
1117
|
+
run_id: snapshot.runId,
|
|
867
1118
|
mode: RUN_MODE_ASYNC,
|
|
868
1119
|
state: snapshot.status,
|
|
869
1120
|
stage: snapshot.phase,
|
|
@@ -900,31 +1151,31 @@ function normalizeRunSnapshot(snapshot) {
|
|
|
900
1151
|
};
|
|
901
1152
|
}
|
|
902
1153
|
|
|
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,
|
|
1154
|
+
function mergePersistedControlRequest(normalized, existing) {
|
|
1155
|
+
const control = {
|
|
1156
|
+
...(normalized?.control || {})
|
|
1157
|
+
};
|
|
1158
|
+
const existingControl = plainRecord(existing?.control);
|
|
1159
|
+
if (!normalized) return control;
|
|
1160
|
+
if (TERMINAL_STATUSES.has(normalized.state)) {
|
|
1161
|
+
if (
|
|
1162
|
+
normalized.state === RUN_STATUS_FAILED
|
|
1163
|
+
&& existingControl.cancel_requested === true
|
|
1164
|
+
&& isCancelShutdownError(normalized.error || normalized.result?.error || "")
|
|
1165
|
+
) {
|
|
1166
|
+
return {
|
|
1167
|
+
...control,
|
|
1168
|
+
pause_requested: true,
|
|
1169
|
+
pause_requested_at: existingControl.pause_requested_at || control.pause_requested_at || new Date().toISOString(),
|
|
1170
|
+
pause_requested_by: existingControl.pause_requested_by || control.pause_requested_by || "cancel_recommend_pipeline_run",
|
|
1171
|
+
cancel_requested: true
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
return control;
|
|
1175
|
+
}
|
|
1176
|
+
if (existingControl.cancel_requested === true) {
|
|
1177
|
+
return {
|
|
1178
|
+
...control,
|
|
928
1179
|
pause_requested: true,
|
|
929
1180
|
pause_requested_at: existingControl.pause_requested_at || control.pause_requested_at || new Date().toISOString(),
|
|
930
1181
|
pause_requested_by: existingControl.pause_requested_by || control.pause_requested_by || "cancel_recommend_pipeline_run",
|
|
@@ -947,82 +1198,86 @@ function mergePersistedControlRequest(normalized, existing) {
|
|
|
947
1198
|
pause_requested_by: null,
|
|
948
1199
|
cancel_requested: false
|
|
949
1200
|
};
|
|
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);
|
|
1201
|
+
}
|
|
1202
|
+
return control;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
function cancelErrorFromShutdown(shutdownError = null) {
|
|
1206
|
+
return {
|
|
1207
|
+
code: "PIPELINE_CANCELED",
|
|
1208
|
+
message: "流水线已取消。",
|
|
1209
|
+
retryable: true,
|
|
1210
|
+
shutdown_error: shutdownError || undefined
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function coerceCanceledTerminalSnapshot(normalized, existing) {
|
|
1215
|
+
const existingControl = plainRecord(existing?.control);
|
|
1216
|
+
const shutdownError = normalized?.error || normalized?.result?.error || null;
|
|
1217
|
+
const shouldWrapCanceledShutdown = (
|
|
1218
|
+
normalized
|
|
1219
|
+
&& (
|
|
1220
|
+
(
|
|
1221
|
+
normalized.state === RUN_STATUS_FAILED
|
|
1222
|
+
&& existingControl.cancel_requested === true
|
|
1223
|
+
)
|
|
1224
|
+
|| normalized.state === RUN_STATUS_CANCELED
|
|
1225
|
+
)
|
|
1226
|
+
&& isCancelShutdownError(shutdownError || "")
|
|
1227
|
+
);
|
|
1228
|
+
if (
|
|
1229
|
+
!shouldWrapCanceledShutdown
|
|
1230
|
+
) {
|
|
1231
|
+
return normalized;
|
|
1232
|
+
}
|
|
1233
|
+
const canceledError = cancelErrorFromShutdown(shutdownError);
|
|
1234
|
+
return {
|
|
1235
|
+
...normalized,
|
|
1236
|
+
state: RUN_STATUS_CANCELED,
|
|
1237
|
+
status: RUN_STATUS_CANCELED,
|
|
1238
|
+
last_message: "流水线已取消;取消收尾时浏览器连接已关闭。",
|
|
1239
|
+
control: {
|
|
1240
|
+
pause_requested: false,
|
|
1241
|
+
pause_requested_at: null,
|
|
1242
|
+
pause_requested_by: null,
|
|
1243
|
+
cancel_requested: false
|
|
1244
|
+
},
|
|
1245
|
+
error: canceledError,
|
|
1246
|
+
result: normalized.result ? {
|
|
1247
|
+
...normalized.result,
|
|
1248
|
+
status: "CANCELED",
|
|
1249
|
+
completion_reason: "canceled_by_user",
|
|
1250
|
+
error: canceledError
|
|
1251
|
+
} : {
|
|
1252
|
+
status: "CANCELED",
|
|
1253
|
+
completion_reason: "canceled_by_user",
|
|
1254
|
+
error: canceledError,
|
|
1255
|
+
run_id: normalized.run_id,
|
|
1256
|
+
processed_count: normalized.progress?.processed || 0,
|
|
1257
|
+
screened_count: normalized.progress?.screened || normalized.progress?.processed || 0,
|
|
1258
|
+
passed_count: normalized.progress?.passed || 0
|
|
1259
|
+
}
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
function persistRecommendRunSnapshot(snapshot, {
|
|
1264
|
+
persistActiveCheckpoint = false
|
|
1265
|
+
} = {}) {
|
|
1266
|
+
let normalized = normalizeRunSnapshot(snapshot);
|
|
1267
|
+
if (!normalized?.run_id) return normalized;
|
|
1268
|
+
const artifacts = getRecommendRunArtifacts(normalized.run_id);
|
|
1269
|
+
if (!artifacts) return normalized;
|
|
1270
|
+
const existing = readJsonFile(artifacts.run_state_path);
|
|
1020
1271
|
normalized.control = mergePersistedControlRequest(normalized, existing);
|
|
1021
1272
|
normalized = coerceCanceledTerminalSnapshot(normalized, existing);
|
|
1022
1273
|
if (persistActiveCheckpoint) {
|
|
1023
1274
|
persistRecommendCheckpointSnapshot(snapshot);
|
|
1024
1275
|
}
|
|
1025
|
-
const
|
|
1276
|
+
const cdpEvidence = buildPersistedCdpEvidence({
|
|
1277
|
+
meta: getRecommendRunMeta(normalized.run_id),
|
|
1278
|
+
persisted: existing || {}
|
|
1279
|
+
});
|
|
1280
|
+
const payload = {
|
|
1026
1281
|
run_id: normalized.run_id,
|
|
1027
1282
|
mode: normalized.mode,
|
|
1028
1283
|
state: normalized.state,
|
|
@@ -1038,65 +1293,67 @@ function persistRecommendRunSnapshot(snapshot, {
|
|
|
1038
1293
|
context: normalized.context,
|
|
1039
1294
|
control: normalized.control,
|
|
1040
1295
|
resume: normalized.resume,
|
|
1041
|
-
error: normalized.error,
|
|
1042
|
-
recovery: normalized.recovery,
|
|
1043
|
-
result: normalized.result,
|
|
1044
|
-
summary: normalized.summary,
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
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
|
-
}
|
|
1296
|
+
error: normalized.error,
|
|
1297
|
+
recovery: normalized.recovery,
|
|
1298
|
+
result: normalized.result,
|
|
1299
|
+
summary: normalized.summary,
|
|
1300
|
+
checkpoint: normalized.checkpoint,
|
|
1301
|
+
artifacts: normalized.artifacts,
|
|
1302
|
+
...cdpEvidence
|
|
1069
1303
|
};
|
|
1070
|
-
writeJsonAtomic(artifacts.run_state_path,
|
|
1071
|
-
return
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
function
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1304
|
+
writeJsonAtomic(artifacts.run_state_path, payload);
|
|
1305
|
+
return normalized;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
function patchPersistedRecommendRunControl(runId, controlPatch = {}, {
|
|
1309
|
+
message = ""
|
|
1310
|
+
} = {}) {
|
|
1311
|
+
const artifacts = getRecommendRunArtifacts(runId);
|
|
1312
|
+
if (!artifacts) return null;
|
|
1313
|
+
const current = readJsonFile(artifacts.run_state_path);
|
|
1314
|
+
const state = normalizeText(current?.state || current?.status || "");
|
|
1315
|
+
if (!current || TERMINAL_STATUSES.has(state)) return null;
|
|
1316
|
+
const now = new Date().toISOString();
|
|
1317
|
+
const patched = {
|
|
1318
|
+
...current,
|
|
1319
|
+
updated_at: now,
|
|
1320
|
+
heartbeat_at: current.heartbeat_at || now,
|
|
1321
|
+
last_message: message || current.last_message || "",
|
|
1322
|
+
control: {
|
|
1323
|
+
...(current.control || {}),
|
|
1324
|
+
...controlPatch
|
|
1325
|
+
}
|
|
1326
|
+
};
|
|
1327
|
+
writeJsonAtomic(artifacts.run_state_path, patched);
|
|
1328
|
+
return patched;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function reconcilePersistedRecommendRunIfNeeded(persisted) {
|
|
1332
|
+
if (!persisted || typeof persisted !== "object") return persisted;
|
|
1333
|
+
const persistedState = normalizeText(persisted.state || persisted.status);
|
|
1334
|
+
if (TERMINAL_STATUSES.has(persistedState)) return persisted;
|
|
1078
1335
|
if (isProcessAlive(persisted.pid)) return persisted;
|
|
1079
1336
|
|
|
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({
|
|
1337
|
+
const runId = normalizeRunId(persisted.run_id || persisted.runId);
|
|
1338
|
+
const artifacts = getRecommendRunArtifacts(runId);
|
|
1339
|
+
const checkpoint = artifacts?.checkpoint_path ? readJsonFile(artifacts.checkpoint_path) : null;
|
|
1340
|
+
const now = new Date().toISOString();
|
|
1341
|
+
const cancelRequested = persisted.control?.cancel_requested === true;
|
|
1342
|
+
const processExitedError = {
|
|
1343
|
+
code: "RUN_PROCESS_EXITED",
|
|
1344
|
+
message: `检测到推荐任务进程已退出(pid=${persisted.pid || "unknown"})。`,
|
|
1345
|
+
retryable: true
|
|
1346
|
+
};
|
|
1347
|
+
const error = cancelRequested
|
|
1348
|
+
? cancelErrorFromShutdown(processExitedError)
|
|
1349
|
+
: {
|
|
1350
|
+
...processExitedError,
|
|
1351
|
+
message: `检测到推荐任务进程已退出(pid=${persisted.pid || "unknown"}),已自动标记为失败。`
|
|
1352
|
+
};
|
|
1353
|
+
return persistRecommendRunSnapshot({
|
|
1097
1354
|
runId,
|
|
1098
1355
|
name: persisted.name || runId,
|
|
1099
|
-
status: cancelRequested ? RUN_STATUS_CANCELED : RUN_STATUS_FAILED,
|
|
1356
|
+
status: cancelRequested ? RUN_STATUS_CANCELED : RUN_STATUS_FAILED,
|
|
1100
1357
|
phase: persisted.stage || persisted.phase || "recommend:orphaned",
|
|
1101
1358
|
progress: persisted.progress || {},
|
|
1102
1359
|
context: persisted.context || {},
|
|
@@ -1122,11 +1379,10 @@ function attachMethodEvidence(payload, runId) {
|
|
|
1122
1379
|
assertNoForbiddenCdpCalls(methodLog);
|
|
1123
1380
|
return {
|
|
1124
1381
|
...payload,
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
chrome: meta.chrome || null
|
|
1382
|
+
...buildPersistedCdpEvidence({
|
|
1383
|
+
meta,
|
|
1384
|
+
persisted: readRecommendRunState(runId) || {}
|
|
1385
|
+
})
|
|
1130
1386
|
};
|
|
1131
1387
|
}
|
|
1132
1388
|
|
|
@@ -1178,17 +1434,17 @@ async function readRecommendJobOptionsFromSession(session) {
|
|
|
1178
1434
|
};
|
|
1179
1435
|
}
|
|
1180
1436
|
|
|
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;
|
|
1437
|
+
export async function listRecommendJobsTool({ workspaceRoot = "", args = {} } = {}) {
|
|
1438
|
+
const routeGuard = guardRecommendRoute(args);
|
|
1439
|
+
if (routeGuard) {
|
|
1440
|
+
return {
|
|
1441
|
+
...routeGuard,
|
|
1442
|
+
stage: "recommend_job_list",
|
|
1443
|
+
message: "list_recommend_jobs is recommend-page only. For chat-only job lists, call list_boss_chat_jobs or prepare_boss_chat_run."
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|
|
1447
|
+
const host = normalizeText(args.host) || DEFAULT_RECOMMEND_HOST;
|
|
1192
1448
|
const port = parsePositiveInteger(
|
|
1193
1449
|
args.port,
|
|
1194
1450
|
configResolution.ok ? configResolution.config.debugPort : DEFAULT_RECOMMEND_PORT
|
|
@@ -1462,146 +1718,146 @@ async function connectRecommendChromeSession({
|
|
|
1462
1718
|
};
|
|
1463
1719
|
}
|
|
1464
1720
|
|
|
1465
|
-
function parseRecommendPipelineRequest(args = {}) {
|
|
1466
|
-
return parseRecommendInstruction({
|
|
1467
|
-
instruction: args.instruction,
|
|
1721
|
+
function parseRecommendPipelineRequest(args = {}) {
|
|
1722
|
+
return parseRecommendInstruction({
|
|
1723
|
+
instruction: args.instruction,
|
|
1468
1724
|
confirmation: args.confirmation,
|
|
1469
1725
|
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");
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
function readOwn(source, keys = []) {
|
|
1730
|
+
if (!source || typeof source !== "object" || Array.isArray(source)) return undefined;
|
|
1731
|
+
for (const key of keys) {
|
|
1732
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) return source[key];
|
|
1733
|
+
}
|
|
1734
|
+
return undefined;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
function getExplicitRestLevel(args = {}) {
|
|
1738
|
+
const behavior = readOwn(args, ["human_behavior", "humanBehavior"]);
|
|
1739
|
+
const raw = readOwn(behavior, ["restLevel", "rest_level"]);
|
|
1740
|
+
const normalized = normalizeText(raw).toLowerCase();
|
|
1741
|
+
return {
|
|
1742
|
+
raw: raw ?? null,
|
|
1743
|
+
restLevel: REST_LEVEL_SET.has(normalized) ? normalized : null,
|
|
1744
|
+
valid: REST_LEVEL_SET.has(normalized),
|
|
1745
|
+
missing: raw === undefined || raw === null || normalizeText(raw) === ""
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
function buildReviewScreenParams(parsed) {
|
|
1750
|
+
return {
|
|
1751
|
+
...(parsed.screenParams || {}),
|
|
1752
|
+
criteria: parsed.screenParams?.criteria || null,
|
|
1753
|
+
criteria_normalized: parsed.criteria_normalized || null,
|
|
1754
|
+
target_count: parsed.screenParams?.target_count ?? parsed.proposed_target_count ?? null,
|
|
1755
|
+
post_action: parsed.screenParams?.post_action || parsed.proposed_post_action || null,
|
|
1756
|
+
max_greet_count: parsed.screenParams?.max_greet_count ?? parsed.proposed_max_greet_count ?? null
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
function buildReviewPageScope(parsed) {
|
|
1761
|
+
return parsed.page_scope || parsed.proposed_page_scope || "recommend";
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
function buildReviewJob(args = {}) {
|
|
1765
|
+
return normalizeText(args.confirmation?.job_value || args.overrides?.job || "") || null;
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
function buildScheduleReview(args = {}) {
|
|
1769
|
+
const scheduleRunAt = normalizeText(args.schedule_run_at || args.scheduleRunAt || args.run_at || args.runAt);
|
|
1770
|
+
const scheduleDelayMinutes = args.schedule_delay_minutes ?? args.scheduleDelayMinutes;
|
|
1771
|
+
const scheduleDelaySeconds = args.schedule_delay_seconds ?? args.scheduleDelaySeconds;
|
|
1772
|
+
if (!scheduleRunAt && scheduleDelayMinutes === undefined && scheduleDelaySeconds === undefined) return null;
|
|
1773
|
+
return {
|
|
1774
|
+
schedule_run_at: scheduleRunAt || null,
|
|
1775
|
+
schedule_delay_minutes: scheduleDelayMinutes ?? null,
|
|
1776
|
+
schedule_delay_seconds: scheduleDelaySeconds ?? null
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
function buildRequiredConfirmations(parsed, args = {}) {
|
|
1781
|
+
const required = [];
|
|
1782
|
+
if (parsed.needs_page_confirmation) required.push("page_scope");
|
|
1527
1783
|
if (parsed.needs_filters_confirmation) required.push("filters");
|
|
1528
1784
|
if (parsed.needs_school_tag_confirmation) required.push("school_tag");
|
|
1529
1785
|
if (parsed.needs_degree_confirmation) required.push("degree");
|
|
1530
1786
|
if (parsed.needs_gender_confirmation) required.push("gender");
|
|
1531
1787
|
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
|
-
}
|
|
1788
|
+
if (parsed.needs_criteria_confirmation) required.push("criteria");
|
|
1789
|
+
if (parsed.needs_target_count_confirmation) required.push("target_count");
|
|
1790
|
+
if (parsed.needs_post_action_confirmation) required.push("post_action");
|
|
1791
|
+
if (parsed.needs_skip_recent_colleague_contacted_confirmation) required.push("skip_recent_colleague_contacted");
|
|
1792
|
+
if ((parsed.suspicious_fields || []).length) required.push("suspicious_fields");
|
|
1793
|
+
|
|
1794
|
+
const confirmation = args.confirmation || {};
|
|
1795
|
+
const jobValue = normalizeText(confirmation.job_value || args.overrides?.job || "");
|
|
1796
|
+
if (!jobValue) required.push("job");
|
|
1797
|
+
const restLevel = getExplicitRestLevel(args);
|
|
1798
|
+
if (!restLevel.valid) required.push("rest_level");
|
|
1799
|
+
const blocksFinalReview = required.some((field) => field !== "rest_level");
|
|
1800
|
+
if (confirmation.final_confirmed !== true && !blocksFinalReview) required.push("final_review");
|
|
1801
|
+
return Array.from(new Set(required));
|
|
1802
|
+
}
|
|
1547
1803
|
|
|
1548
|
-
function buildJobPendingQuestion(args = {}) {
|
|
1549
|
-
const value = normalizeText(args.confirmation?.job_value || args.overrides?.job || "");
|
|
1804
|
+
function buildJobPendingQuestion(args = {}) {
|
|
1805
|
+
const value = normalizeText(args.confirmation?.job_value || args.overrides?.job || "");
|
|
1550
1806
|
return {
|
|
1551
1807
|
field: "job",
|
|
1552
1808
|
question: "请确认推荐页岗位。CDP-only rewrite 会先切换到该岗位,再按所选页面范围执行筛选。",
|
|
1553
1809
|
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,
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
function buildRestLevelPendingQuestion(args = {}) {
|
|
1814
|
+
const restLevel = getExplicitRestLevel(args);
|
|
1815
|
+
return {
|
|
1816
|
+
field: "rest_level",
|
|
1817
|
+
question: restLevel.missing
|
|
1818
|
+
? "请确认本次运行休息强度 rest_level。"
|
|
1819
|
+
: "rest_level 只能是 low / medium / high,请重新确认本次运行休息强度。",
|
|
1820
|
+
value: restLevel.restLevel || restLevel.raw || null,
|
|
1821
|
+
options: REST_LEVEL_OPTIONS.map((value) => ({
|
|
1822
|
+
label: value,
|
|
1823
|
+
value
|
|
1824
|
+
}))
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
function buildSuspiciousFieldsQuestion(parsed) {
|
|
1829
|
+
return {
|
|
1830
|
+
field: "suspicious_fields",
|
|
1831
|
+
question: "检测到需要修正或明确确认的异常字段,请先修正后再启动。",
|
|
1832
|
+
value: parsed.suspicious_fields || []
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
function buildFinalReviewQuestion(parsed, args = {}) {
|
|
1837
|
+
const restLevel = getExplicitRestLevel(args);
|
|
1838
|
+
return {
|
|
1839
|
+
field: "final_review",
|
|
1840
|
+
question: "请最终确认本次推荐页筛选参数无误;确认后设置 final_confirmed=true 即可启动或创建定时任务。",
|
|
1841
|
+
value: {
|
|
1842
|
+
page_scope: buildReviewPageScope(parsed),
|
|
1843
|
+
job: buildReviewJob(args),
|
|
1844
|
+
search_params: parsed.searchParams,
|
|
1845
|
+
screen_params: buildReviewScreenParams(parsed),
|
|
1846
|
+
human_behavior: {
|
|
1847
|
+
restLevel: restLevel.restLevel || null
|
|
1848
|
+
},
|
|
1849
|
+
schedule: buildScheduleReview(args)
|
|
1850
|
+
}
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
function buildNeedInputResponse(parsed, args = {}) {
|
|
1855
|
+
return {
|
|
1856
|
+
status: "NEED_INPUT",
|
|
1857
|
+
missing_fields: parsed.missing_fields,
|
|
1858
|
+
required_confirmations: buildRequiredConfirmations(parsed, args),
|
|
1859
|
+
search_params: parsed.searchParams,
|
|
1860
|
+
screen_params: parsed.screenParams,
|
|
1605
1861
|
pending_questions: parsed.pending_questions,
|
|
1606
1862
|
review: parsed.review,
|
|
1607
1863
|
error: {
|
|
@@ -1612,36 +1868,36 @@ function buildNeedInputResponse(parsed, args = {}) {
|
|
|
1612
1868
|
};
|
|
1613
1869
|
}
|
|
1614
1870
|
|
|
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 || {}),
|
|
1871
|
+
function buildNeedConfirmationResponse(parsed, args, requiredConfirmations) {
|
|
1872
|
+
const pending = [...(parsed.pending_questions || [])];
|
|
1873
|
+
if (requiredConfirmations.includes("suspicious_fields") && !pending.some((item) => item.field === "suspicious_fields")) {
|
|
1874
|
+
pending.push(buildSuspiciousFieldsQuestion(parsed));
|
|
1875
|
+
}
|
|
1876
|
+
if (requiredConfirmations.includes("job") && !pending.some((item) => item.field === "job")) {
|
|
1877
|
+
pending.push(buildJobPendingQuestion(args));
|
|
1878
|
+
}
|
|
1879
|
+
if (requiredConfirmations.includes("rest_level") && !pending.some((item) => item.field === "rest_level")) {
|
|
1880
|
+
pending.push(buildRestLevelPendingQuestion(args));
|
|
1881
|
+
}
|
|
1882
|
+
if (requiredConfirmations.includes("final_review") && !pending.some((item) => item.field === "final_review")) {
|
|
1883
|
+
pending.push(buildFinalReviewQuestion(parsed, args));
|
|
1884
|
+
}
|
|
1885
|
+
return {
|
|
1886
|
+
status: "NEED_CONFIRMATION",
|
|
1887
|
+
required_confirmations: requiredConfirmations,
|
|
1888
|
+
page_scope: buildReviewPageScope(parsed),
|
|
1889
|
+
search_params: parsed.searchParams,
|
|
1890
|
+
screen_params: buildReviewScreenParams(parsed),
|
|
1891
|
+
pending_questions: pending,
|
|
1892
|
+
review: {
|
|
1893
|
+
...(parsed.review || {}),
|
|
1638
1894
|
required_confirmations: requiredConfirmations
|
|
1639
1895
|
}
|
|
1640
1896
|
};
|
|
1641
1897
|
}
|
|
1642
|
-
|
|
1643
|
-
function evaluateRecommendPipelineGate(parsed, args = {}) {
|
|
1644
|
-
if (parsed.missing_fields?.length) return buildNeedInputResponse(parsed, args);
|
|
1898
|
+
|
|
1899
|
+
function evaluateRecommendPipelineGate(parsed, args = {}) {
|
|
1900
|
+
if (parsed.missing_fields?.length) return buildNeedInputResponse(parsed, args);
|
|
1645
1901
|
const requiredConfirmations = buildRequiredConfirmations(parsed, args);
|
|
1646
1902
|
if (requiredConfirmations.length) {
|
|
1647
1903
|
return buildNeedConfirmationResponse(parsed, args, requiredConfirmations);
|
|
@@ -1679,7 +1935,13 @@ function buildRecommendFilter(parsed, args = {}) {
|
|
|
1679
1935
|
return { enabled: false };
|
|
1680
1936
|
}
|
|
1681
1937
|
|
|
1682
|
-
const groups = [
|
|
1938
|
+
const groups = [{
|
|
1939
|
+
group: "activity",
|
|
1940
|
+
labels: [parsed.searchParams?.activity_level || "不限"],
|
|
1941
|
+
selectAllLabels: false,
|
|
1942
|
+
allowUnlimited: true,
|
|
1943
|
+
verifySticky: true
|
|
1944
|
+
}];
|
|
1683
1945
|
const recentNotView = withoutUnlimited(parsed.searchParams?.recent_not_view);
|
|
1684
1946
|
if (recentNotView.length) {
|
|
1685
1947
|
groups.push({
|
|
@@ -1716,8 +1978,12 @@ function buildRecommendFilter(parsed, args = {}) {
|
|
|
1716
1978
|
});
|
|
1717
1979
|
}
|
|
1718
1980
|
|
|
1719
|
-
return
|
|
1720
|
-
|
|
1981
|
+
return {
|
|
1982
|
+
enabled: true,
|
|
1983
|
+
currentCityOnly: parsed.searchParams?.current_city_only === true,
|
|
1984
|
+
filterGroups: groups
|
|
1985
|
+
};
|
|
1986
|
+
}
|
|
1721
1987
|
|
|
1722
1988
|
function normalizeRecommendStartInput(args = {}, parsed, configResolution = null) {
|
|
1723
1989
|
const confirmation = args.confirmation || {};
|
|
@@ -1740,24 +2006,24 @@ function normalizeRecommendStartInput(args = {}, parsed, configResolution = null
|
|
|
1740
2006
|
targetCount,
|
|
1741
2007
|
job: normalizeText(confirmation.job_value || overrides.job || ""),
|
|
1742
2008
|
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
|
-
}
|
|
2009
|
+
filter: buildRecommendFilter(parsed, args),
|
|
2010
|
+
postAction: parsed.screenParams?.post_action || "none",
|
|
2011
|
+
maxGreetCount: Number.isInteger(parsed.screenParams?.max_greet_count)
|
|
2012
|
+
? parsed.screenParams.max_greet_count
|
|
2013
|
+
: null,
|
|
2014
|
+
skipRecentColleagueContacted: parsed.screenParams?.skip_recent_colleague_contacted !== false,
|
|
2015
|
+
colleagueContactWindowDays: 14,
|
|
2016
|
+
screeningMode: normalizeScreeningModeArg(args)
|
|
2017
|
+
};
|
|
2018
|
+
}
|
|
1753
2019
|
|
|
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 {
|
|
2020
|
+
function getRunOptions(args, parsed, normalized, session, configResolution = null) {
|
|
2021
|
+
const slowLive = args.slow_live === true;
|
|
2022
|
+
const executePostAction = args.dry_run_post_action === true
|
|
2023
|
+
? false
|
|
2024
|
+
: args.execute_post_action !== false;
|
|
2025
|
+
const humanBehavior = resolveHumanBehaviorForRun(args, configResolution?.config || {});
|
|
2026
|
+
return {
|
|
1761
2027
|
client: session.client,
|
|
1762
2028
|
targetUrl: RECOMMEND_TARGET_URL,
|
|
1763
2029
|
criteria: normalized.criteria,
|
|
@@ -1787,8 +2053,8 @@ function getRunOptions(args, parsed, normalized, session, configResolution = nul
|
|
|
1787
2053
|
executePostAction,
|
|
1788
2054
|
actionTimeoutMs: parsePositiveInteger(args.action_timeout_ms, slowLive ? 12000 : 8000),
|
|
1789
2055
|
actionIntervalMs: parsePositiveInteger(args.action_interval_ms, 500),
|
|
1790
|
-
actionAfterClickDelayMs: parseNonNegativeInteger(args.action_after_click_delay_ms, slowLive ? 1200 : 900),
|
|
1791
|
-
screeningMode: normalized.screeningMode,
|
|
2056
|
+
actionAfterClickDelayMs: parseNonNegativeInteger(args.action_after_click_delay_ms, slowLive ? 1200 : 900),
|
|
2057
|
+
screeningMode: normalized.screeningMode,
|
|
1792
2058
|
llmConfig: normalized.screeningMode === "llm" && configResolution?.ok ? {
|
|
1793
2059
|
...configResolution.config
|
|
1794
2060
|
} : null,
|
|
@@ -1800,23 +2066,36 @@ function getRunOptions(args, parsed, normalized, session, configResolution = nul
|
|
|
1800
2066
|
args.llm_image_limit,
|
|
1801
2067
|
parsePositiveInteger(configResolution?.config?.llmImageLimit || configResolution?.config?.imageLimit, 8)
|
|
1802
2068
|
),
|
|
1803
|
-
llmImageDetail: normalizeText(
|
|
1804
|
-
args.llm_image_detail || configResolution?.config?.llmImageDetail || configResolution?.config?.imageDetail
|
|
1805
|
-
) || "low",
|
|
1806
|
-
imageOutputDir: resolveBossConfiguredOutputDir("", getRunsDir()),
|
|
1807
|
-
humanRestEnabled: humanBehavior.restEnabled,
|
|
2069
|
+
llmImageDetail: normalizeText(
|
|
2070
|
+
args.llm_image_detail || configResolution?.config?.llmImageDetail || configResolution?.config?.imageDetail
|
|
2071
|
+
) || "low",
|
|
2072
|
+
imageOutputDir: resolveBossConfiguredOutputDir("", getRunsDir()),
|
|
2073
|
+
humanRestEnabled: humanBehavior.restEnabled,
|
|
1808
2074
|
humanBehavior,
|
|
1809
2075
|
skipRecentColleagueContacted: normalized.skipRecentColleagueContacted,
|
|
1810
2076
|
colleagueContactWindowDays: normalized.colleagueContactWindowDays,
|
|
2077
|
+
debugTestMode: isDebugTestMode(args),
|
|
2078
|
+
debugForceListEndAfterProcessed: parsePositiveInteger(
|
|
2079
|
+
args.debug_force_list_end_after_processed,
|
|
2080
|
+
null
|
|
2081
|
+
),
|
|
2082
|
+
debugForceContextRecoveryAfterProcessed: parsePositiveInteger(
|
|
2083
|
+
args.debug_force_context_recovery_after_processed,
|
|
2084
|
+
null
|
|
2085
|
+
),
|
|
2086
|
+
debugForceCdpReconnectAfterProcessed: parsePositiveInteger(
|
|
2087
|
+
args.debug_force_cdp_reconnect_after_processed,
|
|
2088
|
+
null
|
|
2089
|
+
),
|
|
1811
2090
|
name: "mcp-recommend-pipeline-run",
|
|
1812
|
-
parsed
|
|
1813
|
-
};
|
|
1814
|
-
}
|
|
2091
|
+
parsed
|
|
2092
|
+
};
|
|
2093
|
+
}
|
|
1815
2094
|
|
|
1816
|
-
function prepareRecommendPipelineStart(args = {}, { workspaceRoot = "" } = {}) {
|
|
1817
|
-
const routeGuard = guardRecommendRoute(args);
|
|
1818
|
-
if (routeGuard) return { response: routeGuard };
|
|
1819
|
-
const parsed = parseRecommendPipelineRequest(args);
|
|
2095
|
+
function prepareRecommendPipelineStart(args = {}, { workspaceRoot = "" } = {}) {
|
|
2096
|
+
const routeGuard = guardRecommendRoute(args);
|
|
2097
|
+
if (routeGuard) return { response: routeGuard };
|
|
2098
|
+
const parsed = parseRecommendPipelineRequest(args);
|
|
1820
2099
|
const gate = evaluateRecommendPipelineGate(parsed, args);
|
|
1821
2100
|
if (gate) return { response: gate };
|
|
1822
2101
|
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|
|
@@ -2002,72 +2281,72 @@ async function startRecommendPipelineRunInternal(args = {}, { workspaceRoot = ""
|
|
|
2002
2281
|
};
|
|
2003
2282
|
}
|
|
2004
2283
|
|
|
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,
|
|
2284
|
+
export function prepareRecommendPipelineRunTool({ workspaceRoot = "", args = {} } = {}) {
|
|
2285
|
+
const prepared = prepareRecommendPipelineStart(args, { workspaceRoot });
|
|
2286
|
+
if (prepared.response) {
|
|
2287
|
+
return {
|
|
2288
|
+
...prepared.response,
|
|
2289
|
+
cron_ready: false
|
|
2290
|
+
};
|
|
2291
|
+
}
|
|
2292
|
+
const { parsed, normalized } = prepared;
|
|
2293
|
+
const nextAction = {
|
|
2294
|
+
immediate_run: {
|
|
2295
|
+
recommended_next_tool: "start_recommend_pipeline_run",
|
|
2296
|
+
alternate_next_tool: "run_recommend",
|
|
2297
|
+
same_arguments: true,
|
|
2298
|
+
native_mcp_required: true
|
|
2299
|
+
},
|
|
2300
|
+
scheduled_run: {
|
|
2301
|
+
recommended_next_tool: "schedule_recommend_pipeline_run",
|
|
2302
|
+
same_arguments: true,
|
|
2303
|
+
requires_schedule_field: true
|
|
2304
|
+
},
|
|
2305
|
+
do_not_call_prepare_again: true,
|
|
2306
|
+
do_not_use_cli_fallback_when_mcp_tools_available: true,
|
|
2307
|
+
do_not_use_terminal_or_shell: true,
|
|
2308
|
+
forbidden_fallbacks: [
|
|
2309
|
+
"run_command",
|
|
2310
|
+
"terminal",
|
|
2311
|
+
"shell",
|
|
2312
|
+
"CLI detached fallback",
|
|
2313
|
+
"manual JSON-RPC over shell",
|
|
2314
|
+
"npx --stdio"
|
|
2315
|
+
]
|
|
2316
|
+
};
|
|
2317
|
+
const agentGuidance = {
|
|
2318
|
+
host_has_native_mcp: true,
|
|
2319
|
+
native_mcp_required_after_prepare: true,
|
|
2320
|
+
proof: "This prepare_recommend_pipeline_run response was returned by the MCP server, so the host can call MCP tools from this server.",
|
|
2321
|
+
immediate_run: {
|
|
2322
|
+
tool: nextAction.immediate_run.recommended_next_tool,
|
|
2323
|
+
alternate_tool: nextAction.immediate_run.alternate_next_tool,
|
|
2324
|
+
same_arguments: true
|
|
2325
|
+
},
|
|
2326
|
+
scheduled_run: {
|
|
2327
|
+
tool: nextAction.scheduled_run.recommended_next_tool,
|
|
2328
|
+
same_arguments: true
|
|
2329
|
+
},
|
|
2330
|
+
trae_cn: {
|
|
2331
|
+
next_step: "Call boss-recommend/start_recommend_pipeline_run or boss-recommend/run_recommend through the native MCP tool interface.",
|
|
2332
|
+
never_use_terminal_fallback_after_prepare: true
|
|
2333
|
+
},
|
|
2334
|
+
forbidden_when_mcp_tools_are_available: nextAction.forbidden_fallbacks
|
|
2335
|
+
};
|
|
2336
|
+
return {
|
|
2337
|
+
status: "READY",
|
|
2338
|
+
cron_ready: true,
|
|
2339
|
+
prepared_only: true,
|
|
2340
|
+
run_started: false,
|
|
2341
|
+
recommended_next_tool: nextAction.immediate_run.recommended_next_tool,
|
|
2342
|
+
alternate_next_tool: nextAction.immediate_run.alternate_next_tool,
|
|
2343
|
+
next_action: nextAction,
|
|
2344
|
+
agent_guidance: agentGuidance,
|
|
2345
|
+
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.",
|
|
2346
|
+
review: parsed.review,
|
|
2347
|
+
post_action: {
|
|
2348
|
+
requested: normalized.postAction,
|
|
2349
|
+
execute_post_action: args.dry_run_post_action === true ? false : args.execute_post_action !== false,
|
|
2071
2350
|
max_greet_count: normalized.maxGreetCount
|
|
2072
2351
|
},
|
|
2073
2352
|
target_count_semantics: TARGET_COUNT_SEMANTICS
|
|
@@ -2093,29 +2372,26 @@ export function getRecommendPipelineRunTool({ args = {} } = {}) {
|
|
|
2093
2372
|
};
|
|
2094
2373
|
}
|
|
2095
2374
|
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 {
|
|
2375
|
+
const run = recommendRunService.getRecommendRun(runId);
|
|
2376
|
+
const normalizedRun = persistRecommendRunSnapshot(run);
|
|
2377
|
+
return attachMethodEvidence({
|
|
2378
|
+
status: "RUN_STATUS",
|
|
2379
|
+
run: compactRecommendRunForStatus(normalizedRun)
|
|
2380
|
+
}, runId);
|
|
2381
|
+
} catch {
|
|
2103
2382
|
const persisted = readRecommendRunState(runId);
|
|
2104
2383
|
if (persisted) {
|
|
2105
2384
|
const reconciled = compactRecommendRunForStatus(reconcilePersistedRecommendRunIfNeeded(persisted));
|
|
2106
|
-
|
|
2385
|
+
const persistedEvidence = readRecommendRunState(runId) || persisted;
|
|
2386
|
+
return attachPersistedCdpEvidence({
|
|
2107
2387
|
status: "RUN_STATUS",
|
|
2108
2388
|
run: reconciled,
|
|
2109
|
-
persistence: {
|
|
2110
|
-
source: "disk",
|
|
2111
|
-
active_control_available: false,
|
|
2112
|
-
stale_process_reconciled: reconciled?.state !== persisted.state
|
|
2113
|
-
}
|
|
2114
|
-
|
|
2115
|
-
method_summary: {},
|
|
2116
|
-
method_log: [],
|
|
2117
|
-
chrome: null
|
|
2118
|
-
};
|
|
2389
|
+
persistence: {
|
|
2390
|
+
source: "disk",
|
|
2391
|
+
active_control_available: false,
|
|
2392
|
+
stale_process_reconciled: reconciled?.state !== persisted.state
|
|
2393
|
+
}
|
|
2394
|
+
}, persistedEvidence);
|
|
2119
2395
|
}
|
|
2120
2396
|
return {
|
|
2121
2397
|
status: "FAILED",
|
|
@@ -2133,40 +2409,36 @@ export function pauseRecommendPipelineRunTool({ args = {} } = {}) {
|
|
|
2133
2409
|
try {
|
|
2134
2410
|
const before = recommendRunService.getRecommendRun(runId);
|
|
2135
2411
|
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);
|
|
2412
|
+
const normalizedBefore = persistRecommendRunSnapshot(before);
|
|
2413
|
+
return attachMethodEvidence({
|
|
2414
|
+
status: "PAUSE_IGNORED",
|
|
2415
|
+
run: compactRecommendRunForStatus(normalizedBefore),
|
|
2416
|
+
message: "目标任务已结束,无需暂停。"
|
|
2417
|
+
}, runId);
|
|
2142
2418
|
}
|
|
2143
2419
|
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);
|
|
2420
|
+
const normalizedBefore = persistRecommendRunSnapshot(before);
|
|
2421
|
+
return attachMethodEvidence({
|
|
2422
|
+
status: "PAUSE_IGNORED",
|
|
2423
|
+
run: compactRecommendRunForStatus(normalizedBefore),
|
|
2424
|
+
message: "目标任务已经处于 paused 状态。"
|
|
2425
|
+
}, runId);
|
|
2150
2426
|
}
|
|
2151
2427
|
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);
|
|
2428
|
+
const normalizedRun = persistRecommendRunSnapshot(run);
|
|
2429
|
+
return attachMethodEvidence({
|
|
2430
|
+
status: "PAUSE_REQUESTED",
|
|
2431
|
+
run: compactRecommendRunForStatus(normalizedRun),
|
|
2432
|
+
message: "暂停请求已接收,将在当前候选人处理完成后进入 paused。"
|
|
2433
|
+
}, runId);
|
|
2158
2434
|
} catch {
|
|
2159
|
-
const persisted = readRecommendRunState(runId);
|
|
2160
|
-
if (persisted && TERMINAL_STATUSES.has(persisted.state)) {
|
|
2161
|
-
return {
|
|
2435
|
+
const persisted = readRecommendRunState(runId);
|
|
2436
|
+
if (persisted && TERMINAL_STATUSES.has(persisted.state)) {
|
|
2437
|
+
return attachPersistedCdpEvidence({
|
|
2162
2438
|
status: "PAUSE_IGNORED",
|
|
2163
2439
|
run: compactRecommendRunForStatus(persisted),
|
|
2164
|
-
message: "目标任务已结束,无需暂停。"
|
|
2165
|
-
|
|
2166
|
-
method_summary: {},
|
|
2167
|
-
method_log: [],
|
|
2168
|
-
chrome: null
|
|
2169
|
-
};
|
|
2440
|
+
message: "目标任务已结束,无需暂停。"
|
|
2441
|
+
}, persisted);
|
|
2170
2442
|
}
|
|
2171
2443
|
return getRecommendPipelineRunTool({ args });
|
|
2172
2444
|
}
|
|
@@ -2177,28 +2449,28 @@ export function resumeRecommendPipelineRunTool({ args = {} } = {}) {
|
|
|
2177
2449
|
try {
|
|
2178
2450
|
const before = recommendRunService.getRecommendRun(runId);
|
|
2179
2451
|
if (TERMINAL_STATUSES.has(before.status)) {
|
|
2180
|
-
const normalizedBefore = persistRecommendRunSnapshot(before);
|
|
2181
|
-
return attachMethodEvidence({
|
|
2182
|
-
status: "FAILED",
|
|
2452
|
+
const normalizedBefore = persistRecommendRunSnapshot(before);
|
|
2453
|
+
return attachMethodEvidence({
|
|
2454
|
+
status: "FAILED",
|
|
2183
2455
|
error: {
|
|
2184
2456
|
code: "RUN_ALREADY_TERMINATED",
|
|
2185
2457
|
message: "目标任务已结束,无法继续。",
|
|
2186
2458
|
retryable: false
|
|
2187
2459
|
},
|
|
2188
|
-
run: compactRecommendRunForStatus(normalizedBefore)
|
|
2189
|
-
}, runId);
|
|
2460
|
+
run: compactRecommendRunForStatus(normalizedBefore)
|
|
2461
|
+
}, runId);
|
|
2190
2462
|
}
|
|
2191
2463
|
if (before.status !== RUN_STATUS_PAUSED) {
|
|
2192
|
-
const normalizedBefore = persistRecommendRunSnapshot(before);
|
|
2193
|
-
return attachMethodEvidence({
|
|
2194
|
-
status: "FAILED",
|
|
2464
|
+
const normalizedBefore = persistRecommendRunSnapshot(before);
|
|
2465
|
+
return attachMethodEvidence({
|
|
2466
|
+
status: "FAILED",
|
|
2195
2467
|
error: {
|
|
2196
2468
|
code: "RUN_NOT_PAUSED",
|
|
2197
2469
|
message: "仅 paused 状态的 run 才能继续。",
|
|
2198
2470
|
retryable: true
|
|
2199
2471
|
},
|
|
2200
|
-
run: compactRecommendRunForStatus(normalizedBefore)
|
|
2201
|
-
}, runId);
|
|
2472
|
+
run: compactRecommendRunForStatus(normalizedBefore)
|
|
2473
|
+
}, runId);
|
|
2202
2474
|
}
|
|
2203
2475
|
const run = recommendRunService.resumeRecommendRun(runId);
|
|
2204
2476
|
const meta = getRecommendRunMeta(runId);
|
|
@@ -2206,35 +2478,31 @@ export function resumeRecommendPipelineRunTool({ args = {} } = {}) {
|
|
|
2206
2478
|
meta.resumeCount = (meta.resumeCount || 0) + 1;
|
|
2207
2479
|
meta.lastResumedAt = new Date().toISOString();
|
|
2208
2480
|
}
|
|
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 按需轮询。"
|
|
2481
|
+
const normalizedRun = persistRecommendRunSnapshot(run);
|
|
2482
|
+
return attachMethodEvidence({
|
|
2483
|
+
status: "RESUME_REQUESTED",
|
|
2484
|
+
run: compactRecommendRunForStatus(normalizedRun),
|
|
2485
|
+
poll_after_sec: DEFAULT_RECOMMEND_POLL_AFTER_SEC,
|
|
2486
|
+
message: "已恢复 Recommend run,请使用 get_recommend_pipeline_run 按需轮询。"
|
|
2215
2487
|
}, runId);
|
|
2216
2488
|
} catch {
|
|
2217
|
-
const persisted = readRecommendRunState(runId);
|
|
2218
|
-
if (persisted) {
|
|
2219
|
-
return {
|
|
2220
|
-
status: "FAILED",
|
|
2221
|
-
error: {
|
|
2489
|
+
const persisted = readRecommendRunState(runId);
|
|
2490
|
+
if (persisted) {
|
|
2491
|
+
return attachPersistedCdpEvidence({
|
|
2492
|
+
status: "FAILED",
|
|
2493
|
+
error: {
|
|
2222
2494
|
code: TERMINAL_STATUSES.has(persisted.state) ? "RUN_ALREADY_TERMINATED" : "RUN_NOT_ACTIVE",
|
|
2223
2495
|
message: TERMINAL_STATUSES.has(persisted.state)
|
|
2224
2496
|
? "目标任务已结束,无法继续。"
|
|
2225
2497
|
: "该 run 只有磁盘快照,没有当前进程内的活动 CDP 会话,无法安全继续。",
|
|
2226
2498
|
retryable: !TERMINAL_STATUSES.has(persisted.state)
|
|
2227
2499
|
},
|
|
2228
|
-
run: compactRecommendRunForStatus(persisted),
|
|
2500
|
+
run: compactRecommendRunForStatus(persisted),
|
|
2229
2501
|
persistence: {
|
|
2230
2502
|
source: "disk",
|
|
2231
|
-
active_control_available: false
|
|
2232
|
-
}
|
|
2233
|
-
|
|
2234
|
-
method_summary: {},
|
|
2235
|
-
method_log: [],
|
|
2236
|
-
chrome: null
|
|
2237
|
-
};
|
|
2503
|
+
active_control_available: false
|
|
2504
|
+
}
|
|
2505
|
+
}, persisted);
|
|
2238
2506
|
}
|
|
2239
2507
|
return getRecommendPipelineRunTool({ args });
|
|
2240
2508
|
}
|
|
@@ -2245,61 +2513,53 @@ export function cancelRecommendPipelineRunTool({ args = {} } = {}) {
|
|
|
2245
2513
|
try {
|
|
2246
2514
|
const before = recommendRunService.getRecommendRun(runId);
|
|
2247
2515
|
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);
|
|
2516
|
+
const normalizedBefore = persistRecommendRunSnapshot(before);
|
|
2517
|
+
return attachMethodEvidence({
|
|
2518
|
+
status: "CANCEL_IGNORED",
|
|
2519
|
+
run: compactRecommendRunForStatus(normalizedBefore),
|
|
2520
|
+
message: "目标任务已结束,无需取消。"
|
|
2521
|
+
}, runId);
|
|
2254
2522
|
}
|
|
2255
2523
|
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);
|
|
2524
|
+
const normalizedRun = persistRecommendRunSnapshot(run);
|
|
2525
|
+
return attachMethodEvidence({
|
|
2526
|
+
status: "CANCEL_REQUESTED",
|
|
2527
|
+
run: compactRecommendRunForStatus(normalizedRun),
|
|
2528
|
+
message: "已收到取消请求,将在当前候选人处理完成后安全停止。"
|
|
2529
|
+
}, runId);
|
|
2262
2530
|
} catch {
|
|
2263
|
-
const persisted = readRecommendRunState(runId);
|
|
2531
|
+
const persisted = readRecommendRunState(runId);
|
|
2264
2532
|
if (persisted && TERMINAL_STATUSES.has(persisted.state)) {
|
|
2265
|
-
return {
|
|
2533
|
+
return attachPersistedCdpEvidence({
|
|
2266
2534
|
status: "CANCEL_IGNORED",
|
|
2267
2535
|
run: compactRecommendRunForStatus(persisted),
|
|
2268
|
-
message: "目标任务已结束,无需取消。"
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
pause_requested_by: "cancel_recommend_pipeline_run",
|
|
2280
|
-
cancel_requested: true
|
|
2281
|
-
}, {
|
|
2282
|
-
message: cancelMessage
|
|
2536
|
+
message: "目标任务已结束,无需取消。"
|
|
2537
|
+
}, persisted);
|
|
2538
|
+
}
|
|
2539
|
+
const cancelMessage = "已收到取消请求,将由 detached worker 在下一个安全边界停止。";
|
|
2540
|
+
const patched = patchPersistedRecommendRunControl(runId, {
|
|
2541
|
+
pause_requested: true,
|
|
2542
|
+
pause_requested_at: new Date().toISOString(),
|
|
2543
|
+
pause_requested_by: "cancel_recommend_pipeline_run",
|
|
2544
|
+
cancel_requested: true
|
|
2545
|
+
}, {
|
|
2546
|
+
message: cancelMessage
|
|
2283
2547
|
});
|
|
2284
2548
|
if (patched) {
|
|
2285
|
-
return {
|
|
2549
|
+
return attachPersistedCdpEvidence({
|
|
2286
2550
|
status: "CANCEL_REQUESTED",
|
|
2287
2551
|
run: compactRecommendRunForStatus(patched),
|
|
2288
|
-
message: cancelMessage,
|
|
2289
|
-
persistence: {
|
|
2290
|
-
source: "disk",
|
|
2552
|
+
message: cancelMessage,
|
|
2553
|
+
persistence: {
|
|
2554
|
+
source: "disk",
|
|
2291
2555
|
active_control_available: false,
|
|
2292
2556
|
detached_control_requested: true
|
|
2293
|
-
}
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
}
|
|
2300
|
-
return getRecommendPipelineRunTool({ args });
|
|
2301
|
-
}
|
|
2302
|
-
}
|
|
2557
|
+
}
|
|
2558
|
+
}, patched);
|
|
2559
|
+
}
|
|
2560
|
+
return getRecommendPipelineRunTool({ args });
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2303
2563
|
|
|
2304
2564
|
export function getRecommendMcpHealthSnapshot(runId) {
|
|
2305
2565
|
const meta = getRecommendRunMeta(runId);
|