@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
|
@@ -1,238 +1,512 @@
|
|
|
1
|
-
export const CV_ACQUISITION_MODE_UNKNOWN = "unknown";
|
|
2
|
-
export const CV_ACQUISITION_MODE_NETWORK = "network";
|
|
3
|
-
export const CV_ACQUISITION_MODE_IMAGE = "image";
|
|
4
|
-
|
|
5
|
-
export const NETWORK_RESUME_WAIT_MS = 4200;
|
|
6
|
-
export const NETWORK_RESUME_RETRY_WAIT_MS = 2000;
|
|
7
|
-
export const NETWORK_RESUME_IMAGE_MODE_GRACE_MS = 1000;
|
|
8
|
-
export const DEFAULT_MAX_IMAGE_PAGES = 24;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function
|
|
207
|
-
if (!
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
1
|
+
export const CV_ACQUISITION_MODE_UNKNOWN = "unknown";
|
|
2
|
+
export const CV_ACQUISITION_MODE_NETWORK = "network";
|
|
3
|
+
export const CV_ACQUISITION_MODE_IMAGE = "image";
|
|
4
|
+
|
|
5
|
+
export const NETWORK_RESUME_WAIT_MS = 4200;
|
|
6
|
+
export const NETWORK_RESUME_RETRY_WAIT_MS = 2000;
|
|
7
|
+
export const NETWORK_RESUME_IMAGE_MODE_GRACE_MS = 1000;
|
|
8
|
+
export const DEFAULT_MAX_IMAGE_PAGES = 24;
|
|
9
|
+
export const IMAGE_CAPTURE_WORKFLOW_RETRY_LIMIT = 1;
|
|
10
|
+
|
|
11
|
+
const VALID_MODES = new Set([
|
|
12
|
+
CV_ACQUISITION_MODE_UNKNOWN,
|
|
13
|
+
CV_ACQUISITION_MODE_NETWORK,
|
|
14
|
+
CV_ACQUISITION_MODE_IMAGE
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
function normalizeMode(mode) {
|
|
18
|
+
const normalized = String(mode || "").trim().toLowerCase();
|
|
19
|
+
return VALID_MODES.has(normalized) ? normalized : CV_ACQUISITION_MODE_UNKNOWN;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function positiveNumber(value, fallback) {
|
|
23
|
+
const parsed = Number(value);
|
|
24
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function nowIso() {
|
|
28
|
+
return new Date().toISOString();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createCvAcquisitionState({
|
|
32
|
+
mode = CV_ACQUISITION_MODE_UNKNOWN
|
|
33
|
+
} = {}) {
|
|
34
|
+
return {
|
|
35
|
+
schema_version: 1,
|
|
36
|
+
mode: normalizeMode(mode),
|
|
37
|
+
attempts: 0,
|
|
38
|
+
network_hits: 0,
|
|
39
|
+
image_fallbacks: 0,
|
|
40
|
+
misses: 0,
|
|
41
|
+
last_result: null,
|
|
42
|
+
history: []
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getCvNetworkWaitPlan(state = {}, {
|
|
47
|
+
networkWaitMs = NETWORK_RESUME_WAIT_MS,
|
|
48
|
+
networkRetryWaitMs = NETWORK_RESUME_RETRY_WAIT_MS,
|
|
49
|
+
imageModeGraceMs = NETWORK_RESUME_IMAGE_MODE_GRACE_MS
|
|
50
|
+
} = {}) {
|
|
51
|
+
const modeBefore = normalizeMode(state?.mode);
|
|
52
|
+
if (modeBefore === CV_ACQUISITION_MODE_IMAGE) {
|
|
53
|
+
return {
|
|
54
|
+
schema_version: 1,
|
|
55
|
+
mode_before: modeBefore,
|
|
56
|
+
reason: "previous_image_mode_short_network_grace",
|
|
57
|
+
initial_wait_ms: positiveNumber(imageModeGraceMs, NETWORK_RESUME_IMAGE_MODE_GRACE_MS),
|
|
58
|
+
retry_wait_ms: 0
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
schema_version: 1,
|
|
64
|
+
mode_before: modeBefore,
|
|
65
|
+
reason: "network_primary_full_wait",
|
|
66
|
+
initial_wait_ms: positiveNumber(networkWaitMs, NETWORK_RESUME_WAIT_MS),
|
|
67
|
+
retry_wait_ms: positiveNumber(networkRetryWaitMs, NETWORK_RESUME_RETRY_WAIT_MS)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function waitForCvNetworkEvents(waitForNetworkEvents, recorder, {
|
|
72
|
+
waitPlan = getCvNetworkWaitPlan(),
|
|
73
|
+
minCount = 1,
|
|
74
|
+
requireLoaded = true,
|
|
75
|
+
intervalMs = 120
|
|
76
|
+
} = {}) {
|
|
77
|
+
if (typeof waitForNetworkEvents !== "function") {
|
|
78
|
+
throw new Error("waitForCvNetworkEvents requires a domain wait function");
|
|
79
|
+
}
|
|
80
|
+
const started = Date.now();
|
|
81
|
+
const stages = [];
|
|
82
|
+
|
|
83
|
+
const initial = await waitForNetworkEvents(recorder, {
|
|
84
|
+
minCount,
|
|
85
|
+
requireLoaded,
|
|
86
|
+
timeoutMs: waitPlan.initial_wait_ms,
|
|
87
|
+
intervalMs
|
|
88
|
+
});
|
|
89
|
+
stages.push({
|
|
90
|
+
stage: "initial",
|
|
91
|
+
...compactNetworkWait(initial)
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (!initial.ok && waitPlan.retry_wait_ms > 0) {
|
|
95
|
+
const retry = await waitForNetworkEvents(recorder, {
|
|
96
|
+
minCount,
|
|
97
|
+
requireLoaded,
|
|
98
|
+
timeoutMs: waitPlan.retry_wait_ms,
|
|
99
|
+
intervalMs
|
|
100
|
+
});
|
|
101
|
+
stages.push({
|
|
102
|
+
stage: "retry",
|
|
103
|
+
...compactNetworkWait(retry)
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const last = stages[stages.length - 1] || {};
|
|
108
|
+
return {
|
|
109
|
+
ok: stages.some((stage) => stage.ok),
|
|
110
|
+
elapsed_ms: Date.now() - started,
|
|
111
|
+
count: Math.max(...stages.map((stage) => Number(stage.count) || 0), 0),
|
|
112
|
+
total_event_count: last.total_event_count ?? last.count ?? 0,
|
|
113
|
+
wait_plan: waitPlan,
|
|
114
|
+
stages
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function countParsedNetworkProfiles(detailResult = {}) {
|
|
119
|
+
return (detailResult?.parsed_network_profiles || []).filter((item) => item?.ok).length;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function hasParsedNetworkProfile(detailResult = {}) {
|
|
123
|
+
return countParsedNetworkProfiles(detailResult) > 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const RECOVERABLE_IMAGE_CAPTURE_CODES = new Set([
|
|
127
|
+
"IMAGE_CAPTURE_TIMEOUT",
|
|
128
|
+
"IMAGE_CAPTURE_TOTAL_TIMEOUT",
|
|
129
|
+
"IMAGE_CAPTURE_VIEWPORT_DRIFT",
|
|
130
|
+
"IMAGE_CAPTURE_VIEWPORT_UNREADABLE",
|
|
131
|
+
"IMAGE_CAPTURE_TARGET_OUT_OF_VIEW"
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
export function isIncompleteImageEvidence(imageEvidence = null) {
|
|
135
|
+
return Boolean(
|
|
136
|
+
imageEvidence
|
|
137
|
+
&& (imageEvidence.ok === false || imageEvidence.coverage_complete === false)
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function createRequiredImageEvidenceFailure({
|
|
142
|
+
code = "IMAGE_CAPTURE_EVIDENCE_MISSING",
|
|
143
|
+
message = "Required CV image evidence is unavailable",
|
|
144
|
+
source = "image-scroll-sequence",
|
|
145
|
+
metadata = null
|
|
146
|
+
} = {}) {
|
|
147
|
+
return {
|
|
148
|
+
schema_version: 1,
|
|
149
|
+
ok: false,
|
|
150
|
+
source,
|
|
151
|
+
capture_count: 0,
|
|
152
|
+
screenshot_count: 0,
|
|
153
|
+
unique_screenshot_count: 0,
|
|
154
|
+
coverage_complete: false,
|
|
155
|
+
coverage_terminal_reason: "required_image_evidence_unavailable",
|
|
156
|
+
error_code: String(code || "IMAGE_CAPTURE_EVIDENCE_MISSING"),
|
|
157
|
+
error: String(message || "Required CV image evidence is unavailable"),
|
|
158
|
+
file_paths: [],
|
|
159
|
+
llm_file_paths: [],
|
|
160
|
+
metadata: metadata && typeof metadata === "object" ? metadata : null
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function requireCompleteImageEvidence(imageEvidence = null, failureOptions = {}) {
|
|
165
|
+
const filePaths = Array.isArray(imageEvidence?.file_paths)
|
|
166
|
+
? imageEvidence.file_paths.filter(Boolean)
|
|
167
|
+
: [];
|
|
168
|
+
const llmFilePaths = Array.isArray(imageEvidence?.llm_file_paths)
|
|
169
|
+
? imageEvidence.llm_file_paths.filter(Boolean)
|
|
170
|
+
: [];
|
|
171
|
+
const hasPersistedEvidence = filePaths.length > 0 || llmFilePaths.length > 0;
|
|
172
|
+
if (
|
|
173
|
+
imageEvidence
|
|
174
|
+
&& imageEvidence.ok !== false
|
|
175
|
+
&& imageEvidence.coverage_complete === true
|
|
176
|
+
&& hasPersistedEvidence
|
|
177
|
+
) {
|
|
178
|
+
return imageEvidence;
|
|
179
|
+
}
|
|
180
|
+
if (isIncompleteImageEvidence(imageEvidence)) {
|
|
181
|
+
return {
|
|
182
|
+
...imageEvidence,
|
|
183
|
+
ok: false,
|
|
184
|
+
coverage_complete: false,
|
|
185
|
+
llm_file_paths: []
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return createRequiredImageEvidenceFailure(failureOptions);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function isFailedClosedImageAcquisition({
|
|
192
|
+
source = "",
|
|
193
|
+
imageEvidence = null
|
|
194
|
+
} = {}) {
|
|
195
|
+
const normalizedSource = String(source || "").trim().toLowerCase();
|
|
196
|
+
if (isIncompleteImageEvidence(imageEvidence)) return true;
|
|
197
|
+
if (normalizedSource === "missing_capture_node" || normalizedSource === "image_capture_failed") {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
if (normalizedSource === "image") {
|
|
201
|
+
return requireCompleteImageEvidence(imageEvidence).ok === false;
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function isRecoverableImageCaptureWorkflowError(error) {
|
|
207
|
+
if (!error) return false;
|
|
208
|
+
if (RECOVERABLE_IMAGE_CAPTURE_CODES.has(String(error.code || ""))) return true;
|
|
209
|
+
if (/Could not find node with given id|No node with given id|Node is detached|Cannot find node|Could not compute box model/i
|
|
210
|
+
.test(String(error?.message || error || ""))) return true;
|
|
211
|
+
return Boolean(
|
|
212
|
+
error.cdp_outcome_unknown === true
|
|
213
|
+
&& error.cdp_replay_suppressed !== false
|
|
214
|
+
&& String(error.cdp_method || "").includes(".")
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function hasImageCaptureWorkflowRetryBudget(recoveryCount = 0) {
|
|
219
|
+
const normalizedCount = Math.max(0, Math.floor(Number(recoveryCount) || 0));
|
|
220
|
+
return normalizedCount < IMAGE_CAPTURE_WORKFLOW_RETRY_LIMIT;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function createImageCaptureWorkflowRetryTracker({
|
|
224
|
+
retryLimit = IMAGE_CAPTURE_WORKFLOW_RETRY_LIMIT
|
|
225
|
+
} = {}) {
|
|
226
|
+
const normalizedLimit = Math.max(0, Math.floor(Number(retryLimit) || 0));
|
|
227
|
+
const counts = new Map();
|
|
228
|
+
const keyFor = (candidateKey) => String(candidateKey || "").trim() || "__unknown_candidate__";
|
|
229
|
+
return {
|
|
230
|
+
count(candidateKey) {
|
|
231
|
+
return counts.get(keyFor(candidateKey)) || 0;
|
|
232
|
+
},
|
|
233
|
+
hasBudget(candidateKey) {
|
|
234
|
+
return this.count(candidateKey) < normalizedLimit;
|
|
235
|
+
},
|
|
236
|
+
consume(candidateKey) {
|
|
237
|
+
const key = keyFor(candidateKey);
|
|
238
|
+
const previousCount = counts.get(key) || 0;
|
|
239
|
+
if (previousCount >= normalizedLimit) {
|
|
240
|
+
return {
|
|
241
|
+
allowed: false,
|
|
242
|
+
previous_count: previousCount,
|
|
243
|
+
count: previousCount,
|
|
244
|
+
retry_limit: normalizedLimit
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const count = previousCount + 1;
|
|
248
|
+
counts.set(key, count);
|
|
249
|
+
return {
|
|
250
|
+
allowed: true,
|
|
251
|
+
previous_count: previousCount,
|
|
252
|
+
count,
|
|
253
|
+
retry_limit: normalizedLimit
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function imageCaptureResumeCheckpoint(error = null, {
|
|
260
|
+
requireConfirmedPage = false
|
|
261
|
+
} = {}) {
|
|
262
|
+
const seen = new Set();
|
|
263
|
+
let current = error;
|
|
264
|
+
for (let depth = 0; current && depth < 5; depth += 1) {
|
|
265
|
+
if ((typeof current === "object" || typeof current === "function") && seen.has(current)) break;
|
|
266
|
+
if (typeof current === "object" || typeof current === "function") seen.add(current);
|
|
267
|
+
const checkpoint = current?.capture_checkpoint;
|
|
268
|
+
const screenshots = Array.isArray(checkpoint?.screenshots) ? checkpoint.screenshots : [];
|
|
269
|
+
const confirmedCount = Math.max(0, Number(checkpoint?.confirmed_capture_count) || 0);
|
|
270
|
+
const uniqueCount = Math.max(0, Number(checkpoint?.unique_screenshot_count) || 0);
|
|
271
|
+
const hasPersistedPage = screenshots.some((item) => (
|
|
272
|
+
String(item?.file_path || "").trim()
|
|
273
|
+
&& String(item?.sha256 || "").trim()
|
|
274
|
+
));
|
|
275
|
+
if (
|
|
276
|
+
checkpoint?.kind === "cv_capture_coverage_checkpoint"
|
|
277
|
+
&& Number(checkpoint.schema_version) === 1
|
|
278
|
+
&& (
|
|
279
|
+
(confirmedCount > 0 && uniqueCount > 0 && hasPersistedPage)
|
|
280
|
+
|| (!requireConfirmedPage && confirmedCount === 0 && uniqueCount === 0 && screenshots.length === 0)
|
|
281
|
+
)
|
|
282
|
+
) {
|
|
283
|
+
return checkpoint;
|
|
284
|
+
}
|
|
285
|
+
current = current?.cause || null;
|
|
286
|
+
}
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function confirmedImageCaptureResumeCheckpoint(error = null) {
|
|
291
|
+
return imageCaptureResumeCheckpoint(error, { requireConfirmedPage: true });
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export async function reacquireImageCaptureResumeTarget({
|
|
295
|
+
domain = "candidate",
|
|
296
|
+
getRoots,
|
|
297
|
+
ensureViewport,
|
|
298
|
+
getDetailState,
|
|
299
|
+
isDetailAvailable = (state) => Boolean(state),
|
|
300
|
+
waitForTarget
|
|
301
|
+
} = {}) {
|
|
302
|
+
for (const [name, task] of Object.entries({
|
|
303
|
+
getRoots,
|
|
304
|
+
ensureViewport,
|
|
305
|
+
getDetailState,
|
|
306
|
+
waitForTarget
|
|
307
|
+
})) {
|
|
308
|
+
if (typeof task !== "function") {
|
|
309
|
+
throw new TypeError(`reacquireImageCaptureResumeTarget requires ${name}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const rootState = await ensureViewport(await getRoots());
|
|
313
|
+
const detailState = await getDetailState(rootState);
|
|
314
|
+
if (!isDetailAvailable(detailState)) {
|
|
315
|
+
const error = new Error(`${domain} detail is unavailable during image capture resume`);
|
|
316
|
+
error.code = "IMAGE_CAPTURE_RESUME_DETAIL_UNAVAILABLE";
|
|
317
|
+
throw error;
|
|
318
|
+
}
|
|
319
|
+
const targetWait = await waitForTarget(detailState, rootState);
|
|
320
|
+
const target = targetWait?.target || null;
|
|
321
|
+
if (!target?.node_id) {
|
|
322
|
+
const error = new Error(`${domain} CV target is unavailable during image capture resume`);
|
|
323
|
+
error.code = "IMAGE_CAPTURE_RESUME_TARGET_UNAVAILABLE";
|
|
324
|
+
error.target_wait = targetWait || null;
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
root_state: rootState,
|
|
329
|
+
detail_state: detailState,
|
|
330
|
+
target_wait: targetWait,
|
|
331
|
+
target
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export async function attemptImageCaptureCheckpointResume({
|
|
336
|
+
checkpoint,
|
|
337
|
+
reacquire,
|
|
338
|
+
capture
|
|
339
|
+
} = {}) {
|
|
340
|
+
if (!checkpoint || typeof checkpoint !== "object") {
|
|
341
|
+
throw new TypeError("attemptImageCaptureCheckpointResume requires checkpoint");
|
|
342
|
+
}
|
|
343
|
+
if (typeof reacquire !== "function" || typeof capture !== "function") {
|
|
344
|
+
throw new TypeError("attemptImageCaptureCheckpointResume requires reacquire and capture");
|
|
345
|
+
}
|
|
346
|
+
let context = null;
|
|
347
|
+
try {
|
|
348
|
+
context = await reacquire(checkpoint);
|
|
349
|
+
} catch (error) {
|
|
350
|
+
return {
|
|
351
|
+
attempted: true,
|
|
352
|
+
outcome: "reacquire_failed",
|
|
353
|
+
restart_required: false,
|
|
354
|
+
checkpoint,
|
|
355
|
+
context: null,
|
|
356
|
+
evidence: null,
|
|
357
|
+
error
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
const evidence = await capture(context, checkpoint);
|
|
362
|
+
return {
|
|
363
|
+
attempted: true,
|
|
364
|
+
outcome: "completed",
|
|
365
|
+
restart_required: false,
|
|
366
|
+
checkpoint,
|
|
367
|
+
context,
|
|
368
|
+
evidence,
|
|
369
|
+
error: null
|
|
370
|
+
};
|
|
371
|
+
} catch (error) {
|
|
372
|
+
return {
|
|
373
|
+
attempted: true,
|
|
374
|
+
outcome: "capture_failed",
|
|
375
|
+
restart_required: false,
|
|
376
|
+
checkpoint,
|
|
377
|
+
context,
|
|
378
|
+
evidence: null,
|
|
379
|
+
error
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export function summarizeImageEvidence(imageEvidence = null) {
|
|
385
|
+
if (!imageEvidence) return null;
|
|
386
|
+
return {
|
|
387
|
+
ok: !isIncompleteImageEvidence(imageEvidence),
|
|
388
|
+
source: imageEvidence.source || "",
|
|
389
|
+
elapsed_ms: imageEvidence.elapsed_ms || 0,
|
|
390
|
+
capture_count: imageEvidence.capture_count || imageEvidence.screenshot_count || 0,
|
|
391
|
+
screenshot_count: imageEvidence.screenshot_count || 0,
|
|
392
|
+
unique_screenshot_count: imageEvidence.unique_screenshot_count || 0,
|
|
393
|
+
dropped_duplicate_count: imageEvidence.dropped_duplicate_count || 0,
|
|
394
|
+
coverage_complete: imageEvidence.coverage_complete == null
|
|
395
|
+
? null
|
|
396
|
+
: imageEvidence.coverage_complete === true,
|
|
397
|
+
coverage_terminal_reason: imageEvidence.coverage_terminal_reason || null,
|
|
398
|
+
coverage_limit_reached: Boolean(imageEvidence.coverage_limit_reached),
|
|
399
|
+
coverage_ledger_count: Array.isArray(imageEvidence.coverage_ledger)
|
|
400
|
+
? imageEvidence.coverage_ledger.length
|
|
401
|
+
: 0,
|
|
402
|
+
resumed_from_checkpoint: Boolean(imageEvidence.resumed_from_checkpoint),
|
|
403
|
+
resume_checkpoint_id: imageEvidence.resume_checkpoint_id || null,
|
|
404
|
+
resume_confirmed_screenshot_count: imageEvidence.resume_confirmed_screenshot_count || 0,
|
|
405
|
+
resume_confirmed_ledger_count: imageEvidence.resume_confirmed_ledger_count || 0,
|
|
406
|
+
coverage_checkpoint_id: imageEvidence.coverage_checkpoint?.checkpoint_id || null,
|
|
407
|
+
total_byte_length: imageEvidence.total_byte_length || 0,
|
|
408
|
+
original_total_byte_length: imageEvidence.original_total_byte_length || 0,
|
|
409
|
+
llm_screenshot_count: imageEvidence.llm_screenshot_count || 0,
|
|
410
|
+
llm_total_byte_length: imageEvidence.llm_total_byte_length || 0,
|
|
411
|
+
llm_original_total_byte_length: imageEvidence.llm_original_total_byte_length || 0,
|
|
412
|
+
llm_composition_error: imageEvidence.llm_composition_error || null,
|
|
413
|
+
optimization: imageEvidence.optimization || null,
|
|
414
|
+
browser_clip_used: Boolean(imageEvidence.optimization?.browser_clip_used),
|
|
415
|
+
capture_beyond_viewport: Boolean(imageEvidence.optimization?.capture_beyond_viewport),
|
|
416
|
+
scroll_anchor_plan: imageEvidence.scroll_anchor_plan || null,
|
|
417
|
+
stop_boundary_plan: imageEvidence.stop_boundary_plan || null,
|
|
418
|
+
stop_boundary_checks: imageEvidence.stop_boundary_checks || [],
|
|
419
|
+
stop_boundary_result: imageEvidence.stop_boundary_result || null,
|
|
420
|
+
error_code: imageEvidence.error_code || imageEvidence.code || null,
|
|
421
|
+
error: imageEvidence.error || null,
|
|
422
|
+
file_paths: imageEvidence.file_paths || [],
|
|
423
|
+
llm_file_paths: imageEvidence.llm_file_paths || [],
|
|
424
|
+
first_clip: imageEvidence.screenshots?.[0]?.clip || imageEvidence.clip || null
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export function recordCvNetworkHit(state, {
|
|
429
|
+
reason = "parsed_network_profile",
|
|
430
|
+
parsedNetworkProfileCount = 0,
|
|
431
|
+
waitResult = null
|
|
432
|
+
} = {}) {
|
|
433
|
+
return recordCvAcquisitionResult(state, {
|
|
434
|
+
source: CV_ACQUISITION_MODE_NETWORK,
|
|
435
|
+
reason,
|
|
436
|
+
parsed_network_profile_count: parsedNetworkProfileCount,
|
|
437
|
+
wait_result: waitResult
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export function recordCvImageFallback(state, {
|
|
442
|
+
reason = "network_miss_image_fallback",
|
|
443
|
+
parsedNetworkProfileCount = 0,
|
|
444
|
+
waitResult = null,
|
|
445
|
+
imageEvidence = null
|
|
446
|
+
} = {}) {
|
|
447
|
+
return recordCvAcquisitionResult(state, {
|
|
448
|
+
source: CV_ACQUISITION_MODE_IMAGE,
|
|
449
|
+
reason,
|
|
450
|
+
parsed_network_profile_count: parsedNetworkProfileCount,
|
|
451
|
+
wait_result: waitResult,
|
|
452
|
+
image_evidence: summarizeImageEvidence(imageEvidence)
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export function recordCvNetworkMiss(state, {
|
|
457
|
+
reason = "network_miss",
|
|
458
|
+
parsedNetworkProfileCount = 0,
|
|
459
|
+
waitResult = null
|
|
460
|
+
} = {}) {
|
|
461
|
+
return recordCvAcquisitionResult(state, {
|
|
462
|
+
source: "miss",
|
|
463
|
+
reason,
|
|
464
|
+
parsed_network_profile_count: parsedNetworkProfileCount,
|
|
465
|
+
wait_result: waitResult
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export function compactCvAcquisitionState(state = {}) {
|
|
470
|
+
return {
|
|
471
|
+
mode: normalizeMode(state.mode),
|
|
472
|
+
attempts: Number(state.attempts) || 0,
|
|
473
|
+
network_hits: Number(state.network_hits) || 0,
|
|
474
|
+
image_fallbacks: Number(state.image_fallbacks) || 0,
|
|
475
|
+
misses: Number(state.misses) || 0,
|
|
476
|
+
last_result: state.last_result || null
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function recordCvAcquisitionResult(state, result) {
|
|
481
|
+
if (!state || typeof state !== "object") {
|
|
482
|
+
throw new Error("CV acquisition state is required");
|
|
483
|
+
}
|
|
484
|
+
const recorded = {
|
|
485
|
+
schema_version: 1,
|
|
486
|
+
recorded_at: nowIso(),
|
|
487
|
+
...result
|
|
488
|
+
};
|
|
489
|
+
state.attempts = (Number(state.attempts) || 0) + 1;
|
|
490
|
+
if (result.source === CV_ACQUISITION_MODE_NETWORK) {
|
|
491
|
+
state.mode = CV_ACQUISITION_MODE_NETWORK;
|
|
492
|
+
state.network_hits = (Number(state.network_hits) || 0) + 1;
|
|
493
|
+
} else if (result.source === CV_ACQUISITION_MODE_IMAGE) {
|
|
494
|
+
state.mode = CV_ACQUISITION_MODE_IMAGE;
|
|
495
|
+
state.image_fallbacks = (Number(state.image_fallbacks) || 0) + 1;
|
|
496
|
+
} else {
|
|
497
|
+
state.misses = (Number(state.misses) || 0) + 1;
|
|
498
|
+
}
|
|
499
|
+
state.last_result = recorded;
|
|
500
|
+
if (!Array.isArray(state.history)) state.history = [];
|
|
501
|
+
state.history.push(recorded);
|
|
502
|
+
return compactCvAcquisitionState(state);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function compactNetworkWait(waitResult = {}) {
|
|
506
|
+
return {
|
|
507
|
+
ok: Boolean(waitResult?.ok),
|
|
508
|
+
elapsed_ms: waitResult?.elapsed_ms || 0,
|
|
509
|
+
count: waitResult?.count || 0,
|
|
510
|
+
total_event_count: waitResult?.total_event_count ?? waitResult?.events?.length ?? 0
|
|
511
|
+
};
|
|
512
|
+
}
|