@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.
Files changed (248) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +64 -115
  3. package/dist/cli.mjs +23211 -0
  4. package/dist/prompts/research-knowledge-relevance-triage.md +35 -0
  5. package/dist/prompts/research-knowledge-search-extractor.md +41 -0
  6. package/dist/prompts/research-tool-usage.md +92 -0
  7. package/dist/prompts/researcher.md +89 -0
  8. package/dist/prompts/system-coordinator.md +75 -0
  9. package/dist/prompts/system-lead-evaluator.md +118 -0
  10. package/dist/thread-worker.mjs +2611 -0
  11. package/dist/webgpu-probe.mjs +80 -0
  12. package/docs/AGENT-SKILL.md +102 -0
  13. package/docs/ARCHITECTURE.md +306 -80
  14. package/docs/CONFIGURATION.md +249 -0
  15. package/docs/KNOWLEDGE-STORE.md +213 -0
  16. package/docs/PI-EXTENSION.md +91 -0
  17. package/docs/SDK.md +113 -0
  18. package/docs/THIRD-PARTY-NOTICES.md +64 -0
  19. package/package.json +102 -36
  20. package/scripts/build.cjs +140 -0
  21. package/scripts/cleanup.cjs +149 -0
  22. package/scripts/prepare.cjs +28 -0
  23. package/scripts/setup.cjs +134 -0
  24. package/skills/pi-research/README.md +90 -0
  25. package/skills/pi-research/SKILL.md +102 -0
  26. package/skills/pi-research/references/configuration.md +77 -0
  27. package/skills/pi-research/scripts/run.mjs +237 -0
  28. package/src/cleanup/research-cleanup.ts +140 -0
  29. package/src/cli.ts +1044 -0
  30. package/src/config.ts +1036 -147
  31. package/src/constants.ts +115 -84
  32. package/src/core/health-check-service.ts +265 -0
  33. package/src/core/interfaces/health-check-interfaces.ts +50 -0
  34. package/src/core/interfaces/knowledge-interfaces.ts +190 -0
  35. package/src/core/interfaces/observer-interfaces.ts +67 -0
  36. package/src/core/interfaces/orchestration-interfaces.ts +86 -0
  37. package/src/core/interfaces/planning-interfaces.ts +100 -0
  38. package/src/core/interfaces/process-interfaces.ts +37 -0
  39. package/src/core/interfaces/research-plan-types.ts +52 -0
  40. package/src/core/interfaces/scheduler-interfaces.ts +91 -0
  41. package/src/core/interfaces/service-names.ts +39 -0
  42. package/src/core/interfaces/state-manager-interfaces.ts +29 -0
  43. package/src/core/interfaces/steering-interfaces.ts +31 -0
  44. package/src/core/llm/agentic-repair.ts +169 -0
  45. package/src/core/llm/inject-date.ts +52 -0
  46. package/src/core/llm/llm-timeout.ts +64 -0
  47. package/src/core/llm/llm-utils.ts +86 -0
  48. package/src/core/llm/model-registry-factory.ts +287 -0
  49. package/src/core/llm/pi-ai-completion.ts +29 -0
  50. package/src/core/llm/prompts.ts +50 -0
  51. package/src/core/llm/research-model-resolver.ts +91 -0
  52. package/src/core/planning-constants.ts +13 -0
  53. package/src/core/planning-service.ts +649 -0
  54. package/src/core/planning-utils.ts +299 -0
  55. package/src/core/scheduler-factory.ts +48 -0
  56. package/src/core/scheduler-service.ts +123 -0
  57. package/src/core/service-initialization.ts +194 -0
  58. package/src/core/service-interfaces.ts +48 -0
  59. package/src/core/service-registry.ts +659 -0
  60. package/src/healthcheck/index.ts +271 -265
  61. package/src/healthcheck/registry.ts +130 -0
  62. package/src/index.ts +639 -50
  63. package/src/infrastructure/browser/browser-cleanup.ts +309 -0
  64. package/src/infrastructure/browser/browser-client.ts +199 -0
  65. package/src/infrastructure/browser/browser-error-utils.ts +160 -0
  66. package/src/infrastructure/browser/browser-lifecycle.ts +120 -0
  67. package/src/infrastructure/browser/browser-server.ts +174 -0
  68. package/src/infrastructure/browser/browser-task-scheduler.ts +461 -0
  69. package/src/infrastructure/browser/cleanup-utils.ts +155 -0
  70. package/src/infrastructure/browser/client-agent.ts +31 -0
  71. package/src/infrastructure/browser/config.ts +288 -0
  72. package/src/infrastructure/browser/ensure-browser.ts +205 -0
  73. package/src/infrastructure/browser/index.ts +37 -0
  74. package/src/infrastructure/browser/priority-task-queue.ts +226 -0
  75. package/src/infrastructure/browser/scheduler-factory.ts +354 -0
  76. package/src/infrastructure/browser/task-execution-service.ts +209 -0
  77. package/src/infrastructure/browser/thread-worker-browser.ts +282 -0
  78. package/src/infrastructure/browser/thread-worker-lifecycle.ts +323 -0
  79. package/src/infrastructure/browser/thread-worker-messaging.ts +662 -0
  80. package/src/infrastructure/browser/thread-worker-types.ts +25 -0
  81. package/src/infrastructure/browser/thread-worker.mjs +2611 -0
  82. package/src/infrastructure/browser/thread-worker.ts +168 -0
  83. package/src/infrastructure/browser/worker-pool-manager.ts +449 -0
  84. package/src/infrastructure/embedding/embedding-client.ts +205 -0
  85. package/src/infrastructure/embedding/embedding-factory.ts +324 -0
  86. package/src/infrastructure/embedding/embedding-server.ts +535 -0
  87. package/src/infrastructure/file-lock-service.ts +592 -0
  88. package/src/infrastructure/gpu-resource-service.ts +191 -0
  89. package/src/infrastructure/knowledge-store-service.ts +475 -0
  90. package/src/infrastructure/metrics-service.ts +129 -0
  91. package/src/infrastructure/process-lifecycle-service.ts +253 -0
  92. package/src/infrastructure/scheduler-factory-service.ts +58 -0
  93. package/src/infrastructure/service-initialization.ts +292 -0
  94. package/src/infrastructure/state/state-backup-manager.ts +290 -0
  95. package/src/infrastructure/state/state-browser-api.ts +62 -0
  96. package/src/infrastructure/state/state-browser-manager.ts +70 -0
  97. package/src/infrastructure/state/state-manager-service.ts +264 -0
  98. package/src/infrastructure/state/state-manager.ts +547 -0
  99. package/src/infrastructure/state/state-metrics.ts +92 -0
  100. package/src/infrastructure/state/state-migration.ts +103 -0
  101. package/src/infrastructure/state/state-path-configuration.ts +119 -0
  102. package/src/infrastructure/state/state-session-api.ts +94 -0
  103. package/src/infrastructure/state/state-session-manager.ts +127 -0
  104. package/src/infrastructure/state/state-validator.ts +93 -0
  105. package/src/infrastructure/types/state-types.ts +85 -0
  106. package/src/knowledge/chunker.ts +157 -0
  107. package/src/knowledge/embedder-init.ts +413 -0
  108. package/src/knowledge/embedder-utils.ts +296 -0
  109. package/src/knowledge/embedder.ts +592 -0
  110. package/src/knowledge/index.ts +126 -0
  111. package/src/knowledge/lancedb-loader.ts +35 -0
  112. package/src/knowledge/migration.ts +17 -0
  113. package/src/knowledge/model-config.ts +132 -0
  114. package/src/knowledge/onnx-env.ts +48 -0
  115. package/src/knowledge/store-operations.ts +291 -0
  116. package/src/knowledge/store-schema.ts +79 -0
  117. package/src/knowledge/store.ts +1251 -0
  118. package/src/knowledge/transformers-loader.ts +31 -0
  119. package/src/knowledge/webgpu-probe.mjs +80 -0
  120. package/src/knowledge/webgpu-viability.ts +255 -0
  121. package/src/knowledge/writer-queue.ts +252 -0
  122. package/src/logger.ts +316 -122
  123. package/src/observers/research-observer-impl.ts +459 -0
  124. package/src/orchestration/deep-research-orchestrator.ts +524 -773
  125. package/src/orchestration/deep-research-types.ts +47 -35
  126. package/src/orchestration/headless-observer.ts +114 -0
  127. package/src/orchestration/orchestration-types.ts +33 -0
  128. package/src/orchestration/quick-research-orchestrator.ts +463 -0
  129. package/src/orchestration/research-orchestration-service.ts +549 -0
  130. package/src/orchestration/research-session-service.ts +178 -0
  131. package/src/orchestration/research-synthesis-service.ts +348 -0
  132. package/src/orchestration/researcher-executor.ts +394 -0
  133. package/src/orchestration/researcher.ts +115 -42
  134. package/src/orchestration/service-initialization.ts +109 -0
  135. package/src/orchestration/session-context.ts +7 -3
  136. package/src/orchestration/session-state.ts +672 -0
  137. package/src/prompts/research-knowledge-relevance-triage.md +35 -0
  138. package/src/prompts/research-knowledge-search-extractor.md +41 -0
  139. package/src/prompts/research-tool-usage.md +63 -16
  140. package/src/prompts/researcher.md +59 -136
  141. package/src/prompts/system-coordinator.md +64 -13
  142. package/src/prompts/system-lead-evaluator.md +82 -189
  143. package/src/research-config.ts +872 -0
  144. package/src/sdk.ts +847 -0
  145. package/src/security/cisa-kev.ts +72 -9
  146. package/src/security/cvss.ts +92 -0
  147. package/src/security/ecosystem.ts +45 -0
  148. package/src/security/github-advisories.ts +168 -428
  149. package/src/security/github-advisory-types.ts +282 -0
  150. package/src/security/index.ts +63 -177
  151. package/src/security/nvd-client.ts +550 -0
  152. package/src/security/nvd-types.ts +179 -0
  153. package/src/security/osv-client.ts +246 -0
  154. package/src/security/osv-types.ts +285 -0
  155. package/src/security/types.ts +67 -53
  156. package/src/skill-install/skill-installer.ts +519 -0
  157. package/src/stackexchange/index.ts +81 -27
  158. package/src/stackexchange/output/compact.ts +7 -6
  159. package/src/stackexchange/output/table.ts +11 -7
  160. package/src/stackexchange/queries.ts +0 -10
  161. package/src/stackexchange/rest-client.ts +141 -66
  162. package/src/stackexchange/types.ts +0 -17
  163. package/src/tool.ts +15 -423
  164. package/src/tools/grep.ts +32 -310
  165. package/src/tools/health-tool-definition.ts +101 -0
  166. package/src/tools/index.ts +38 -9
  167. package/src/tools/research-knowledge-search.ts +974 -0
  168. package/src/tools/research-knowledge-types.ts +77 -0
  169. package/src/tools/research-tool-definition.ts +494 -0
  170. package/src/tools/scrape.ts +286 -388
  171. package/src/tools/search.ts +98 -153
  172. package/src/tools/security.ts +107 -80
  173. package/src/tools/stackexchange.ts +58 -35
  174. package/src/tools/youtube-transcript.ts +163 -0
  175. package/src/tui/knowledge-search-panel.ts +86 -0
  176. package/src/tui/pulse.ts +86 -0
  177. package/src/tui/research-health.ts +124 -0
  178. package/src/tui/research-panel-color-utils.ts +340 -0
  179. package/src/tui/research-panel-format.ts +54 -0
  180. package/src/tui/research-panel-state.ts +404 -0
  181. package/src/tui/research-panel-wave.ts +280 -0
  182. package/src/tui/research-panel.ts +339 -435
  183. package/src/tui/research-tui-manager.ts +233 -0
  184. package/src/tui/tui-controller.ts +137 -0
  185. package/src/types/extension-context.ts +18 -49
  186. package/src/types/index.ts +61 -0
  187. package/src/types/llm.ts +68 -29
  188. package/src/types/research-panel-types.ts +61 -0
  189. package/src/utils/circuit-breaker.ts +146 -0
  190. package/src/utils/citation-utils.ts +97 -0
  191. package/src/utils/disk-space-checker.ts +55 -0
  192. package/src/utils/error-tracker.ts +265 -0
  193. package/src/utils/host-config.ts +45 -0
  194. package/src/utils/input-validation.ts +8 -10
  195. package/src/utils/json-utils.ts +150 -16
  196. package/src/utils/llm-usage.ts +54 -0
  197. package/src/utils/log-rotation.ts +125 -0
  198. package/src/utils/log-utils.ts +159 -0
  199. package/src/utils/make-resource-loader.ts +3 -1
  200. package/src/utils/metrics-summary.ts +459 -0
  201. package/src/utils/metrics.ts +302 -0
  202. package/src/utils/research-export.ts +28 -12
  203. package/src/utils/safe-unref.ts +31 -0
  204. package/src/utils/shared-links.ts +195 -90
  205. package/src/utils/shutdown-manager.ts +65 -0
  206. package/src/utils/stdio-capture.ts +407 -0
  207. package/src/utils/text-utils.ts +264 -7
  208. package/src/utils/tool-usage-tracker.ts +83 -16
  209. package/src/utils/url-utils.ts +255 -0
  210. package/src/utils/user-agent.ts +28 -0
  211. package/src/web-research/browser-search.ts +179 -0
  212. package/src/web-research/retry-utils.ts +173 -77
  213. package/src/web-research/scraper-types.ts +72 -0
  214. package/src/web-research/scraper-utils.ts +434 -0
  215. package/src/web-research/search.ts +78 -218
  216. package/src/web-research/types.ts +9 -24
  217. package/src/web-research/utils.ts +2 -143
  218. package/src/web-research/web-scraper.ts +477 -0
  219. package/src/youtube/index.ts +154 -0
  220. package/src/youtube/potoken.ts +259 -0
  221. package/src/youtube/transcript-client.ts +390 -0
  222. package/src/youtube/video-id.ts +118 -0
  223. package/config/searxng/default-settings.yml +0 -189
  224. package/config/searxng/limiter.toml +0 -34
  225. package/config/tooling/eslint.config.js +0 -73
  226. package/config/tooling/vitest.config.integration.ts +0 -17
  227. package/config/tooling/vitest.config.ts +0 -40
  228. package/config/tooling/vitest.config.unit.ts +0 -12
  229. package/docs/assets/README-banner.jpg +0 -0
  230. package/scripts/setup.js +0 -174
  231. package/src/infrastructure/searxng-lifecycle.ts +0 -577
  232. package/src/infrastructure/searxng-manager.ts +0 -1183
  233. package/src/infrastructure/state-manager.ts +0 -918
  234. package/src/orchestration/deep-research-reducer.ts +0 -129
  235. package/src/orchestration/id-utils.ts +0 -63
  236. package/src/security/nvd.ts +0 -532
  237. package/src/security/osv.ts +0 -487
  238. package/src/stackexchange/cache.ts +0 -111
  239. package/src/stackexchange/output/index.ts +0 -7
  240. package/src/stackexchange/output/json.ts +0 -21
  241. package/src/tui/searxng-status.ts +0 -80
  242. package/src/utils/inject-date.ts +0 -29
  243. package/src/utils/research-error.ts +0 -205
  244. package/src/utils/searxng-config.ts +0 -94
  245. package/src/utils/session-state.ts +0 -295
  246. package/src/web-research/dependencies.ts +0 -20
  247. package/src/web-research/scrapers.ts +0 -935
  248. package/tsconfig.json +0 -40
@@ -1,235 +1,95 @@
1
1
  /**
2
- * Web Research Extension - Search Only (search)
2
+ * Web Research Extension - Search Orchestrator
3
3
  *
4
- * Search via SearXNG and return URLs, titles, and snippets.
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 { getConfig } from '../config.ts';
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
- // 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
16
+ // Multiple Query Search
38
17
  // ============================================================================
39
18
 
40
19
  /**
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
20
+ * Search multiple queries via the Browser-based Search Queue.
69
21
  *
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
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 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 ?? [];
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
- // 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
- }
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
- 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})` };
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
- // Search filter options passed through to SearXNG
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 interface SearxngSearchOptions {
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: SearXNGResult[];
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+chromium';
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
- 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;
28
+ error?: string;
42
29
  }
43
30
 
44
- // Timeouts
45
- export const PRIMARY_SCRAPER_TIMEOUT = 15000; // fetch layer
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
- * 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
8
+ * Common module checking utility
82
9
  */
83
10
  export function checkModule(name: string): boolean {
84
11
  try {
85
- require.resolve(name);
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; }