@lincoln504/pi-research 0.1.12 → 1.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 +1 -1
- package/README.md +64 -115
- package/dist/cli.mjs +23211 -0
- package/dist/prompts/research-knowledge-relevance-triage.md +35 -0
- package/dist/prompts/research-knowledge-search-extractor.md +41 -0
- package/dist/prompts/research-tool-usage.md +92 -0
- package/dist/prompts/researcher.md +89 -0
- package/dist/prompts/system-coordinator.md +75 -0
- package/dist/prompts/system-lead-evaluator.md +118 -0
- package/dist/thread-worker.mjs +2611 -0
- package/dist/webgpu-probe.mjs +80 -0
- package/docs/AGENT-SKILL.md +102 -0
- package/docs/ARCHITECTURE.md +306 -80
- package/docs/CONFIGURATION.md +249 -0
- package/docs/KNOWLEDGE-STORE.md +213 -0
- package/docs/PI-EXTENSION.md +91 -0
- package/docs/SDK.md +113 -0
- package/docs/THIRD-PARTY-NOTICES.md +64 -0
- package/package.json +102 -36
- package/scripts/build.cjs +140 -0
- package/scripts/cleanup.cjs +149 -0
- package/scripts/prepare.cjs +28 -0
- package/scripts/setup.cjs +134 -0
- package/skills/pi-research/README.md +90 -0
- package/skills/pi-research/SKILL.md +102 -0
- package/skills/pi-research/references/configuration.md +77 -0
- package/skills/pi-research/scripts/run.mjs +237 -0
- package/src/cleanup/research-cleanup.ts +140 -0
- package/src/cli.ts +1044 -0
- package/src/config.ts +1036 -147
- package/src/constants.ts +115 -84
- package/src/core/health-check-service.ts +265 -0
- package/src/core/interfaces/health-check-interfaces.ts +50 -0
- package/src/core/interfaces/knowledge-interfaces.ts +190 -0
- package/src/core/interfaces/observer-interfaces.ts +67 -0
- package/src/core/interfaces/orchestration-interfaces.ts +86 -0
- package/src/core/interfaces/planning-interfaces.ts +100 -0
- package/src/core/interfaces/process-interfaces.ts +37 -0
- package/src/core/interfaces/research-plan-types.ts +52 -0
- package/src/core/interfaces/scheduler-interfaces.ts +91 -0
- package/src/core/interfaces/service-names.ts +39 -0
- package/src/core/interfaces/state-manager-interfaces.ts +29 -0
- package/src/core/interfaces/steering-interfaces.ts +31 -0
- package/src/core/llm/agentic-repair.ts +169 -0
- package/src/core/llm/inject-date.ts +52 -0
- package/src/core/llm/llm-timeout.ts +64 -0
- package/src/core/llm/llm-utils.ts +86 -0
- package/src/core/llm/model-registry-factory.ts +287 -0
- package/src/core/llm/pi-ai-completion.ts +29 -0
- package/src/core/llm/prompts.ts +50 -0
- package/src/core/llm/research-model-resolver.ts +91 -0
- package/src/core/planning-constants.ts +13 -0
- package/src/core/planning-service.ts +649 -0
- package/src/core/planning-utils.ts +299 -0
- package/src/core/scheduler-factory.ts +48 -0
- package/src/core/scheduler-service.ts +123 -0
- package/src/core/service-initialization.ts +194 -0
- package/src/core/service-interfaces.ts +48 -0
- package/src/core/service-registry.ts +659 -0
- package/src/healthcheck/index.ts +271 -265
- package/src/healthcheck/registry.ts +130 -0
- package/src/index.ts +639 -50
- package/src/infrastructure/browser/browser-cleanup.ts +309 -0
- package/src/infrastructure/browser/browser-client.ts +199 -0
- package/src/infrastructure/browser/browser-error-utils.ts +160 -0
- package/src/infrastructure/browser/browser-lifecycle.ts +120 -0
- package/src/infrastructure/browser/browser-server.ts +174 -0
- package/src/infrastructure/browser/browser-task-scheduler.ts +461 -0
- package/src/infrastructure/browser/cleanup-utils.ts +155 -0
- package/src/infrastructure/browser/client-agent.ts +31 -0
- package/src/infrastructure/browser/config.ts +288 -0
- package/src/infrastructure/browser/ensure-browser.ts +205 -0
- package/src/infrastructure/browser/index.ts +37 -0
- package/src/infrastructure/browser/priority-task-queue.ts +226 -0
- package/src/infrastructure/browser/scheduler-factory.ts +354 -0
- package/src/infrastructure/browser/task-execution-service.ts +209 -0
- package/src/infrastructure/browser/thread-worker-browser.ts +282 -0
- package/src/infrastructure/browser/thread-worker-lifecycle.ts +323 -0
- package/src/infrastructure/browser/thread-worker-messaging.ts +662 -0
- package/src/infrastructure/browser/thread-worker-types.ts +25 -0
- package/src/infrastructure/browser/thread-worker.mjs +2611 -0
- package/src/infrastructure/browser/thread-worker.ts +168 -0
- package/src/infrastructure/browser/worker-pool-manager.ts +449 -0
- package/src/infrastructure/embedding/embedding-client.ts +205 -0
- package/src/infrastructure/embedding/embedding-factory.ts +324 -0
- package/src/infrastructure/embedding/embedding-server.ts +535 -0
- package/src/infrastructure/file-lock-service.ts +592 -0
- package/src/infrastructure/gpu-resource-service.ts +191 -0
- package/src/infrastructure/knowledge-store-service.ts +475 -0
- package/src/infrastructure/metrics-service.ts +129 -0
- package/src/infrastructure/process-lifecycle-service.ts +253 -0
- package/src/infrastructure/scheduler-factory-service.ts +58 -0
- package/src/infrastructure/service-initialization.ts +292 -0
- package/src/infrastructure/state/state-backup-manager.ts +290 -0
- package/src/infrastructure/state/state-browser-api.ts +62 -0
- package/src/infrastructure/state/state-browser-manager.ts +70 -0
- package/src/infrastructure/state/state-manager-service.ts +264 -0
- package/src/infrastructure/state/state-manager.ts +547 -0
- package/src/infrastructure/state/state-metrics.ts +92 -0
- package/src/infrastructure/state/state-migration.ts +103 -0
- package/src/infrastructure/state/state-path-configuration.ts +119 -0
- package/src/infrastructure/state/state-session-api.ts +94 -0
- package/src/infrastructure/state/state-session-manager.ts +127 -0
- package/src/infrastructure/state/state-validator.ts +93 -0
- package/src/infrastructure/types/state-types.ts +85 -0
- package/src/knowledge/chunker.ts +157 -0
- package/src/knowledge/embedder-init.ts +413 -0
- package/src/knowledge/embedder-utils.ts +296 -0
- package/src/knowledge/embedder.ts +592 -0
- package/src/knowledge/index.ts +126 -0
- package/src/knowledge/lancedb-loader.ts +35 -0
- package/src/knowledge/migration.ts +17 -0
- package/src/knowledge/model-config.ts +132 -0
- package/src/knowledge/onnx-env.ts +48 -0
- package/src/knowledge/store-operations.ts +291 -0
- package/src/knowledge/store-schema.ts +79 -0
- package/src/knowledge/store.ts +1251 -0
- package/src/knowledge/transformers-loader.ts +31 -0
- package/src/knowledge/webgpu-probe.mjs +80 -0
- package/src/knowledge/webgpu-viability.ts +255 -0
- package/src/knowledge/writer-queue.ts +252 -0
- package/src/logger.ts +316 -122
- package/src/observers/research-observer-impl.ts +459 -0
- package/src/orchestration/deep-research-orchestrator.ts +524 -773
- package/src/orchestration/deep-research-types.ts +47 -35
- package/src/orchestration/headless-observer.ts +114 -0
- package/src/orchestration/orchestration-types.ts +33 -0
- package/src/orchestration/quick-research-orchestrator.ts +463 -0
- package/src/orchestration/research-orchestration-service.ts +549 -0
- package/src/orchestration/research-session-service.ts +178 -0
- package/src/orchestration/research-synthesis-service.ts +348 -0
- package/src/orchestration/researcher-executor.ts +394 -0
- package/src/orchestration/researcher.ts +115 -42
- package/src/orchestration/service-initialization.ts +109 -0
- package/src/orchestration/session-context.ts +7 -3
- package/src/orchestration/session-state.ts +672 -0
- package/src/prompts/research-knowledge-relevance-triage.md +35 -0
- package/src/prompts/research-knowledge-search-extractor.md +41 -0
- package/src/prompts/research-tool-usage.md +63 -16
- package/src/prompts/researcher.md +59 -136
- package/src/prompts/system-coordinator.md +64 -13
- package/src/prompts/system-lead-evaluator.md +82 -189
- package/src/research-config.ts +872 -0
- package/src/sdk.ts +847 -0
- package/src/security/cisa-kev.ts +72 -9
- package/src/security/cvss.ts +92 -0
- package/src/security/ecosystem.ts +45 -0
- package/src/security/github-advisories.ts +168 -428
- package/src/security/github-advisory-types.ts +282 -0
- package/src/security/index.ts +63 -177
- package/src/security/nvd-client.ts +550 -0
- package/src/security/nvd-types.ts +179 -0
- package/src/security/osv-client.ts +246 -0
- package/src/security/osv-types.ts +285 -0
- package/src/security/types.ts +67 -53
- package/src/skill-install/skill-installer.ts +519 -0
- package/src/stackexchange/index.ts +81 -27
- package/src/stackexchange/output/compact.ts +7 -6
- package/src/stackexchange/output/table.ts +11 -7
- package/src/stackexchange/queries.ts +0 -10
- package/src/stackexchange/rest-client.ts +141 -66
- package/src/stackexchange/types.ts +0 -17
- package/src/tool.ts +15 -423
- package/src/tools/grep.ts +32 -310
- package/src/tools/health-tool-definition.ts +101 -0
- package/src/tools/index.ts +38 -9
- package/src/tools/research-knowledge-search.ts +974 -0
- package/src/tools/research-knowledge-types.ts +77 -0
- package/src/tools/research-tool-definition.ts +494 -0
- package/src/tools/scrape.ts +286 -388
- package/src/tools/search.ts +98 -153
- package/src/tools/security.ts +107 -80
- package/src/tools/stackexchange.ts +58 -35
- package/src/tools/youtube-transcript.ts +163 -0
- package/src/tui/knowledge-search-panel.ts +86 -0
- package/src/tui/pulse.ts +86 -0
- package/src/tui/research-health.ts +124 -0
- package/src/tui/research-panel-color-utils.ts +340 -0
- package/src/tui/research-panel-format.ts +54 -0
- package/src/tui/research-panel-state.ts +404 -0
- package/src/tui/research-panel-wave.ts +280 -0
- package/src/tui/research-panel.ts +339 -435
- package/src/tui/research-tui-manager.ts +233 -0
- package/src/tui/tui-controller.ts +137 -0
- package/src/types/extension-context.ts +18 -49
- package/src/types/index.ts +61 -0
- package/src/types/llm.ts +68 -29
- package/src/types/research-panel-types.ts +61 -0
- package/src/utils/circuit-breaker.ts +146 -0
- package/src/utils/citation-utils.ts +97 -0
- package/src/utils/disk-space-checker.ts +55 -0
- package/src/utils/error-tracker.ts +265 -0
- package/src/utils/host-config.ts +45 -0
- package/src/utils/input-validation.ts +8 -10
- package/src/utils/json-utils.ts +150 -16
- package/src/utils/llm-usage.ts +54 -0
- package/src/utils/log-rotation.ts +125 -0
- package/src/utils/log-utils.ts +159 -0
- package/src/utils/make-resource-loader.ts +3 -1
- package/src/utils/metrics-summary.ts +459 -0
- package/src/utils/metrics.ts +302 -0
- package/src/utils/research-export.ts +28 -12
- package/src/utils/safe-unref.ts +31 -0
- package/src/utils/shared-links.ts +195 -90
- package/src/utils/shutdown-manager.ts +65 -0
- package/src/utils/stdio-capture.ts +407 -0
- package/src/utils/text-utils.ts +264 -7
- package/src/utils/tool-usage-tracker.ts +83 -16
- package/src/utils/url-utils.ts +255 -0
- package/src/utils/user-agent.ts +28 -0
- package/src/web-research/browser-search.ts +179 -0
- package/src/web-research/retry-utils.ts +173 -77
- package/src/web-research/scraper-types.ts +72 -0
- package/src/web-research/scraper-utils.ts +434 -0
- package/src/web-research/search.ts +78 -218
- package/src/web-research/types.ts +9 -24
- package/src/web-research/utils.ts +2 -143
- package/src/web-research/web-scraper.ts +477 -0
- package/src/youtube/index.ts +154 -0
- package/src/youtube/potoken.ts +259 -0
- package/src/youtube/transcript-client.ts +390 -0
- package/src/youtube/video-id.ts +118 -0
- package/config/searxng/default-settings.yml +0 -189
- package/config/searxng/limiter.toml +0 -34
- package/config/tooling/eslint.config.js +0 -73
- package/config/tooling/vitest.config.integration.ts +0 -17
- package/config/tooling/vitest.config.ts +0 -40
- package/config/tooling/vitest.config.unit.ts +0 -12
- package/docs/assets/README-banner.jpg +0 -0
- package/scripts/setup.js +0 -174
- package/src/infrastructure/searxng-lifecycle.ts +0 -577
- package/src/infrastructure/searxng-manager.ts +0 -1183
- package/src/infrastructure/state-manager.ts +0 -918
- package/src/orchestration/deep-research-reducer.ts +0 -129
- package/src/orchestration/id-utils.ts +0 -63
- package/src/security/nvd.ts +0 -532
- package/src/security/osv.ts +0 -487
- package/src/stackexchange/cache.ts +0 -111
- package/src/stackexchange/output/index.ts +0 -7
- package/src/stackexchange/output/json.ts +0 -21
- package/src/tui/searxng-status.ts +0 -80
- package/src/utils/inject-date.ts +0 -29
- package/src/utils/research-error.ts +0 -205
- package/src/utils/searxng-config.ts +0 -94
- package/src/utils/session-state.ts +0 -295
- package/src/web-research/dependencies.ts +0 -20
- package/src/web-research/scrapers.ts +0 -935
- package/tsconfig.json +0 -40
|
@@ -1,235 +1,95 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Web Research Extension - Search
|
|
2
|
+
* Web Research Extension - Search Orchestrator
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Exclusively uses browser-based search (DuckDuckGo Lite) via a managed pool.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { getSearxngUrl } from './utils.ts';
|
|
8
|
-
import { createTimeoutSignal } from './retry-utils.ts';
|
|
9
|
-
import type { SearXNGResult, QueryResultWithError, SearxngSearchOptions } from './types.ts';
|
|
10
7
|
import { logger } from '../logger.ts';
|
|
11
|
-
import {
|
|
8
|
+
import { performSearch } from './browser-search.ts';
|
|
9
|
+
import type { QueryResultWithError } from './types.ts';
|
|
10
|
+
import type { Config } from '../config.ts';
|
|
11
|
+
import { metrics } from '../utils/metrics.ts';
|
|
12
|
+
import { getServiceContainer } from '../core/service-registry.ts';
|
|
13
|
+
import type { ServiceContainer } from '../core/service-registry.ts';
|
|
12
14
|
|
|
13
15
|
// ============================================================================
|
|
14
|
-
//
|
|
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
|
|
16
|
+
// Multiple Query Search
|
|
38
17
|
// ============================================================================
|
|
39
18
|
|
|
40
19
|
/**
|
|
41
|
-
*
|
|
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
|
|
20
|
+
* Search multiple queries via the Browser-based Search Queue.
|
|
69
21
|
*
|
|
70
|
-
* @param
|
|
71
|
-
* @param
|
|
72
|
-
* @
|
|
73
|
-
* @
|
|
22
|
+
* @param queries - Array of search query strings
|
|
23
|
+
* @param config - Optional configuration override
|
|
24
|
+
* @param signal - Optional AbortSignal
|
|
25
|
+
* @param onProgress - Optional callback with cumulative link count across completed queries
|
|
26
|
+
* @returns Promise<QueryResultWithError[]> - Array of search results with error information
|
|
74
27
|
*/
|
|
75
|
-
async function
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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 ?? [];
|
|
28
|
+
export async function search(
|
|
29
|
+
queries: string[],
|
|
30
|
+
config?: Config,
|
|
31
|
+
signal?: AbortSignal,
|
|
32
|
+
onProgress?: (links: number) => void,
|
|
33
|
+
container: ServiceContainer = getServiceContainer()
|
|
34
|
+
): Promise<QueryResultWithError[]> {
|
|
35
|
+
if (queries.length === 0) return [];
|
|
137
36
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
}
|
|
37
|
+
logger.log(`[Search] Orchestrating ${queries.length} queries via Browser Queue...`);
|
|
38
|
+
metrics.observe('search_query_count_per_request', queries.length);
|
|
39
|
+
const searchStart = Date.now();
|
|
149
40
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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})` };
|
|
41
|
+
try {
|
|
42
|
+
const resultMap = await performSearch(queries, config, signal, onProgress, container);
|
|
43
|
+
const searchDuration = Date.now() - searchStart;
|
|
44
|
+
metrics.observe('search_latency_ms', searchDuration);
|
|
45
|
+
|
|
46
|
+
let totalResults = 0;
|
|
47
|
+
let successfulQueries = 0;
|
|
48
|
+
let failedQueries = 0;
|
|
49
|
+
|
|
50
|
+
const results = queries.map(q => {
|
|
51
|
+
const qResults = resultMap.get(q) || [];
|
|
52
|
+
const result: QueryResultWithError = { query: q, results: qResults };
|
|
53
|
+
|
|
54
|
+
if (qResults.length > 0) {
|
|
55
|
+
totalResults += qResults.length;
|
|
56
|
+
successfulQueries++;
|
|
57
|
+
metrics.observe('search_results_per_query', qResults.length);
|
|
58
|
+
} else {
|
|
59
|
+
result.error = {
|
|
60
|
+
type: 'empty_results',
|
|
61
|
+
message: 'Browser-based search returned no relevant results for this query (the query may be too narrow, or the site returned nothing usable).'
|
|
62
|
+
};
|
|
63
|
+
failedQueries++;
|
|
196
64
|
}
|
|
65
|
+
return result;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
metrics.observe('search_results_total', totalResults);
|
|
69
|
+
metrics.increment('search_queries_total', successfulQueries, { status: 'success' });
|
|
70
|
+
metrics.increment('search_queries_total', failedQueries, { status: 'failed' });
|
|
71
|
+
metrics.observe('search_success_ratio', successfulQueries / queries.length);
|
|
72
|
+
|
|
73
|
+
return results;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
const searchDuration = Date.now() - searchStart;
|
|
76
|
+
metrics.observe('search_latency_ms', searchDuration, { status: 'error' });
|
|
77
|
+
metrics.increment('search_queries_total', queries.length, { status: 'error' });
|
|
78
|
+
metrics.increment('search_errors_total', 1, { error_type: 'search_failed' });
|
|
79
|
+
|
|
80
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
81
|
+
// Re-throw total search failure so the orchestrator can surface a clear error
|
|
82
|
+
// rather than silently producing an empty synthesis with zero links.
|
|
83
|
+
if (message.includes('Search completely failed')) {
|
|
84
|
+
metrics.increment('search_errors_total', 1, { error_type: 'total_search_failure' });
|
|
85
|
+
throw error;
|
|
197
86
|
}
|
|
87
|
+
logger.error(`[Search] Orchestration failed:`, error);
|
|
88
|
+
metrics.increment('search_errors_total', 1, { error_type: 'orchestration_error' });
|
|
89
|
+
return queries.map(q => ({
|
|
90
|
+
query: q,
|
|
91
|
+
results: [],
|
|
92
|
+
error: { type: 'unknown', message }
|
|
93
|
+
}));
|
|
198
94
|
}
|
|
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
|
-
}
|
|
95
|
+
}
|
|
@@ -4,19 +4,14 @@
|
|
|
4
4
|
* All TypeScript interfaces and types for the web research functionality
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
export type SearchFreshness = 'any' | 'day' | 'week' | 'month' | 'year';
|
|
9
|
-
export type SearchSourceType = 'general' | 'news' | 'github';
|
|
7
|
+
import type { SearchResult, ScrapeResult } from '../core/interfaces/scheduler-interfaces.ts';
|
|
10
8
|
|
|
11
|
-
export
|
|
12
|
-
freshness?: SearchFreshness;
|
|
13
|
-
sourceType?: SearchSourceType;
|
|
14
|
-
}
|
|
9
|
+
export type { SearchResult, ScrapeResult };
|
|
15
10
|
|
|
16
11
|
// Search result with per-query error context.
|
|
17
12
|
export interface QueryResultWithError {
|
|
18
13
|
query: string;
|
|
19
|
-
results:
|
|
14
|
+
results: SearchResult[];
|
|
20
15
|
error?: {
|
|
21
16
|
type: 'empty_results' | 'service_unavailable' | 'timeout' | 'network_error' | 'unknown';
|
|
22
17
|
message: string;
|
|
@@ -24,24 +19,14 @@ export interface QueryResultWithError {
|
|
|
24
19
|
}
|
|
25
20
|
|
|
26
21
|
// Scraper types
|
|
27
|
-
export type Layer = 'fetch' | 'playwright+
|
|
22
|
+
export type Layer = 'fetch' | 'playwright+camoufox';
|
|
28
23
|
|
|
29
24
|
export interface ScrapeLayerResult {
|
|
25
|
+
source: 'fetch' | 'playwright';
|
|
26
|
+
layer: 'fetch' | 'playwright' | 'playwright+camoufox';
|
|
30
27
|
markdown: string;
|
|
31
|
-
|
|
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;
|
|
28
|
+
error?: string;
|
|
42
29
|
}
|
|
43
30
|
|
|
44
|
-
|
|
45
|
-
export const
|
|
46
|
-
export const FALLBACK_SCRAPER_TIMEOUT = 20000; // Chromium layer
|
|
47
|
-
|
|
31
|
+
/** Timeout for the lightweight fetch layer only (not the full playwright scraper path). */
|
|
32
|
+
export const FETCH_LAYER_TIMEOUT = 10000;
|
|
@@ -4,155 +4,14 @@
|
|
|
4
4
|
* Helper functions, type guards, and utilities
|
|
5
5
|
*/
|
|
6
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
7
|
/**
|
|
40
|
-
*
|
|
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
|
|
8
|
+
* Common module checking utility
|
|
82
9
|
*/
|
|
83
10
|
export function checkModule(name: string): boolean {
|
|
84
11
|
try {
|
|
85
|
-
|
|
12
|
+
import.meta.resolve(name);
|
|
86
13
|
return true;
|
|
87
14
|
} catch {
|
|
88
15
|
return false;
|
|
89
16
|
}
|
|
90
17
|
}
|
|
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; }
|