@reconcrap/boss-recommend-mcp 2.0.56 → 2.1.0
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/config/screening-config.example.json +1 -0
- package/package.json +2 -1
- package/skills/boss-chat/README.md +2 -1
- package/skills/boss-chat/SKILL.md +9 -1
- package/skills/boss-recommend-pipeline/README.md +1 -0
- package/skills/boss-recommend-pipeline/SKILL.md +5 -1
- package/skills/boss-recruit-pipeline/README.md +2 -0
- package/skills/boss-recruit-pipeline/SKILL.md +8 -0
- package/src/chat-mcp.js +397 -3
- package/src/cli.js +92 -50
- package/src/core/browser/index.js +162 -1
- package/src/core/self-heal/viewport.js +1 -1
- package/src/detached-worker.js +99 -0
- package/src/domains/chat/run-service.js +43 -35
- package/src/domains/recommend/run-service.js +6 -1
- package/src/domains/recruit/run-service.js +8 -1
- package/src/index.js +46 -3
- package/src/recruit-mcp.js +545 -11
package/src/cli.js
CHANGED
|
@@ -63,12 +63,13 @@ const installConfigDefaults = Object.freeze({
|
|
|
63
63
|
llmMaxRetries: 3,
|
|
64
64
|
llmImageLimit: 8,
|
|
65
65
|
llmImageDetail: "low",
|
|
66
|
-
humanRestEnabled: true,
|
|
67
|
-
humanBehavior: {
|
|
68
|
-
enabled: true,
|
|
69
|
-
profile: "paced_with_rests"
|
|
70
|
-
|
|
71
|
-
}
|
|
66
|
+
humanRestEnabled: true,
|
|
67
|
+
humanBehavior: {
|
|
68
|
+
enabled: true,
|
|
69
|
+
profile: "paced_with_rests",
|
|
70
|
+
restLevel: "low"
|
|
71
|
+
}
|
|
72
|
+
});
|
|
72
73
|
const bossChatRuntimeChildDirs = ["logs", "runs", "profiles", "reports", "artifacts", "state"];
|
|
73
74
|
const bossChatCliUnsupportedStartCode = "CHAT_CLI_ASYNC_UNSUPPORTED_CDP_ONLY";
|
|
74
75
|
const calibrateUnsupportedCode = "CALIBRATE_UNSUPPORTED_CDP_ONLY";
|
|
@@ -613,14 +614,20 @@ function parseBossChatTargetCountOption(raw) {
|
|
|
613
614
|
return parsed ?? text;
|
|
614
615
|
}
|
|
615
616
|
|
|
616
|
-
function parseBooleanOption(raw, fallback = undefined) {
|
|
617
|
-
if (raw === undefined || raw === null || raw === "") return fallback;
|
|
618
|
-
if (raw === true) return true;
|
|
619
|
-
const normalized = String(raw).trim().toLowerCase();
|
|
620
|
-
if (["true", "1", "yes", "y", "on"].includes(normalized)) return true;
|
|
621
|
-
if (["false", "0", "no", "n", "off"].includes(normalized)) return false;
|
|
622
|
-
return fallback;
|
|
623
|
-
}
|
|
617
|
+
function parseBooleanOption(raw, fallback = undefined) {
|
|
618
|
+
if (raw === undefined || raw === null || raw === "") return fallback;
|
|
619
|
+
if (raw === true) return true;
|
|
620
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
621
|
+
if (["true", "1", "yes", "y", "on"].includes(normalized)) return true;
|
|
622
|
+
if (["false", "0", "no", "n", "off"].includes(normalized)) return false;
|
|
623
|
+
return fallback;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function parseRestLevelOption(raw) {
|
|
627
|
+
if (raw === undefined || raw === null || raw === "") return undefined;
|
|
628
|
+
const normalized = String(raw).trim().toLowerCase();
|
|
629
|
+
return ["low", "medium", "high"].includes(normalized) ? normalized : undefined;
|
|
630
|
+
}
|
|
624
631
|
|
|
625
632
|
function normalizePageScope(value) {
|
|
626
633
|
const normalized = String(value || "").trim().toLowerCase();
|
|
@@ -1271,20 +1278,33 @@ async function resolveCliConfigTarget(options = {}) {
|
|
|
1271
1278
|
};
|
|
1272
1279
|
}
|
|
1273
1280
|
|
|
1274
|
-
function applyMissingInstallConfigDefaults(config = {}) {
|
|
1275
|
-
const nextConfig = { ...config };
|
|
1276
|
-
const patchedKeys = [];
|
|
1277
|
-
for (const [key, defaultValue] of Object.entries(installConfigDefaults)) {
|
|
1281
|
+
function applyMissingInstallConfigDefaults(config = {}) {
|
|
1282
|
+
const nextConfig = { ...config };
|
|
1283
|
+
const patchedKeys = [];
|
|
1284
|
+
for (const [key, defaultValue] of Object.entries(installConfigDefaults)) {
|
|
1278
1285
|
if (!Object.prototype.hasOwnProperty.call(nextConfig, key)) {
|
|
1279
1286
|
nextConfig[key] = defaultValue;
|
|
1280
|
-
patchedKeys.push(key);
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
nextConfig
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1287
|
+
patchedKeys.push(key);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
if (
|
|
1291
|
+
nextConfig.humanBehavior
|
|
1292
|
+
&& typeof nextConfig.humanBehavior === "object"
|
|
1293
|
+
&& !Array.isArray(nextConfig.humanBehavior)
|
|
1294
|
+
&& !Object.prototype.hasOwnProperty.call(nextConfig.humanBehavior, "restLevel")
|
|
1295
|
+
&& !Object.prototype.hasOwnProperty.call(nextConfig.humanBehavior, "rest_level")
|
|
1296
|
+
) {
|
|
1297
|
+
nextConfig.humanBehavior = {
|
|
1298
|
+
...nextConfig.humanBehavior,
|
|
1299
|
+
restLevel: "low"
|
|
1300
|
+
};
|
|
1301
|
+
patchedKeys.push("humanBehavior.restLevel");
|
|
1302
|
+
}
|
|
1303
|
+
return {
|
|
1304
|
+
nextConfig,
|
|
1305
|
+
patchedKeys
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1288
1308
|
|
|
1289
1309
|
async function ensureUserConfig(options = {}) {
|
|
1290
1310
|
const { configPath, workspacePreferred } = await resolveCliConfigTarget(options);
|
|
@@ -2721,19 +2741,30 @@ async function runPipelineOnce(options = {}) {
|
|
|
2721
2741
|
const workspaceRoot = getWorkspaceRoot(options);
|
|
2722
2742
|
const port = parsePositivePort(options.port) || parsePositivePort(process.env.BOSS_RECOMMEND_CHROME_PORT) || 9222;
|
|
2723
2743
|
|
|
2724
|
-
const args = {
|
|
2725
|
-
instruction,
|
|
2726
|
-
confirmation: confirmation ?? undefined,
|
|
2727
|
-
overrides: overrides ?? undefined,
|
|
2728
|
-
follow_up: followUp ?? undefined,
|
|
2744
|
+
const args = {
|
|
2745
|
+
instruction,
|
|
2746
|
+
confirmation: confirmation ?? undefined,
|
|
2747
|
+
overrides: overrides ?? undefined,
|
|
2748
|
+
follow_up: followUp ?? undefined,
|
|
2729
2749
|
host: typeof options.host === "string" && options.host.trim() ? options.host.trim() : undefined,
|
|
2730
2750
|
port,
|
|
2731
2751
|
target_url_includes: typeof options["target-url-includes"] === "string" && options["target-url-includes"].trim()
|
|
2732
2752
|
? options["target-url-includes"].trim()
|
|
2733
2753
|
: undefined,
|
|
2734
2754
|
allow_navigate: !(options["no-navigate"] === true || options.noNavigate === true || options.allow_navigate === false),
|
|
2735
|
-
slow_live: options["slow-live"] === true || options.slowLive === true || options.slow_live === true
|
|
2736
|
-
};
|
|
2755
|
+
slow_live: options["slow-live"] === true || options.slowLive === true || options.slow_live === true
|
|
2756
|
+
};
|
|
2757
|
+
const restLevel = parseRestLevelOption(
|
|
2758
|
+
options["rest-level"]
|
|
2759
|
+
?? options.rest_level
|
|
2760
|
+
?? options["human-behavior-rest-level"]
|
|
2761
|
+
?? options.human_behavior_rest_level
|
|
2762
|
+
);
|
|
2763
|
+
if (restLevel) {
|
|
2764
|
+
args.human_behavior = {
|
|
2765
|
+
restLevel
|
|
2766
|
+
};
|
|
2767
|
+
}
|
|
2737
2768
|
|
|
2738
2769
|
const optionalPassthrough = [
|
|
2739
2770
|
"detail_limit",
|
|
@@ -2810,16 +2841,22 @@ async function listRecommendJobsCli(options = {}) {
|
|
|
2810
2841
|
}));
|
|
2811
2842
|
}
|
|
2812
2843
|
|
|
2813
|
-
function buildBossChatCliInput(options = {}) {
|
|
2814
|
-
const greetingTextRaw =
|
|
2815
|
-
options["greeting-text"]
|
|
2816
|
-
?? options.greeting_text
|
|
2817
|
-
?? options.greetingText
|
|
2818
|
-
?? options.greeting;
|
|
2819
|
-
const greetingText = typeof greetingTextRaw === "string" ? greetingTextRaw.trim() : undefined;
|
|
2820
|
-
const targetUrlIncludes = String(options["target-url-includes"] || options.target_url_includes || "").trim();
|
|
2821
|
-
const host = String(options.host || "").trim();
|
|
2822
|
-
|
|
2844
|
+
function buildBossChatCliInput(options = {}) {
|
|
2845
|
+
const greetingTextRaw =
|
|
2846
|
+
options["greeting-text"]
|
|
2847
|
+
?? options.greeting_text
|
|
2848
|
+
?? options.greetingText
|
|
2849
|
+
?? options.greeting;
|
|
2850
|
+
const greetingText = typeof greetingTextRaw === "string" ? greetingTextRaw.trim() : undefined;
|
|
2851
|
+
const targetUrlIncludes = String(options["target-url-includes"] || options.target_url_includes || "").trim();
|
|
2852
|
+
const host = String(options.host || "").trim();
|
|
2853
|
+
const restLevel = parseRestLevelOption(
|
|
2854
|
+
options["rest-level"]
|
|
2855
|
+
?? options.rest_level
|
|
2856
|
+
?? options["human-behavior-rest-level"]
|
|
2857
|
+
?? options.human_behavior_rest_level
|
|
2858
|
+
);
|
|
2859
|
+
return {
|
|
2823
2860
|
profile: typeof options.profile === "string" ? options.profile.trim() : undefined,
|
|
2824
2861
|
job: typeof options.job === "string" ? options.job.trim() : undefined,
|
|
2825
2862
|
start_from: String(options["start-from"] || options.start_from || "").trim().toLowerCase() || undefined,
|
|
@@ -2837,13 +2874,18 @@ function buildBossChatCliInput(options = {}) {
|
|
|
2837
2874
|
dry_run: options["dry-run"] === true || options.dryRun === true,
|
|
2838
2875
|
no_state: options["no-state"] === true || options.noState === true,
|
|
2839
2876
|
human_behavior_enabled: parseBooleanOption(options["human-behavior-enabled"] ?? options.human_behavior_enabled),
|
|
2840
|
-
human_behavior_profile: typeof (options["human-behavior-profile"] ?? options.human_behavior_profile) === "string"
|
|
2841
|
-
? (options["human-behavior-profile"] ?? options.human_behavior_profile).trim()
|
|
2842
|
-
: undefined,
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
}
|
|
2877
|
+
human_behavior_profile: typeof (options["human-behavior-profile"] ?? options.human_behavior_profile) === "string"
|
|
2878
|
+
? (options["human-behavior-profile"] ?? options.human_behavior_profile).trim()
|
|
2879
|
+
: undefined,
|
|
2880
|
+
human_behavior: restLevel
|
|
2881
|
+
? {
|
|
2882
|
+
restLevel
|
|
2883
|
+
}
|
|
2884
|
+
: undefined,
|
|
2885
|
+
safe_pacing: parseBooleanOption(options["safe-pacing"] ?? options.safe_pacing),
|
|
2886
|
+
batch_rest_enabled: parseBooleanOption(options["batch-rest"] ?? options.batch_rest_enabled)
|
|
2887
|
+
};
|
|
2888
|
+
}
|
|
2847
2889
|
|
|
2848
2890
|
function getBossChatCliRunTarget(options = {}) {
|
|
2849
2891
|
return {
|
|
@@ -86,6 +86,46 @@ const HUMAN_BEHAVIOR_PROFILE_ALIASES = Object.freeze({
|
|
|
86
86
|
rests: "paced_with_rests",
|
|
87
87
|
rest: "paced_with_rests"
|
|
88
88
|
});
|
|
89
|
+
const DEFAULT_HUMAN_REST_LEVEL = "low";
|
|
90
|
+
const HUMAN_REST_LEVEL_ALIASES = Object.freeze({
|
|
91
|
+
default: "low",
|
|
92
|
+
light: "low",
|
|
93
|
+
normal: "medium",
|
|
94
|
+
med: "medium",
|
|
95
|
+
heavy: "high"
|
|
96
|
+
});
|
|
97
|
+
const HUMAN_REST_LEVEL_PROFILES = Object.freeze({
|
|
98
|
+
medium: Object.freeze({
|
|
99
|
+
targetRestMs: 30 * 60 * 1000,
|
|
100
|
+
targetCandidateCount: 700,
|
|
101
|
+
targetWindowMs: 5 * 60 * 60 * 1000,
|
|
102
|
+
intervalMin: 4,
|
|
103
|
+
intervalMax: 16,
|
|
104
|
+
longRestProbability: 0.22,
|
|
105
|
+
shortRestMinMs: 8000,
|
|
106
|
+
shortRestMaxMs: 45000,
|
|
107
|
+
longRestMinMs: 60000,
|
|
108
|
+
longRestMaxMs: 180000,
|
|
109
|
+
minDebtToRestMs: 8000,
|
|
110
|
+
forceDebtMs: 90000,
|
|
111
|
+
maxOverspendMs: 15000
|
|
112
|
+
}),
|
|
113
|
+
high: Object.freeze({
|
|
114
|
+
targetRestMs: 60 * 60 * 1000,
|
|
115
|
+
targetCandidateCount: 700,
|
|
116
|
+
targetWindowMs: 5 * 60 * 60 * 1000,
|
|
117
|
+
intervalMin: 3,
|
|
118
|
+
intervalMax: 12,
|
|
119
|
+
longRestProbability: 0.28,
|
|
120
|
+
shortRestMinMs: 12000,
|
|
121
|
+
shortRestMaxMs: 75000,
|
|
122
|
+
longRestMinMs: 90000,
|
|
123
|
+
longRestMaxMs: 300000,
|
|
124
|
+
minDebtToRestMs: 12000,
|
|
125
|
+
forceDebtMs: 150000,
|
|
126
|
+
maxOverspendMs: 25000
|
|
127
|
+
})
|
|
128
|
+
});
|
|
89
129
|
|
|
90
130
|
function clampNumber(value, min, max) {
|
|
91
131
|
const number = Number(value);
|
|
@@ -152,6 +192,14 @@ export function normalizeHumanBehaviorProfile(raw, fallback = "baseline") {
|
|
|
152
192
|
: fallback;
|
|
153
193
|
}
|
|
154
194
|
|
|
195
|
+
export function normalizeHumanRestLevel(raw, fallback = DEFAULT_HUMAN_REST_LEVEL) {
|
|
196
|
+
const normalized = String(raw || "").trim().toLowerCase().replace(/[\s-]+/g, "_");
|
|
197
|
+
const level = HUMAN_REST_LEVEL_ALIASES[normalized] || normalized;
|
|
198
|
+
return level === "low" || level === "medium" || level === "high"
|
|
199
|
+
? level
|
|
200
|
+
: fallback;
|
|
201
|
+
}
|
|
202
|
+
|
|
155
203
|
export function normalizeHumanBehaviorOptions(raw = null, {
|
|
156
204
|
legacyEnabled = false,
|
|
157
205
|
safePacing = null,
|
|
@@ -231,6 +279,10 @@ export function normalizeHumanBehaviorOptions(raw = null, {
|
|
|
231
279
|
readFirstOption(rawObject, ["batchRest", "batch_rest", "batchRestEnabled", "batch_rest_enabled"]),
|
|
232
280
|
profileDefaults.batchRest
|
|
233
281
|
);
|
|
282
|
+
const restLevel = normalizeHumanRestLevel(
|
|
283
|
+
readFirstOption(rawObject, ["restLevel", "rest_level"]),
|
|
284
|
+
DEFAULT_HUMAN_REST_LEVEL
|
|
285
|
+
);
|
|
234
286
|
if (batchRestFlag !== null) {
|
|
235
287
|
batchRest = batchRestFlag;
|
|
236
288
|
if (batchRestFlag === true && readFirstOption(rawObject, ["shortRest", "short_rest", "randomRest", "random_rest"]) === undefined) {
|
|
@@ -248,6 +300,7 @@ export function normalizeHumanBehaviorOptions(raw = null, {
|
|
|
248
300
|
shortRest: enabled && shortRest === true,
|
|
249
301
|
batchRest: enabled && batchRest === true,
|
|
250
302
|
actionCooldown: enabled && actionCooldown === true,
|
|
303
|
+
restLevel,
|
|
251
304
|
restEnabled: enabled && (shortRest === true || batchRest === true)
|
|
252
305
|
};
|
|
253
306
|
}
|
|
@@ -404,6 +457,8 @@ export function createHumanRestController({
|
|
|
404
457
|
shortRestEnabled = true,
|
|
405
458
|
batchRestEnabled = true,
|
|
406
459
|
random = Math.random,
|
|
460
|
+
nowFn = Date.now,
|
|
461
|
+
restLevel = DEFAULT_HUMAN_REST_LEVEL,
|
|
407
462
|
shortRestProbability = 0.08,
|
|
408
463
|
shortRestMinMs = 3000,
|
|
409
464
|
shortRestMaxMs = 7000,
|
|
@@ -413,12 +468,26 @@ export function createHumanRestController({
|
|
|
413
468
|
batchRestMaxMs = 30000
|
|
414
469
|
} = {}) {
|
|
415
470
|
const nextRandom = normalizeRandom(random);
|
|
471
|
+
const readNow = typeof nowFn === "function" ? nowFn : Date.now;
|
|
472
|
+
const normalizedRestLevel = normalizeHumanRestLevel(restLevel);
|
|
473
|
+
const budgetProfile = (shortRestEnabled !== false || batchRestEnabled !== false)
|
|
474
|
+
? HUMAN_REST_LEVEL_PROFILES[normalizedRestLevel] || null
|
|
475
|
+
: null;
|
|
476
|
+
const nextBudgetRestInterval = () => budgetProfile
|
|
477
|
+
? randomIntegerBetween(nextRandom, budgetProfile.intervalMin, budgetProfile.intervalMax)
|
|
478
|
+
: 0;
|
|
416
479
|
const state = {
|
|
417
480
|
enabled: enabled === true,
|
|
481
|
+
rest_level: normalizedRestLevel,
|
|
418
482
|
short_rest_enabled: enabled === true && shortRestEnabled !== false,
|
|
419
483
|
batch_rest_enabled: enabled === true && batchRestEnabled !== false,
|
|
420
484
|
rest_counter: 0,
|
|
421
485
|
rest_threshold: Math.max(1, Math.floor(Number(batchThresholdBase) || 25) + Math.floor(nextRandom() * Math.max(1, Number(batchThresholdJitter) || 1))),
|
|
486
|
+
processed_count: 0,
|
|
487
|
+
candidates_since_last_rest: 0,
|
|
488
|
+
candidates_until_next_rest: nextBudgetRestInterval(),
|
|
489
|
+
active_elapsed_ms: 0,
|
|
490
|
+
last_active_at_ms: Number(readNow()) || 0,
|
|
422
491
|
rest_count: 0,
|
|
423
492
|
total_rest_ms: 0
|
|
424
493
|
};
|
|
@@ -427,6 +496,67 @@ export function createHumanRestController({
|
|
|
427
496
|
state.rest_threshold = Math.max(1, Math.floor(Number(batchThresholdBase) || 25) + Math.floor(nextRandom() * Math.max(1, Number(batchThresholdJitter) || 1)));
|
|
428
497
|
}
|
|
429
498
|
|
|
499
|
+
function updateActiveElapsed() {
|
|
500
|
+
const now = Number(readNow()) || 0;
|
|
501
|
+
if (state.last_active_at_ms >= 0 && now >= state.last_active_at_ms) {
|
|
502
|
+
state.active_elapsed_ms += now - state.last_active_at_ms;
|
|
503
|
+
}
|
|
504
|
+
state.last_active_at_ms = now;
|
|
505
|
+
return now;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function getBudgetTargetMs() {
|
|
509
|
+
if (!budgetProfile) return 0;
|
|
510
|
+
const candidateTarget = state.processed_count * (budgetProfile.targetRestMs / budgetProfile.targetCandidateCount);
|
|
511
|
+
const elapsedTarget = state.active_elapsed_ms * (budgetProfile.targetRestMs / budgetProfile.targetWindowMs);
|
|
512
|
+
return Math.max(candidateTarget, elapsedTarget);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function chooseBudgetRestPause(debtMs) {
|
|
516
|
+
const longRest = nextRandom() < budgetProfile.longRestProbability;
|
|
517
|
+
const minMs = longRest ? budgetProfile.longRestMinMs : budgetProfile.shortRestMinMs;
|
|
518
|
+
const maxMs = longRest ? budgetProfile.longRestMaxMs : budgetProfile.shortRestMaxMs;
|
|
519
|
+
const scaleMin = longRest ? 0.75 : 0.38;
|
|
520
|
+
const scaleMax = longRest ? 1.1 : 0.78;
|
|
521
|
+
const desiredMs = debtMs * randomBetween(nextRandom, scaleMin, scaleMax);
|
|
522
|
+
const randomizedMs = randomBetween(nextRandom, minMs, maxMs);
|
|
523
|
+
const blendedMs = Math.max(minMs, Math.min(maxMs, (desiredMs + randomizedMs) / 2));
|
|
524
|
+
const maxAllowedMs = Math.max(minMs, debtMs + budgetProfile.maxOverspendMs);
|
|
525
|
+
return {
|
|
526
|
+
pauseMs: Math.round(Math.min(blendedMs, maxAllowedMs)),
|
|
527
|
+
restSize: longRest ? "long" : "short"
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
async function takeBudgetBreakIfNeeded(sleeper) {
|
|
532
|
+
state.processed_count += 1;
|
|
533
|
+
state.candidates_since_last_rest += 1;
|
|
534
|
+
state.candidates_until_next_rest -= 1;
|
|
535
|
+
const debtMs = getBudgetTargetMs() - state.total_rest_ms;
|
|
536
|
+
const intervalDue = state.candidates_until_next_rest <= 0;
|
|
537
|
+
const forceDue = debtMs >= budgetProfile.forceDebtMs;
|
|
538
|
+
if (!intervalDue && !forceDue) {
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
if (debtMs < budgetProfile.minDebtToRestMs) {
|
|
542
|
+
if (intervalDue) state.candidates_until_next_rest = nextBudgetRestInterval();
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
const { pauseMs, restSize } = chooseBudgetRestPause(debtMs);
|
|
546
|
+
await sleeper(pauseMs);
|
|
547
|
+
const event = {
|
|
548
|
+
kind: "random_rest",
|
|
549
|
+
rest_level: normalizedRestLevel,
|
|
550
|
+
rest_size: restSize,
|
|
551
|
+
pause_ms: pauseMs,
|
|
552
|
+
processed_since_last_rest: state.candidates_since_last_rest,
|
|
553
|
+
rest_budget_debt_ms: Math.round(Math.max(0, debtMs))
|
|
554
|
+
};
|
|
555
|
+
state.candidates_since_last_rest = 0;
|
|
556
|
+
state.candidates_until_next_rest = nextBudgetRestInterval();
|
|
557
|
+
return event;
|
|
558
|
+
}
|
|
559
|
+
|
|
430
560
|
async function takeBreakIfNeeded({ sleepFn = sleep } = {}) {
|
|
431
561
|
if (!state.enabled) {
|
|
432
562
|
return {
|
|
@@ -438,18 +568,44 @@ export function createHumanRestController({
|
|
|
438
568
|
};
|
|
439
569
|
}
|
|
440
570
|
const sleeper = typeof sleepFn === "function" ? sleepFn : sleep;
|
|
571
|
+
updateActiveElapsed();
|
|
572
|
+
if (budgetProfile) {
|
|
573
|
+
const budgetEvent = await takeBudgetBreakIfNeeded(sleeper);
|
|
574
|
+
const pauseMs = budgetEvent?.pause_ms || 0;
|
|
575
|
+
if (pauseMs > 0) {
|
|
576
|
+
state.rest_count += 1;
|
|
577
|
+
state.total_rest_ms += pauseMs;
|
|
578
|
+
state.last_active_at_ms = Number(readNow()) || state.last_active_at_ms;
|
|
579
|
+
}
|
|
580
|
+
return {
|
|
581
|
+
enabled: true,
|
|
582
|
+
rested: Boolean(budgetEvent),
|
|
583
|
+
pause_ms: pauseMs,
|
|
584
|
+
rest_level: normalizedRestLevel,
|
|
585
|
+
rest_counter: state.rest_counter,
|
|
586
|
+
rest_threshold: state.rest_threshold,
|
|
587
|
+
processed_count: state.processed_count,
|
|
588
|
+
candidates_until_next_rest: state.candidates_until_next_rest,
|
|
589
|
+
active_elapsed_ms: state.active_elapsed_ms,
|
|
590
|
+
rest_count: state.rest_count,
|
|
591
|
+
total_rest_ms: state.total_rest_ms,
|
|
592
|
+
events: budgetEvent ? [budgetEvent] : []
|
|
593
|
+
};
|
|
594
|
+
}
|
|
441
595
|
state.rest_counter += 1;
|
|
596
|
+
state.processed_count += 1;
|
|
442
597
|
const events = [];
|
|
443
598
|
if (state.short_rest_enabled && nextRandom() < Math.max(0, Number(shortRestProbability) || 0)) {
|
|
444
599
|
const pauseMs = Math.round(randomBetween(nextRandom, shortRestMinMs, shortRestMaxMs));
|
|
445
600
|
await sleeper(pauseMs);
|
|
446
|
-
events.push({ kind: "random_rest", pause_ms: pauseMs });
|
|
601
|
+
events.push({ kind: "random_rest", rest_level: normalizedRestLevel, pause_ms: pauseMs });
|
|
447
602
|
}
|
|
448
603
|
if (state.batch_rest_enabled && state.rest_counter >= state.rest_threshold) {
|
|
449
604
|
const pauseMs = Math.round(randomBetween(nextRandom, batchRestMinMs, batchRestMaxMs));
|
|
450
605
|
await sleeper(pauseMs);
|
|
451
606
|
events.push({
|
|
452
607
|
kind: "batch_rest",
|
|
608
|
+
rest_level: normalizedRestLevel,
|
|
453
609
|
pause_ms: pauseMs,
|
|
454
610
|
processed_since_last_batch_rest: state.rest_counter
|
|
455
611
|
});
|
|
@@ -460,13 +616,17 @@ export function createHumanRestController({
|
|
|
460
616
|
if (pauseMs > 0) {
|
|
461
617
|
state.rest_count += events.length;
|
|
462
618
|
state.total_rest_ms += pauseMs;
|
|
619
|
+
state.last_active_at_ms = Number(readNow()) || state.last_active_at_ms;
|
|
463
620
|
}
|
|
464
621
|
return {
|
|
465
622
|
enabled: true,
|
|
466
623
|
rested: events.length > 0,
|
|
467
624
|
pause_ms: pauseMs,
|
|
625
|
+
rest_level: normalizedRestLevel,
|
|
468
626
|
rest_counter: state.rest_counter,
|
|
469
627
|
rest_threshold: state.rest_threshold,
|
|
628
|
+
processed_count: state.processed_count,
|
|
629
|
+
active_elapsed_ms: state.active_elapsed_ms,
|
|
470
630
|
rest_count: state.rest_count,
|
|
471
631
|
total_rest_ms: state.total_rest_ms,
|
|
472
632
|
events
|
|
@@ -743,6 +903,7 @@ export function buildBossChromeLaunchArgs({
|
|
|
743
903
|
...LID_CLOSED_SAFE_CHROME_ARGS,
|
|
744
904
|
...parseExtraChromeArgs(process.env.BOSS_MCP_EXTRA_CHROME_ARGS),
|
|
745
905
|
...extraArgs,
|
|
906
|
+
"--start-maximized",
|
|
746
907
|
"--new-window",
|
|
747
908
|
url
|
|
748
909
|
];
|
|
@@ -326,7 +326,7 @@ export async function toggleWindowStateForViewportRecovery(client, {
|
|
|
326
326
|
const currentInfo = await getCurrentWindowInfo(client);
|
|
327
327
|
const currentState = normalizeText(currentInfo?.bounds?.windowState || "").toLowerCase();
|
|
328
328
|
const sequence = currentState === "normal"
|
|
329
|
-
? ["maximized"
|
|
329
|
+
? ["maximized"]
|
|
330
330
|
: ["normal", "maximized"];
|
|
331
331
|
const attempts = [];
|
|
332
332
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
markBossChatDetachedWorkerFailed,
|
|
4
|
+
runBossChatDetachedWorker
|
|
5
|
+
} from "./chat-mcp.js";
|
|
6
|
+
import {
|
|
7
|
+
markBossRecruitDetachedWorkerFailed,
|
|
8
|
+
runBossRecruitDetachedWorker
|
|
9
|
+
} from "./recruit-mcp.js";
|
|
10
|
+
|
|
11
|
+
function normalizeText(value) {
|
|
12
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseArgs(argv = []) {
|
|
16
|
+
const args = {};
|
|
17
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
18
|
+
const item = argv[index];
|
|
19
|
+
if (item === "--domain") {
|
|
20
|
+
args.domain = normalizeText(argv[index + 1]).toLowerCase();
|
|
21
|
+
index += 1;
|
|
22
|
+
} else if (item === "--run-id") {
|
|
23
|
+
args.runId = normalizeText(argv[index + 1]);
|
|
24
|
+
index += 1;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return args;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function markFailed(domain, runId, error, options = {}) {
|
|
31
|
+
if (domain === "chat") {
|
|
32
|
+
return markBossChatDetachedWorkerFailed(runId, error, options);
|
|
33
|
+
}
|
|
34
|
+
if (domain === "recruit") {
|
|
35
|
+
return markBossRecruitDetachedWorkerFailed(runId, error, options);
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function installFailureHandlers(domain, runId) {
|
|
41
|
+
let handled = false;
|
|
42
|
+
const failOnce = (error, options = {}) => {
|
|
43
|
+
if (handled) return;
|
|
44
|
+
handled = true;
|
|
45
|
+
try {
|
|
46
|
+
markFailed(domain, runId, error, options);
|
|
47
|
+
} catch (markError) {
|
|
48
|
+
console.error("[boss-recommend-mcp] failed to persist detached worker failure", markError);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
process.on("uncaughtException", (error) => {
|
|
53
|
+
console.error("[boss-recommend-mcp] detached worker uncaught exception", error);
|
|
54
|
+
failOnce(error, { code: "DETACHED_WORKER_UNCAUGHT_EXCEPTION" });
|
|
55
|
+
process.exit(1);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
process.on("unhandledRejection", (reason) => {
|
|
59
|
+
console.error("[boss-recommend-mcp] detached worker unhandled rejection", reason);
|
|
60
|
+
const error = reason instanceof Error ? reason : new Error(normalizeText(reason) || "Unhandled promise rejection");
|
|
61
|
+
failOnce(error, { code: "DETACHED_WORKER_UNHANDLED_REJECTION" });
|
|
62
|
+
process.exit(1);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
for (const signal of ["SIGTERM", "SIGINT", "SIGHUP"]) {
|
|
66
|
+
process.on(signal, () => {
|
|
67
|
+
const error = new Error(`detached ${domain} worker received ${signal}`);
|
|
68
|
+
console.error("[boss-recommend-mcp] detached worker received signal", signal);
|
|
69
|
+
failOnce(error, { code: "DETACHED_WORKER_SIGNAL" });
|
|
70
|
+
const signalExitCodes = { SIGHUP: 129, SIGINT: 130, SIGTERM: 143 };
|
|
71
|
+
process.exit(signalExitCodes[signal] || 1);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function main() {
|
|
77
|
+
const options = parseArgs(process.argv.slice(2));
|
|
78
|
+
if (!options.domain || !options.runId) {
|
|
79
|
+
console.error("[boss-recommend-mcp] detached worker requires --domain and --run-id");
|
|
80
|
+
process.exitCode = 1;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
installFailureHandlers(options.domain, options.runId);
|
|
84
|
+
const result = options.domain === "chat"
|
|
85
|
+
? await runBossChatDetachedWorker({ runId: options.runId })
|
|
86
|
+
: options.domain === "recruit"
|
|
87
|
+
? await runBossRecruitDetachedWorker({ runId: options.runId })
|
|
88
|
+
: { ok: false, error: `Unsupported detached worker domain: ${options.domain}` };
|
|
89
|
+
if (!result?.ok) {
|
|
90
|
+
process.exitCode = 1;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await main().catch((error) => {
|
|
95
|
+
const options = parseArgs(process.argv.slice(2));
|
|
96
|
+
console.error("[boss-recommend-mcp] detached worker failed", error);
|
|
97
|
+
markFailed(options.domain, options.runId, error, { code: "DETACHED_WORKER_FAILED" });
|
|
98
|
+
process.exitCode = 1;
|
|
99
|
+
});
|