@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,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
- const VALID_MODES = new Set([
11
- CV_ACQUISITION_MODE_UNKNOWN,
12
- CV_ACQUISITION_MODE_NETWORK,
13
- CV_ACQUISITION_MODE_IMAGE
14
- ]);
15
-
16
- function normalizeMode(mode) {
17
- const normalized = String(mode || "").trim().toLowerCase();
18
- return VALID_MODES.has(normalized) ? normalized : CV_ACQUISITION_MODE_UNKNOWN;
19
- }
20
-
21
- function positiveNumber(value, fallback) {
22
- const parsed = Number(value);
23
- return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
24
- }
25
-
26
- function nowIso() {
27
- return new Date().toISOString();
28
- }
29
-
30
- export function createCvAcquisitionState({
31
- mode = CV_ACQUISITION_MODE_UNKNOWN
32
- } = {}) {
33
- return {
34
- schema_version: 1,
35
- mode: normalizeMode(mode),
36
- attempts: 0,
37
- network_hits: 0,
38
- image_fallbacks: 0,
39
- misses: 0,
40
- last_result: null,
41
- history: []
42
- };
43
- }
44
-
45
- export function getCvNetworkWaitPlan(state = {}, {
46
- networkWaitMs = NETWORK_RESUME_WAIT_MS,
47
- networkRetryWaitMs = NETWORK_RESUME_RETRY_WAIT_MS,
48
- imageModeGraceMs = NETWORK_RESUME_IMAGE_MODE_GRACE_MS
49
- } = {}) {
50
- const modeBefore = normalizeMode(state?.mode);
51
- if (modeBefore === CV_ACQUISITION_MODE_IMAGE) {
52
- return {
53
- schema_version: 1,
54
- mode_before: modeBefore,
55
- reason: "previous_image_mode_short_network_grace",
56
- initial_wait_ms: positiveNumber(imageModeGraceMs, NETWORK_RESUME_IMAGE_MODE_GRACE_MS),
57
- retry_wait_ms: 0
58
- };
59
- }
60
-
61
- return {
62
- schema_version: 1,
63
- mode_before: modeBefore,
64
- reason: "network_primary_full_wait",
65
- initial_wait_ms: positiveNumber(networkWaitMs, NETWORK_RESUME_WAIT_MS),
66
- retry_wait_ms: positiveNumber(networkRetryWaitMs, NETWORK_RESUME_RETRY_WAIT_MS)
67
- };
68
- }
69
-
70
- export async function waitForCvNetworkEvents(waitForNetworkEvents, recorder, {
71
- waitPlan = getCvNetworkWaitPlan(),
72
- minCount = 1,
73
- requireLoaded = true,
74
- intervalMs = 120
75
- } = {}) {
76
- if (typeof waitForNetworkEvents !== "function") {
77
- throw new Error("waitForCvNetworkEvents requires a domain wait function");
78
- }
79
- const started = Date.now();
80
- const stages = [];
81
-
82
- const initial = await waitForNetworkEvents(recorder, {
83
- minCount,
84
- requireLoaded,
85
- timeoutMs: waitPlan.initial_wait_ms,
86
- intervalMs
87
- });
88
- stages.push({
89
- stage: "initial",
90
- ...compactNetworkWait(initial)
91
- });
92
-
93
- if (!initial.ok && waitPlan.retry_wait_ms > 0) {
94
- const retry = await waitForNetworkEvents(recorder, {
95
- minCount,
96
- requireLoaded,
97
- timeoutMs: waitPlan.retry_wait_ms,
98
- intervalMs
99
- });
100
- stages.push({
101
- stage: "retry",
102
- ...compactNetworkWait(retry)
103
- });
104
- }
105
-
106
- const last = stages[stages.length - 1] || {};
107
- return {
108
- ok: stages.some((stage) => stage.ok),
109
- elapsed_ms: Date.now() - started,
110
- count: Math.max(...stages.map((stage) => Number(stage.count) || 0), 0),
111
- total_event_count: last.total_event_count ?? last.count ?? 0,
112
- wait_plan: waitPlan,
113
- stages
114
- };
115
- }
116
-
117
- export function countParsedNetworkProfiles(detailResult = {}) {
118
- return (detailResult?.parsed_network_profiles || []).filter((item) => item?.ok).length;
119
- }
120
-
121
- export function hasParsedNetworkProfile(detailResult = {}) {
122
- return countParsedNetworkProfiles(detailResult) > 0;
123
- }
124
-
125
- export function summarizeImageEvidence(imageEvidence = null) {
126
- if (!imageEvidence) return null;
127
- return {
128
- ok: imageEvidence.ok !== false,
129
- source: imageEvidence.source || "",
130
- elapsed_ms: imageEvidence.elapsed_ms || 0,
131
- capture_count: imageEvidence.capture_count || imageEvidence.screenshot_count || 0,
132
- screenshot_count: imageEvidence.screenshot_count || 0,
133
- unique_screenshot_count: imageEvidence.unique_screenshot_count || 0,
134
- dropped_duplicate_count: imageEvidence.dropped_duplicate_count || 0,
135
- total_byte_length: imageEvidence.total_byte_length || 0,
136
- original_total_byte_length: imageEvidence.original_total_byte_length || 0,
137
- llm_screenshot_count: imageEvidence.llm_screenshot_count || 0,
138
- llm_total_byte_length: imageEvidence.llm_total_byte_length || 0,
139
- llm_original_total_byte_length: imageEvidence.llm_original_total_byte_length || 0,
140
- llm_composition_error: imageEvidence.llm_composition_error || null,
141
- optimization: imageEvidence.optimization || null,
142
- scroll_anchor_plan: imageEvidence.scroll_anchor_plan || null,
143
- stop_boundary_plan: imageEvidence.stop_boundary_plan || null,
144
- stop_boundary_checks: imageEvidence.stop_boundary_checks || [],
145
- stop_boundary_result: imageEvidence.stop_boundary_result || null,
146
- error_code: imageEvidence.error_code || imageEvidence.code || null,
147
- error: imageEvidence.error || null,
148
- file_paths: imageEvidence.file_paths || [],
149
- llm_file_paths: imageEvidence.llm_file_paths || [],
150
- first_clip: imageEvidence.screenshots?.[0]?.clip || imageEvidence.clip || null
151
- };
152
- }
153
-
154
- export function recordCvNetworkHit(state, {
155
- reason = "parsed_network_profile",
156
- parsedNetworkProfileCount = 0,
157
- waitResult = null
158
- } = {}) {
159
- return recordCvAcquisitionResult(state, {
160
- source: CV_ACQUISITION_MODE_NETWORK,
161
- reason,
162
- parsed_network_profile_count: parsedNetworkProfileCount,
163
- wait_result: waitResult
164
- });
165
- }
166
-
167
- export function recordCvImageFallback(state, {
168
- reason = "network_miss_image_fallback",
169
- parsedNetworkProfileCount = 0,
170
- waitResult = null,
171
- imageEvidence = null
172
- } = {}) {
173
- return recordCvAcquisitionResult(state, {
174
- source: CV_ACQUISITION_MODE_IMAGE,
175
- reason,
176
- parsed_network_profile_count: parsedNetworkProfileCount,
177
- wait_result: waitResult,
178
- image_evidence: summarizeImageEvidence(imageEvidence)
179
- });
180
- }
181
-
182
- export function recordCvNetworkMiss(state, {
183
- reason = "network_miss",
184
- parsedNetworkProfileCount = 0,
185
- waitResult = null
186
- } = {}) {
187
- return recordCvAcquisitionResult(state, {
188
- source: "miss",
189
- reason,
190
- parsed_network_profile_count: parsedNetworkProfileCount,
191
- wait_result: waitResult
192
- });
193
- }
194
-
195
- export function compactCvAcquisitionState(state = {}) {
196
- return {
197
- mode: normalizeMode(state.mode),
198
- attempts: Number(state.attempts) || 0,
199
- network_hits: Number(state.network_hits) || 0,
200
- image_fallbacks: Number(state.image_fallbacks) || 0,
201
- misses: Number(state.misses) || 0,
202
- last_result: state.last_result || null
203
- };
204
- }
205
-
206
- function recordCvAcquisitionResult(state, result) {
207
- if (!state || typeof state !== "object") {
208
- throw new Error("CV acquisition state is required");
209
- }
210
- const recorded = {
211
- schema_version: 1,
212
- recorded_at: nowIso(),
213
- ...result
214
- };
215
- state.attempts = (Number(state.attempts) || 0) + 1;
216
- if (result.source === CV_ACQUISITION_MODE_NETWORK) {
217
- state.mode = CV_ACQUISITION_MODE_NETWORK;
218
- state.network_hits = (Number(state.network_hits) || 0) + 1;
219
- } else if (result.source === CV_ACQUISITION_MODE_IMAGE) {
220
- state.mode = CV_ACQUISITION_MODE_IMAGE;
221
- state.image_fallbacks = (Number(state.image_fallbacks) || 0) + 1;
222
- } else {
223
- state.misses = (Number(state.misses) || 0) + 1;
224
- }
225
- state.last_result = recorded;
226
- if (!Array.isArray(state.history)) state.history = [];
227
- state.history.push(recorded);
228
- return compactCvAcquisitionState(state);
229
- }
230
-
231
- function compactNetworkWait(waitResult = {}) {
232
- return {
233
- ok: Boolean(waitResult?.ok),
234
- elapsed_ms: waitResult?.elapsed_ms || 0,
235
- count: waitResult?.count || 0,
236
- total_event_count: waitResult?.total_event_count ?? waitResult?.events?.length ?? 0
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
+ }