@reconcrap/boss-recommend-mcp 2.1.22 → 2.1.24

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.
Files changed (66) hide show
  1. package/README.md +5 -0
  2. package/bin/boss-recommend-mcp.js +4 -4
  3. package/package.json +14 -8
  4. package/scripts/install-macos.sh +280 -280
  5. package/scripts/postinstall.cjs +44 -44
  6. package/skills/boss-chat/README.md +43 -42
  7. package/skills/boss-chat/SKILL.md +107 -106
  8. package/skills/boss-recommend-pipeline/README.md +13 -13
  9. package/skills/boss-recommend-pipeline/SKILL.md +47 -47
  10. package/skills/boss-recruit-pipeline/README.md +19 -19
  11. package/skills/boss-recruit-pipeline/SKILL.md +89 -89
  12. package/src/chat-mcp.js +301 -127
  13. package/src/core/boss-cards/index.js +199 -199
  14. package/src/core/browser/index.js +291 -114
  15. package/src/core/capture/index.js +2930 -1201
  16. package/src/core/cv-acquisition/index.js +512 -238
  17. package/src/core/cv-capture-target/index.js +513 -299
  18. package/src/core/greet-quota/index.js +71 -71
  19. package/src/core/infinite-list/index.js +11 -2
  20. package/src/core/reporting/legacy-csv.js +12 -12
  21. package/src/core/run/detached-launcher.js +305 -0
  22. package/src/core/run/index.js +112 -42
  23. package/src/core/run/timing.js +33 -33
  24. package/src/core/run/windows-detached-worker.ps1 +106 -0
  25. package/src/core/screening/index.js +2135 -2135
  26. package/src/core/self-heal/index.js +989 -973
  27. package/src/core/self-heal/viewport.js +1505 -564
  28. package/src/detached-worker.js +99 -99
  29. package/src/domains/chat/action-journal.js +443 -0
  30. package/src/domains/chat/cards.js +137 -137
  31. package/src/domains/chat/constants.js +9 -9
  32. package/src/domains/chat/detail.js +1684 -401
  33. package/src/domains/chat/index.js +8 -7
  34. package/src/domains/chat/jobs.js +620 -620
  35. package/src/domains/chat/page-guard.js +157 -122
  36. package/src/domains/chat/roots.js +56 -56
  37. package/src/domains/chat/run-service.js +1801 -760
  38. package/src/domains/common/account-rights-panel.js +314 -314
  39. package/src/domains/common/recovery-settle.js +159 -159
  40. package/src/domains/recommend/actions.js +515 -472
  41. package/src/domains/recommend/cards.js +243 -243
  42. package/src/domains/recommend/colleague-contact.js +333 -333
  43. package/src/domains/recommend/constants.js +92 -92
  44. package/src/domains/recommend/detail.js +12 -3
  45. package/src/domains/recommend/filters.js +611 -611
  46. package/src/domains/recommend/index.js +9 -9
  47. package/src/domains/recommend/jobs.js +542 -542
  48. package/src/domains/recommend/location.js +736 -736
  49. package/src/domains/recommend/refresh.js +410 -329
  50. package/src/domains/recommend/roots.js +80 -80
  51. package/src/domains/recommend/run-service.js +1783 -592
  52. package/src/domains/recommend/scopes.js +246 -246
  53. package/src/domains/recruit/actions.js +277 -277
  54. package/src/domains/recruit/cards.js +74 -74
  55. package/src/domains/recruit/constants.js +236 -236
  56. package/src/domains/recruit/detail.js +588 -588
  57. package/src/domains/recruit/index.js +9 -9
  58. package/src/domains/recruit/instruction-parser.js +866 -866
  59. package/src/domains/recruit/refresh.js +45 -45
  60. package/src/domains/recruit/roots.js +68 -68
  61. package/src/domains/recruit/run-service.js +1817 -1620
  62. package/src/domains/recruit/search.js +3229 -3229
  63. package/src/index.js +124 -5
  64. package/src/parser.js +1296 -1296
  65. package/src/recommend-mcp.js +515 -80
  66. package/src/recommend-scheduler.js +66 -0
@@ -1,71 +1,71 @@
1
- import { normalizeText } from "../screening/index.js";
2
-
3
- export const GREET_CREDITS_EXHAUSTED_CODE = "GREET_CREDITS_EXHAUSTED";
4
-
5
- function coerceQuota(quota) {
6
- if (!quota || typeof quota !== "object") return null;
7
- return {
8
- found: Boolean(quota.found),
9
- text: normalizeText(quota.text),
10
- numerator: Number.isFinite(Number(quota.numerator)) ? Number(quota.numerator) : null,
11
- denominator: Number.isFinite(Number(quota.denominator)) ? Number(quota.denominator) : null,
12
- exhausted: Boolean(quota.exhausted)
13
- };
14
- }
15
-
16
- export function parseGreetQuota(label = "") {
17
- const text = normalizeText(label);
18
- const match = text.match(/立即沟通\s*[\((]\s*(\d+)\s*[//]\s*(\d+)\s*[\))]/);
19
- if (!match) {
20
- return {
21
- found: false,
22
- text,
23
- numerator: null,
24
- denominator: null,
25
- exhausted: false
26
- };
27
- }
28
- const numerator = Number(match[1]);
29
- const denominator = Number(match[2]);
30
- return {
31
- found: true,
32
- text,
33
- numerator,
34
- denominator,
35
- exhausted: numerator > denominator
36
- };
37
- }
38
-
39
- export function normalizeGreetQuotaSource(source = "") {
40
- return coerceQuota(source) || parseGreetQuota(source);
41
- }
42
-
43
- export function assertGreetQuotaAvailable(source = "") {
44
- const quota = normalizeGreetQuotaSource(source);
45
- if (quota.exhausted) {
46
- const error = new Error(
47
- `Greet credits exhausted according to Boss quota text: ${quota.numerator}/${quota.denominator}`
48
- );
49
- error.code = GREET_CREDITS_EXHAUSTED_CODE;
50
- error.greet_quota = quota;
51
- throw error;
52
- }
53
- return quota;
54
- }
55
-
56
- export function describeGreetQuotaAfterSpend(source = "") {
57
- const quota = normalizeGreetQuotaSource(source);
58
- if (!quota.found || quota.numerator === null || quota.denominator === null) {
59
- return {
60
- ...quota,
61
- remaining_after_spend: null,
62
- exhausted_after_spend: false
63
- };
64
- }
65
- const remaining = quota.denominator - quota.numerator;
66
- return {
67
- ...quota,
68
- remaining_after_spend: remaining,
69
- exhausted_after_spend: remaining < quota.numerator
70
- };
71
- }
1
+ import { normalizeText } from "../screening/index.js";
2
+
3
+ export const GREET_CREDITS_EXHAUSTED_CODE = "GREET_CREDITS_EXHAUSTED";
4
+
5
+ function coerceQuota(quota) {
6
+ if (!quota || typeof quota !== "object") return null;
7
+ return {
8
+ found: Boolean(quota.found),
9
+ text: normalizeText(quota.text),
10
+ numerator: Number.isFinite(Number(quota.numerator)) ? Number(quota.numerator) : null,
11
+ denominator: Number.isFinite(Number(quota.denominator)) ? Number(quota.denominator) : null,
12
+ exhausted: Boolean(quota.exhausted)
13
+ };
14
+ }
15
+
16
+ export function parseGreetQuota(label = "") {
17
+ const text = normalizeText(label);
18
+ const match = text.match(/立即沟通\s*[\((]\s*(\d+)\s*[//]\s*(\d+)\s*[\))]/);
19
+ if (!match) {
20
+ return {
21
+ found: false,
22
+ text,
23
+ numerator: null,
24
+ denominator: null,
25
+ exhausted: false
26
+ };
27
+ }
28
+ const numerator = Number(match[1]);
29
+ const denominator = Number(match[2]);
30
+ return {
31
+ found: true,
32
+ text,
33
+ numerator,
34
+ denominator,
35
+ exhausted: numerator > denominator
36
+ };
37
+ }
38
+
39
+ export function normalizeGreetQuotaSource(source = "") {
40
+ return coerceQuota(source) || parseGreetQuota(source);
41
+ }
42
+
43
+ export function assertGreetQuotaAvailable(source = "") {
44
+ const quota = normalizeGreetQuotaSource(source);
45
+ if (quota.exhausted) {
46
+ const error = new Error(
47
+ `Greet credits exhausted according to Boss quota text: ${quota.numerator}/${quota.denominator}`
48
+ );
49
+ error.code = GREET_CREDITS_EXHAUSTED_CODE;
50
+ error.greet_quota = quota;
51
+ throw error;
52
+ }
53
+ return quota;
54
+ }
55
+
56
+ export function describeGreetQuotaAfterSpend(source = "") {
57
+ const quota = normalizeGreetQuotaSource(source);
58
+ if (!quota.found || quota.numerator === null || quota.denominator === null) {
59
+ return {
60
+ ...quota,
61
+ remaining_after_spend: null,
62
+ exhausted_after_spend: false
63
+ };
64
+ }
65
+ const remaining = quota.denominator - quota.numerator;
66
+ return {
67
+ ...quota,
68
+ remaining_after_spend: remaining,
69
+ exhausted_after_spend: remaining < quota.numerator
70
+ };
71
+ }
@@ -893,7 +893,8 @@ export async function readVisibleInfiniteListItems({
893
893
  nodeIds = [],
894
894
  readCandidate,
895
895
  keyForCandidate = candidateKeyFromProfile,
896
- state = null
896
+ state = null,
897
+ shouldRethrowReadError = null
897
898
  } = {}) {
898
899
  if (typeof readCandidate !== "function") {
899
900
  throw new Error("readVisibleInfiniteListItems requires readCandidate");
@@ -915,6 +916,12 @@ export async function readVisibleInfiniteListItems({
915
916
  error: error?.message || String(error)
916
917
  });
917
918
  }
919
+ if (
920
+ typeof shouldRethrowReadError === "function"
921
+ && shouldRethrowReadError(error, { nodeId, visibleIndex })
922
+ ) {
923
+ throw error;
924
+ }
918
925
  continue;
919
926
  }
920
927
  const key = keyForCandidate(candidate, {
@@ -1134,6 +1141,7 @@ export async function getNextInfiniteListCandidate({
1134
1141
  readCandidate,
1135
1142
  detectBottomMarker = null,
1136
1143
  keyForCandidate = candidateKeyFromProfile,
1144
+ shouldRethrowReadError = null,
1137
1145
  maxScrolls = 20,
1138
1146
  stableSignatureLimit = 2,
1139
1147
  minScrollsBeforeEnd = 3,
@@ -1160,7 +1168,8 @@ export async function getNextInfiniteListCandidate({
1160
1168
  nodeIds,
1161
1169
  readCandidate,
1162
1170
  keyForCandidate,
1163
- state
1171
+ state,
1172
+ shouldRethrowReadError
1164
1173
  });
1165
1174
  const signature = updateInfiniteListVisibleSignature(state, items);
1166
1175
  const next = firstUnseenInfiniteListItem(state, items);
@@ -41,18 +41,18 @@ export const LEGACY_RESULT_HEADER = [
41
41
  ];
42
42
 
43
43
  const SEARCH_PARAM_ORDER = [
44
- "school_tag",
45
- "degree",
46
- "degrees",
47
- "gender",
48
- "recent_not_view",
49
- "current_city_only",
50
- "activity_level",
51
- "city",
52
- "schools",
53
- "keyword",
54
- "filter_recent_viewed",
55
- "skip_recent_colleague_contacted",
44
+ "school_tag",
45
+ "degree",
46
+ "degrees",
47
+ "gender",
48
+ "recent_not_view",
49
+ "current_city_only",
50
+ "activity_level",
51
+ "city",
52
+ "schools",
53
+ "keyword",
54
+ "filter_recent_viewed",
55
+ "skip_recent_colleague_contacted",
56
56
  "job",
57
57
  "start_from",
58
58
  "target_count",
@@ -0,0 +1,305 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import process from "node:process";
4
+ import { spawn, spawnSync } from "node:child_process";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const WINDOWS_WRAPPER_PATH = fileURLToPath(new URL("./windows-detached-worker.ps1", import.meta.url));
8
+ const SAFE_DOMAIN_PATTERN = /^[a-z][a-z0-9_-]{0,31}$/;
9
+ const SAFE_RUN_ID_PATTERN = /^[A-Za-z0-9._-]+$/;
10
+ const SAFE_DOMAINS = new Set(["chat", "recommend", "recruit"]);
11
+
12
+ function createLauncherError(code, message, cause = null) {
13
+ const error = new Error(message);
14
+ error.code = code;
15
+ if (cause) error.cause = cause;
16
+ return error;
17
+ }
18
+
19
+ function ensureLogFile(filePath) {
20
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
21
+ fs.closeSync(fs.openSync(filePath, "a"));
22
+ }
23
+
24
+ function assertControlledPath(value, label, platform) {
25
+ const normalized = String(value || "");
26
+ const pathApi = platform === "win32" ? path.win32 : path;
27
+ if (!normalized || !pathApi.isAbsolute(normalized)) {
28
+ throw createLauncherError("DETACHED_WORKER_PATH_INVALID", `${label} must be an absolute path`);
29
+ }
30
+ if (/[\0\r\n"]/.test(normalized)) {
31
+ throw createLauncherError("DETACHED_WORKER_PATH_INVALID", `${label} contains unsupported characters`);
32
+ }
33
+ return normalized;
34
+ }
35
+
36
+ function assertControlledToken(value, label, pattern) {
37
+ const normalized = String(value || "");
38
+ if (!pattern.test(normalized)) {
39
+ throw createLauncherError("DETACHED_WORKER_ARGUMENT_INVALID", `${label} contains unsupported characters`);
40
+ }
41
+ return normalized;
42
+ }
43
+
44
+ export function quoteWindowsCommandLineArgument(value) {
45
+ const input = String(value ?? "");
46
+ if (input.includes("\0")) {
47
+ throw createLauncherError("DETACHED_WORKER_ARGUMENT_INVALID", "Windows command-line arguments cannot contain NUL");
48
+ }
49
+ if (input && !/[\s"]/.test(input)) return input;
50
+ let output = '"';
51
+ let backslashes = 0;
52
+ for (const character of input) {
53
+ if (character === "\\") {
54
+ backslashes += 1;
55
+ continue;
56
+ }
57
+ if (character === '"') {
58
+ output += "\\".repeat((backslashes * 2) + 1);
59
+ output += '"';
60
+ backslashes = 0;
61
+ continue;
62
+ }
63
+ output += "\\".repeat(backslashes);
64
+ output += character;
65
+ backslashes = 0;
66
+ }
67
+ output += "\\".repeat(backslashes * 2);
68
+ output += '"';
69
+ return output;
70
+ }
71
+
72
+ function quotePowerShellLiteral(value) {
73
+ return `'${String(value ?? "").replaceAll("'", "''")}'`;
74
+ }
75
+
76
+ function windowsPowerShellPath(environment = process.env) {
77
+ const systemRoot = String(environment?.SystemRoot || environment?.WINDIR || "").trim();
78
+ return systemRoot
79
+ ? path.win32.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe")
80
+ : "powershell.exe";
81
+ }
82
+
83
+ export function buildWindowsDetachedWorkerCommand({
84
+ powershellPath,
85
+ wrapperScriptPath,
86
+ nodePath,
87
+ workerScriptPath,
88
+ domain,
89
+ runId,
90
+ stdoutPath,
91
+ stderrPath,
92
+ chatRuntimeHomePath = "",
93
+ screenConfigPath = ""
94
+ }) {
95
+ const args = [
96
+ powershellPath,
97
+ "-NoProfile",
98
+ "-NonInteractive",
99
+ "-ExecutionPolicy",
100
+ "Bypass",
101
+ "-WindowStyle",
102
+ "Hidden",
103
+ "-File",
104
+ wrapperScriptPath,
105
+ "-NodePath",
106
+ nodePath,
107
+ "-WorkerScriptPath",
108
+ workerScriptPath,
109
+ "-Domain",
110
+ domain,
111
+ "-RunId",
112
+ runId,
113
+ "-StdoutPath",
114
+ stdoutPath,
115
+ "-StderrPath",
116
+ stderrPath
117
+ ];
118
+ if (chatRuntimeHomePath) {
119
+ args.push("-ChatRuntimeHomePath", chatRuntimeHomePath);
120
+ }
121
+ if (screenConfigPath) {
122
+ args.push("-ScreenConfigPath", screenConfigPath);
123
+ }
124
+ return args.map(quoteWindowsCommandLineArgument).join(" ");
125
+ }
126
+
127
+ export function buildWindowsCimEncodedCommand(commandLine) {
128
+ const script = [
129
+ "$ErrorActionPreference = 'Stop'",
130
+ `$commandLine = ${quotePowerShellLiteral(commandLine)}`,
131
+ "$result = Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{ CommandLine = $commandLine }",
132
+ "$payload = [ordered]@{ return_value = [int]$result.ReturnValue; process_id = [int]$result.ProcessId }",
133
+ "[Console]::Out.Write(($payload | ConvertTo-Json -Compress))",
134
+ "if ([int]$result.ReturnValue -ne 0) { exit 1 }"
135
+ ].join("; ");
136
+ return Buffer.from(script, "utf16le").toString("base64");
137
+ }
138
+
139
+ function parseCimPayload(stdout = "") {
140
+ const lines = String(stdout || "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
141
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
142
+ try {
143
+ const parsed = JSON.parse(lines[index]);
144
+ if (parsed && typeof parsed === "object") return parsed;
145
+ } catch {
146
+ // PowerShell may emit non-JSON diagnostics before the compact payload.
147
+ }
148
+ }
149
+ return null;
150
+ }
151
+
152
+ function launchWindowsDetachedWorker(options) {
153
+ const {
154
+ nodePath,
155
+ workerScriptPath,
156
+ domain,
157
+ runId,
158
+ stdoutPath,
159
+ stderrPath,
160
+ chatRuntimeHomePath,
161
+ screenConfigPath,
162
+ wrapperScriptPath = WINDOWS_WRAPPER_PATH,
163
+ powershellPath = windowsPowerShellPath(options.environment),
164
+ spawnSyncImpl = spawnSync
165
+ } = options;
166
+ const commandLine = buildWindowsDetachedWorkerCommand({
167
+ powershellPath,
168
+ wrapperScriptPath,
169
+ nodePath,
170
+ workerScriptPath,
171
+ domain,
172
+ runId,
173
+ stdoutPath,
174
+ stderrPath,
175
+ chatRuntimeHomePath,
176
+ screenConfigPath
177
+ });
178
+ const encodedCommand = buildWindowsCimEncodedCommand(commandLine);
179
+ const result = spawnSyncImpl(powershellPath, [
180
+ "-NoProfile",
181
+ "-NonInteractive",
182
+ "-ExecutionPolicy",
183
+ "Bypass",
184
+ "-EncodedCommand",
185
+ encodedCommand
186
+ ], {
187
+ encoding: "utf8",
188
+ windowsHide: true,
189
+ stdio: ["ignore", "pipe", "pipe"]
190
+ });
191
+ if (result?.error) {
192
+ throw createLauncherError(
193
+ "WINDOWS_CIM_LAUNCH_FAILED",
194
+ `Unable to invoke the Windows detached-worker launcher: ${result.error.message || result.error}`,
195
+ result.error
196
+ );
197
+ }
198
+ const payload = parseCimPayload(result?.stdout);
199
+ const returnValue = Number(payload?.return_value);
200
+ const workerPid = Number(payload?.process_id);
201
+ if (Number.isFinite(returnValue) && returnValue !== 0) {
202
+ throw createLauncherError(
203
+ "WINDOWS_CIM_CREATE_FAILED",
204
+ `Win32_Process.Create returned ${returnValue} while launching the detached worker.`
205
+ );
206
+ }
207
+ if (result?.signal || !Number.isInteger(result?.status) || result.status !== 0) {
208
+ const stderr = String(result?.stderr || "").trim();
209
+ throw createLauncherError(
210
+ "WINDOWS_CIM_LAUNCH_FAILED",
211
+ stderr || `PowerShell CIM launcher exited with status ${result?.status ?? "unknown"}.`
212
+ );
213
+ }
214
+ if (!Number.isInteger(workerPid) || workerPid <= 0 || returnValue !== 0) {
215
+ throw createLauncherError(
216
+ "WINDOWS_CIM_RESULT_INVALID",
217
+ "Win32_Process.Create did not return a valid detached worker PID."
218
+ );
219
+ }
220
+ return {
221
+ pid: workerPid,
222
+ unref() {}
223
+ };
224
+ }
225
+
226
+ function launchPosixDetachedWorker({
227
+ nodePath,
228
+ workerScriptPath,
229
+ domain,
230
+ runId,
231
+ stdoutPath,
232
+ stderrPath,
233
+ spawnImpl = spawn,
234
+ environment = process.env
235
+ }) {
236
+ const stdoutFd = fs.openSync(stdoutPath, "a");
237
+ const stderrFd = fs.openSync(stderrPath, "a");
238
+ try {
239
+ return spawnImpl(nodePath, [
240
+ workerScriptPath,
241
+ "--domain",
242
+ domain,
243
+ "--run-id",
244
+ runId
245
+ ], {
246
+ detached: true,
247
+ stdio: ["ignore", stdoutFd, stderrFd],
248
+ windowsHide: true,
249
+ env: environment
250
+ });
251
+ } finally {
252
+ fs.closeSync(stdoutFd);
253
+ fs.closeSync(stderrFd);
254
+ }
255
+ }
256
+
257
+ export function launchDetachedWorker(options = {}) {
258
+ const platform = String(options.platform || process.platform);
259
+ const nodePath = assertControlledPath(options.nodePath || process.execPath, "nodePath", platform);
260
+ const workerScriptPath = assertControlledPath(options.workerScriptPath, "workerScriptPath", platform);
261
+ const stdoutPath = assertControlledPath(options.stdoutPath, "stdoutPath", platform);
262
+ const stderrPath = assertControlledPath(options.stderrPath, "stderrPath", platform);
263
+ const domain = assertControlledToken(options.domain, "domain", SAFE_DOMAIN_PATTERN);
264
+ if (!SAFE_DOMAINS.has(domain)) {
265
+ throw createLauncherError("DETACHED_WORKER_ARGUMENT_INVALID", "domain is not supported by the detached launcher");
266
+ }
267
+ const runId = assertControlledToken(options.runId, "runId", SAFE_RUN_ID_PATTERN);
268
+ const wrapperScriptPath = platform === "win32"
269
+ ? assertControlledPath(options.wrapperScriptPath || WINDOWS_WRAPPER_PATH, "wrapperScriptPath", platform)
270
+ : options.wrapperScriptPath;
271
+ const chatRuntimeHomePath = options.chatRuntimeHomePath
272
+ ? assertControlledPath(options.chatRuntimeHomePath, "chatRuntimeHomePath", platform)
273
+ : "";
274
+ const screenConfigPath = options.screenConfigPath
275
+ ? assertControlledPath(options.screenConfigPath, "screenConfigPath", platform)
276
+ : "";
277
+ const prepareLogFile = typeof options.prepareLogFileImpl === "function"
278
+ ? options.prepareLogFileImpl
279
+ : ensureLogFile;
280
+ prepareLogFile(stdoutPath);
281
+ prepareLogFile(stderrPath);
282
+ const normalized = {
283
+ ...options,
284
+ platform,
285
+ nodePath,
286
+ workerScriptPath,
287
+ stdoutPath,
288
+ stderrPath,
289
+ domain,
290
+ runId,
291
+ wrapperScriptPath,
292
+ chatRuntimeHomePath,
293
+ screenConfigPath
294
+ };
295
+ return platform === "win32"
296
+ ? launchWindowsDetachedWorker(normalized)
297
+ : launchPosixDetachedWorker(normalized);
298
+ }
299
+
300
+ export const __testables = {
301
+ WINDOWS_WRAPPER_PATH,
302
+ parseCimPayload,
303
+ quotePowerShellLiteral,
304
+ windowsPowerShellPath
305
+ };