@reconcrap/boss-recommend-mcp 2.1.21 → 2.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +5 -2
  2. package/bin/boss-recommend-mcp.js +4 -4
  3. package/config/screening-config.example.json +33 -33
  4. package/package.json +8 -8
  5. package/scripts/install-macos.sh +280 -280
  6. package/scripts/postinstall.cjs +44 -44
  7. package/skills/boss-chat/README.md +42 -42
  8. package/skills/boss-chat/SKILL.md +106 -106
  9. package/skills/boss-recommend-pipeline/README.md +13 -13
  10. package/skills/boss-recommend-pipeline/SKILL.md +219 -214
  11. package/skills/boss-recruit-pipeline/README.md +19 -19
  12. package/skills/boss-recruit-pipeline/SKILL.md +89 -89
  13. package/src/chat-mcp.js +127 -127
  14. package/src/chat-runtime-config.js +775 -775
  15. package/src/cli.js +573 -573
  16. package/src/core/boss-cards/index.js +199 -199
  17. package/src/core/browser/index.js +2419 -2415
  18. package/src/core/capture/index.js +1201 -1201
  19. package/src/core/cv-acquisition/index.js +238 -238
  20. package/src/core/cv-capture-target/index.js +299 -299
  21. package/src/core/greet-quota/index.js +71 -71
  22. package/src/core/infinite-list/index.js +1326 -1326
  23. package/src/core/reporting/legacy-csv.js +334 -332
  24. package/src/core/run/index.js +32 -32
  25. package/src/core/run/timing.js +33 -33
  26. package/src/core/screening/index.js +2135 -2135
  27. package/src/core/self-heal/index.js +973 -973
  28. package/src/core/self-heal/viewport.js +564 -564
  29. package/src/detached-worker.js +99 -99
  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 +113 -113
  33. package/src/domains/chat/index.js +7 -7
  34. package/src/domains/chat/jobs.js +620 -620
  35. package/src/domains/chat/page-guard.js +122 -122
  36. package/src/domains/chat/roots.js +56 -56
  37. package/src/domains/chat/run-service.js +571 -571
  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 +472 -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 +228 -159
  44. package/src/domains/recommend/detail.js +650 -650
  45. package/src/domains/recommend/filters.js +748 -377
  46. package/src/domains/recommend/index.js +4 -3
  47. package/src/domains/recommend/jobs.js +542 -542
  48. package/src/domains/recommend/location.js +736 -0
  49. package/src/domains/recommend/refresh.js +504 -361
  50. package/src/domains/recommend/roots.js +80 -80
  51. package/src/domains/recommend/run-service.js +987 -854
  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 +1620 -1620
  62. package/src/domains/recruit/search.js +3229 -3229
  63. package/src/index.js +13 -0
  64. package/src/parser.js +376 -8
  65. package/src/recommend-mcp.js +929 -915
  66. package/src/recommend-scheduler.js +496 -496
  67. package/src/recruit-mcp.js +2121 -2121
@@ -1,299 +1,299 @@
1
- import {
2
- getFrameDocumentNodeId,
3
- getNodeBox,
4
- querySelector,
5
- querySelectorAll,
6
- sleep
7
- } from "../browser/index.js";
8
-
9
- export const CV_CAPTURE_TARGET_SELECTORS = Object.freeze([
10
- ".resume-center-side .resume-detail-wrap",
11
- ".resume-container .resume-detail-wrap",
12
- ".resume-container .resume-content-wrap",
13
- ".resume-item-detail",
14
- ".resume-detail-wrap",
15
- ".resume-content-wrap",
16
- ".resume-common-wrap",
17
- ".new-resume-online-main-ui",
18
- ".resume-detail",
19
- ".resume-recommend",
20
- "canvas#resume",
21
- ".resume-container"
22
- ]);
23
-
24
- const IFRAME_BODY_SELECTORS = Object.freeze(["body", "html"]);
25
-
26
- function slotNodeId(slot = null) {
27
- return Number(slot?.node_id || slot?.nodeId || 0) || 0;
28
- }
29
-
30
- function rootNodeId(root = null) {
31
- return Number(root?.nodeId || root?.node_id || root?.root_node_id || 0) || 0;
32
- }
33
-
34
- function normalizeRootName(root = null, fallback = "") {
35
- return String(root?.name || root?.root || fallback || "").trim() || null;
36
- }
37
-
38
- function uniqueRoots(roots = []) {
39
- const seen = new Set();
40
- const result = [];
41
- for (const root of roots) {
42
- const nodeId = rootNodeId(root);
43
- if (!nodeId || seen.has(nodeId)) continue;
44
- seen.add(nodeId);
45
- result.push({
46
- ...root,
47
- name: normalizeRootName(root),
48
- nodeId
49
- });
50
- }
51
- return result;
52
- }
53
-
54
- function slotAsRoot(slot = null, fallbackName = "") {
55
- const nodeId = slotNodeId(slot);
56
- if (!nodeId) return null;
57
- return {
58
- name: normalizeRootName(slot, fallbackName),
59
- nodeId,
60
- selector: slot?.selector || null,
61
- root_node_id: slot?.root_node_id || null
62
- };
63
- }
64
-
65
- function isVisibleBox(box = null) {
66
- return box?.rect?.width > 2 && box?.rect?.height > 2;
67
- }
68
-
69
- async function readVisibleBox(client, nodeId) {
70
- if (!nodeId) return null;
71
- try {
72
- const box = await getNodeBox(client, nodeId);
73
- return isVisibleBox(box) ? box : null;
74
- } catch {
75
- return null;
76
- }
77
- }
78
-
79
- function isCvScopedSelector(selector = "") {
80
- const normalized = String(selector || "").trim();
81
- if (!normalized) return false;
82
- if (/boss-popup|boss-dialog|dialog-wrap|geek-detail-modal|\bmodal\b|new-chat-resume-dialog-main-ui/i.test(normalized)) {
83
- return false;
84
- }
85
- return /resume-item-detail|resume-detail-wrap|resume-content-wrap|resume-common-wrap|new-resume-online-main-ui|resume-detail(?:\b|[.#:])|resume-recommend|canvas#resume|resume-container/i.test(normalized);
86
- }
87
-
88
- function buildTarget({
89
- domain = "",
90
- nodeId,
91
- source,
92
- selector = null,
93
- root = null,
94
- rootNodeId = null,
95
- box = null,
96
- iframeNodeId = null,
97
- iframeDocumentNodeId = null,
98
- fallback = false
99
- } = {}) {
100
- return {
101
- schema_version: 1,
102
- domain: domain || null,
103
- node_id: nodeId,
104
- source,
105
- selector,
106
- root,
107
- root_node_id: rootNodeId || null,
108
- iframe_node_id: iframeNodeId || null,
109
- iframe_document_node_id: iframeDocumentNodeId || null,
110
- cv_only: !fallback,
111
- fallback: Boolean(fallback),
112
- rect: box?.rect || null,
113
- center: box?.center || null
114
- };
115
- }
116
-
117
- async function firstVisibleSelectorTarget(client, roots = [], selectors = CV_CAPTURE_TARGET_SELECTORS, {
118
- domain = "",
119
- source = "cv_selector",
120
- iframeNodeId = null,
121
- iframeDocumentNodeId = null
122
- } = {}) {
123
- for (const root of uniqueRoots(roots)) {
124
- for (const selector of selectors) {
125
- let nodeIds = [];
126
- try {
127
- nodeIds = await querySelectorAll(client, root.nodeId, selector);
128
- } catch {
129
- nodeIds = [];
130
- }
131
- for (const nodeId of nodeIds) {
132
- const box = await readVisibleBox(client, nodeId);
133
- if (!box) continue;
134
- return buildTarget({
135
- domain,
136
- nodeId,
137
- source,
138
- selector,
139
- root: root.name,
140
- rootNodeId: root.nodeId,
141
- box,
142
- iframeNodeId,
143
- iframeDocumentNodeId
144
- });
145
- }
146
- }
147
- }
148
- return null;
149
- }
150
-
151
- async function visibleSlotTarget(client, slot = null, {
152
- domain = "",
153
- source = "cv_slot",
154
- fallback = false
155
- } = {}) {
156
- const nodeId = slotNodeId(slot);
157
- if (!nodeId) return null;
158
- if (!fallback && !isCvScopedSelector(slot?.selector)) return null;
159
- const box = await readVisibleBox(client, nodeId);
160
- if (!box) return null;
161
- return buildTarget({
162
- domain,
163
- nodeId,
164
- source,
165
- selector: slot?.selector || null,
166
- root: slot?.root || null,
167
- rootNodeId: slot?.root_node_id || null,
168
- box,
169
- fallback
170
- });
171
- }
172
-
173
- async function resolveIframeCaptureTarget(client, resumeIframe = null, {
174
- domain = "",
175
- selectors = CV_CAPTURE_TARGET_SELECTORS
176
- } = {}) {
177
- const iframeNodeId = slotNodeId(resumeIframe);
178
- if (!iframeNodeId) return null;
179
-
180
- try {
181
- const documentNodeId = await getFrameDocumentNodeId(client, iframeNodeId);
182
- const selectorTarget = await firstVisibleSelectorTarget(client, [{
183
- name: "resume-iframe-document",
184
- nodeId: documentNodeId
185
- }], selectors, {
186
- domain,
187
- source: "resume_iframe_cv_selector",
188
- iframeNodeId,
189
- iframeDocumentNodeId: documentNodeId
190
- });
191
- if (selectorTarget) return selectorTarget;
192
-
193
- for (const selector of IFRAME_BODY_SELECTORS) {
194
- const nodeId = await querySelector(client, documentNodeId, selector).catch(() => 0);
195
- const box = await readVisibleBox(client, nodeId);
196
- if (!box) continue;
197
- return buildTarget({
198
- domain,
199
- nodeId,
200
- source: "resume_iframe_body",
201
- selector,
202
- root: "resume-iframe-document",
203
- rootNodeId: documentNodeId,
204
- box,
205
- iframeNodeId,
206
- iframeDocumentNodeId: documentNodeId
207
- });
208
- }
209
- } catch {}
210
-
211
- const iframeBox = await readVisibleBox(client, iframeNodeId);
212
- if (!iframeBox) return null;
213
- return buildTarget({
214
- domain,
215
- nodeId: iframeNodeId,
216
- source: "resume_iframe_element",
217
- selector: resumeIframe?.selector || null,
218
- root: resumeIframe?.root || null,
219
- rootNodeId: resumeIframe?.root_node_id || null,
220
- box: iframeBox,
221
- iframeNodeId,
222
- fallback: false
223
- });
224
- }
225
-
226
- async function resolveSlotCaptureTarget(client, slot = null, {
227
- domain = "",
228
- slotName = "content",
229
- selectors = CV_CAPTURE_TARGET_SELECTORS
230
- } = {}) {
231
- const root = slotAsRoot(slot, slotName);
232
- const selectorTarget = root
233
- ? await firstVisibleSelectorTarget(client, [root], selectors, {
234
- domain,
235
- source: `${slotName}_cv_selector`
236
- })
237
- : null;
238
- if (selectorTarget) return selectorTarget;
239
- return visibleSlotTarget(client, slot, {
240
- domain,
241
- source: `${slotName}_cv_slot`
242
- });
243
- }
244
-
245
- export async function resolveCvCaptureTarget(client, detailState = null, {
246
- domain = "",
247
- selectors = CV_CAPTURE_TARGET_SELECTORS
248
- } = {}) {
249
- const iframeTarget = await resolveIframeCaptureTarget(client, detailState?.resumeIframe, {
250
- domain,
251
- selectors
252
- });
253
- if (iframeTarget) return iframeTarget;
254
-
255
- const contentTarget = await resolveSlotCaptureTarget(client, detailState?.content, {
256
- domain,
257
- slotName: "content",
258
- selectors
259
- });
260
- if (contentTarget) return contentTarget;
261
-
262
- const popupTarget = await resolveSlotCaptureTarget(client, detailState?.popup, {
263
- domain,
264
- slotName: "popup",
265
- selectors
266
- });
267
- if (popupTarget) return popupTarget;
268
-
269
- return firstVisibleSelectorTarget(client, detailState?.roots || [], selectors, {
270
- domain,
271
- source: "root_cv_selector"
272
- });
273
- }
274
-
275
- export async function waitForCvCaptureTarget(client, detailState = null, {
276
- timeoutMs = 6000,
277
- intervalMs = 250,
278
- ...options
279
- } = {}) {
280
- const started = Date.now();
281
- let target = null;
282
- while (Date.now() - started <= Math.max(0, Number(timeoutMs) || 0)) {
283
- target = await resolveCvCaptureTarget(client, detailState, options);
284
- if (target?.node_id) {
285
- return {
286
- ok: true,
287
- elapsed_ms: Date.now() - started,
288
- target
289
- };
290
- }
291
- await sleep(Math.max(50, Number(intervalMs) || 250));
292
- }
293
- target = await resolveCvCaptureTarget(client, detailState, options);
294
- return {
295
- ok: Boolean(target?.node_id),
296
- elapsed_ms: Date.now() - started,
297
- target: target || null
298
- };
299
- }
1
+ import {
2
+ getFrameDocumentNodeId,
3
+ getNodeBox,
4
+ querySelector,
5
+ querySelectorAll,
6
+ sleep
7
+ } from "../browser/index.js";
8
+
9
+ export const CV_CAPTURE_TARGET_SELECTORS = Object.freeze([
10
+ ".resume-center-side .resume-detail-wrap",
11
+ ".resume-container .resume-detail-wrap",
12
+ ".resume-container .resume-content-wrap",
13
+ ".resume-item-detail",
14
+ ".resume-detail-wrap",
15
+ ".resume-content-wrap",
16
+ ".resume-common-wrap",
17
+ ".new-resume-online-main-ui",
18
+ ".resume-detail",
19
+ ".resume-recommend",
20
+ "canvas#resume",
21
+ ".resume-container"
22
+ ]);
23
+
24
+ const IFRAME_BODY_SELECTORS = Object.freeze(["body", "html"]);
25
+
26
+ function slotNodeId(slot = null) {
27
+ return Number(slot?.node_id || slot?.nodeId || 0) || 0;
28
+ }
29
+
30
+ function rootNodeId(root = null) {
31
+ return Number(root?.nodeId || root?.node_id || root?.root_node_id || 0) || 0;
32
+ }
33
+
34
+ function normalizeRootName(root = null, fallback = "") {
35
+ return String(root?.name || root?.root || fallback || "").trim() || null;
36
+ }
37
+
38
+ function uniqueRoots(roots = []) {
39
+ const seen = new Set();
40
+ const result = [];
41
+ for (const root of roots) {
42
+ const nodeId = rootNodeId(root);
43
+ if (!nodeId || seen.has(nodeId)) continue;
44
+ seen.add(nodeId);
45
+ result.push({
46
+ ...root,
47
+ name: normalizeRootName(root),
48
+ nodeId
49
+ });
50
+ }
51
+ return result;
52
+ }
53
+
54
+ function slotAsRoot(slot = null, fallbackName = "") {
55
+ const nodeId = slotNodeId(slot);
56
+ if (!nodeId) return null;
57
+ return {
58
+ name: normalizeRootName(slot, fallbackName),
59
+ nodeId,
60
+ selector: slot?.selector || null,
61
+ root_node_id: slot?.root_node_id || null
62
+ };
63
+ }
64
+
65
+ function isVisibleBox(box = null) {
66
+ return box?.rect?.width > 2 && box?.rect?.height > 2;
67
+ }
68
+
69
+ async function readVisibleBox(client, nodeId) {
70
+ if (!nodeId) return null;
71
+ try {
72
+ const box = await getNodeBox(client, nodeId);
73
+ return isVisibleBox(box) ? box : null;
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+
79
+ function isCvScopedSelector(selector = "") {
80
+ const normalized = String(selector || "").trim();
81
+ if (!normalized) return false;
82
+ if (/boss-popup|boss-dialog|dialog-wrap|geek-detail-modal|\bmodal\b|new-chat-resume-dialog-main-ui/i.test(normalized)) {
83
+ return false;
84
+ }
85
+ return /resume-item-detail|resume-detail-wrap|resume-content-wrap|resume-common-wrap|new-resume-online-main-ui|resume-detail(?:\b|[.#:])|resume-recommend|canvas#resume|resume-container/i.test(normalized);
86
+ }
87
+
88
+ function buildTarget({
89
+ domain = "",
90
+ nodeId,
91
+ source,
92
+ selector = null,
93
+ root = null,
94
+ rootNodeId = null,
95
+ box = null,
96
+ iframeNodeId = null,
97
+ iframeDocumentNodeId = null,
98
+ fallback = false
99
+ } = {}) {
100
+ return {
101
+ schema_version: 1,
102
+ domain: domain || null,
103
+ node_id: nodeId,
104
+ source,
105
+ selector,
106
+ root,
107
+ root_node_id: rootNodeId || null,
108
+ iframe_node_id: iframeNodeId || null,
109
+ iframe_document_node_id: iframeDocumentNodeId || null,
110
+ cv_only: !fallback,
111
+ fallback: Boolean(fallback),
112
+ rect: box?.rect || null,
113
+ center: box?.center || null
114
+ };
115
+ }
116
+
117
+ async function firstVisibleSelectorTarget(client, roots = [], selectors = CV_CAPTURE_TARGET_SELECTORS, {
118
+ domain = "",
119
+ source = "cv_selector",
120
+ iframeNodeId = null,
121
+ iframeDocumentNodeId = null
122
+ } = {}) {
123
+ for (const root of uniqueRoots(roots)) {
124
+ for (const selector of selectors) {
125
+ let nodeIds = [];
126
+ try {
127
+ nodeIds = await querySelectorAll(client, root.nodeId, selector);
128
+ } catch {
129
+ nodeIds = [];
130
+ }
131
+ for (const nodeId of nodeIds) {
132
+ const box = await readVisibleBox(client, nodeId);
133
+ if (!box) continue;
134
+ return buildTarget({
135
+ domain,
136
+ nodeId,
137
+ source,
138
+ selector,
139
+ root: root.name,
140
+ rootNodeId: root.nodeId,
141
+ box,
142
+ iframeNodeId,
143
+ iframeDocumentNodeId
144
+ });
145
+ }
146
+ }
147
+ }
148
+ return null;
149
+ }
150
+
151
+ async function visibleSlotTarget(client, slot = null, {
152
+ domain = "",
153
+ source = "cv_slot",
154
+ fallback = false
155
+ } = {}) {
156
+ const nodeId = slotNodeId(slot);
157
+ if (!nodeId) return null;
158
+ if (!fallback && !isCvScopedSelector(slot?.selector)) return null;
159
+ const box = await readVisibleBox(client, nodeId);
160
+ if (!box) return null;
161
+ return buildTarget({
162
+ domain,
163
+ nodeId,
164
+ source,
165
+ selector: slot?.selector || null,
166
+ root: slot?.root || null,
167
+ rootNodeId: slot?.root_node_id || null,
168
+ box,
169
+ fallback
170
+ });
171
+ }
172
+
173
+ async function resolveIframeCaptureTarget(client, resumeIframe = null, {
174
+ domain = "",
175
+ selectors = CV_CAPTURE_TARGET_SELECTORS
176
+ } = {}) {
177
+ const iframeNodeId = slotNodeId(resumeIframe);
178
+ if (!iframeNodeId) return null;
179
+
180
+ try {
181
+ const documentNodeId = await getFrameDocumentNodeId(client, iframeNodeId);
182
+ const selectorTarget = await firstVisibleSelectorTarget(client, [{
183
+ name: "resume-iframe-document",
184
+ nodeId: documentNodeId
185
+ }], selectors, {
186
+ domain,
187
+ source: "resume_iframe_cv_selector",
188
+ iframeNodeId,
189
+ iframeDocumentNodeId: documentNodeId
190
+ });
191
+ if (selectorTarget) return selectorTarget;
192
+
193
+ for (const selector of IFRAME_BODY_SELECTORS) {
194
+ const nodeId = await querySelector(client, documentNodeId, selector).catch(() => 0);
195
+ const box = await readVisibleBox(client, nodeId);
196
+ if (!box) continue;
197
+ return buildTarget({
198
+ domain,
199
+ nodeId,
200
+ source: "resume_iframe_body",
201
+ selector,
202
+ root: "resume-iframe-document",
203
+ rootNodeId: documentNodeId,
204
+ box,
205
+ iframeNodeId,
206
+ iframeDocumentNodeId: documentNodeId
207
+ });
208
+ }
209
+ } catch {}
210
+
211
+ const iframeBox = await readVisibleBox(client, iframeNodeId);
212
+ if (!iframeBox) return null;
213
+ return buildTarget({
214
+ domain,
215
+ nodeId: iframeNodeId,
216
+ source: "resume_iframe_element",
217
+ selector: resumeIframe?.selector || null,
218
+ root: resumeIframe?.root || null,
219
+ rootNodeId: resumeIframe?.root_node_id || null,
220
+ box: iframeBox,
221
+ iframeNodeId,
222
+ fallback: false
223
+ });
224
+ }
225
+
226
+ async function resolveSlotCaptureTarget(client, slot = null, {
227
+ domain = "",
228
+ slotName = "content",
229
+ selectors = CV_CAPTURE_TARGET_SELECTORS
230
+ } = {}) {
231
+ const root = slotAsRoot(slot, slotName);
232
+ const selectorTarget = root
233
+ ? await firstVisibleSelectorTarget(client, [root], selectors, {
234
+ domain,
235
+ source: `${slotName}_cv_selector`
236
+ })
237
+ : null;
238
+ if (selectorTarget) return selectorTarget;
239
+ return visibleSlotTarget(client, slot, {
240
+ domain,
241
+ source: `${slotName}_cv_slot`
242
+ });
243
+ }
244
+
245
+ export async function resolveCvCaptureTarget(client, detailState = null, {
246
+ domain = "",
247
+ selectors = CV_CAPTURE_TARGET_SELECTORS
248
+ } = {}) {
249
+ const iframeTarget = await resolveIframeCaptureTarget(client, detailState?.resumeIframe, {
250
+ domain,
251
+ selectors
252
+ });
253
+ if (iframeTarget) return iframeTarget;
254
+
255
+ const contentTarget = await resolveSlotCaptureTarget(client, detailState?.content, {
256
+ domain,
257
+ slotName: "content",
258
+ selectors
259
+ });
260
+ if (contentTarget) return contentTarget;
261
+
262
+ const popupTarget = await resolveSlotCaptureTarget(client, detailState?.popup, {
263
+ domain,
264
+ slotName: "popup",
265
+ selectors
266
+ });
267
+ if (popupTarget) return popupTarget;
268
+
269
+ return firstVisibleSelectorTarget(client, detailState?.roots || [], selectors, {
270
+ domain,
271
+ source: "root_cv_selector"
272
+ });
273
+ }
274
+
275
+ export async function waitForCvCaptureTarget(client, detailState = null, {
276
+ timeoutMs = 6000,
277
+ intervalMs = 250,
278
+ ...options
279
+ } = {}) {
280
+ const started = Date.now();
281
+ let target = null;
282
+ while (Date.now() - started <= Math.max(0, Number(timeoutMs) || 0)) {
283
+ target = await resolveCvCaptureTarget(client, detailState, options);
284
+ if (target?.node_id) {
285
+ return {
286
+ ok: true,
287
+ elapsed_ms: Date.now() - started,
288
+ target
289
+ };
290
+ }
291
+ await sleep(Math.max(50, Number(intervalMs) || 250));
292
+ }
293
+ target = await resolveCvCaptureTarget(client, detailState, options);
294
+ return {
295
+ ok: Boolean(target?.node_id),
296
+ elapsed_ms: Date.now() - started,
297
+ target: target || null
298
+ };
299
+ }