@lincoln504/pi-research 0.0.0
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/LICENSE +21 -0
- package/README.md +131 -0
- package/config/searxng/default-settings.yml +189 -0
- package/config/searxng/limiter.toml +34 -0
- package/config/tooling/eslint.config.js +73 -0
- package/config/tooling/vitest.config.integration.ts +17 -0
- package/config/tooling/vitest.config.ts +40 -0
- package/config/tooling/vitest.config.unit.ts +12 -0
- package/docs/ARCHITECTURE.md +109 -0
- package/docs/assets/README-banner.jpg +0 -0
- package/package.json +92 -0
- package/scripts/setup.js +174 -0
- package/src/config.ts +206 -0
- package/src/constants.ts +148 -0
- package/src/healthcheck/index.ts +309 -0
- package/src/index.ts +71 -0
- package/src/infrastructure/searxng-lifecycle.ts +577 -0
- package/src/infrastructure/searxng-manager.ts +1173 -0
- package/src/infrastructure/state-manager.ts +918 -0
- package/src/logger.ts +246 -0
- package/src/orchestration/deep-research-orchestrator.ts +848 -0
- package/src/orchestration/deep-research-reducer.ts +129 -0
- package/src/orchestration/deep-research-types.ts +54 -0
- package/src/orchestration/id-utils.ts +63 -0
- package/src/orchestration/researcher.ts +104 -0
- package/src/orchestration/session-context.ts +43 -0
- package/src/prompts/research-tool-usage.md +45 -0
- package/src/prompts/researcher.md +166 -0
- package/src/prompts/system-coordinator.md +24 -0
- package/src/prompts/system-lead-evaluator.md +225 -0
- package/src/security/cisa-kev.ts +242 -0
- package/src/security/github-advisories.ts +607 -0
- package/src/security/index.ts +406 -0
- package/src/security/nvd.ts +532 -0
- package/src/security/osv.ts +487 -0
- package/src/security/types.ts +138 -0
- package/src/stackexchange/cache.ts +111 -0
- package/src/stackexchange/index.ts +352 -0
- package/src/stackexchange/output/compact.ts +64 -0
- package/src/stackexchange/output/index.ts +7 -0
- package/src/stackexchange/output/json.ts +21 -0
- package/src/stackexchange/output/table.ts +123 -0
- package/src/stackexchange/queries.ts +168 -0
- package/src/stackexchange/rest-client.ts +127 -0
- package/src/stackexchange/types.ts +129 -0
- package/src/tool.ts +430 -0
- package/src/tools/grep.ts +335 -0
- package/src/tools/index.ts +77 -0
- package/src/tools/scrape.ts +451 -0
- package/src/tools/search.ts +188 -0
- package/src/tools/security.ts +292 -0
- package/src/tools/stackexchange.ts +101 -0
- package/src/tui/research-panel.ts +576 -0
- package/src/tui/searxng-status.ts +80 -0
- package/src/types/extension-context.ts +60 -0
- package/src/types/llm.ts +111 -0
- package/src/utils/inject-date.ts +29 -0
- package/src/utils/input-validation.ts +103 -0
- package/src/utils/json-utils.ts +262 -0
- package/src/utils/make-resource-loader.ts +43 -0
- package/src/utils/research-error.ts +205 -0
- package/src/utils/research-export.ts +199 -0
- package/src/utils/searxng-config.ts +94 -0
- package/src/utils/session-state.ts +295 -0
- package/src/utils/shared-links.ts +123 -0
- package/src/utils/shutdown-manager.ts +54 -0
- package/src/utils/text-utils.ts +83 -0
- package/src/utils/tool-usage-tracker.ts +135 -0
- package/src/web-research/dependencies.ts +20 -0
- package/src/web-research/retry-utils.ts +232 -0
- package/src/web-research/scrapers.ts +935 -0
- package/src/web-research/search.ts +235 -0
- package/src/web-research/types.ts +47 -0
- package/src/web-research/utils.ts +158 -0
- package/tsconfig.json +40 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Research Extension - Search Only (search)
|
|
3
|
+
*
|
|
4
|
+
* Search via SearXNG and return URLs, titles, and snippets.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getSearxngUrl } from './utils.ts';
|
|
8
|
+
import { createTimeoutSignal } from './retry-utils.ts';
|
|
9
|
+
import type { SearXNGResult, QueryResultWithError, SearxngSearchOptions } from './types.ts';
|
|
10
|
+
import { logger } from '../logger.ts';
|
|
11
|
+
import { getConfig } from '../config.ts';
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Type Definitions
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Type for SearXNG API response
|
|
19
|
+
*/
|
|
20
|
+
interface SearxngApiResponse {
|
|
21
|
+
results?: SearXNGResult[];
|
|
22
|
+
query: string;
|
|
23
|
+
total: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Error with cause property
|
|
28
|
+
*/
|
|
29
|
+
interface ErrorWithCause extends Error {
|
|
30
|
+
cause?: unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Re-export search options type for consumers that import from this module
|
|
34
|
+
export type { SearxngSearchOptions } from './types.ts';
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// SearXNG Search Functions
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Map our sourceType to the SearXNG categories parameter value.
|
|
42
|
+
* SearXNG category names: general, news, it (tech/GitHub), images, videos, etc.
|
|
43
|
+
* 'docs' has no SearXNG equivalent so we leave it as general.
|
|
44
|
+
*/
|
|
45
|
+
function sourceTypeToCategory(sourceType: SearxngSearchOptions['sourceType']): string {
|
|
46
|
+
switch (sourceType) {
|
|
47
|
+
case 'news': return 'news';
|
|
48
|
+
case 'github': return 'it';
|
|
49
|
+
default: return 'general';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Map our freshness value to the SearXNG time_range parameter value.
|
|
55
|
+
* Returns undefined when no filter should be applied.
|
|
56
|
+
*/
|
|
57
|
+
function freshnessToTimeRange(freshness: SearxngSearchOptions['freshness']): string | undefined {
|
|
58
|
+
switch (freshness) {
|
|
59
|
+
case 'day': return 'day';
|
|
60
|
+
case 'week': return 'week';
|
|
61
|
+
case 'month': return 'month';
|
|
62
|
+
case 'year': return 'year';
|
|
63
|
+
default: return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Search SearXNG API for a single query
|
|
69
|
+
*
|
|
70
|
+
* @param query - The search query string
|
|
71
|
+
* @param options - Optional freshness and sourceType filters
|
|
72
|
+
* @returns Promise<SearXNGResult[]> - Array of search results
|
|
73
|
+
* @throws {Error} When network error, timeout, or API error occurs
|
|
74
|
+
*/
|
|
75
|
+
async function searchSearxng(query: string, options?: SearxngSearchOptions, signal?: AbortSignal): Promise<SearXNGResult[]> {
|
|
76
|
+
const baseUrl = getSearxngUrl();
|
|
77
|
+
|
|
78
|
+
// Build URL with query parameters
|
|
79
|
+
const url = new URL(`${baseUrl}/search`);
|
|
80
|
+
url.searchParams.append('q', query);
|
|
81
|
+
url.searchParams.append('format', 'json');
|
|
82
|
+
// Restrict to the configured language. Without this, SearXNG uses its instance
|
|
83
|
+
// defaults which can return mixed-language results.
|
|
84
|
+
url.searchParams.append('language', getConfig().SEARCH_LANGUAGE);
|
|
85
|
+
url.searchParams.append('categories', sourceTypeToCategory(options?.sourceType));
|
|
86
|
+
// Apply time filter when requested (omit param entirely for 'any' / undefined)
|
|
87
|
+
const timeRange = freshnessToTimeRange(options?.freshness);
|
|
88
|
+
if (timeRange !== undefined) {
|
|
89
|
+
url.searchParams.append('time_range', timeRange);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Make GET request
|
|
93
|
+
let response: Response;
|
|
94
|
+
const fetch = globalThis.fetch;
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
// createTimeoutSignal combines the 45s timeout with any caller-provided signal,
|
|
98
|
+
// so Ctrl+C during a search aborts the fetch immediately rather than waiting for timeout.
|
|
99
|
+
response = await fetch(url.toString(), {
|
|
100
|
+
signal: createTimeoutSignal(45000, signal),
|
|
101
|
+
});
|
|
102
|
+
} catch (error) {
|
|
103
|
+
// Handle network errors, DNS failures, connection refused, etc.
|
|
104
|
+
if (error instanceof Error) {
|
|
105
|
+
if (error.name === 'AbortError' || error.name === 'TimeoutError' || error.name === 'DOMException') {
|
|
106
|
+
const timeoutError = new Error(
|
|
107
|
+
`SearXNG request timed out: ${error.message}`,
|
|
108
|
+
) as ErrorWithCause;
|
|
109
|
+
timeoutError.cause = error;
|
|
110
|
+
throw timeoutError;
|
|
111
|
+
}
|
|
112
|
+
const networkError = new Error(
|
|
113
|
+
`SearXNG network error: ${error.message}`,
|
|
114
|
+
) as ErrorWithCause;
|
|
115
|
+
networkError.cause = error;
|
|
116
|
+
throw networkError;
|
|
117
|
+
}
|
|
118
|
+
const unknownError = new Error(
|
|
119
|
+
`SearXNG network error: ${String(error)}`,
|
|
120
|
+
) as ErrorWithCause;
|
|
121
|
+
unknownError.cause = error;
|
|
122
|
+
throw unknownError;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
const apiError = new Error(
|
|
127
|
+
`SearXNG request failed: ${response.status} ${response.statusText}`,
|
|
128
|
+
) as ErrorWithCause;
|
|
129
|
+
apiError.cause = { status: response.status, statusText: response.statusText };
|
|
130
|
+
throw apiError;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const data = await response.json() as SearxngApiResponse;
|
|
134
|
+
|
|
135
|
+
// Return results or empty array using nullish coalescing
|
|
136
|
+
const results = data.results ?? [];
|
|
137
|
+
|
|
138
|
+
// Diagnostics: Log if we got no results or suspiciously few results
|
|
139
|
+
if (results.length === 0) {
|
|
140
|
+
if (logger.debug) {
|
|
141
|
+
logger.debug(`[Web Research] No results for query: ${query}`);
|
|
142
|
+
}
|
|
143
|
+
} else if (results.length < 3 && query.length > 10) {
|
|
144
|
+
// Very few results for a long query might indicate search quality issues
|
|
145
|
+
if (logger.debug) {
|
|
146
|
+
logger.debug(`[Web Research] Low result count (${results.length}) for query: ${query}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return results;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ============================================================================
|
|
154
|
+
// Error Classification
|
|
155
|
+
// ============================================================================
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Classify error type for better error reporting
|
|
159
|
+
*
|
|
160
|
+
* @param error - The unknown error to classify
|
|
161
|
+
* @returns QueryResultWithError['error'] - Classified error object
|
|
162
|
+
*/
|
|
163
|
+
function classifyError(error: unknown): QueryResultWithError['error'] {
|
|
164
|
+
const errorStr = String(error);
|
|
165
|
+
|
|
166
|
+
// Timeout errors — check BEFORE network errors because "SearXNG network error: ...timeout" messages
|
|
167
|
+
// contain "network" and would otherwise be misclassified as network_error.
|
|
168
|
+
if (errorStr.includes('timed out') || errorStr.includes('timeout') ||
|
|
169
|
+
errorStr.includes('AbortError') || errorStr.includes('TimeoutError')) {
|
|
170
|
+
return { type: 'timeout', message: 'Search request timed out' };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Network-related errors: connection refused, DNS failures, network unreachable, etc.
|
|
174
|
+
if (errorStr.includes('fetch failed') ||
|
|
175
|
+
errorStr.includes('ECONNREFUSED') ||
|
|
176
|
+
errorStr.includes('ENOTFOUND') ||
|
|
177
|
+
errorStr.includes('ECONNRESET') ||
|
|
178
|
+
errorStr.includes('ETIMEDOUT') ||
|
|
179
|
+
errorStr.includes('EHOSTUNREACH') ||
|
|
180
|
+
errorStr.includes('ENETUNREACH') ||
|
|
181
|
+
errorStr.includes('network') ||
|
|
182
|
+
errorStr.includes('Network error')) {
|
|
183
|
+
return { type: 'network_error', message: 'Network error - unable to connect to SearXNG service' };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (errorStr.includes('SearXNG request failed')) {
|
|
187
|
+
// Check status code for more specific info
|
|
188
|
+
const match = errorStr.match(/(\d{3})/);
|
|
189
|
+
if (match !== null) {
|
|
190
|
+
const status = parseInt(match[1] ?? '0', 10);
|
|
191
|
+
if (status >= 500) {
|
|
192
|
+
return { type: 'service_unavailable', message: `SearXNG server error (${status})` };
|
|
193
|
+
}
|
|
194
|
+
if (status >= 400) {
|
|
195
|
+
return { type: 'service_unavailable', message: `SearXNG client error (${status})` };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return { type: 'unknown', message: errorStr.length > 0 ? errorStr : 'Unknown error' };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ============================================================================
|
|
204
|
+
// Multiple Query Search
|
|
205
|
+
// ============================================================================
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Search multiple queries via SearXNG
|
|
209
|
+
*
|
|
210
|
+
* @param queries - Array of search query strings
|
|
211
|
+
* @param options - Optional freshness and sourceType filters applied uniformly to all queries
|
|
212
|
+
* @param signal - Optional AbortSignal; combined with the per-request timeout
|
|
213
|
+
* @returns Promise<QueryResultWithError[]> - Array of search results with error information
|
|
214
|
+
*/
|
|
215
|
+
export function search(queries: string[], options?: SearxngSearchOptions, signal?: AbortSignal): Promise<QueryResultWithError[]> {
|
|
216
|
+
const searchPromises = queries.map((query: string): Promise<QueryResultWithError> => {
|
|
217
|
+
return searchSearxng(query, options, signal)
|
|
218
|
+
.then((results: SearXNGResult[]): QueryResultWithError => {
|
|
219
|
+
const result: QueryResultWithError = { query, results };
|
|
220
|
+
// Explicitly mark as empty results if no results returned
|
|
221
|
+
if (results.length === 0) {
|
|
222
|
+
result.error = { type: 'empty_results', message: 'No results found for this query' };
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
})
|
|
226
|
+
.catch((error: unknown): QueryResultWithError => {
|
|
227
|
+
logger.warn(`[Web Research] Search failed for "${query}":`, error);
|
|
228
|
+
const result: QueryResultWithError = { query, results: [] };
|
|
229
|
+
result.error = classifyError(error);
|
|
230
|
+
return result;
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
return Promise.all(searchPromises);
|
|
235
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Research Extension - Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* All TypeScript interfaces and types for the web research functionality
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Search filter options passed through to SearXNG
|
|
8
|
+
export type SearchFreshness = 'any' | 'day' | 'week' | 'month' | 'year';
|
|
9
|
+
export type SearchSourceType = 'general' | 'news' | 'github';
|
|
10
|
+
|
|
11
|
+
export interface SearxngSearchOptions {
|
|
12
|
+
freshness?: SearchFreshness;
|
|
13
|
+
sourceType?: SearchSourceType;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Search result with per-query error context.
|
|
17
|
+
export interface QueryResultWithError {
|
|
18
|
+
query: string;
|
|
19
|
+
results: SearXNGResult[];
|
|
20
|
+
error?: {
|
|
21
|
+
type: 'empty_results' | 'service_unavailable' | 'timeout' | 'network_error' | 'unknown';
|
|
22
|
+
message: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Scraper types
|
|
27
|
+
export type Layer = 'fetch' | 'playwright+chromium';
|
|
28
|
+
|
|
29
|
+
export interface ScrapeLayerResult {
|
|
30
|
+
markdown: string;
|
|
31
|
+
source: string;
|
|
32
|
+
layer: Layer;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// SearXNG types
|
|
36
|
+
export interface SearXNGResult {
|
|
37
|
+
title: string;
|
|
38
|
+
url: string;
|
|
39
|
+
content: string;
|
|
40
|
+
engine?: string;
|
|
41
|
+
score?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Timeouts
|
|
45
|
+
export const PRIMARY_SCRAPER_TIMEOUT = 15000; // fetch layer
|
|
46
|
+
export const FALLBACK_SCRAPER_TIMEOUT = 20000; // Chromium layer
|
|
47
|
+
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Research Extension - Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helper functions, type guards, and utilities
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createRequire } from 'module';
|
|
8
|
+
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* SearXNG Manager interface
|
|
13
|
+
* Provides method to retrieve the SearXNG URL
|
|
14
|
+
*/
|
|
15
|
+
interface SearxngManager {
|
|
16
|
+
getSearxngUrl(): string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Browser context tracking interface
|
|
21
|
+
* Contains browser, context, page objects and a unique identifier
|
|
22
|
+
*/
|
|
23
|
+
interface TrackedContext {
|
|
24
|
+
browser: {
|
|
25
|
+
close(): Promise<void>;
|
|
26
|
+
};
|
|
27
|
+
context?: {
|
|
28
|
+
close(): Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
page?: {
|
|
31
|
+
close(): Promise<void>;
|
|
32
|
+
};
|
|
33
|
+
id: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Manager instance (set by extension)
|
|
37
|
+
let searxngManager: SearxngManager | null = null;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Set the SearXNG manager instance (called by extension on session start)
|
|
41
|
+
*/
|
|
42
|
+
export function setSearxngManager(manager: SearxngManager): void {
|
|
43
|
+
searxngManager = manager;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get SearXNG URL
|
|
48
|
+
* Gets URL from manager instance
|
|
49
|
+
*/
|
|
50
|
+
export function getSearxngUrl(): string {
|
|
51
|
+
if (!searxngManager) {
|
|
52
|
+
throw new Error('SearXNG manager not initialized. Session may not have started properly.');
|
|
53
|
+
}
|
|
54
|
+
return searxngManager.getSearxngUrl();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Active browser contexts tracking - keyed by ID for O(1) lookup
|
|
58
|
+
const activeContexts = new Map<string, TrackedContext>();
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Reset connection counters (Legacy stub for compatibility)
|
|
62
|
+
*/
|
|
63
|
+
export function resetConnectionCounters(): void {}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Clear all tracked contexts without closing them
|
|
67
|
+
* Used when browser is closed externally (e.g., via stopChromium())
|
|
68
|
+
*/
|
|
69
|
+
export function clearTrackedContexts(): void {
|
|
70
|
+
activeContexts.clear();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Generate unique ID for context tracking
|
|
75
|
+
*/
|
|
76
|
+
function generateContextId(): string {
|
|
77
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if a Node.js module is available
|
|
82
|
+
*/
|
|
83
|
+
export function checkModule(name: string): boolean {
|
|
84
|
+
try {
|
|
85
|
+
require.resolve(name);
|
|
86
|
+
return true;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Clean up all active browser contexts
|
|
94
|
+
* IMPORTANT: This does NOT close the browser - only pages and contexts
|
|
95
|
+
* The singleton browser is managed exclusively by stopChromium() in scrapers.ts
|
|
96
|
+
*/
|
|
97
|
+
export async function cleanupAllContexts(): Promise<void> {
|
|
98
|
+
const cleanupPromises: Promise<void>[] = [];
|
|
99
|
+
|
|
100
|
+
for (const ctx of activeContexts.values()) {
|
|
101
|
+
cleanupPromises.push(
|
|
102
|
+
(async (): Promise<void> => {
|
|
103
|
+
try {
|
|
104
|
+
// Close page first, then context
|
|
105
|
+
// Do NOT close browser here - it's managed by stopChromium()
|
|
106
|
+
if (ctx.page !== undefined) {
|
|
107
|
+
await ctx.page.close().catch(() => {});
|
|
108
|
+
}
|
|
109
|
+
if (ctx.context !== undefined) {
|
|
110
|
+
await ctx.context.close().catch(() => {});
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
// Ignore cleanup errors
|
|
114
|
+
}
|
|
115
|
+
})(),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Clear AFTER promises complete to prevent race condition
|
|
120
|
+
await Promise.allSettled(cleanupPromises);
|
|
121
|
+
activeContexts.clear();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Track a browser context for cleanup
|
|
126
|
+
*/
|
|
127
|
+
export function trackContext(
|
|
128
|
+
browser: { close(): Promise<void> },
|
|
129
|
+
context: { close(): Promise<void> },
|
|
130
|
+
page: { close(): Promise<void> },
|
|
131
|
+
): string {
|
|
132
|
+
const id = generateContextId();
|
|
133
|
+
activeContexts.set(id, { browser, context, page, id });
|
|
134
|
+
return id;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Untrack a browser context by ID
|
|
139
|
+
*/
|
|
140
|
+
export function untrackContextById(id: string): void {
|
|
141
|
+
activeContexts.delete(id);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Validate maxConcurrency
|
|
146
|
+
*/
|
|
147
|
+
export function validateMaxConcurrency(value?: number): number {
|
|
148
|
+
if (value === undefined) {
|
|
149
|
+
return 10; // Default
|
|
150
|
+
}
|
|
151
|
+
return Math.min(Math.max(1, Math.floor(value)), 20); // Clamp between 1 and 20
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Legacy compatibility stubs
|
|
155
|
+
export function incrementConnectionCount(): void {}
|
|
156
|
+
export function decrementConnectionCount(): void {}
|
|
157
|
+
export function onConnectionCountChange(_callback: any): () => void { return () => {}; }
|
|
158
|
+
export function getActiveConnectionCount(): number { return 0; }
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"strictNullChecks": true,
|
|
9
|
+
"strictFunctionTypes": true,
|
|
10
|
+
"strictBindCallApply": true,
|
|
11
|
+
"strictPropertyInitialization": true,
|
|
12
|
+
"noImplicitAny": true,
|
|
13
|
+
"noImplicitThis": true,
|
|
14
|
+
"alwaysStrict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"noImplicitReturns": true,
|
|
18
|
+
"noFallthroughCasesInSwitch": true,
|
|
19
|
+
"noUncheckedIndexedAccess": true,
|
|
20
|
+
"noImplicitOverride": true,
|
|
21
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
22
|
+
"exactOptionalPropertyTypes": false,
|
|
23
|
+
"esModuleInterop": true,
|
|
24
|
+
"skipLibCheck": true,
|
|
25
|
+
"resolveJsonModule": true,
|
|
26
|
+
"allowSyntheticDefaultImports": true,
|
|
27
|
+
"forceConsistentCasingInFileNames": true,
|
|
28
|
+
"noEmit": true,
|
|
29
|
+
"allowImportingTsExtensions": true,
|
|
30
|
+
"types": ["node"],
|
|
31
|
+
"paths": {}
|
|
32
|
+
},
|
|
33
|
+
"include": [
|
|
34
|
+
"src/**/*"
|
|
35
|
+
],
|
|
36
|
+
"exclude": [
|
|
37
|
+
"node_modules",
|
|
38
|
+
".tmp"
|
|
39
|
+
]
|
|
40
|
+
}
|