@reconcrap/boss-recommend-mcp 2.1.23 → 2.1.25
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 -0
- package/bin/boss-recommend-mcp.js +4 -4
- package/package.json +6 -1
- package/scripts/install-macos.sh +280 -280
- package/scripts/postinstall.cjs +44 -44
- package/skills/boss-chat/README.md +43 -42
- package/skills/boss-chat/SKILL.md +107 -106
- package/skills/boss-recommend-pipeline/README.md +13 -13
- package/skills/boss-recommend-pipeline/SKILL.md +47 -47
- package/skills/boss-recruit-pipeline/README.md +19 -19
- package/skills/boss-recruit-pipeline/SKILL.md +89 -89
- package/src/chat-mcp.js +301 -127
- package/src/chat-runtime-config.js +7 -5
- package/src/core/boss-cards/index.js +199 -199
- package/src/core/browser/index.js +302 -145
- package/src/core/capture/index.js +2930 -1201
- package/src/core/cv-acquisition/index.js +512 -238
- package/src/core/cv-capture-target/index.js +513 -299
- package/src/core/greet-quota/index.js +71 -71
- package/src/core/infinite-list/index.js +31 -31
- package/src/core/reporting/legacy-csv.js +12 -12
- package/src/core/run/detached-launcher.js +305 -0
- package/src/core/run/index.js +109 -55
- package/src/core/run/timing.js +33 -33
- package/src/core/run/windows-detached-worker.ps1 +106 -0
- package/src/core/screening/index.js +2400 -2135
- package/src/core/self-heal/index.js +989 -973
- package/src/core/self-heal/viewport.js +1505 -564
- package/src/detached-worker.js +99 -99
- package/src/domains/chat/action-journal.js +536 -0
- package/src/domains/chat/cards.js +137 -137
- package/src/domains/chat/constants.js +9 -9
- package/src/domains/chat/detail.js +1684 -401
- package/src/domains/chat/index.js +8 -7
- package/src/domains/chat/jobs.js +620 -620
- package/src/domains/chat/page-guard.js +157 -122
- package/src/domains/chat/roots.js +56 -56
- package/src/domains/chat/run-service.js +1801 -760
- 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 +1219 -472
- package/src/domains/recommend/cards.js +243 -243
- package/src/domains/recommend/colleague-contact.js +1079 -333
- package/src/domains/recommend/constants.js +92 -92
- package/src/domains/recommend/detail.js +4037 -136
- package/src/domains/recommend/filters.js +608 -590
- package/src/domains/recommend/index.js +9 -9
- package/src/domains/recommend/jobs.js +571 -542
- package/src/domains/recommend/location.js +754 -707
- package/src/domains/recommend/refresh.js +677 -392
- package/src/domains/recommend/roots.js +80 -80
- package/src/domains/recommend/run-service.js +4048 -1447
- 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 +1817 -1620
- package/src/domains/recruit/search.js +3229 -3229
- package/src/index.js +16 -1
- package/src/parser.js +1296 -1296
- package/src/recommend-mcp.js +1061 -450
- package/src/recommend-scheduler.js +75 -75
package/src/chat-mcp.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
6
|
import {
|
|
7
7
|
assertNoForbiddenCdpCalls,
|
|
8
8
|
bringPageToFront,
|
|
@@ -34,8 +34,10 @@ import {
|
|
|
34
34
|
resolveChatSelfHealRoots,
|
|
35
35
|
runSelfHealCheck
|
|
36
36
|
} from "./core/self-heal/index.js";
|
|
37
|
-
import {
|
|
38
|
-
|
|
37
|
+
import {
|
|
38
|
+
CHAT_COLLECT_CV_PROCESSING_FLOOR_MAX_MS,
|
|
39
|
+
CHAT_COLLECT_CV_PROCESSING_FLOOR_MIN_MS,
|
|
40
|
+
CHAT_TARGET_URL,
|
|
39
41
|
closeChatResumeModal,
|
|
40
42
|
closeChatJobDropdown,
|
|
41
43
|
createChatRunService,
|
|
@@ -54,7 +56,8 @@ import {
|
|
|
54
56
|
resolveHumanBehaviorForRun,
|
|
55
57
|
resolveBossScreeningConfig
|
|
56
58
|
} from "./chat-runtime-config.js";
|
|
57
|
-
import { DEFAULT_MAX_IMAGE_PAGES } from "./core/cv-acquisition/index.js";
|
|
59
|
+
import { DEFAULT_MAX_IMAGE_PAGES } from "./core/cv-acquisition/index.js";
|
|
60
|
+
import { launchDetachedWorker } from "./core/run/detached-launcher.js";
|
|
58
61
|
|
|
59
62
|
const DEFAULT_CHAT_HOST = "127.0.0.1";
|
|
60
63
|
const DEFAULT_CHAT_PORT = 9222;
|
|
@@ -85,11 +88,15 @@ const ARTIFACT_STATUSES = new Set([
|
|
|
85
88
|
RUN_STATUS_PAUSED
|
|
86
89
|
]);
|
|
87
90
|
|
|
88
|
-
const STALE_PROCESS_STATUSES = new Set([
|
|
89
|
-
"queued",
|
|
90
|
-
"running",
|
|
91
|
-
RUN_STATUS_CANCELING
|
|
92
|
-
]);
|
|
91
|
+
const STALE_PROCESS_STATUSES = new Set([
|
|
92
|
+
"queued",
|
|
93
|
+
"running",
|
|
94
|
+
RUN_STATUS_CANCELING
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
const CHAT_ATOMIC_RENAME_RETRY_CODES = new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
98
|
+
const CHAT_ATOMIC_RENAME_RETRY_DELAYS_MS = Object.freeze([25, 50, 100, 200, 400, 500]);
|
|
99
|
+
const CHAT_ATOMIC_RENAME_SLEEP_CELL = new Int32Array(new SharedArrayBuffer(4));
|
|
93
100
|
|
|
94
101
|
const CHAT_REQUEST_RESUME_ACTIONS = new Set([
|
|
95
102
|
"request_cv",
|
|
@@ -115,10 +122,11 @@ const CHAT_DISABLE_REQUEST_RESUME_ACTIONS = new Set([
|
|
|
115
122
|
"不请求简历"
|
|
116
123
|
]);
|
|
117
124
|
|
|
118
|
-
let chatWorkflowImpl = runChatWorkflow;
|
|
119
|
-
let chatConnectorImpl = connectChatChromeSession;
|
|
120
|
-
let chatJobReaderImpl = readChatJobOptionsFromSession;
|
|
121
|
-
let
|
|
125
|
+
let chatWorkflowImpl = runChatWorkflow;
|
|
126
|
+
let chatConnectorImpl = connectChatChromeSession;
|
|
127
|
+
let chatJobReaderImpl = readChatJobOptionsFromSession;
|
|
128
|
+
let chatDetachedWorkerLauncherImpl = launchDetachedWorker;
|
|
129
|
+
let chatRunService = createChatRunService({
|
|
122
130
|
idPrefix: "mcp_chat",
|
|
123
131
|
workflow: (...args) => chatWorkflowImpl(...args),
|
|
124
132
|
onSnapshot: persistChatLifecycleSnapshot
|
|
@@ -187,12 +195,60 @@ function ensureDirectory(dirPath) {
|
|
|
187
195
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
188
196
|
}
|
|
189
197
|
|
|
190
|
-
function
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
198
|
+
function sleepSync(milliseconds) {
|
|
199
|
+
const delayMs = Math.max(0, Math.floor(Number(milliseconds) || 0));
|
|
200
|
+
if (delayMs <= 0) return;
|
|
201
|
+
Atomics.wait(CHAT_ATOMIC_RENAME_SLEEP_CELL, 0, 0, delayMs);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function writeJsonAtomic(filePath, payload, {
|
|
205
|
+
renameSyncImpl = fs.renameSync,
|
|
206
|
+
sleepSyncImpl = sleepSync,
|
|
207
|
+
randomUUIDImpl = randomUUID
|
|
208
|
+
} = {}) {
|
|
209
|
+
ensureDirectory(path.dirname(filePath));
|
|
210
|
+
const tempPath = `${filePath}.${process.pid}.${randomUUIDImpl()}.tmp`;
|
|
211
|
+
let renameAttempts = 0;
|
|
212
|
+
const retryDelays = [];
|
|
213
|
+
try {
|
|
214
|
+
fs.writeFileSync(tempPath, `${JSON.stringify(payload, null, 2)}\n`, {
|
|
215
|
+
encoding: "utf8",
|
|
216
|
+
flag: "wx",
|
|
217
|
+
mode: 0o600
|
|
218
|
+
});
|
|
219
|
+
while (true) {
|
|
220
|
+
renameAttempts += 1;
|
|
221
|
+
try {
|
|
222
|
+
renameSyncImpl(tempPath, filePath);
|
|
223
|
+
return;
|
|
224
|
+
} catch (error) {
|
|
225
|
+
const retryDelayMs = CHAT_ATOMIC_RENAME_RETRY_DELAYS_MS[renameAttempts - 1];
|
|
226
|
+
if (
|
|
227
|
+
!CHAT_ATOMIC_RENAME_RETRY_CODES.has(error?.code)
|
|
228
|
+
|| retryDelayMs === undefined
|
|
229
|
+
) {
|
|
230
|
+
try {
|
|
231
|
+
error.atomic_rename_attempts = renameAttempts;
|
|
232
|
+
error.atomic_rename_retry_delays_ms = [...retryDelays];
|
|
233
|
+
error.atomic_rename_source = tempPath;
|
|
234
|
+
error.atomic_rename_target = filePath;
|
|
235
|
+
} catch {
|
|
236
|
+
// Preserve the original filesystem error when it is not extensible.
|
|
237
|
+
}
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
retryDelays.push(retryDelayMs);
|
|
241
|
+
sleepSyncImpl(retryDelayMs);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} finally {
|
|
245
|
+
try {
|
|
246
|
+
if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
|
|
247
|
+
} catch {
|
|
248
|
+
// Best-effort cleanup only; never hide the original persistence outcome.
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
196
252
|
|
|
197
253
|
function readJsonFile(filePath) {
|
|
198
254
|
try {
|
|
@@ -218,9 +274,10 @@ function selectedChatJobForCsv(meta = {}, snapshot = {}) {
|
|
|
218
274
|
};
|
|
219
275
|
}
|
|
220
276
|
|
|
221
|
-
function buildChatCsvInputRows(snapshot = {}, meta = {}) {
|
|
222
|
-
const normalized = meta.normalized || {};
|
|
223
|
-
const context = snapshot.context || {};
|
|
277
|
+
function buildChatCsvInputRows(snapshot = {}, meta = {}) {
|
|
278
|
+
const normalized = meta.normalized || {};
|
|
279
|
+
const context = snapshot.context || {};
|
|
280
|
+
const processingFloor = snapshot.summary?.collect_cv_processing_floor || {};
|
|
224
281
|
const postAction = shouldRequestChatResume(meta.args, context)
|
|
225
282
|
? "request_cv"
|
|
226
283
|
: normalizeText(meta.args?.post_action || meta.args?.action || "") || "none";
|
|
@@ -243,12 +300,18 @@ function buildChatCsvInputRows(snapshot = {}, meta = {}) {
|
|
|
243
300
|
max_greet_count: meta.args?.max_greet_count ?? ""
|
|
244
301
|
},
|
|
245
302
|
followUp: meta.args?.follow_up || null,
|
|
246
|
-
extraRows: [
|
|
247
|
-
["chat_params.greeting_text", normalized.greetingText || meta.args?.greeting_text || meta.args?.greetingText || context.greeting_text || DEFAULT_CHAT_GREETING_TEXT],
|
|
248
|
-
["chat_params.profile", normalized.profile || meta.args?.profile || context.profile || "default"]
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
303
|
+
extraRows: [
|
|
304
|
+
["chat_params.greeting_text", normalized.greetingText || meta.args?.greeting_text || meta.args?.greetingText || context.greeting_text || DEFAULT_CHAT_GREETING_TEXT],
|
|
305
|
+
["chat_params.profile", normalized.profile || meta.args?.profile || context.profile || "default"],
|
|
306
|
+
["chat_params.collect_cv_processing_floor_enabled", context.collect_cv_processing_floor_enabled === true],
|
|
307
|
+
["chat_params.collect_cv_processing_floor_min_ms", context.collect_cv_processing_floor_min_ms ?? ""],
|
|
308
|
+
["chat_params.collect_cv_processing_floor_max_ms", context.collect_cv_processing_floor_max_ms ?? ""],
|
|
309
|
+
["chat_params.collect_cv_processing_floor_count", processingFloor.count ?? snapshot.progress?.collect_cv_processing_floor_count ?? 0],
|
|
310
|
+
["chat_params.collect_cv_processing_floor_delay_requested_ms", processingFloor.total_delay_requested_ms ?? snapshot.progress?.collect_cv_processing_floor_delay_requested_ms ?? 0],
|
|
311
|
+
["chat_params.collect_cv_processing_floor_delay_ms", processingFloor.total_delay_elapsed_ms ?? snapshot.progress?.collect_cv_processing_floor_delay_ms ?? 0]
|
|
312
|
+
]
|
|
313
|
+
});
|
|
314
|
+
}
|
|
252
315
|
|
|
253
316
|
function writeChatLegacyCsvAtomic(filePath, rows = [], snapshot = {}, meta = {}) {
|
|
254
317
|
writeLegacyScreenCsv(filePath, {
|
|
@@ -257,11 +320,37 @@ function writeChatLegacyCsvAtomic(filePath, rows = [], snapshot = {}, meta = {})
|
|
|
257
320
|
});
|
|
258
321
|
}
|
|
259
322
|
|
|
260
|
-
function readChatRunState(runId) {
|
|
261
|
-
const artifacts = getChatRunArtifacts(runId);
|
|
262
|
-
if (!artifacts) return null;
|
|
263
|
-
return readJsonFile(artifacts.run_state_path);
|
|
264
|
-
}
|
|
323
|
+
function readChatRunState(runId) {
|
|
324
|
+
const artifacts = getChatRunArtifacts(runId);
|
|
325
|
+
if (!artifacts) return null;
|
|
326
|
+
return readJsonFile(artifacts.run_state_path);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function hasChatCheckpointPayload(value) {
|
|
330
|
+
return Boolean(
|
|
331
|
+
value
|
|
332
|
+
&& typeof value === "object"
|
|
333
|
+
&& !Array.isArray(value)
|
|
334
|
+
&& Object.keys(value).length > 0
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function readHydratedChatCheckpoint(persisted = {}) {
|
|
339
|
+
const runId = normalizeRunId(persisted.run_id || persisted.runId);
|
|
340
|
+
const artifacts = getChatRunArtifacts(runId);
|
|
341
|
+
const checkpointPaths = [
|
|
342
|
+
persisted.resume?.checkpoint_path,
|
|
343
|
+
persisted.artifacts?.checkpoint_path,
|
|
344
|
+
artifacts?.checkpoint_path
|
|
345
|
+
].map((value) => normalizeText(value)).filter(Boolean);
|
|
346
|
+
for (const checkpointPath of new Set(checkpointPaths)) {
|
|
347
|
+
const checkpoint = readJsonFile(checkpointPath);
|
|
348
|
+
if (checkpoint) return checkpoint;
|
|
349
|
+
}
|
|
350
|
+
return persisted.checkpoint && typeof persisted.checkpoint === "object"
|
|
351
|
+
? persisted.checkpoint
|
|
352
|
+
: {};
|
|
353
|
+
}
|
|
265
354
|
|
|
266
355
|
function writeChatRunState(runId, payload) {
|
|
267
356
|
const artifacts = getChatRunArtifacts(runId);
|
|
@@ -281,10 +370,13 @@ function buildInitialChatDetachedState(runId, {
|
|
|
281
370
|
pid = process.pid
|
|
282
371
|
} = {}) {
|
|
283
372
|
const artifacts = getChatRunArtifacts(runId);
|
|
284
|
-
const now = new Date().toISOString();
|
|
285
|
-
const isAllTarget = normalized.publicTargetCount === "all";
|
|
286
|
-
const processedLimit = isAllTarget ? CHAT_ALL_MAX_CANDIDATES : Math.max(1, Number(normalized.targetCount) || 1);
|
|
287
|
-
|
|
373
|
+
const now = new Date().toISOString();
|
|
374
|
+
const isAllTarget = normalized.publicTargetCount === "all";
|
|
375
|
+
const processedLimit = isAllTarget ? CHAT_ALL_MAX_CANDIDATES : Math.max(1, Number(normalized.targetCount) || 1);
|
|
376
|
+
const criteria = normalizeText(normalized.criteria || args.criteria || "");
|
|
377
|
+
const screeningMode = normalizeScreeningModeArg(args, { ...normalized, criteria });
|
|
378
|
+
const collectCvProcessingFloorEnabled = screeningMode === "collect_cv";
|
|
379
|
+
return {
|
|
288
380
|
run_id: runId,
|
|
289
381
|
mode: RUN_MODE_ASYNC,
|
|
290
382
|
state: "queued",
|
|
@@ -306,9 +398,16 @@ function buildInitialChatDetachedState(runId, {
|
|
|
306
398
|
passed: 0,
|
|
307
399
|
skipped: 0,
|
|
308
400
|
requested: 0,
|
|
309
|
-
request_satisfied: 0,
|
|
310
|
-
request_skipped: 0,
|
|
311
|
-
greet_count: 0
|
|
401
|
+
request_satisfied: 0,
|
|
402
|
+
request_skipped: 0,
|
|
403
|
+
greet_count: 0,
|
|
404
|
+
collect_cv_processing_floor_enabled: collectCvProcessingFloorEnabled,
|
|
405
|
+
collect_cv_processing_floor_min_ms: collectCvProcessingFloorEnabled ? CHAT_COLLECT_CV_PROCESSING_FLOOR_MIN_MS : null,
|
|
406
|
+
collect_cv_processing_floor_max_ms: collectCvProcessingFloorEnabled ? CHAT_COLLECT_CV_PROCESSING_FLOOR_MAX_MS : null,
|
|
407
|
+
collect_cv_processing_floor_count: 0,
|
|
408
|
+
collect_cv_processing_floor_delay_requested_ms: 0,
|
|
409
|
+
collect_cv_processing_floor_delay_ms: 0,
|
|
410
|
+
collect_cv_processing_floor_current: null
|
|
312
411
|
},
|
|
313
412
|
last_message: "Boss chat detached worker is queued.",
|
|
314
413
|
context: {
|
|
@@ -318,7 +417,13 @@ function buildInitialChatDetachedState(runId, {
|
|
|
318
417
|
profile: normalized.profile || args.profile || "default",
|
|
319
418
|
job: normalized.job || args.job || "",
|
|
320
419
|
start_from: normalized.startFrom || args.start_from || "",
|
|
321
|
-
criteria
|
|
420
|
+
criteria,
|
|
421
|
+
criteria_present: Boolean(criteria),
|
|
422
|
+
screening_mode: screeningMode,
|
|
423
|
+
cv_collection_mode: collectCvProcessingFloorEnabled,
|
|
424
|
+
collect_cv_processing_floor_enabled: collectCvProcessingFloorEnabled,
|
|
425
|
+
collect_cv_processing_floor_min_ms: collectCvProcessingFloorEnabled ? CHAT_COLLECT_CV_PROCESSING_FLOOR_MIN_MS : null,
|
|
426
|
+
collect_cv_processing_floor_max_ms: collectCvProcessingFloorEnabled ? CHAT_COLLECT_CV_PROCESSING_FLOOR_MAX_MS : null,
|
|
322
427
|
greeting_text: normalized.greetingText || args.greeting_text || args.greetingText || DEFAULT_CHAT_GREETING_TEXT,
|
|
323
428
|
target_count: normalized.publicTargetCount ?? normalized.targetCount ?? null,
|
|
324
429
|
target_count_semantics: TARGET_COUNT_SEMANTICS,
|
|
@@ -385,33 +490,42 @@ function patchPersistedChatControl(runId, controlPatch = {}, {
|
|
|
385
490
|
};
|
|
386
491
|
}
|
|
387
492
|
|
|
388
|
-
function launchDetachedChatWorker(runId) {
|
|
389
|
-
const artifacts = getChatRunArtifacts(runId);
|
|
390
|
-
if (!artifacts) throw new Error("Invalid chat run_id");
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
493
|
+
function launchDetachedChatWorker(runId, { screenConfigPath = "" } = {}) {
|
|
494
|
+
const artifacts = getChatRunArtifacts(runId);
|
|
495
|
+
if (!artifacts) throw new Error("Invalid chat run_id");
|
|
496
|
+
const child = chatDetachedWorkerLauncherImpl({
|
|
497
|
+
nodePath: process.execPath,
|
|
498
|
+
workerScriptPath: DETACHED_WORKER_SCRIPT,
|
|
499
|
+
domain: "chat",
|
|
500
|
+
runId,
|
|
501
|
+
stdoutPath: artifacts.worker_stdout_path,
|
|
502
|
+
stderrPath: artifacts.worker_stderr_path,
|
|
503
|
+
chatRuntimeHomePath: getBossChatDataDir(),
|
|
504
|
+
screenConfigPath,
|
|
505
|
+
environment: process.env
|
|
506
|
+
});
|
|
507
|
+
if (typeof child?.once === "function") {
|
|
508
|
+
child.once("error", (error) => {
|
|
509
|
+
markBossChatDetachedWorkerFailed(runId, error, {
|
|
510
|
+
code: "CHAT_WORKER_PROCESS_ERROR",
|
|
511
|
+
message: "Boss chat detached worker process emitted an asynchronous error."
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
child.once("exit", (exitCode, signal) => {
|
|
515
|
+
const normalizedSignal = normalizeText(signal);
|
|
516
|
+
const exitLabel = Number.isInteger(exitCode) ? `code=${exitCode}` : "code=unknown";
|
|
517
|
+
const signalLabel = normalizedSignal ? `, signal=${normalizedSignal}` : "";
|
|
518
|
+
const error = new Error(`Boss chat detached worker exited before writing a terminal state (${exitLabel}${signalLabel}).`);
|
|
519
|
+
markBossChatDetachedWorkerFailed(runId, error, {
|
|
520
|
+
code: "CHAT_WORKER_EXITED_EARLY",
|
|
521
|
+
workerExitCode: Number.isInteger(exitCode) ? exitCode : null,
|
|
522
|
+
workerSignal: normalizedSignal || null
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
if (typeof child?.unref === "function") child.unref();
|
|
527
|
+
return child;
|
|
528
|
+
}
|
|
415
529
|
|
|
416
530
|
function toIsoOrNull(value) {
|
|
417
531
|
const normalized = normalizeText(value);
|
|
@@ -500,16 +614,22 @@ function getChatRunMeta(runId) {
|
|
|
500
614
|
return chatRunMeta.get(runId) || {};
|
|
501
615
|
}
|
|
502
616
|
|
|
503
|
-
function ensureChatRunArtifacts(snapshot) {
|
|
617
|
+
function ensureChatRunArtifacts(snapshot) {
|
|
504
618
|
const artifacts = getChatRunArtifacts(snapshot?.runId || snapshot?.run_id);
|
|
505
619
|
if (!artifacts) return null;
|
|
506
620
|
|
|
507
621
|
const meta = getChatRunMeta(snapshot?.runId || snapshot?.run_id);
|
|
508
|
-
const
|
|
509
|
-
? snapshot.checkpoint
|
|
510
|
-
: {};
|
|
511
|
-
|
|
512
|
-
|
|
622
|
+
const snapshotCheckpoint = snapshot?.checkpoint && typeof snapshot.checkpoint === "object"
|
|
623
|
+
? snapshot.checkpoint
|
|
624
|
+
: {};
|
|
625
|
+
const existingCheckpoint = readJsonFile(artifacts.checkpoint_path);
|
|
626
|
+
const checkpoint = hasChatCheckpointPayload(snapshotCheckpoint)
|
|
627
|
+
? snapshotCheckpoint
|
|
628
|
+
: existingCheckpoint || snapshotCheckpoint;
|
|
629
|
+
if (hasChatCheckpointPayload(snapshotCheckpoint) || !existingCheckpoint) {
|
|
630
|
+
writeJsonAtomic(artifacts.checkpoint_path, checkpoint);
|
|
631
|
+
}
|
|
632
|
+
if (meta) meta.checkpointPath = artifacts.checkpoint_path;
|
|
513
633
|
|
|
514
634
|
const summary = snapshot?.summary && typeof snapshot.summary === "object" ? snapshot.summary : null;
|
|
515
635
|
const checkpointResults = Array.isArray(checkpoint.results) ? checkpoint.results : [];
|
|
@@ -547,16 +667,21 @@ function ensureChatRunArtifacts(snapshot) {
|
|
|
547
667
|
return artifacts;
|
|
548
668
|
}
|
|
549
669
|
|
|
550
|
-
function persistChatCheckpointSnapshot(normalized) {
|
|
551
|
-
const artifacts = getChatRunArtifacts(normalized?.run_id || normalized?.runId);
|
|
552
|
-
if (!artifacts) return;
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
const
|
|
558
|
-
if (
|
|
559
|
-
|
|
670
|
+
function persistChatCheckpointSnapshot(normalized) {
|
|
671
|
+
const artifacts = getChatRunArtifacts(normalized?.run_id || normalized?.runId);
|
|
672
|
+
if (!artifacts) return;
|
|
673
|
+
const meta = getChatRunMeta(normalized?.run_id || normalized?.runId);
|
|
674
|
+
const checkpoint = normalized?.checkpoint && typeof normalized.checkpoint === "object"
|
|
675
|
+
? normalized.checkpoint
|
|
676
|
+
: {};
|
|
677
|
+
const existingCheckpoint = readJsonFile(artifacts.checkpoint_path);
|
|
678
|
+
if (!hasChatCheckpointPayload(checkpoint) && existingCheckpoint) {
|
|
679
|
+
if (meta) meta.checkpointPath = artifacts.checkpoint_path;
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
writeJsonAtomic(artifacts.checkpoint_path, checkpoint);
|
|
683
|
+
if (meta) meta.checkpointPath = artifacts.checkpoint_path;
|
|
684
|
+
}
|
|
560
685
|
|
|
561
686
|
function isPidAlive(pid) {
|
|
562
687
|
const numericPid = Number(pid);
|
|
@@ -570,15 +695,15 @@ function isPidAlive(pid) {
|
|
|
570
695
|
}
|
|
571
696
|
}
|
|
572
697
|
|
|
573
|
-
function snapshotFromPersistedChatRun(persisted = {}) {
|
|
574
|
-
return {
|
|
698
|
+
function snapshotFromPersistedChatRun(persisted = {}) {
|
|
699
|
+
return {
|
|
575
700
|
runId: persisted.run_id || persisted.runId,
|
|
576
701
|
name: persisted.name || persisted.run_id || persisted.runId,
|
|
577
702
|
status: persisted.status || persisted.state,
|
|
578
703
|
phase: persisted.stage || persisted.phase,
|
|
579
704
|
progress: persisted.progress || {},
|
|
580
705
|
context: persisted.context || {},
|
|
581
|
-
checkpoint: persisted
|
|
706
|
+
checkpoint: readHydratedChatCheckpoint(persisted),
|
|
582
707
|
startedAt: persisted.started_at || persisted.startedAt,
|
|
583
708
|
updatedAt: persisted.updated_at || persisted.updatedAt,
|
|
584
709
|
completedAt: persisted.completed_at || persisted.completedAt || null,
|
|
@@ -613,7 +738,7 @@ function attachLegacyArtifactsToPersistedChatRun(persisted = {}) {
|
|
|
613
738
|
return persistDiskChatRun(runId, next);
|
|
614
739
|
}
|
|
615
740
|
|
|
616
|
-
function finalizePersistedChatRun(persisted = {}, {
|
|
741
|
+
function finalizePersistedChatRun(persisted = {}, {
|
|
617
742
|
status = RUN_STATUS_FAILED,
|
|
618
743
|
error = null,
|
|
619
744
|
message = ""
|
|
@@ -621,30 +746,51 @@ function finalizePersistedChatRun(persisted = {}, {
|
|
|
621
746
|
const runId = normalizeRunId(persisted.run_id || persisted.runId);
|
|
622
747
|
if (!runId) return persisted;
|
|
623
748
|
const now = new Date().toISOString();
|
|
624
|
-
const normalizedError = status === RUN_STATUS_FAILED
|
|
625
|
-
? {
|
|
626
|
-
name: error?.name || "Error",
|
|
627
|
-
code: error?.code || "STALE_RUN_PROCESS_EXITED",
|
|
628
|
-
message: error?.message || message || "Boss chat run process exited before it wrote a terminal state."
|
|
629
|
-
}
|
|
630
|
-
: null;
|
|
631
|
-
|
|
749
|
+
const normalizedError = status === RUN_STATUS_FAILED
|
|
750
|
+
? {
|
|
751
|
+
name: error?.name || "Error",
|
|
752
|
+
code: error?.code || "STALE_RUN_PROCESS_EXITED",
|
|
753
|
+
message: error?.message || message || "Boss chat run process exited before it wrote a terminal state."
|
|
754
|
+
}
|
|
755
|
+
: null;
|
|
756
|
+
if (normalizedError) {
|
|
757
|
+
for (const field of ["worker_error_code", "worker_exit_code", "worker_signal"]) {
|
|
758
|
+
if (error?.[field] !== undefined && error?.[field] !== null && error?.[field] !== "") {
|
|
759
|
+
normalizedError[field] = error[field];
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (normalizeText(error?.stack || "")) {
|
|
763
|
+
normalizedError.stack = String(error.stack).slice(0, 8000);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
const workerLastHeartbeatAt = normalizeText(
|
|
767
|
+
persisted.recovery?.worker_last_heartbeat_at
|
|
768
|
+
|| persisted.heartbeat_at
|
|
769
|
+
|| ""
|
|
770
|
+
) || null;
|
|
771
|
+
const next = {
|
|
632
772
|
...persisted,
|
|
633
773
|
run_id: runId,
|
|
634
774
|
state: status,
|
|
635
775
|
status,
|
|
636
|
-
stage: persisted.stage || persisted.phase || "chat:stale",
|
|
637
|
-
updated_at: now,
|
|
638
|
-
heartbeat_at: now,
|
|
639
|
-
completed_at: persisted.completed_at || now,
|
|
776
|
+
stage: persisted.stage || persisted.phase || "chat:stale",
|
|
777
|
+
updated_at: now,
|
|
778
|
+
heartbeat_at: persisted.heartbeat_at || now,
|
|
779
|
+
completed_at: persisted.completed_at || now,
|
|
640
780
|
last_message: normalizedError?.message || message || status,
|
|
641
781
|
control: {
|
|
642
782
|
...(persisted.control || {}),
|
|
643
783
|
cancel_requested: false
|
|
644
784
|
},
|
|
645
|
-
error: normalizedError,
|
|
646
|
-
summary: persisted.summary || null
|
|
647
|
-
|
|
785
|
+
error: normalizedError,
|
|
786
|
+
summary: persisted.summary || null,
|
|
787
|
+
recovery: {
|
|
788
|
+
...(persisted.recovery && typeof persisted.recovery === "object" ? persisted.recovery : {}),
|
|
789
|
+
worker_last_heartbeat_at: workerLastHeartbeatAt,
|
|
790
|
+
reconciled_at: now,
|
|
791
|
+
reconciliation_reason: normalizedError?.code || status
|
|
792
|
+
}
|
|
793
|
+
};
|
|
648
794
|
return attachLegacyArtifactsToPersistedChatRun(next);
|
|
649
795
|
}
|
|
650
796
|
|
|
@@ -654,11 +800,21 @@ export function markBossChatDetachedWorkerFailed(runId, error, options = {}) {
|
|
|
654
800
|
const persisted = readChatRunState(normalizedRunId) || buildInitialChatDetachedState(normalizedRunId, {});
|
|
655
801
|
const state = normalizeText(persisted.state || persisted.status);
|
|
656
802
|
if (TERMINAL_STATUSES.has(state)) return persisted;
|
|
657
|
-
const errorPayload = {
|
|
803
|
+
const errorPayload = {
|
|
658
804
|
name: error?.name || "Error",
|
|
659
805
|
code: options.code || error?.code || "CHAT_WORKER_UNHANDLED_EXCEPTION",
|
|
660
|
-
message: normalizeText(error?.message || error || options.message) || "Boss chat detached worker exited unexpectedly."
|
|
661
|
-
};
|
|
806
|
+
message: normalizeText(error?.message || error || options.message) || "Boss chat detached worker exited unexpectedly."
|
|
807
|
+
};
|
|
808
|
+
const workerErrorCode = normalizeText(error?.code || "");
|
|
809
|
+
if (workerErrorCode && workerErrorCode !== errorPayload.code) {
|
|
810
|
+
errorPayload.worker_error_code = workerErrorCode;
|
|
811
|
+
}
|
|
812
|
+
if (Number.isInteger(options.workerExitCode)) {
|
|
813
|
+
errorPayload.worker_exit_code = options.workerExitCode;
|
|
814
|
+
}
|
|
815
|
+
if (normalizeText(options.workerSignal || "")) {
|
|
816
|
+
errorPayload.worker_signal = normalizeText(options.workerSignal);
|
|
817
|
+
}
|
|
662
818
|
if (normalizeText(error?.stack || "")) {
|
|
663
819
|
errorPayload.stack = String(error.stack).slice(0, 8000);
|
|
664
820
|
}
|
|
@@ -1346,9 +1502,10 @@ function getRunOptions(args, normalized, session, { workspaceRoot = "", configRe
|
|
|
1346
1502
|
listSettleMs: parsePositiveInteger(args.list_settle_ms, slowLive ? 1800 : 1200),
|
|
1347
1503
|
listFallbackPoint: null,
|
|
1348
1504
|
imageOutputDir: resolveBossConfiguredOutputDir("", getChatRunsDir()),
|
|
1349
|
-
humanRestEnabled: humanBehavior.restEnabled,
|
|
1350
|
-
humanBehavior,
|
|
1351
|
-
|
|
1505
|
+
humanRestEnabled: humanBehavior.restEnabled,
|
|
1506
|
+
humanBehavior,
|
|
1507
|
+
actionJournalScope: `boss-chat-cdp:${normalized.host}:${normalized.port}`,
|
|
1508
|
+
name: "mcp-boss-chat-run"
|
|
1352
1509
|
};
|
|
1353
1510
|
}
|
|
1354
1511
|
|
|
@@ -1816,7 +1973,9 @@ export async function startBossChatDetachedRunTool({ workspaceRoot = "", args =
|
|
|
1816
1973
|
|
|
1817
1974
|
let child;
|
|
1818
1975
|
try {
|
|
1819
|
-
child = launchDetachedChatWorker(runId
|
|
1976
|
+
child = launchDetachedChatWorker(runId, {
|
|
1977
|
+
screenConfigPath: defaultConfigResolution?.config_path || ""
|
|
1978
|
+
});
|
|
1820
1979
|
const now = new Date().toISOString();
|
|
1821
1980
|
const latest = readChatRunState(runId) || initial;
|
|
1822
1981
|
const latestState = normalizeText(latest.state || latest.status);
|
|
@@ -2181,24 +2340,38 @@ export function cancelBossChatRunTool({ args = {} } = {}) {
|
|
|
2181
2340
|
}
|
|
2182
2341
|
}
|
|
2183
2342
|
|
|
2184
|
-
export function __setChatMcpConnectorForTests(nextConnector) {
|
|
2185
|
-
chatConnectorImpl = typeof nextConnector === "function" ? nextConnector : connectChatChromeSession;
|
|
2186
|
-
}
|
|
2187
|
-
|
|
2188
|
-
export function
|
|
2343
|
+
export function __setChatMcpConnectorForTests(nextConnector) {
|
|
2344
|
+
chatConnectorImpl = typeof nextConnector === "function" ? nextConnector : connectChatChromeSession;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
export function __writeChatJsonAtomicForTests(filePath, payload, options = {}) {
|
|
2348
|
+
return writeJsonAtomic(filePath, payload, options);
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
export function __setChatMcpJobReaderForTests(nextReader) {
|
|
2189
2352
|
chatJobReaderImpl = typeof nextReader === "function" ? nextReader : readChatJobOptionsFromSession;
|
|
2190
2353
|
}
|
|
2191
2354
|
|
|
2192
|
-
export function __setChatMcpWorkflowForTests(nextWorkflow) {
|
|
2355
|
+
export function __setChatMcpWorkflowForTests(nextWorkflow) {
|
|
2193
2356
|
chatWorkflowImpl = typeof nextWorkflow === "function" ? nextWorkflow : runChatWorkflow;
|
|
2194
2357
|
chatRunService = createChatRunService({
|
|
2195
2358
|
idPrefix: "mcp_chat",
|
|
2196
2359
|
workflow: (...args) => chatWorkflowImpl(...args),
|
|
2197
2360
|
onSnapshot: persistChatLifecycleSnapshot
|
|
2198
2361
|
});
|
|
2199
|
-
}
|
|
2200
|
-
|
|
2201
|
-
export function
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
export function __setChatMcpSpawnForTests(nextSpawn) {
|
|
2365
|
+
chatDetachedWorkerLauncherImpl = typeof nextSpawn === "function"
|
|
2366
|
+
? (options) => launchDetachedWorker({
|
|
2367
|
+
...options,
|
|
2368
|
+
platform: "test-posix",
|
|
2369
|
+
spawnImpl: nextSpawn
|
|
2370
|
+
})
|
|
2371
|
+
: launchDetachedWorker;
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
export function __resetChatMcpStateForTests() {
|
|
2202
2375
|
for (const meta of chatRunMeta.values()) {
|
|
2203
2376
|
try {
|
|
2204
2377
|
meta.session?.close?.();
|
|
@@ -2207,7 +2380,8 @@ export function __resetChatMcpStateForTests() {
|
|
|
2207
2380
|
}
|
|
2208
2381
|
}
|
|
2209
2382
|
chatRunMeta.clear();
|
|
2210
|
-
__setChatMcpConnectorForTests(null);
|
|
2211
|
-
__setChatMcpJobReaderForTests(null);
|
|
2212
|
-
__setChatMcpWorkflowForTests(null);
|
|
2213
|
-
|
|
2383
|
+
__setChatMcpConnectorForTests(null);
|
|
2384
|
+
__setChatMcpJobReaderForTests(null);
|
|
2385
|
+
__setChatMcpWorkflowForTests(null);
|
|
2386
|
+
__setChatMcpSpawnForTests(null);
|
|
2387
|
+
}
|
|
@@ -277,11 +277,13 @@ export function resolveHumanBehaviorForRun(args = {}, config = {}) {
|
|
|
277
277
|
override.enabled = rawBehavior;
|
|
278
278
|
} else if (typeof rawBehavior === "string") {
|
|
279
279
|
applyHumanBehaviorProfileDefaults(override, rawBehavior);
|
|
280
|
-
} else if (rawBehavior && typeof rawBehavior === "object" && !Array.isArray(rawBehavior)) {
|
|
281
|
-
const rawProfile = readFirstOwn(rawBehavior, ["profile", "mode", "behaviorProfile", "behavior_profile"]);
|
|
282
|
-
if (rawProfile !== undefined) applyHumanBehaviorProfileDefaults(override, rawProfile);
|
|
283
|
-
Object.assign(override, rawBehavior);
|
|
284
|
-
|
|
280
|
+
} else if (rawBehavior && typeof rawBehavior === "object" && !Array.isArray(rawBehavior)) {
|
|
281
|
+
const rawProfile = readFirstOwn(rawBehavior, ["profile", "mode", "behaviorProfile", "behavior_profile"]);
|
|
282
|
+
if (rawProfile !== undefined) applyHumanBehaviorProfileDefaults(override, rawProfile);
|
|
283
|
+
Object.assign(override, rawBehavior);
|
|
284
|
+
const rawRestLevel = readFirstOwn(rawBehavior, ["restLevel", "rest_level"]);
|
|
285
|
+
if (rawRestLevel !== undefined) override.restLevel = rawRestLevel;
|
|
286
|
+
}
|
|
285
287
|
|
|
286
288
|
const profile = readFirstOwn(args, ["human_behavior_profile", "humanBehaviorProfile"]);
|
|
287
289
|
if (profile !== undefined) applyHumanBehaviorProfileDefaults(override, profile);
|