@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.
Files changed (67) hide show
  1. package/README.md +5 -0
  2. package/bin/boss-recommend-mcp.js +4 -4
  3. package/package.json +6 -1
  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/chat-runtime-config.js +7 -5
  14. package/src/core/boss-cards/index.js +199 -199
  15. package/src/core/browser/index.js +302 -145
  16. package/src/core/capture/index.js +2930 -1201
  17. package/src/core/cv-acquisition/index.js +512 -238
  18. package/src/core/cv-capture-target/index.js +513 -299
  19. package/src/core/greet-quota/index.js +71 -71
  20. package/src/core/infinite-list/index.js +31 -31
  21. package/src/core/reporting/legacy-csv.js +12 -12
  22. package/src/core/run/detached-launcher.js +305 -0
  23. package/src/core/run/index.js +109 -55
  24. package/src/core/run/timing.js +33 -33
  25. package/src/core/run/windows-detached-worker.ps1 +106 -0
  26. package/src/core/screening/index.js +2400 -2135
  27. package/src/core/self-heal/index.js +989 -973
  28. package/src/core/self-heal/viewport.js +1505 -564
  29. package/src/detached-worker.js +99 -99
  30. package/src/domains/chat/action-journal.js +536 -0
  31. package/src/domains/chat/cards.js +137 -137
  32. package/src/domains/chat/constants.js +9 -9
  33. package/src/domains/chat/detail.js +1684 -401
  34. package/src/domains/chat/index.js +8 -7
  35. package/src/domains/chat/jobs.js +620 -620
  36. package/src/domains/chat/page-guard.js +157 -122
  37. package/src/domains/chat/roots.js +56 -56
  38. package/src/domains/chat/run-service.js +1801 -760
  39. package/src/domains/common/account-rights-panel.js +314 -314
  40. package/src/domains/common/recovery-settle.js +159 -159
  41. package/src/domains/recommend/actions.js +1219 -472
  42. package/src/domains/recommend/cards.js +243 -243
  43. package/src/domains/recommend/colleague-contact.js +1079 -333
  44. package/src/domains/recommend/constants.js +92 -92
  45. package/src/domains/recommend/detail.js +4037 -136
  46. package/src/domains/recommend/filters.js +608 -590
  47. package/src/domains/recommend/index.js +9 -9
  48. package/src/domains/recommend/jobs.js +571 -542
  49. package/src/domains/recommend/location.js +754 -707
  50. package/src/domains/recommend/refresh.js +677 -392
  51. package/src/domains/recommend/roots.js +80 -80
  52. package/src/domains/recommend/run-service.js +4048 -1447
  53. package/src/domains/recommend/scopes.js +246 -246
  54. package/src/domains/recruit/actions.js +277 -277
  55. package/src/domains/recruit/cards.js +74 -74
  56. package/src/domains/recruit/constants.js +236 -236
  57. package/src/domains/recruit/detail.js +588 -588
  58. package/src/domains/recruit/index.js +9 -9
  59. package/src/domains/recruit/instruction-parser.js +866 -866
  60. package/src/domains/recruit/refresh.js +45 -45
  61. package/src/domains/recruit/roots.js +68 -68
  62. package/src/domains/recruit/run-service.js +1817 -1620
  63. package/src/domains/recruit/search.js +3229 -3229
  64. package/src/index.js +16 -1
  65. package/src/parser.js +1296 -1296
  66. package/src/recommend-mcp.js +1061 -450
  67. 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
- 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
+ }