mcp-researchpowerpack 7.0.11 → 7.0.13

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 (70) hide show
  1. package/dist/index.js +4662 -21
  2. package/dist/index.js.map +4 -4
  3. package/dist/mcp-use.json +2 -2
  4. package/dist/src/clients/jina.js +202 -16
  5. package/dist/src/clients/jina.js.map +3 -3
  6. package/dist/src/clients/kernel.js +254 -7
  7. package/dist/src/clients/kernel.js.map +4 -4
  8. package/dist/src/clients/reddit.js +326 -23
  9. package/dist/src/clients/reddit.js.map +4 -4
  10. package/dist/src/clients/scraper.js +345 -22
  11. package/dist/src/clients/scraper.js.map +4 -4
  12. package/dist/src/clients/search.js +316 -20
  13. package/dist/src/clients/search.js.map +4 -4
  14. package/dist/src/config/index.js +39 -10
  15. package/dist/src/config/index.js.map +3 -3
  16. package/dist/src/effect/errors.js +130 -5
  17. package/dist/src/effect/errors.js.map +3 -3
  18. package/dist/src/effect/runtime.js +1893 -4
  19. package/dist/src/effect/runtime.js.map +4 -4
  20. package/dist/src/effect/services.js +2124 -22
  21. package/dist/src/effect/services.js.map +4 -4
  22. package/dist/src/schemas/scrape-links.js +6 -5
  23. package/dist/src/schemas/scrape-links.js.map +1 -1
  24. package/dist/src/schemas/start-research.js +2 -1
  25. package/dist/src/schemas/start-research.js.map +1 -1
  26. package/dist/src/schemas/web-search.js +9 -8
  27. package/dist/src/schemas/web-search.js.map +1 -1
  28. package/dist/src/services/llm-processor.js +406 -25
  29. package/dist/src/services/llm-processor.js.map +4 -4
  30. package/dist/src/services/markdown-cleaner.js +6 -5
  31. package/dist/src/services/markdown-cleaner.js.map +1 -1
  32. package/dist/src/tools/mcp-helpers.js +2 -1
  33. package/dist/src/tools/mcp-helpers.js.map +1 -1
  34. package/dist/src/tools/registry.js +4629 -3
  35. package/dist/src/tools/registry.js.map +4 -4
  36. package/dist/src/tools/scrape.js +2610 -80
  37. package/dist/src/tools/scrape.js.map +4 -4
  38. package/dist/src/tools/search.js +2388 -59
  39. package/dist/src/tools/search.js.map +4 -4
  40. package/dist/src/tools/start-research.js +2030 -23
  41. package/dist/src/tools/start-research.js.map +4 -4
  42. package/dist/src/tools/utils.js +98 -7
  43. package/dist/src/tools/utils.js.map +3 -3
  44. package/dist/src/utils/concurrency.js +1 -0
  45. package/dist/src/utils/concurrency.js.map +1 -1
  46. package/dist/src/utils/content-extractor.js +27 -2
  47. package/dist/src/utils/content-extractor.js.map +3 -3
  48. package/dist/src/utils/content-quality.js +4 -3
  49. package/dist/src/utils/content-quality.js.map +1 -1
  50. package/dist/src/utils/errors.js +26 -3
  51. package/dist/src/utils/errors.js.map +3 -3
  52. package/dist/src/utils/logger.js +1 -0
  53. package/dist/src/utils/logger.js.map +1 -1
  54. package/dist/src/utils/markdown-formatter.js +1 -0
  55. package/dist/src/utils/markdown-formatter.js.map +1 -1
  56. package/dist/src/utils/query-relax.js +9 -8
  57. package/dist/src/utils/query-relax.js.map +1 -1
  58. package/dist/src/utils/response.js +3 -2
  59. package/dist/src/utils/response.js.map +1 -1
  60. package/dist/src/utils/retry.js +5 -4
  61. package/dist/src/utils/retry.js.map +1 -1
  62. package/dist/src/utils/sanitize.js +4 -3
  63. package/dist/src/utils/sanitize.js.map +1 -1
  64. package/dist/src/utils/source-type.js +4 -3
  65. package/dist/src/utils/source-type.js.map +1 -1
  66. package/dist/src/utils/url-aggregator.js +112 -11
  67. package/dist/src/utils/url-aggregator.js.map +3 -3
  68. package/dist/src/version.js +7 -6
  69. package/dist/src/version.js.map +1 -1
  70. package/package.json +3 -3
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../index.ts"],
4
- "sourcesContent": ["#!/usr/bin/env node\n\n// Expand libuv thread pool for parallel DNS lookups (default 4 is too low for 20+ concurrent connections)\nif (!process.env.UV_THREADPOOL_SIZE) {\n process.env.UV_THREADPOOL_SIZE = '8';\n}\n\nimport { Logger } from 'mcp-use';\nimport {\n InMemorySessionStore,\n InMemoryStreamManager,\n MCPServer,\n object,\n type ServerConfig,\n} from 'mcp-use/server';\n\nimport { SERVER } from './src/config/index.js';\nimport { getLLMHealth } from './src/services/llm-processor.js';\nimport { registerAllTools } from './src/tools/registry.js';\n\nconst DEFAULT_PORT = 3000 as const;\nconst SHUTDOWN_TIMEOUT_MS = 10_000 as const;\nconst WEBSITE_URL = 'https://github.com/yigitkonur/mcp-researchpowerpack' as const;\nconst LOCAL_DEFAULT_HOST = '127.0.0.1' as const;\n\ntype CleanupFn = () => Promise<void>;\n\nconst startupLogger = Logger.get('startup');\n\nfunction parseCsvEnv(value: string | undefined): string[] | undefined {\n if (!value) return undefined;\n\n const parts = value\n .split(',')\n .map((part) => part.trim())\n .filter(Boolean);\n\n return parts.length > 0 ? parts : undefined;\n}\n\nfunction parsePort(value: string | undefined, fallback: number): number {\n const parsed = Number.parseInt(value ?? '', 10);\n if (Number.isFinite(parsed) && parsed > 0) {\n return parsed;\n }\n\n return fallback;\n}\n\nfunction resolvePort(): number {\n const portFlagIndex = process.argv.findIndex((arg) => arg === '--port');\n if (portFlagIndex >= 0) {\n return parsePort(process.argv[portFlagIndex + 1], DEFAULT_PORT);\n }\n\n return parsePort(process.env.PORT, DEFAULT_PORT);\n}\n\nfunction resolveHost(): string {\n const explicitHost = process.env.HOST?.trim();\n if (explicitHost) {\n return explicitHost;\n }\n\n // Cloud runtimes typically inject PORT and expect the process to listen on all interfaces.\n if (process.env.PORT?.trim()) {\n return '0.0.0.0';\n }\n\n return LOCAL_DEFAULT_HOST;\n}\n\nfunction buildCors(allowedOrigins: string[] | undefined): ServerConfig['cors'] {\n if (!allowedOrigins || allowedOrigins.length === 0) {\n return undefined;\n }\n\n return {\n origin: allowedOrigins,\n allowMethods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS'],\n allowHeaders: [\n 'Content-Type',\n 'Accept',\n 'Authorization',\n 'mcp-protocol-version',\n 'mcp-session-id',\n 'X-Proxy-Token',\n 'X-Target-URL',\n ],\n exposeHeaders: ['mcp-session-id'],\n };\n}\n\nfunction configureLogging(): void {\n Logger.configure({\n level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',\n format: 'minimal',\n });\n\n const debug = process.env.DEBUG?.trim();\n if (debug === '2') {\n Logger.setDebug(2);\n } else if (debug) {\n Logger.setDebug(1);\n }\n}\n\nfunction normalizeOrigin(value: string, envName: string): string {\n try {\n return new URL(value).origin;\n } catch {\n throw new Error(`${envName} must contain absolute URLs with protocol. Received: ${value}`);\n }\n}\n\nfunction resolveAllowedOrigins(): string[] | undefined {\n const explicitOrigins = parseCsvEnv(process.env.ALLOWED_ORIGINS);\n if (explicitOrigins && explicitOrigins.length > 0) {\n return explicitOrigins.map(origin => normalizeOrigin(origin, 'ALLOWED_ORIGINS'));\n }\n\n return undefined;\n}\n\nfunction buildSessionConfig(): {\n sessionConfig: Pick<ServerConfig, 'sessionStore' | 'streamManager'>;\n cleanupFns: CleanupFn[];\n} {\n return {\n sessionConfig: {\n sessionStore: new InMemorySessionStore(),\n streamManager: new InMemoryStreamManager(),\n },\n cleanupFns: [],\n };\n}\n\nfunction buildHealthPayload(server: MCPServer, startedAt: number) {\n const llm = getLLMHealth();\n // Distinguish \"never probed\" (checkedAt === null) from \"probed and failed\"\n // (checkedAt set, ok=false). The raw `lastPlannerOk` defaults to `false`\n // at startup, which would mislead operators into thinking the LLM is\n // broken before it has been exercised once.\n const plannerOkForHealth = llm.lastPlannerCheckedAt === null ? null : llm.lastPlannerOk;\n const extractorOkForHealth = llm.lastExtractorCheckedAt === null ? null : llm.lastExtractorOk;\n return {\n status: 'ok',\n name: SERVER.NAME,\n version: SERVER.VERSION,\n transport: 'http',\n uptime_seconds: Math.floor((Date.now() - startedAt) / 1000),\n active_sessions: server.getActiveSessions().length,\n llm_planner_ok: plannerOkForHealth,\n llm_extractor_ok: extractorOkForHealth,\n llm_planner_checked_at: llm.lastPlannerCheckedAt,\n llm_extractor_checked_at: llm.lastExtractorCheckedAt,\n llm_planner_error: llm.lastPlannerError,\n llm_extractor_error: llm.lastExtractorError,\n planner_configured: llm.plannerConfigured,\n extractor_configured: llm.extractorConfigured,\n // Counter surfacing lets operators diagnose gate behavior from outside\n // the process (see src/tools/start-research.ts for the gate semantics).\n consecutive_planner_failures: llm.consecutivePlannerFailures,\n consecutive_extractor_failures: llm.consecutiveExtractorFailures,\n timestamp: new Date().toISOString(),\n };\n}\n\nasync function main(): Promise<void> {\n configureLogging();\n\n const isProduction = process.env.NODE_ENV === 'production';\n const host = resolveHost();\n const port = resolvePort();\n const baseUrl = process.env.MCP_URL?.trim() || undefined;\n const allowedOrigins = resolveAllowedOrigins();\n\n const { sessionConfig, cleanupFns } = buildSessionConfig();\n\n startupLogger.info(`Starting ${SERVER.NAME} v${SERVER.VERSION}`);\n startupLogger.info(`Binding HTTP server to ${host}:${port}`);\n if (allowedOrigins && allowedOrigins.length > 0) {\n startupLogger.info(`Host validation enabled for origins: ${allowedOrigins.join(', ')}`);\n } else if (isProduction) {\n if (!baseUrl) {\n startupLogger.error(\n 'Production mode requires ALLOWED_ORIGINS or MCP_URL to be set. ' +\n 'Without host validation, the server is vulnerable to DNS rebinding attacks. ' +\n 'Set ALLOWED_ORIGINS to the public deployment URL or custom domain.',\n );\n process.exit(1);\n }\n startupLogger.warn(\n 'Host validation is disabled because ALLOWED_ORIGINS is not set. ' +\n 'MCP_URL is set, so the server will start \u2014 but set ALLOWED_ORIGINS for full origin protection.',\n );\n } else {\n startupLogger.info('Host validation disabled for local development');\n }\n\n const server = new MCPServer({\n name: SERVER.NAME,\n title: 'Research Powerpack',\n version: SERVER.VERSION,\n description: SERVER.DESCRIPTION,\n websiteUrl: WEBSITE_URL,\n host,\n baseUrl,\n cors: buildCors(allowedOrigins),\n allowedOrigins,\n ...sessionConfig,\n });\n\n registerAllTools(server);\n\n // Advertise our LLM-augmentation capability via the MCP `experimental`\n // namespace so capability-aware clients can branch at initialize-time\n // instead of parsing per-call footers. mcp-use creates a fresh native MCP\n // server per session via `getServerForSession()`, so we patch that factory\n // to register our experimental capability on every session. The capability\n // values are read fresh on each session so health flips are observable.\n // See: docs/code-review/context/06-mcp-use-best-practices-primer.md (#3, #6).\n try {\n type Native = { server?: { registerCapabilities?: (caps: Record<string, unknown>) => void } };\n type Patched = { getServerForSession?: (sessionId?: string) => Native };\n const patched = server as unknown as Patched;\n const original = patched.getServerForSession?.bind(server);\n if (original) {\n patched.getServerForSession = (sessionId?: string): Native => {\n const native = original(sessionId);\n try {\n const llm = getLLMHealth();\n native.server?.registerCapabilities?.({\n experimental: {\n research_powerpack: {\n planner_available: llm.plannerConfigured,\n extractor_available: llm.extractorConfigured,\n planner_model: process.env.LLM_MODEL ?? null,\n extractor_model: process.env.LLM_MODEL ?? null,\n },\n },\n });\n } catch {\n // Capability registration is advisory; never block session creation.\n }\n return native;\n };\n }\n } catch (err) {\n startupLogger.warn(`Could not patch session-server factory: ${String(err)}`);\n }\n\n const startedAt = Date.now();\n\n server.get('/health', (c) => c.json(buildHealthPayload(server, startedAt)));\n server.get('/healthz', (c) => c.json(buildHealthPayload(server, startedAt)));\n\n // Some MCP clients (Claude Desktop, Cursor, VS Code) proactively probe\n // /.well-known/oauth-protected-resource before receiving any 401, per the\n // MCP 2025-03-26 spec. Without these routes the server returns 404 and some\n // clients surface a spurious \"authentication required\" error. A minimal PRM\n // response with no authorization_servers field explicitly signals that this\n // server requires no authentication.\n const resourceBaseUrl = baseUrl ?? `http://${host}:${port}`;\n server.get('/.well-known/oauth-protected-resource', (c) =>\n c.json({ resource: resourceBaseUrl }),\n );\n server.get('/.well-known/oauth-protected-resource/mcp', (c) =>\n c.json({ resource: `${resourceBaseUrl}/mcp` }),\n );\n\n server.resource(\n {\n name: 'server-health',\n uri: 'health://status',\n description: 'Current server health, uptime, and active MCP session count.',\n mimeType: 'application/json',\n },\n async () => object(buildHealthPayload(server, startedAt)),\n );\n\n let isShuttingDown = false;\n\n async function shutdown(signal: string, exitCode: number): Promise<void> {\n if (isShuttingDown) return;\n isShuttingDown = true;\n\n const forceExit = setTimeout(() => {\n startupLogger.error(`Forced exit after ${SHUTDOWN_TIMEOUT_MS}ms (${signal})`);\n process.exit(1);\n }, SHUTDOWN_TIMEOUT_MS);\n\n try {\n startupLogger.warn(`Shutdown signal received: ${signal}`);\n await server.close();\n\n for (const cleanupFn of cleanupFns) {\n await cleanupFn();\n }\n\n clearTimeout(forceExit);\n process.exit(exitCode);\n } catch (error) {\n clearTimeout(forceExit);\n const message = error instanceof Error ? (error.stack ?? error.message) : String(error);\n startupLogger.error(`Error while stopping server: ${message}`);\n process.exit(1);\n }\n }\n\n process.on('SIGTERM', () => {\n void shutdown('SIGTERM', 0);\n });\n\n process.on('SIGINT', () => {\n void shutdown('SIGINT', 0);\n });\n\n process.on('uncaughtException', (error) => {\n startupLogger.error(`Uncaught exception: ${error.stack ?? error.message}`);\n void shutdown('uncaughtException', 1);\n });\n\n process.on('unhandledRejection', (reason) => {\n startupLogger.error(`Unhandled rejection: ${String(reason)}`);\n void shutdown('unhandledRejection', 1);\n });\n\n await server.listen(port);\n\n startupLogger.info(`${SERVER.NAME} v${SERVER.VERSION} listening on http://${host}:${port}/mcp`);\n}\n\nvoid main().catch((error) => {\n const message = error instanceof Error ? (error.stack ?? error.message) : String(error);\n startupLogger.error(`Server failed to start: ${message}`);\n process.exit(1);\n});\n"],
5
- "mappings": "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",
6
- "names": []
3
+ "sources": ["../index.ts", "../src/config/index.ts", "../src/version.ts", "../src/services/llm-processor.ts", "../src/schemas/web-search.ts", "../src/utils/logger.ts", "../src/utils/errors.ts", "../src/tools/scrape.ts", "../src/schemas/scrape-links.ts", "../src/utils/retry.ts", "../src/clients/jina.ts", "../src/services/markdown-cleaner.ts", "../src/utils/markdown-formatter.ts", "../src/utils/content-extractor.ts", "../src/utils/source-type.ts", "../src/utils/content-quality.ts", "../src/effect/runtime.ts", "../src/effect/services.ts", "../src/utils/concurrency.ts", "../src/clients/search.ts", "../src/clients/kernel.ts", "../src/clients/reddit.ts", "../src/effect/errors.ts", "../src/utils/response.ts", "../src/tools/mcp-helpers.ts", "../src/tools/search.ts", "../src/utils/url-aggregator.ts", "../src/utils/sanitize.ts", "../src/utils/query-relax.ts", "../src/tools/start-research.ts", "../src/schemas/start-research.ts", "../src/tools/registry.ts"],
4
+ "sourcesContent": ["#!/usr/bin/env node\n\n// Expand libuv thread pool for parallel DNS lookups (default 4 is too low for 20+ concurrent connections)\nif (!process.env.UV_THREADPOOL_SIZE) {\n process.env.UV_THREADPOOL_SIZE = '8';\n}\n\nimport { Logger } from 'mcp-use';\nimport {\n InMemorySessionStore,\n InMemoryStreamManager,\n MCPServer,\n object,\n type ServerConfig,\n} from 'mcp-use/server';\n\nimport { SERVER } from './src/config/index.js';\nimport { getLLMHealth } from './src/services/llm-processor.js';\nimport { registerAllTools } from './src/tools/registry.js';\n\nconst DEFAULT_PORT = 3000 as const;\nconst SHUTDOWN_TIMEOUT_MS = 10_000 as const;\nconst WEBSITE_URL = 'https://github.com/yigitkonur/mcp-researchpowerpack' as const;\nconst LOCAL_DEFAULT_HOST = '127.0.0.1' as const;\n\ntype CleanupFn = () => Promise<void>;\n\nconst startupLogger = Logger.get('startup');\n\nfunction parseCsvEnv(value: string | undefined): string[] | undefined {\n if (!value) return undefined;\n\n const parts = value\n .split(',')\n .map((part) => part.trim())\n .filter(Boolean);\n\n return parts.length > 0 ? parts : undefined;\n}\n\nfunction parsePort(value: string | undefined, fallback: number): number {\n const parsed = Number.parseInt(value ?? '', 10);\n if (Number.isFinite(parsed) && parsed > 0) {\n return parsed;\n }\n\n return fallback;\n}\n\nfunction resolvePort(): number {\n const portFlagIndex = process.argv.findIndex((arg) => arg === '--port');\n if (portFlagIndex >= 0) {\n return parsePort(process.argv[portFlagIndex + 1], DEFAULT_PORT);\n }\n\n return parsePort(process.env.PORT, DEFAULT_PORT);\n}\n\nfunction resolveHost(): string {\n const explicitHost = process.env.HOST?.trim();\n if (explicitHost) {\n return explicitHost;\n }\n\n // Cloud runtimes typically inject PORT and expect the process to listen on all interfaces.\n if (process.env.PORT?.trim()) {\n return '0.0.0.0';\n }\n\n return LOCAL_DEFAULT_HOST;\n}\n\nfunction buildCors(allowedOrigins: string[] | undefined): ServerConfig['cors'] {\n if (!allowedOrigins || allowedOrigins.length === 0) {\n return undefined;\n }\n\n return {\n origin: allowedOrigins,\n allowMethods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'OPTIONS'],\n allowHeaders: [\n 'Content-Type',\n 'Accept',\n 'Authorization',\n 'mcp-protocol-version',\n 'mcp-session-id',\n 'X-Proxy-Token',\n 'X-Target-URL',\n ],\n exposeHeaders: ['mcp-session-id'],\n };\n}\n\nfunction configureLogging(): void {\n Logger.configure({\n level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',\n format: 'minimal',\n });\n\n const debug = process.env.DEBUG?.trim();\n if (debug === '2') {\n Logger.setDebug(2);\n } else if (debug) {\n Logger.setDebug(1);\n }\n}\n\nfunction normalizeOrigin(value: string, envName: string): string {\n try {\n return new URL(value).origin;\n } catch {\n throw new Error(`${envName} must contain absolute URLs with protocol. Received: ${value}`);\n }\n}\n\nfunction resolveAllowedOrigins(): string[] | undefined {\n const explicitOrigins = parseCsvEnv(process.env.ALLOWED_ORIGINS);\n if (explicitOrigins && explicitOrigins.length > 0) {\n return explicitOrigins.map(origin => normalizeOrigin(origin, 'ALLOWED_ORIGINS'));\n }\n\n return undefined;\n}\n\nfunction buildSessionConfig(): {\n sessionConfig: Pick<ServerConfig, 'sessionStore' | 'streamManager'>;\n cleanupFns: CleanupFn[];\n} {\n return {\n sessionConfig: {\n sessionStore: new InMemorySessionStore(),\n streamManager: new InMemoryStreamManager(),\n },\n cleanupFns: [],\n };\n}\n\nfunction buildHealthPayload(server: MCPServer, startedAt: number) {\n const llm = getLLMHealth();\n // Distinguish \"never probed\" (checkedAt === null) from \"probed and failed\"\n // (checkedAt set, ok=false). The raw `lastPlannerOk` defaults to `false`\n // at startup, which would mislead operators into thinking the LLM is\n // broken before it has been exercised once.\n const plannerOkForHealth = llm.lastPlannerCheckedAt === null ? null : llm.lastPlannerOk;\n const extractorOkForHealth = llm.lastExtractorCheckedAt === null ? null : llm.lastExtractorOk;\n return {\n status: 'ok',\n name: SERVER.NAME,\n version: SERVER.VERSION,\n transport: 'http',\n uptime_seconds: Math.floor((Date.now() - startedAt) / 1000),\n active_sessions: server.getActiveSessions().length,\n llm_planner_ok: plannerOkForHealth,\n llm_extractor_ok: extractorOkForHealth,\n llm_planner_checked_at: llm.lastPlannerCheckedAt,\n llm_extractor_checked_at: llm.lastExtractorCheckedAt,\n llm_planner_error: llm.lastPlannerError,\n llm_extractor_error: llm.lastExtractorError,\n planner_configured: llm.plannerConfigured,\n extractor_configured: llm.extractorConfigured,\n // Counter surfacing lets operators diagnose gate behavior from outside\n // the process (see src/tools/start-research.ts for the gate semantics).\n consecutive_planner_failures: llm.consecutivePlannerFailures,\n consecutive_extractor_failures: llm.consecutiveExtractorFailures,\n timestamp: new Date().toISOString(),\n };\n}\n\nasync function main(): Promise<void> {\n configureLogging();\n\n const isProduction = process.env.NODE_ENV === 'production';\n const host = resolveHost();\n const port = resolvePort();\n const baseUrl = process.env.MCP_URL?.trim() || undefined;\n const allowedOrigins = resolveAllowedOrigins();\n\n const { sessionConfig, cleanupFns } = buildSessionConfig();\n\n startupLogger.info(`Starting ${SERVER.NAME} v${SERVER.VERSION}`);\n startupLogger.info(`Binding HTTP server to ${host}:${port}`);\n if (allowedOrigins && allowedOrigins.length > 0) {\n startupLogger.info(`Host validation enabled for origins: ${allowedOrigins.join(', ')}`);\n } else if (isProduction) {\n if (!baseUrl) {\n startupLogger.error(\n 'Production mode requires ALLOWED_ORIGINS or MCP_URL to be set. ' +\n 'Without host validation, the server is vulnerable to DNS rebinding attacks. ' +\n 'Set ALLOWED_ORIGINS to the public deployment URL or custom domain.',\n );\n process.exit(1);\n }\n startupLogger.warn(\n 'Host validation is disabled because ALLOWED_ORIGINS is not set. ' +\n 'MCP_URL is set, so the server will start \u2014 but set ALLOWED_ORIGINS for full origin protection.',\n );\n } else {\n startupLogger.info('Host validation disabled for local development');\n }\n\n const server = new MCPServer({\n name: SERVER.NAME,\n title: 'Research Powerpack',\n version: SERVER.VERSION,\n description: SERVER.DESCRIPTION,\n websiteUrl: WEBSITE_URL,\n host,\n baseUrl,\n cors: buildCors(allowedOrigins),\n allowedOrigins,\n ...sessionConfig,\n });\n\n registerAllTools(server);\n\n // Advertise our LLM-augmentation capability via the MCP `experimental`\n // namespace so capability-aware clients can branch at initialize-time\n // instead of parsing per-call footers. mcp-use creates a fresh native MCP\n // server per session via `getServerForSession()`, so we patch that factory\n // to register our experimental capability on every session. The capability\n // values are read fresh on each session so health flips are observable.\n // See: docs/code-review/context/06-mcp-use-best-practices-primer.md (#3, #6).\n try {\n type Native = { server?: { registerCapabilities?: (caps: Record<string, unknown>) => void } };\n type Patched = { getServerForSession?: (sessionId?: string) => Native };\n const patched = server as unknown as Patched;\n const original = patched.getServerForSession?.bind(server);\n if (original) {\n patched.getServerForSession = (sessionId?: string): Native => {\n const native = original(sessionId);\n try {\n const llm = getLLMHealth();\n native.server?.registerCapabilities?.({\n experimental: {\n research_powerpack: {\n planner_available: llm.plannerConfigured,\n extractor_available: llm.extractorConfigured,\n planner_model: process.env.LLM_MODEL ?? null,\n extractor_model: process.env.LLM_MODEL ?? null,\n },\n },\n });\n } catch {\n // Capability registration is advisory; never block session creation.\n }\n return native;\n };\n }\n } catch (err) {\n startupLogger.warn(`Could not patch session-server factory: ${String(err)}`);\n }\n\n const startedAt = Date.now();\n\n server.get('/health', (c) => c.json(buildHealthPayload(server, startedAt)));\n server.get('/healthz', (c) => c.json(buildHealthPayload(server, startedAt)));\n\n // Some MCP clients (Claude Desktop, Cursor, VS Code) proactively probe\n // /.well-known/oauth-protected-resource before receiving any 401, per the\n // MCP 2025-03-26 spec. Without these routes the server returns 404 and some\n // clients surface a spurious \"authentication required\" error. A minimal PRM\n // response with no authorization_servers field explicitly signals that this\n // server requires no authentication.\n const resourceBaseUrl = baseUrl ?? `http://${host}:${port}`;\n server.get('/.well-known/oauth-protected-resource', (c) =>\n c.json({ resource: resourceBaseUrl }),\n );\n server.get('/.well-known/oauth-protected-resource/mcp', (c) =>\n c.json({ resource: `${resourceBaseUrl}/mcp` }),\n );\n\n server.resource(\n {\n name: 'server-health',\n uri: 'health://status',\n description: 'Current server health, uptime, and active MCP session count.',\n mimeType: 'application/json',\n },\n async () => object(buildHealthPayload(server, startedAt)),\n );\n\n let isShuttingDown = false;\n\n async function shutdown(signal: string, exitCode: number): Promise<void> {\n if (isShuttingDown) return;\n isShuttingDown = true;\n\n const forceExit = setTimeout(() => {\n startupLogger.error(`Forced exit after ${SHUTDOWN_TIMEOUT_MS}ms (${signal})`);\n process.exit(1);\n }, SHUTDOWN_TIMEOUT_MS);\n\n try {\n startupLogger.warn(`Shutdown signal received: ${signal}`);\n await server.close();\n\n for (const cleanupFn of cleanupFns) {\n await cleanupFn();\n }\n\n clearTimeout(forceExit);\n process.exit(exitCode);\n } catch (error) {\n clearTimeout(forceExit);\n const message = error instanceof Error ? (error.stack ?? error.message) : String(error);\n startupLogger.error(`Error while stopping server: ${message}`);\n process.exit(1);\n }\n }\n\n process.on('SIGTERM', () => {\n void shutdown('SIGTERM', 0);\n });\n\n process.on('SIGINT', () => {\n void shutdown('SIGINT', 0);\n });\n\n process.on('uncaughtException', (error) => {\n startupLogger.error(`Uncaught exception: ${error.stack ?? error.message}`);\n void shutdown('uncaughtException', 1);\n });\n\n process.on('unhandledRejection', (reason) => {\n startupLogger.error(`Unhandled rejection: ${String(reason)}`);\n void shutdown('unhandledRejection', 1);\n });\n\n await server.listen(port);\n\n startupLogger.info(`${SERVER.NAME} v${SERVER.VERSION} listening on http://${host}:${port}/mcp`);\n}\n\nvoid main().catch((error) => {\n const message = error instanceof Error ? (error.stack ?? error.message) : String(error);\n startupLogger.error(`Server failed to start: ${message}`);\n process.exit(1);\n});\n", "/**\n * Consolidated configuration\n * All environment variables, constants, and LLM config in one place\n */\n\nimport { Logger } from 'mcp-use';\n\nimport { VERSION, PACKAGE_NAME, PACKAGE_DESCRIPTION } from '../version.js';\n\n// ============================================================================\n// Safe Integer Parsing Helper\n// ============================================================================\n\n/**\n * Safely parse an integer from environment variable with bounds checking\n */\nfunction safeParseInt(\n value: string | undefined,\n defaultVal: number,\n min: number,\n max: number\n): number {\n const logger = Logger.get('config');\n\n if (!value) {\n return defaultVal;\n }\n\n const parsed = parseInt(value, 10);\n\n if (isNaN(parsed)) {\n logger.warn(`Invalid number \"${value}\", using default ${defaultVal}`);\n return defaultVal;\n }\n\n if (parsed < min) {\n logger.warn(`Value ${parsed} below minimum ${min}, clamping to ${min}`);\n return min;\n }\n\n if (parsed > max) {\n logger.warn(`Value ${parsed} above maximum ${max}, clamping to ${max}`);\n return max;\n }\n\n return parsed;\n}\n\n\n// ============================================================================\n// Environment Parsing\n// ============================================================================\n\ninterface EnvConfig {\n SCRAPER_API_KEY: string;\n SEARCH_API_KEY: string | undefined;\n REDDIT_CLIENT_ID: string | undefined;\n REDDIT_CLIENT_SECRET: string | undefined;\n JINA_API_KEY: string | undefined;\n KERNEL_API_KEY: string | undefined;\n KERNEL_PROJECT: string | undefined;\n}\n\nlet cachedEnv: EnvConfig | null = null;\n\nexport function parseEnv(): EnvConfig {\n if (cachedEnv) return cachedEnv;\n cachedEnv = {\n SCRAPER_API_KEY: process.env.SCRAPEDO_API_KEY || '',\n SEARCH_API_KEY: process.env.SERPER_API_KEY || undefined,\n REDDIT_CLIENT_ID: process.env.REDDIT_CLIENT_ID || undefined,\n REDDIT_CLIENT_SECRET: process.env.REDDIT_CLIENT_SECRET || undefined,\n JINA_API_KEY: process.env.JINA_API_KEY || undefined,\n KERNEL_API_KEY: process.env.KERNEL_API_KEY || undefined,\n KERNEL_PROJECT: process.env.KERNEL_PROJECT || undefined,\n };\n return cachedEnv;\n}\n\n// ============================================================================\n// MCP Server Configuration\n// ============================================================================\n\nexport const SERVER = {\n NAME: PACKAGE_NAME,\n VERSION: VERSION,\n DESCRIPTION: PACKAGE_DESCRIPTION,\n} as const;\n\n// ============================================================================\n// Capability Detection (which features are available based on ENV)\n// ============================================================================\n\nexport interface Capabilities {\n reddit: boolean; // REDDIT_CLIENT_ID + REDDIT_CLIENT_SECRET\n search: boolean; // SERPER_API_KEY or JINA_API_KEY\n serperSearch: boolean; // SERPER_API_KEY\n jina: boolean; // JINA_API_KEY (Reader auth + Search)\n scraping: boolean; // SCRAPEDO_API_KEY\n kernel: boolean; // KERNEL_API_KEY\n llmExtraction: boolean; // LLM_API_KEY + LLM_BASE_URL + LLM_MODEL\n}\n\nexport function getCapabilities(): Capabilities {\n const env = parseEnv();\n return {\n reddit: !!(env.REDDIT_CLIENT_ID && env.REDDIT_CLIENT_SECRET),\n search: !!(env.SEARCH_API_KEY || env.JINA_API_KEY),\n serperSearch: !!env.SEARCH_API_KEY,\n jina: !!env.JINA_API_KEY,\n scraping: !!env.SCRAPER_API_KEY,\n kernel: !!env.KERNEL_API_KEY,\n llmExtraction: getLLMConfigStatus().configured,\n };\n}\n\nexport function getMissingEnvMessage(capability: keyof Capabilities): string {\n const messages: Record<keyof Capabilities, string> = {\n reddit: '\u274C **Reddit scraping unavailable.** Set `REDDIT_CLIENT_ID` and `REDDIT_CLIENT_SECRET` to enable threaded Reddit post fetching in `raw-scrape-links` and `smart-scrape-links`.\\n\\n\uD83D\uDC49 Create a Reddit app at: https://www.reddit.com/prefs/apps (select \"script\" type)',\n search: '\u274C **Search unavailable.** Set `SERPER_API_KEY` or `JINA_API_KEY` to enable `raw-web-search` and `smart-web-search`.\\n\\n\uD83D\uDC49 Serper provides Google SERPs; Jina Search is used as a fallback/search-only provider.',\n serperSearch: '\u274C **Serper search unavailable.** Set `SERPER_API_KEY` to enable Google-backed primary search.',\n jina: '\u274C **Jina unavailable.** Set `JINA_API_KEY` to enable Jina Search and authenticated Jina Reader requests.',\n scraping: '\u26A0\uFE0F **Scrape.do proxy fallback unavailable.** Set `SCRAPEDO_API_KEY` to enable Jina Reader retries through Scrape.do proxy mode.\\n\\n\uD83D\uDC49 Sign up at: https://scrape.do (1,000 free credits)',\n kernel: '\u274C **Kernel browser rendering unavailable.** Set `KERNEL_API_KEY` to enable optional browser-render fallback for raw/smart scraping.',\n llmExtraction: '\u26A0\uFE0F **AI extraction disabled.** Set `LLM_API_KEY`, `LLM_BASE_URL`, and `LLM_MODEL` to enable `smart-web-search` and `smart-scrape-links`.\\n\\nUse the raw tools when you need markdown without LLM processing.',\n };\n return messages[capability];\n}\n\n// ============================================================================\n// Concurrency Limits\n// ============================================================================\n\nexport const CONCURRENCY = {\n SEARCH: safeParseInt(process.env.CONCURRENCY_SEARCH, 50, 1, 200),\n SCRAPER: safeParseInt(process.env.CONCURRENCY_SCRAPER, 50, 1, 200),\n JINA_READER: safeParseInt(process.env.CONCURRENCY_JINA_READER, 50, 1, 200),\n REDDIT: safeParseInt(process.env.CONCURRENCY_REDDIT, 50, 1, 200),\n LLM_EXTRACTION: safeParseInt(process.env.LLM_CONCURRENCY, 50, 1, 200),\n KERNEL: safeParseInt(process.env.CONCURRENCY_KERNEL, 3, 1, 20),\n} as const;\n\nexport const SCRAPER = {\n BATCH_SIZE: 30,\n EXTRACTION_PREFIX: 'Extract from document only \u2014 never hallucinate or add external knowledge.',\n EXTRACTION_SUFFIX: 'First line = content, not preamble. No confirmation messages.',\n} as const;\n\n// ============================================================================\n// Reddit Configuration\n// ============================================================================\n\nexport const REDDIT = {\n BATCH_SIZE: 10,\n MAX_WORDS_PER_POST: 50_000,\n MAX_WORDS_TOTAL: 500_000,\n MIN_POSTS: 1,\n MAX_POSTS: 50,\n RETRY_COUNT: 5,\n RETRY_DELAYS: [2000, 4000, 8000, 16000, 32000] as const,\n} as const;\n\n// ============================================================================\n// CTR Weights for URL Ranking (inspired from CTR research)\n// ============================================================================\n\nexport const CTR_WEIGHTS: Record<number, number> = {\n 1: 100.00,\n 2: 60.00,\n 3: 48.89,\n 4: 33.33,\n 5: 28.89,\n 6: 26.44,\n 7: 24.44,\n 8: 17.78,\n 9: 13.33,\n 10: 12.56,\n} as const;\n\n// ============================================================================\n// LLM Configuration\n//\n// Required vars (all must be set together when LLM is enabled):\n// LLM_API_KEY \u2014 API key for the OpenAI-compatible endpoint\n// LLM_BASE_URL \u2014 endpoint base URL (e.g. https://server.up.railway.app/v1)\n// LLM_MODEL \u2014 primary model (e.g. gpt-5.4-mini)\n//\n// Optional:\n// LLM_FALLBACK_MODEL \u2014 model to use after primary exhausts all retries (e.g. gpt-5.4)\n// LLM_CONCURRENCY \u2014 parallel LLM calls (default: 50)\n//\n// Reasoning effort is always 'low' \u2014 not configurable.\n// ============================================================================\n\ninterface LlmExtractionConfig {\n readonly MODEL: string;\n readonly FALLBACK_MODEL: string;\n readonly BASE_URL: string;\n readonly API_KEY: string;\n}\n\nexport type LLMRequiredEnvVar = 'LLM_API_KEY' | 'LLM_BASE_URL' | 'LLM_MODEL';\n\nexport interface LLMConfigStatus {\n readonly configured: boolean;\n readonly apiKeyPresent: boolean;\n readonly baseUrlPresent: boolean;\n readonly modelPresent: boolean;\n readonly missingVars: readonly LLMRequiredEnvVar[];\n readonly error: string | null;\n}\n\nlet cachedLlmConfigStatus: LLMConfigStatus | null = null;\n\nexport function getLLMConfigStatus(): LLMConfigStatus {\n if (cachedLlmConfigStatus) return cachedLlmConfigStatus;\n\n const apiKeyPresent = !!process.env.LLM_API_KEY?.trim();\n const baseUrlPresent = !!process.env.LLM_BASE_URL?.trim();\n const modelPresent = !!process.env.LLM_MODEL?.trim();\n const missingVars: LLMRequiredEnvVar[] = [];\n\n if (!apiKeyPresent) missingVars.push('LLM_API_KEY');\n if (!baseUrlPresent) missingVars.push('LLM_BASE_URL');\n if (!modelPresent) missingVars.push('LLM_MODEL');\n\n const configured = missingVars.length === 0;\n cachedLlmConfigStatus = {\n configured,\n apiKeyPresent,\n baseUrlPresent,\n modelPresent,\n missingVars,\n error: configured\n ? null\n : `LLM disabled: missing ${missingVars.join(', ')}`,\n };\n return cachedLlmConfigStatus;\n}\n\n/**\n * Test-only \u2014 drop every env-derived cache so a test can mutate process.env\n * and re-read fresh values. Covers the LLM config caches AND the parseEnv()\n * cache (which holds SCRAPEDO_API_KEY, SERPER_API_KEY, REDDIT_CLIENT_*,\n * JINA_API_KEY, KERNEL_API_KEY, KERNEL_PROJECT). Tests that scrub non-LLM env vars must also see a clean\n * env on the next parseEnv() call, otherwise getCapabilities() returns\n * stale flags.\n */\nexport function _resetLLMConfigStatusForTests(): void {\n cachedLlmConfigStatus = null;\n cachedLlmExtraction = null;\n cachedEnv = null;\n}\n\nlet cachedLlmExtraction: LlmExtractionConfig | null = null;\n\nfunction getLlmExtraction(): LlmExtractionConfig {\n if (cachedLlmExtraction) return cachedLlmExtraction;\n\n const apiKey = process.env.LLM_API_KEY?.trim() || '';\n const baseUrl = process.env.LLM_BASE_URL?.trim();\n const model = process.env.LLM_MODEL?.trim();\n const fallbackModel = process.env.LLM_FALLBACK_MODEL?.trim() || '';\n\n if (apiKey && !baseUrl) {\n throw new Error(\n 'LLM_BASE_URL is required when LLM_API_KEY is set. ' +\n 'Set LLM_BASE_URL to your OpenAI-compatible endpoint.',\n );\n }\n if (apiKey && !model) {\n throw new Error(\n 'LLM_MODEL is required when LLM_API_KEY is set.',\n );\n }\n\n cachedLlmExtraction = {\n API_KEY: apiKey,\n BASE_URL: baseUrl || '',\n MODEL: model || '',\n FALLBACK_MODEL: fallbackModel,\n };\n return cachedLlmExtraction;\n}\n\nexport const LLM_EXTRACTION: LlmExtractionConfig = new Proxy({} as LlmExtractionConfig, {\n get(_target, prop: string) {\n return getLlmExtraction()[prop as keyof LlmExtractionConfig];\n },\n});\n", "/**\n * Version Module - Single Source of Truth\n * \n * This module reads package metadata from package.json at runtime and keeps a\n * fallback copy for environments where package.json cannot be resolved.\n * \n * Usage:\n * import { VERSION, PACKAGE_NAME } from './version.js';\n */\n\nimport { createRequire } from 'module';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\n\n// Defaults used if package.json cannot be loaded at runtime.\nconst DEFAULT_PACKAGE_INFO = {\n version: '3.9.5',\n name: 'mcp-researchpowerpack-http',\n description: 'Research Powerpack MCP Server',\n} as const;\n\nlet packageJson: { version: string; name: string; description: string } = { ...DEFAULT_PACKAGE_INFO };\n\ntry {\n if (typeof import.meta.url === 'string' && import.meta.url.startsWith('file:')) {\n const _require = createRequire(import.meta.url);\n const _dirname = dirname(fileURLToPath(import.meta.url));\n try {\n packageJson = _require(join(_dirname, '..', 'package.json'));\n } catch {\n packageJson = _require(join(_dirname, '..', '..', 'package.json'));\n }\n }\n} catch {\n // Keep hardcoded defaults when package.json is unavailable\n}\n\n/**\n * Package version from package.json\n * This is the single source of truth for versioning\n */\nexport const VERSION: string = packageJson.version;\n\n/**\n * Package name from package.json\n */\nexport const PACKAGE_NAME: string = packageJson.name;\n\n/**\n * Package description from package.json\n */\nexport const PACKAGE_DESCRIPTION: string = packageJson.description;\n\n/**\n * Formatted version string for user agents and logging\n * Example: \"mcp-researchpowerpack-http/3.2.0\"\n */\nexport const USER_AGENT_VERSION: string = `${PACKAGE_NAME}/${VERSION}`;\n\n// VERSION_INFO removed - unused, individual exports sufficient\n", "/**\n * LLM Processor for content extraction\n * Uses any OpenAI-compatible endpoint. Reasoning effort is always 'low'.\n * Primary model exhausts its retries first; fallback model (LLM_FALLBACK_MODEL) then\n * gets up to FALLBACK_RETRY_COUNT additional attempts before the call fails.\n * NEVER throws \u2014 always returns a valid result.\n */\n\nimport OpenAI from 'openai';\nimport { LLM_EXTRACTION, getCapabilities } from '../config/index.js';\nimport { QUERY_REWRITE_PAIR_GUIDANCE_TEXT } from '../schemas/web-search.js';\nimport {\n classifyError,\n sleep,\n ErrorCode,\n withStallProtection,\n type StructuredError,\n} from '../utils/errors.js';\nimport { mcpLog } from '../utils/logger.js';\n\n/** Maximum input characters for LLM processing (~125k tokens, sized for the larger fallback model) */\nconst MAX_LLM_INPUT_CHARS = 500_000 as const;\n\n/**\n * Maximum input characters for the primary model when it has a smaller context window.\n * Used when an input would exceed the mini model's limits so the call goes straight to fallback\n * instead of burning retries on guaranteed context_length_exceeded errors.\n */\nconst MAX_PRIMARY_MODEL_INPUT_CHARS = 100_000 as const;\n\n/** LLM client timeout in milliseconds */\nconst LLM_CLIENT_TIMEOUT_MS = 600_000 as const;\n\n/** Jitter factor for exponential backoff */\nconst BACKOFF_JITTER_FACTOR = 0.3 as const;\n\n/** Stall detection timeout \u2014 abort if no response in this time */\nconst LLM_STALL_TIMEOUT_MS = 75_000 as const;\n\n/** Hard request deadline for LLM calls */\nconst LLM_REQUEST_DEADLINE_MS = 150_000 as const;\n\n// ============================================================================\n// LLM health tracking \u2014 surfaced via health://status so capability-aware\n// clients can branch on degraded mode without parsing per-call footers.\n// ============================================================================\n\ntype LLMHealthKind = 'planner' | 'extractor';\n\nexport interface LLMHealthSnapshot {\n readonly lastPlannerOk: boolean;\n readonly lastExtractorOk: boolean;\n readonly lastPlannerCheckedAt: string | null;\n readonly lastExtractorCheckedAt: string | null;\n readonly lastPlannerError: string | null;\n readonly lastExtractorError: string | null;\n readonly plannerConfigured: boolean;\n readonly extractorConfigured: boolean;\n /** Failures since the last success. Reset to 0 on `markLLMSuccess`. */\n readonly consecutivePlannerFailures: number;\n readonly consecutiveExtractorFailures: number;\n}\n\nconst llmHealth = {\n lastPlannerOk: false,\n lastExtractorOk: false,\n lastPlannerCheckedAt: null as string | null,\n lastExtractorCheckedAt: null as string | null,\n lastPlannerError: null as string | null,\n lastExtractorError: null as string | null,\n consecutivePlannerFailures: 0,\n consecutiveExtractorFailures: 0,\n};\n\nexport function markLLMSuccess(kind: LLMHealthKind): void {\n const ts = new Date().toISOString();\n if (kind === 'planner') {\n llmHealth.lastPlannerOk = true;\n llmHealth.lastPlannerCheckedAt = ts;\n llmHealth.lastPlannerError = null;\n llmHealth.consecutivePlannerFailures = 0;\n } else {\n llmHealth.lastExtractorOk = true;\n llmHealth.lastExtractorCheckedAt = ts;\n llmHealth.lastExtractorError = null;\n llmHealth.consecutiveExtractorFailures = 0;\n }\n}\n\nexport function markLLMFailure(kind: LLMHealthKind, err: unknown): void {\n const ts = new Date().toISOString();\n const message = err instanceof Error ? err.message : String(err ?? 'unknown error');\n if (kind === 'planner') {\n llmHealth.lastPlannerOk = false;\n llmHealth.lastPlannerCheckedAt = ts;\n llmHealth.lastPlannerError = message;\n llmHealth.consecutivePlannerFailures += 1;\n } else {\n llmHealth.lastExtractorOk = false;\n llmHealth.lastExtractorCheckedAt = ts;\n llmHealth.lastExtractorError = message;\n llmHealth.consecutiveExtractorFailures += 1;\n }\n}\n\nexport function getLLMHealth(): LLMHealthSnapshot {\n const cap = getCapabilities();\n return {\n lastPlannerOk: llmHealth.lastPlannerOk,\n lastExtractorOk: llmHealth.lastExtractorOk,\n lastPlannerCheckedAt: llmHealth.lastPlannerCheckedAt,\n lastExtractorCheckedAt: llmHealth.lastExtractorCheckedAt,\n lastPlannerError: llmHealth.lastPlannerError,\n lastExtractorError: llmHealth.lastExtractorError,\n // Static capability \u2014 based on env presence at boot. Runtime health (above)\n // tells whether the last attempt actually succeeded.\n plannerConfigured: cap.llmExtraction,\n extractorConfigured: cap.llmExtraction,\n consecutivePlannerFailures: llmHealth.consecutivePlannerFailures,\n consecutiveExtractorFailures: llmHealth.consecutiveExtractorFailures,\n };\n}\n\n/** Test-only \u2014 reset state between tests. Not exported from index. */\nexport function _resetLLMHealthForTests(): void {\n llmHealth.lastPlannerOk = false;\n llmHealth.lastExtractorOk = false;\n llmHealth.lastPlannerCheckedAt = null;\n llmHealth.lastExtractorCheckedAt = null;\n llmHealth.lastPlannerError = null;\n llmHealth.lastExtractorError = null;\n llmHealth.consecutivePlannerFailures = 0;\n llmHealth.consecutiveExtractorFailures = 0;\n}\n\ninterface ProcessingConfig {\n readonly enabled: boolean;\n readonly extract: string | undefined;\n readonly url?: string;\n}\n\ninterface LLMResult {\n readonly content: string;\n readonly processed: boolean;\n readonly error?: string;\n readonly errorDetails?: StructuredError;\n}\n\n// LLM-specific retry configuration\nconst LLM_RETRY_CONFIG = {\n maxRetries: 2,\n baseDelayMs: 1000,\n maxDelayMs: 5000,\n} as const;\n\n/** Number of additional attempts using the fallback model after primary exhausts. */\nconst FALLBACK_RETRY_COUNT = 3 as const;\n\n// OpenAI-compatible retryable error codes (using Set for type-safe lookup)\nconst RETRYABLE_LLM_ERROR_CODES = new Set([\n 'rate_limit_exceeded',\n 'server_error',\n 'timeout',\n 'service_unavailable',\n]);\n\n/** Type guard for errors with an HTTP status code */\nfunction hasStatus(error: unknown): error is { status: number } {\n return (\n typeof error === 'object' &&\n error !== null &&\n 'status' in error &&\n typeof (error as Record<string, unknown>).status === 'number'\n );\n}\n\nlet llmClient: OpenAI | null = null;\n\ninterface ChatCompletionTextResponse {\n readonly choices?: ReadonlyArray<{\n readonly message?: {\n readonly content?: string | null;\n } | null;\n } | null>;\n}\n\nexport interface OpenAITextGenerator {\n readonly chat: {\n readonly completions: {\n readonly create: (\n body: OpenAI.ChatCompletionCreateParamsNonStreaming,\n options: { readonly signal?: AbortSignal; readonly timeout: number },\n ) => Promise<ChatCompletionTextResponse>;\n };\n };\n}\n\ninterface LLMTextSuccess {\n readonly content: string;\n readonly model: string;\n}\n\ninterface LLMTextEmptyFailure {\n readonly content: null;\n readonly model: string;\n readonly error: string;\n readonly failureKind: 'empty';\n}\n\ninterface LLMTextProviderFailure {\n readonly content: null;\n readonly model: string;\n readonly error: string;\n readonly failureKind: 'provider';\n readonly errorCause: unknown;\n}\n\ntype LLMTextFailure = LLMTextEmptyFailure | LLMTextProviderFailure;\n\nexport type LLMTextResponse = LLMTextSuccess | LLMTextFailure;\n\nexport function createLLMProcessor(): OpenAI | null {\n if (!getCapabilities().llmExtraction) return null;\n\n if (!llmClient) {\n llmClient = new OpenAI({\n baseURL: LLM_EXTRACTION.BASE_URL,\n apiKey: LLM_EXTRACTION.API_KEY,\n timeout: LLM_CLIENT_TIMEOUT_MS,\n maxRetries: 0,\n defaultHeaders: { 'X-Title': 'mcp-research-powerpack' },\n });\n mcpLog('info', `LLM extraction configured (model: ${LLM_EXTRACTION.MODEL}, baseURL: ${LLM_EXTRACTION.BASE_URL})`, 'llm');\n }\n return llmClient;\n}\n\nfunction buildChatRequestBody(model: string, prompt: string): Record<string, unknown> {\n return {\n model,\n messages: [{ role: 'user', content: prompt }],\n reasoning_effort: 'low',\n };\n}\n\nfunction normalizeProviderError(err: unknown, message: string): unknown {\n if (typeof err === 'object' && err !== null) return err;\n return new Error(message);\n}\n\nfunction getProviderFailure(response: LLMTextResponse): unknown | null {\n if (response.content !== null || response.failureKind !== 'provider') return null;\n return response.errorCause;\n}\n\nfunction emptyLLMExtractionResult(content: string): LLMResult {\n return {\n content,\n processed: false,\n error: 'LLM returned empty response',\n errorDetails: {\n code: ErrorCode.INTERNAL_ERROR,\n message: 'LLM returned empty response',\n retryable: false,\n },\n };\n}\n\nexport async function requestText(\n processor: OpenAITextGenerator,\n prompt: string,\n operationLabel: string,\n signal?: AbortSignal,\n modelOverride?: string,\n): Promise<LLMTextResponse> {\n const model = modelOverride || LLM_EXTRACTION.MODEL;\n\n try {\n const response = await withStallProtection(\n (stallSignal) => processor.chat.completions.create(\n buildChatRequestBody(model, prompt) as unknown as OpenAI.ChatCompletionCreateParamsNonStreaming,\n {\n signal: signal ? AbortSignal.any([stallSignal, signal]) : stallSignal,\n timeout: LLM_REQUEST_DEADLINE_MS,\n },\n ),\n LLM_STALL_TIMEOUT_MS,\n 3,\n `${operationLabel} (${model})`,\n );\n\n const content = response.choices?.[0]?.message?.content?.trim();\n if (content) {\n return { content, model };\n }\n\n const err = `Empty response from model ${model}`;\n mcpLog('warning', `${operationLabel} returned empty content for model ${model}`, 'llm');\n return { content: null, model, error: err, failureKind: 'empty' };\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n mcpLog('warning', `${operationLabel} failed for model ${model}: ${message}`, 'llm');\n return {\n content: null,\n model,\n error: message,\n failureKind: 'provider',\n errorCause: normalizeProviderError(err, message),\n };\n }\n}\n\n/**\n * Single LLM call with automatic fallback model.\n * Tries the primary model once; if it fails and LLM_FALLBACK_MODEL is set,\n * retries up to FALLBACK_RETRY_COUNT times on the fallback model.\n * Used for single-shot calls (classify, brief, refine queries).\n */\nexport async function requestTextWithFallback(\n processor: OpenAITextGenerator,\n prompt: string,\n operationLabel: string,\n signal?: AbortSignal,\n): Promise<LLMTextResponse> {\n const primary = await requestText(processor, prompt, operationLabel, signal);\n if (primary.content !== null) return primary;\n\n const fallbackModel = LLM_EXTRACTION.FALLBACK_MODEL;\n if (!fallbackModel) return primary;\n\n mcpLog('warning', `Primary model failed, switching to fallback ${fallbackModel}`, 'llm');\n\n let lastFailure: LLMTextFailure = primary;\n for (let attempt = 0; attempt < FALLBACK_RETRY_COUNT; attempt++) {\n if (attempt > 0) {\n const delayMs = calculateLLMBackoff(attempt - 1);\n mcpLog('warning', `Fallback retry ${attempt}/${FALLBACK_RETRY_COUNT - 1} in ${delayMs}ms`, 'llm');\n try { await sleep(delayMs, signal); } catch { break; }\n }\n const result = await requestText(processor, prompt, `${operationLabel} [fallback]`, signal, fallbackModel);\n if (result.content !== null) return result;\n lastFailure = result;\n }\n\n return lastFailure;\n}\n\n/**\n * Check if an LLM error is retryable\n */\nfunction isRetryableLLMError(error: unknown): boolean {\n if (!error || typeof error !== 'object') return false;\n\n // Stall/timeout protection errors - always retry these\n const stallCode = (error as { code?: string })?.code;\n if (stallCode === 'ESTALLED' || stallCode === 'ETIMEDOUT') {\n return true;\n }\n\n // Check HTTP status codes\n if (hasStatus(error)) {\n if (error.status === 429 || error.status === 500 || error.status === 502 || error.status === 503 || error.status === 504) {\n return true;\n }\n }\n\n // Check error codes from the OpenAI-compatible endpoint\n const record = error as Record<string, unknown>;\n const code = typeof record.code === 'string' ? record.code : undefined;\n const nested =\n typeof record.error === 'object' && record.error !== null\n ? (record.error as Record<string, unknown>)\n : null;\n const errorCode =\n code ??\n (nested && typeof nested.code === 'string' ? nested.code : undefined) ??\n (nested && typeof nested.type === 'string' ? nested.type : undefined);\n if (errorCode && RETRYABLE_LLM_ERROR_CODES.has(errorCode)) {\n return true;\n }\n\n // Check message for common patterns\n const message = typeof record.message === 'string' ? record.message.toLowerCase() : '';\n if (\n message.includes('rate limit') ||\n message.includes('timeout') ||\n message.includes('timed out') ||\n message.includes('service unavailable') ||\n message.includes('server error') ||\n message.includes('connection') ||\n message.includes('econnreset')\n ) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Detect \"the prompt is too long for this model\" errors.\n * These are NOT retryable on the same model \u2014 we should skip remaining primary retries\n * and go straight to the fallback model (which has a larger context window).\n */\nfunction isContextWindowError(error: unknown): boolean {\n if (!error || typeof error !== 'object') return false;\n\n const record = error as Record<string, unknown>;\n const nested =\n typeof record.error === 'object' && record.error !== null\n ? (record.error as Record<string, unknown>)\n : null;\n\n const code = typeof record.code === 'string' ? record.code : undefined;\n const nestedCode = nested && typeof nested.code === 'string' ? nested.code : undefined;\n if (code === 'context_length_exceeded' || nestedCode === 'context_length_exceeded') {\n return true;\n }\n\n const messages: string[] = [];\n if (typeof record.message === 'string') messages.push(record.message);\n if (nested && typeof nested.message === 'string') messages.push(nested.message);\n const combined = messages.join(' ').toLowerCase();\n return (\n combined.includes('context length') ||\n combined.includes('context window') ||\n combined.includes('maximum context') ||\n combined.includes('maximum tokens') ||\n combined.includes('token limit') ||\n combined.includes('too many tokens') ||\n combined.includes('prompt is too long') ||\n combined.includes('reduce the length')\n );\n}\n\n/**\n * Calculate backoff delay with jitter for LLM retries\n */\nfunction calculateLLMBackoff(attempt: number): number {\n const exponentialDelay = LLM_RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt);\n const jitter = Math.random() * BACKOFF_JITTER_FACTOR * exponentialDelay;\n return Math.min(exponentialDelay + jitter, LLM_RETRY_CONFIG.maxDelayMs);\n}\n\n/**\n * Process content with LLM extraction\n * NEVER throws - always returns a valid LLMResult\n * Implements retry logic with exponential backoff for transient failures\n */\nexport async function processContentWithLLM(\n content: string,\n config: ProcessingConfig,\n processor?: OpenAITextGenerator | null,\n signal?: AbortSignal\n): Promise<LLMResult> {\n // Early returns for invalid/skip conditions\n if (!config.enabled) {\n return { content, processed: false };\n }\n\n if (!processor) {\n return {\n content,\n processed: false,\n error: 'LLM processor not available (LLM_API_KEY, LLM_BASE_URL, and LLM_MODEL must all be set)',\n errorDetails: {\n code: ErrorCode.AUTH_ERROR,\n message: 'LLM processor not available',\n retryable: false,\n },\n };\n }\n\n if (!content?.trim()) {\n return { content: content || '', processed: false, error: 'Empty content provided' };\n }\n\n // Truncate extremely long content to avoid blowing past even the fallback model's context.\n const truncatedContent = content.length > MAX_LLM_INPUT_CHARS\n ? content.substring(0, MAX_LLM_INPUT_CHARS) + '\\n\\n[Content truncated due to length]'\n : content;\n\n // If the prompt would exceed the primary (mini) model's smaller context window,\n // skip it entirely and go straight to the fallback model. Saves burning retries\n // on guaranteed context_length_exceeded errors.\n const skipPrimaryForSize =\n truncatedContent.length > MAX_PRIMARY_MODEL_INPUT_CHARS && !!LLM_EXTRACTION.FALLBACK_MODEL;\n\n // Sanitize URL before sending to LLM: drop query string and fragment\n // so signed URLs, session tokens, auth params, or tracking hashes never\n // land in a third-party LLM prompt. Keep origin + path for page-type classification.\n const safeUrl = (() => {\n if (!config.url) return undefined;\n try {\n const u = new URL(config.url);\n return `${u.origin}${u.pathname}`;\n } catch {\n return undefined;\n }\n })();\n const urlLine = safeUrl ? `PAGE URL: ${safeUrl}\\n\\n` : '';\n\n const prompt = config.extract\n ? `You are a factual extractor for a research agent. Extract ONLY the information that matches the instruction below. Do not summarize, interpret, or editorialize.\n\n${urlLine}EXTRACTION INSTRUCTION: ${config.extract}\n\nSTEP 1 \u2014 Classify this page. Look at the URL if present, plus structural cues (code blocks, table patterns, comment threads, marketing copy). Pick ONE:\n\\`docs | changelog | github-readme | github-thread | reddit | hackernews | forum | blog | marketing | announcement | qa | cve | paper | release-notes | other\\`\n\nSTEP 2 \u2014 Adjust emphasis by page type:\n- docs / changelog / github-readme / release-notes \u2192 API signatures, version numbers, flags, exact config keys, code blocks. Copy verbatim. Preserve tables as tables.\n- github-thread \u2192 weight MAINTAINER comments (label \"[maintainer]\") over drive-by commenters. Preserve stacktraces verbatim. Capture chronological resolution \u2014 what was decided and when. Link the accepted-fix commit/PR if referenced.\n- reddit / hackernews / forum \u2192 lived experience. Quote verbatim with attribution (\"u/foo wrote: \u2026\" or \"user <name>\"). Prioritize replies with stack details, specific failure stories, or replies that contradict the OP. Record overall sentiment distribution as one bullet if clear skew (\"~70% agree / ~20% dissent / rest off-topic\"). Drop context-free opinions (\"this sucks\") from Matches.\n- blog \u2192 prioritize concrete reproductions, code, measurements. If the author makes a claim without evidence, mark \"[unsourced claim]\".\n- marketing / announcement \u2192 pricing tiers, feature matrices verbatim, free-tier quotas, enterprise contact. Preserve tables as tables. Treat roadmap/future-tense claims skeptically \u2014 note them as \"[announced, not shipped]\" when framing is future-tense.\n- qa (stackoverflow) \u2192 accepted answer's code + high-voted disagreements. Always note the answer date \u2014 SO rots.\n- cve \u2192 CVSS vector verbatim, CWE, CPE ranges, affected versions, fix version, references. Each with its label.\n- paper \u2192 claim, method, dataset, benchmark numbers, comparison baseline. Preserve numeric deltas verbatim.\n\nSTEP 3 \u2014 Emit markdown with these sections, in order:\n\n## Source\n- URL: <verbatim if visible, else \"unknown\">\n- Page type: <the type you picked>\n- Page date: <verbatim if visible, else \"not visible\">\n- Author / maintainer (if identifiable): <verbatim>\n\n## Matches\nOne bullet per distinct piece of matching info:\n- **<short label>** \u2014 the information. Quote VERBATIM for: numbers, versions, dates, API names, prices, error messages, stacktraces, CVSS vectors, benchmark scores, command flags, proper nouns, and people's words. Backticks for code/identifiers. Preserve tables.\n\n## Not found\nEvery part of the extraction instruction this page did NOT answer. Be explicit. Example: \"Enterprise pricing contact \u2014 not present on this page.\"\n\n## Follow-up signals\nShort bullets \u2014 NEW angles this page surfaced that the agent should investigate. Include: new terms, unexpected vendor names, contradicting claims, referenced-but-unscraped URLs. Copy URLs VERBATIM from the source; if only anchor text is visible, write \"anchor: <text> (URL not in scraped content)\". Skip this section if nothing new surfaced. Do NOT invent.\n\n## Contradictions\n(Include this section only if the page contains internally contradictory claims.) Bullet each contradiction with both sides quoted verbatim.\n\n## Truncation\n(Include only if content appears cut mid-element.) \"Content cut mid-<table row / code block / comment / paragraph>; extraction may be incomplete for <section>.\"\n\nRULES:\n- Never paraphrase numbers, versions, code, or quoted text.\n- If an instruction item is not answered, it goes in \"Not found\" \u2014 do NOT invent an answer to please the caller.\n- Preserve code blocks, command examples, tables exactly.\n- Do NOT add commentary or recommendations outside \"Follow-up signals\".\n- Page language \u2260 English: quote verbatim in the original language AND provide a parenthetical gloss in English.\n- Page appears gated (login wall, paywall, JS-render-empty shell) or near-empty: BEFORE dismissing the page, look for ANY visible text \u2014 og:title, og:description, meta description, headline, author name, nav labels, teaser/preview sentences, visible comment snippets. If ANY such text exists, extract it as usual under \\`## Source\\` + \\`## Matches\\`, and list the blocked facets under \\`## Not found\\`. Prefix the first \\`## Matches\\` bullet with \\`**[partial \u2014 <reason>]**\\` so the caller knows the body is gated (reasons: \\`login-wall | paywall | JS-render-empty | truncated-before-relevant-section\\`). ONLY when there is NO visible extractable text at all (< 50 words AND no og:* AND no headline AND no preview), return exactly one line:\n \\`## Matches\\\\n_Page did not load: <reason>_\\`\n Valid reasons: \\`404 | login-wall | paywall | JS-render-empty | non-text-asset | truncated-before-relevant-section\\`.\n\nContent:\n${truncatedContent}`\n : `Clean the following page content: drop navigation, ads, cookie banners, footers, author bios, related-article lists. Preserve headings, paragraphs, code blocks, tables, and inline links as \\`[text](url)\\`. Do NOT summarize \u2014 preserve the full body.\n\n${urlLine}Content:\n${truncatedContent}`;\n\n let lastError: StructuredError | undefined;\n\n // Phase 1: primary model with up to LLM_RETRY_CONFIG.maxRetries retries.\n // Skip entirely when the input is too big for the primary's context window.\n if (skipPrimaryForSize) {\n mcpLog(\n 'info',\n `Input ${truncatedContent.length} chars exceeds primary model cap (${MAX_PRIMARY_MODEL_INPUT_CHARS}); routing directly to fallback`,\n 'llm',\n );\n } else {\n for (let attempt = 0; attempt <= LLM_RETRY_CONFIG.maxRetries; attempt++) {\n try {\n if (attempt === 0) {\n mcpLog('info', `Starting extraction with ${LLM_EXTRACTION.MODEL}`, 'llm');\n } else {\n mcpLog('warning', `Retry attempt ${attempt}/${LLM_RETRY_CONFIG.maxRetries}`, 'llm');\n }\n\n const response = await requestText(processor, prompt, 'LLM extraction', signal);\n\n if (response.content !== null) {\n mcpLog('info', `Successfully extracted ${response.content.length} characters`, 'llm');\n markLLMSuccess('extractor');\n return { content: response.content, processed: true };\n }\n\n const providerFailure = getProviderFailure(response);\n if (providerFailure) {\n throw providerFailure;\n }\n\n // Empty response \u2014 not retryable\n mcpLog('warning', 'Received empty response from LLM', 'llm');\n markLLMFailure('extractor', 'LLM returned empty response');\n return emptyLLMExtractionResult(content);\n\n } catch (err: unknown) {\n lastError = classifyError(err);\n const status = hasStatus(err) ? err.status : undefined;\n const code = typeof err === 'object' && err !== null && 'code' in err\n ? String((err as Record<string, unknown>).code)\n : undefined;\n const ctxErr = isContextWindowError(err);\n mcpLog('error', `Error (attempt ${attempt + 1}): ${lastError.message} [status=${status}, code=${code}, retryable=${isRetryableLLMError(err)}, context_window=${ctxErr}]`, 'llm');\n\n // Context window errors are not retryable on the same model \u2014 jump to fallback.\n if (ctxErr) {\n mcpLog('warning', 'Context window exceeded on primary \u2014 skipping remaining retries, routing to fallback', 'llm');\n break;\n }\n\n if (isRetryableLLMError(err) && attempt < LLM_RETRY_CONFIG.maxRetries) {\n const delayMs = calculateLLMBackoff(attempt);\n mcpLog('warning', `Retrying in ${delayMs}ms...`, 'llm');\n try { await sleep(delayMs, signal); } catch { break; }\n continue;\n }\n break;\n }\n }\n }\n\n // Phase 2: fallback model \u2014 FALLBACK_RETRY_COUNT attempts before giving up\n const fallbackModel = LLM_EXTRACTION.FALLBACK_MODEL;\n if (fallbackModel) {\n mcpLog('warning', `Primary exhausted, switching to fallback ${fallbackModel}`, 'llm');\n for (let attempt = 0; attempt < FALLBACK_RETRY_COUNT; attempt++) {\n if (attempt > 0) {\n const delayMs = calculateLLMBackoff(attempt - 1);\n mcpLog('warning', `Fallback retry ${attempt}/${FALLBACK_RETRY_COUNT - 1} in ${delayMs}ms`, 'llm');\n try { await sleep(delayMs, signal); } catch { break; }\n }\n try {\n const response = await requestText(processor, prompt, 'LLM extraction [fallback]', signal, fallbackModel);\n if (response.content !== null) {\n mcpLog('info', `Fallback extracted ${response.content.length} characters`, 'llm');\n markLLMSuccess('extractor');\n return { content: response.content, processed: true };\n }\n\n const providerFailure = getProviderFailure(response);\n if (providerFailure) {\n throw providerFailure;\n }\n\n mcpLog('warning', 'Fallback returned empty response', 'llm');\n markLLMFailure('extractor', 'LLM returned empty response');\n return emptyLLMExtractionResult(content);\n } catch (err: unknown) {\n lastError = classifyError(err);\n mcpLog('error', `Fallback error (attempt ${attempt + 1}): ${lastError.message}`, 'llm');\n // Stop burning attempts on errors that won't change with another try.\n // Context-window errors are deterministic on the same model (the prompt\n // is identical every retry) \u2014 see isContextWindowError docstring and\n // the matching primary-loop short-circuit at line 608. Only keep\n // retrying on transient failures (rate-limit / 5xx / stall).\n if (isContextWindowError(err) || !isRetryableLLMError(err)) break;\n }\n }\n }\n\n const errorMessage = lastError?.message || 'Unknown LLM error';\n mcpLog('error', `All attempts failed: ${errorMessage}. Returning original content.`, 'llm');\n markLLMFailure('extractor', errorMessage);\n\n return {\n content,\n processed: false,\n error: `LLM extraction failed: ${errorMessage}`,\n errorDetails: lastError || {\n code: ErrorCode.UNKNOWN_ERROR,\n message: errorMessage,\n retryable: false,\n },\n };\n}\n\n// ============================================================================\n// Web-Search Result Classification\n// ============================================================================\n\n/** Maximum URLs to send to the LLM for classification */\nconst MAX_CLASSIFICATION_URLS = 50 as const;\n\n/** Classification tiers */\ntype ClassificationTier = 'HIGHLY_RELEVANT' | 'MAYBE_RELEVANT' | 'OTHER';\n\nexport interface ClassificationEntry {\n readonly rank: number;\n readonly tier: ClassificationTier;\n readonly source_type?: string;\n readonly reason?: string;\n}\n\nexport interface ClassificationGap {\n readonly id: number;\n readonly description: string;\n}\n\nexport interface ClassificationResult {\n readonly title: string;\n readonly synthesis: string;\n readonly results: ClassificationEntry[];\n readonly refine_queries?: Array<{\n readonly query: string;\n readonly rationale: string;\n readonly gap_id?: number;\n }>;\n readonly confidence?: 'high' | 'medium' | 'low';\n readonly confidence_reason?: string;\n readonly gaps?: ClassificationGap[];\n}\n\nexport interface RefineQuerySuggestion {\n readonly query: string;\n readonly rationale: string;\n readonly gap_id?: number;\n readonly gap_description?: string;\n}\n\n/**\n * Classify smart search results by relevance to an objective using the LLM.\n * Sends only titles, snippets, and domain names \u2014 does NOT fetch URLs.\n * Returns null on failure (caller should fall back to raw output).\n */\nexport async function classifySearchResults(\n rankedUrls: ReadonlyArray<{\n readonly rank: number;\n readonly url: string;\n readonly title: string;\n readonly snippet: string;\n readonly frequency: number;\n readonly queries: string[];\n }>,\n objective: string,\n totalQueries: number,\n processor: OpenAI,\n previousQueries: readonly string[] = [],\n): Promise<{ result: ClassificationResult | null; error?: string }> {\n const urlsToClassify = rankedUrls.slice(0, MAX_CLASSIFICATION_URLS);\n\n // Descending static weights fed to the LLM. Higher-ranked URLs get a bigger\n // weight so the classifier biases HIGHLY_RELEVANT toward them. The weights\n // here are a shown-to-LLM summary, not the internal CTR ranking (which\n // still runs in url-aggregator.ts). Rank 11+ all bucket to w=1.\n const STATIC_WEIGHTS = [30, 20, 15, 10, 8, 6, 5, 4, 3, 2] as const;\n const weightForRank = (rank: number): number => STATIC_WEIGHTS[rank - 1] ?? 1;\n\n // Build compressed result list \u2014 weight + title + domain + snippet (truncated)\n const lines: string[] = [];\n for (const url of urlsToClassify) {\n let domain: string;\n try {\n domain = new URL(url.url).hostname.replace(/^www\\./, '');\n } catch {\n domain = url.url;\n }\n const snippet = url.snippet.length > 120\n ? url.snippet.slice(0, 117) + '...'\n : url.snippet;\n lines.push(`[${url.rank}] w=${weightForRank(url.rank)} ${url.title} \u2014 ${domain} \u2014 ${snippet}`);\n }\n\n const prevQueriesBlock = previousQueries.length > 0\n ? previousQueries.map((q) => `- ${q}`).join('\\n')\n : '- (none provided)';\n const today = new Date().toISOString().slice(0, 10);\n\n const prompt = `You are the relevance filter for a research agent. Classify each search result below against the objective and produce a structured analysis.\n\nOBJECTIVE: ${objective}\nTODAY: ${today}\n\nPREVIOUS QUERIES (already run \u2014 do NOT paraphrase in refine_queries):\n${prevQueriesBlock}\n\nReturn ONLY a JSON object (no markdown, no code fences):\n\n{\n \"title\": \"2\u20138 word label for this RESULT CLUSTER (not the objective)\",\n \"synthesis\": \"3\u20135 sentences grounded in the results. Every non-trivial claim cites a rank in [brackets], e.g. '[3] documents the flag; [7][12] report it is broken on macOS.' A synthesis with zero citations is invalid.\",\n \"confidence\": \"high | medium | low\",\n \"confidence_reason\": \"one sentence \u2014 why\",\n \"gaps\": [\n { \"id\": 0, \"description\": \"specific, actionable thing the current results do NOT answer \u2014 not 'more info needed'\" }\n ],\n \"refine_queries\": [\n { \"query\": \"concrete next search\", \"gap_id\": 0, \"rationale\": \"\u226412 words\" }\n ],\n \"results\": [\n {\n \"rank\": 1,\n \"tier\": \"HIGHLY_RELEVANT | MAYBE_RELEVANT | OTHER\",\n \"source_type\": \"vendor_doc | github | reddit | hackernews | blog | news | marketing | stackoverflow | cve | paper | release_notes | aggregator | other\",\n \"reason\": \"\u226412 words citing the snippet cue that drove the tier\"\n }\n ]\n}\n\nWEIGHT SCHEME: each row is prefixed with a weight (w=N). Higher weight means the URL ranked better across input queries \u2014 prefer HIGHLY_RELEVANT for high-weight rows when content matches the objective. Weight alone never justifies HIGHLY_RELEVANT; snippet cues still drive the decision.\n\nSOURCE-OF-TRUTH RUBRIC (the \"primary source\" is goal-dependent \u2014 infer goal type from the objective):\n- spec / API / config questions \u2192 vendor_doc, github (README, RFC), release_notes are primary\n- bug / failure-mode questions \u2192 github (issue/PR), stackoverflow are primary\n- migration / sentiment / lived-experience \u2192 reddit, hackernews, blog are primary; docs are secondary\n- pricing / commercial \u2192 marketing (the vendor's own pricing page IS the primary source, but treat feature lists skeptically)\n- security / CVE \u2192 cve databases, distro security trackers (nvd.nist.gov, security-tracker.debian.org, ubuntu.com/security) are primary\n- synthesis / open-ended \u2192 blend; no single type is primary\n- product launch \u2192 vendor_doc + news + marketing for the launch itself; blogs + reddit for independent verification\n\nFRESHNESS: proportional to topic velocity. For a week-old release, demote anything older than 30 days. For general tech questions, demote older than 18 months. For stable protocols (HTTP, TCP, POSIX), don't demote by age.\n\nCONFIDENCE:\n- high = \u22653 HIGHLY_RELEVANT results from INDEPENDENT domains agree on the core answer\n- medium = \u22652 HIGHLY_RELEVANT exist but disagree or share a domain; OR a single authoritative primary source answers it\n- low = otherwise; snippet-only judgments cap at medium\n\nREFINE QUERIES \u2014 each MUST differ from every previousQuery by:\n- a new operator (site:, quotes, verbatim version number), OR\n- a domain-specific noun ABSENT from every prior query\nAdding a year alone does NOT count as differentiation.\nEach refine_query MUST reference a specific gap_id from the gaps array above.\nProduce 4\u20138 refine_queries total. Cover: (a) a primary-source probe, (b) a temporal sharpener, (c) a failure-mode or comparison probe, (d) at least one new-term probe seeded by a specific result's snippet.\n\nRULES:\n- Classify ALL ${urlsToClassify.length} results. Do not skip or collapse any.\n- Use only the three tier values.\n- Judge from title + domain + snippet only. Do NOT invent facts not present in the snippet.\n- If ALL results are OTHER: synthesis = \"\", confidence = \"low\", and \\`gaps\\` must explicitly state why the current queries missed the target.\n- Casing: tier = UPPERCASE_WITH_UNDERSCORES, confidence = lowercase.\n\nSEARCH RESULTS (${urlsToClassify.length} URLs from ${totalQueries} queries):\n${lines.join('\\n')}`;\n\n try {\n mcpLog('info', `Classifying ${urlsToClassify.length} URLs against objective`, 'llm');\n\n const response = await requestTextWithFallback(\n processor,\n prompt,\n 'Search classification',\n );\n\n if (response.content === null) {\n const errMsg = response.error ?? 'LLM returned empty classification response';\n markLLMFailure('planner', errMsg);\n return { result: null, error: errMsg };\n }\n\n // Strip markdown code fences if present\n const cleaned = response.content.replace(/^```(?:json)?\\s*\\n?/m, '').replace(/\\n?```\\s*$/m, '').trim();\n const parsed = JSON.parse(cleaned) as ClassificationResult;\n\n // Validate the response shape.\n // Note: synthesis is typed not truthy \u2014 the prompt explicitly instructs an empty string\n // for the all-OTHER case, and we must not reject that.\n if (!parsed.title || typeof parsed.synthesis !== 'string' || !Array.isArray(parsed.results)) {\n const errMsg = 'LLM response missing required fields (title, synthesis, results)';\n markLLMFailure('planner', errMsg);\n return { result: null, error: errMsg };\n }\n\n mcpLog('info', `Classification complete: ${parsed.results.filter(r => r.tier === 'HIGHLY_RELEVANT').length} highly relevant`, 'llm');\n markLLMSuccess('planner');\n return { result: parsed };\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n mcpLog('error', `Classification failed: ${message}`, 'llm');\n markLLMFailure('planner', message);\n return { result: null, error: `Classification failed: ${message}` };\n }\n}\n\nexport async function suggestRefineQueriesForRawMode(\n rankedUrls: ReadonlyArray<{\n readonly rank: number;\n readonly url: string;\n readonly title: string;\n }>,\n objective: string,\n originalQueries: readonly string[],\n processor: OpenAI,\n): Promise<{ result: RefineQuerySuggestion[]; error?: string }> {\n const urlsToSummarize = rankedUrls.slice(0, 12);\n const lines = urlsToSummarize.map((url) => {\n let domain: string;\n try {\n domain = new URL(url.url).hostname.replace(/^www\\./, '');\n } catch {\n domain = url.url;\n }\n return `[${url.rank}] ${url.title} \u2014 ${domain}`;\n });\n\n const prompt = `You are generating follow-up search queries for an agent using raw search results.\n\nReturn ONLY a JSON object (no markdown, no code fences):\n{\n \"refine_queries\": [\n { \"query\": \"next search query\", \"gap_description\": \"what gap this closes\", \"rationale\": \"\u226412 words on why\" }\n ]\n}\n\nOBJECTIVE: ${objective}\n\nPREVIOUS QUERIES (already run \u2014 do NOT paraphrase):\n${originalQueries.map((query) => `- ${query}`).join('\\n')}\n\nTOP RESULT TITLES (to seed new-term probes):\n${lines.join('\\n')}\n\nRULES:\n- Produce 4\u20136 diverse follow-ups. Cover: (a) a primary-source probe (site:, RFC, vendor docs); (b) a temporal sharpener (changelog, version number); (c) a failure-mode or comparison probe; (d) at least one new-term probe seeded by a specific result title.\n- Each query MUST differ from every previousQuery by either a new operator (site:, quotes, a verbatim version number) OR a domain-specific noun absent from every prior query. Adding a year alone does NOT count.\n- Each refine_query MUST include a \\`gap_description\\` naming what the current results don't answer.\n- Do not include URLs.\n- Keep rationales \u226412 words.`;\n\n try {\n const response = await requestTextWithFallback(\n processor,\n prompt,\n 'Raw-mode refine query generation',\n );\n\n if (response.content === null) {\n const errMsg = response.error ?? 'LLM returned empty raw-mode refine query response';\n markLLMFailure('planner', errMsg);\n return { result: [], error: errMsg };\n }\n\n const cleaned = response.content.replace(/^```(?:json)?\\s*\\n?/m, '').replace(/\\n?```\\s*$/m, '').trim();\n const parsed = JSON.parse(cleaned) as { refine_queries?: RefineQuerySuggestion[] };\n\n markLLMSuccess('planner');\n return { result: Array.isArray(parsed.refine_queries) ? parsed.refine_queries : [] };\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n mcpLog('error', `Raw-mode refine query generation failed: ${message}`, 'llm');\n markLLMFailure('planner', message);\n return { result: [], error: message };\n }\n}\n\n// ============================================================================\n// Research Brief \u2014 goal-aware orientation (called by start-research)\n// ============================================================================\n\nexport type PrimaryBranch = 'reddit' | 'web' | 'both';\n\nexport interface ResearchBriefStep {\n readonly tool: 'raw-web-search' | 'smart-web-search' | 'raw-scrape-links' | 'smart-scrape-links';\n readonly reason: string;\n}\n\nexport interface ResearchBrief {\n readonly goal_class: string;\n readonly goal_class_reason: string;\n readonly primary_branch: PrimaryBranch;\n readonly primary_branch_reason: string;\n readonly freshness_window: string;\n readonly first_call_sequence: readonly ResearchBriefStep[];\n readonly keyword_seeds: readonly string[];\n readonly iteration_hints: readonly string[];\n readonly gaps_to_watch: readonly string[];\n readonly stop_criteria: readonly string[];\n}\n\nconst VALID_GOAL_CLASSES = new Set([\n 'spec', 'bug', 'migration', 'sentiment', 'pricing', 'security',\n 'synthesis', 'product_launch', 'other',\n]);\n\nconst VALID_FRESHNESS = new Set(['days', 'weeks', 'months', 'years']);\nconst VALID_BRANCHES = new Set<PrimaryBranch>(['reddit', 'web', 'both']);\nconst VALID_STEP_TOOLS = new Set(['raw-web-search', 'smart-web-search', 'raw-scrape-links', 'smart-scrape-links']);\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((v) => typeof v === 'string');\n}\n\nfunction isStepArray(value: unknown): value is ResearchBriefStep[] {\n return Array.isArray(value) && value.every((s) => {\n if (typeof s !== 'object' || s === null) return false;\n const tool = (s as Record<string, unknown>).tool;\n const reason = (s as Record<string, unknown>).reason;\n return typeof tool === 'string'\n && VALID_STEP_TOOLS.has(tool)\n && typeof reason === 'string'\n && reason.trim().length > 0;\n });\n}\n\nexport function parseResearchBrief(raw: string): ResearchBrief | null {\n try {\n const cleaned = raw.replace(/^```(?:json)?\\s*\\n?/m, '').replace(/\\n?```\\s*$/m, '').trim();\n const parsed = JSON.parse(cleaned) as Record<string, unknown>;\n\n const goal_class = typeof parsed.goal_class === 'string' ? parsed.goal_class : null;\n if (!goal_class || !VALID_GOAL_CLASSES.has(goal_class)) return null;\n\n const freshness_window = typeof parsed.freshness_window === 'string' ? parsed.freshness_window : null;\n if (!freshness_window || !VALID_FRESHNESS.has(freshness_window)) return null;\n\n const primary_branch = parsed.primary_branch;\n if (typeof primary_branch !== 'string' || !VALID_BRANCHES.has(primary_branch as PrimaryBranch)) return null;\n\n if (!isStepArray(parsed.first_call_sequence) || parsed.first_call_sequence.length === 0) return null;\n if (!isStringArray(parsed.keyword_seeds) || parsed.keyword_seeds.length === 0) return null;\n\n return {\n goal_class,\n goal_class_reason: typeof parsed.goal_class_reason === 'string' ? parsed.goal_class_reason : '',\n primary_branch: primary_branch as PrimaryBranch,\n primary_branch_reason: typeof parsed.primary_branch_reason === 'string' ? parsed.primary_branch_reason : '',\n freshness_window,\n first_call_sequence: parsed.first_call_sequence,\n keyword_seeds: parsed.keyword_seeds.filter((s) => s.trim().length > 0),\n iteration_hints: isStringArray(parsed.iteration_hints) ? parsed.iteration_hints : [],\n gaps_to_watch: isStringArray(parsed.gaps_to_watch) ? parsed.gaps_to_watch : [],\n stop_criteria: isStringArray(parsed.stop_criteria) ? parsed.stop_criteria : [],\n };\n } catch {\n return null;\n }\n}\n\nexport async function generateResearchBrief(\n goal: string,\n processor: OpenAI,\n signal?: AbortSignal,\n): Promise<ResearchBrief | null> {\n const today = new Date().toISOString().slice(0, 10);\n\n const prompt = `You are a research planner. An agent is about to run a multi-pass research loop on the goal below using 5 tools:\n\n - start-research: orientation and this brief\n - raw-web-search: raw search fan-out, keywords only, up to 50 keywords per call, no LLM; best for breadth, audit trails, and candidate URL capture\n - smart-web-search: search fan-out + required LLM prioritization/classification over titles/snippets, scope: web|reddit|both, up to 50 keywords per call; best for triage after a strong diverse keyword set\n - raw-scrape-links: fetch URLs as full markdown, urls only, no LLM; Reddit permalinks return threaded comments; best for complete context and ambiguous sources\n - smart-scrape-links: fetch URLs then required LLM extraction over page bodies; Reddit permalinks return threaded comments before extraction; best for focused evidence extraction once facets are known\n\nProduce a tailored JSON brief.\n\nGOAL: ${goal}\nTODAY: ${today}\n\nReturn ONLY a JSON object (no markdown, no code fences):\n\n{\n \"goal_class\": \"spec | bug | migration | sentiment | pricing | security | synthesis | product_launch | other\",\n \"goal_class_reason\": \"one sentence \u2014 why this class\",\n \"primary_branch\": \"reddit | web | both\",\n \"primary_branch_reason\": \"one sentence \u2014 why this branch leads\",\n \"freshness_window\": \"days | weeks | months | years\",\n \"first_call_sequence\": [\n { \"tool\": \"raw-web-search | smart-web-search | raw-scrape-links | smart-scrape-links\", \"reason\": \"what this call establishes for the agent\" }\n ],\n \"keyword_seeds\": [\"25\u201350 concrete search keywords \u2014 flat list, to be fired in the first search call as keywords\"],\n \"iteration_hints\": [\"2\u20135 pointers on which harvested terms / follow-up signals to watch for after pass 1\"],\n \"gaps_to_watch\": [\"2\u20135 concrete questions the agent MUST verify or the answer is incomplete\"],\n \"stop_criteria\": [\"2\u20134 checkable conditions \u2014 all must hold before the agent declares done\"]\n}\n\nRULES:\n\nprimary_branch:\n- \"reddit\" \u2192 sentiment / migration / lived-experience / community-consensus goals. Usually leads with raw-web-search using Reddit-focused keywords, then raw-scrape-links on post permalinks to preserve full comments.\n- \"web\" \u2192 spec / bug / pricing / CVE / API / primary-source goals. Usually leads with raw-web-search for maximum candidate breadth OR smart-web-search scope:\"web\" when the initial keyword set is already diverse and needs prioritization.\n- \"both\" \u2192 opinion-heavy AND needs official sources (e.g. product launch + practitioner reception).\n\nfirst_call_sequence:\n- 1\u20133 steps.\n- Use raw-web-search when the first need is recall: many distinct source classes, exact candidate URLs, Reddit permalink discovery, or cheap follow-up passes.\n- Use smart-web-search when the first need is prioritization: the agent has 10\u201350 distinct keyword probes and needs HIGHLY/MAYBE tiers, gaps, and refine queries. Smart search reads snippets only; never plan it as final evidence.\n- Use raw-scrape-links when complete page/thread context is valuable, the extraction shape is unclear, or Reddit comments are the source of truth.\n- Use smart-scrape-links when the extraction shape is known (facets separated by |) and the agent needs compact evidence from page bodies; this is usually the highest-value smart tool for answer construction.\n- reddit-first: step 1 = raw-web-search with Reddit permalink probes; step 2 = raw-scrape-links on best post permalinks for full comments. Add smart-web-search only when there are many candidate posts to triage.\n- web-first: step 1 = raw-web-search for broad URL capture OR smart-web-search scope:\"web\" for prioritizing a diverse keyword fan-out; step 2 = smart-scrape-links on selected URLs when extraction facets are known, otherwise raw-scrape-links first.\n- both: step 1 = parallel search calls split by source need; step 2 = raw-scrape-links for full evidence and smart-scrape-links for final extraction.\n\nkeyword_seeds:\n- 25\u201350 total. Narrow bug \u2192 fewer. Open synthesis \u2192 more.\n- Write Google retrieval probes, not topic labels.\n- For each broad idea, first do a bad \u2192 better rewrite in your head: replace a vague phrase with a query that names the evidence source class, discriminating anchor terms, and one useful operator when possible.\n- ${QUERY_REWRITE_PAIR_GUIDANCE_TEXT}\n- Use operators where helpful (site:, quotes, verbatim version numbers, exact error text, package names, release/version strings).\n- DIVERSE facets \u2014 same noun-phrase cannot repeat across seeds with adjectives-only variation.\n- Optimize keyword_seeds for distinct coverage first. Smart-web-search can prioritize a broad result set, but it cannot compensate for a narrow or repetitive keyword set.\n- Do NOT invent vendor names you are uncertain exist.\n- For \\`site:<domain>\\` filters, ONLY use domains you are highly confident are real. Safe choices: \\`github.com\\`, \\`stackoverflow.com\\`, \\`reddit.com\\`, \\`news.ycombinator.com\\`, \\`arxiv.org\\`, \\`nvd.nist.gov\\`, \\`pypi.org\\`, \\`npmjs.com\\`, plus any canonical homepage/docs domain explicitly spelled out in the goal itself (e.g. goal names \"Cursor\" \u2192 \\`cursor.com\\`/\\`docs.cursor.com\\` is acceptable). If you don't know the product's real docs domain, leave the query open (no \\`site:\\`) instead of guessing.\n\nfreshness_window:\n- If the goal mentions a recent release / date / version, use \"days\" or \"weeks\".\n- Stable protocols / APIs \u2192 \"months\" or \"years\".`;\n\n try {\n const response = await requestTextWithFallback(\n processor,\n prompt,\n 'Research brief generation',\n signal,\n );\n\n if (response.content === null) {\n mcpLog('warning', `Research brief generation returned no content: ${response.error ?? 'unknown'}`, 'llm');\n markLLMFailure('planner', response.error ?? 'empty response');\n return null;\n }\n\n const brief = parseResearchBrief(response.content);\n if (!brief) {\n mcpLog('warning', 'Research brief JSON parse or shape validation failed', 'llm');\n markLLMFailure('planner', 'brief parse/validation failed');\n return null;\n }\n\n markLLMSuccess('planner');\n return brief;\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n mcpLog('warning', `Research brief generation failed: ${message}`, 'llm');\n markLLMFailure('planner', message);\n return null;\n }\n}\n\nexport function renderResearchBrief(brief: ResearchBrief): string {\n const lines: string[] = [];\n\n lines.push('## Your research brief (goal-tailored)');\n lines.push('');\n lines.push(`**Goal class**: \\`${brief.goal_class}\\` \u2014 ${brief.goal_class_reason}`);\n lines.push(`**Primary branch**: \\`${brief.primary_branch}\\` \u2014 ${brief.primary_branch_reason}`);\n lines.push(`**Freshness**: \\`${brief.freshness_window}\\``);\n lines.push('');\n\n if (brief.first_call_sequence.length > 0) {\n lines.push('### First-call sequence');\n brief.first_call_sequence.forEach((step, i) => {\n lines.push(`${i + 1}. \\`${step.tool}\\` \u2014 ${step.reason}`);\n });\n lines.push('');\n }\n\n if (brief.keyword_seeds.length > 0) {\n lines.push(`### Keyword seeds (${brief.keyword_seeds.length}) \u2014 fire these in your first search call as a flat \\`keywords\\` array`);\n for (const seed of brief.keyword_seeds) {\n lines.push(`- ${seed}`);\n }\n lines.push('');\n }\n\n if (brief.iteration_hints.length > 0) {\n lines.push('### Iteration hints (harvest new terms from scrape extracts\\' `## Follow-up signals`)');\n for (const hint of brief.iteration_hints) lines.push(`- ${hint}`);\n lines.push('');\n }\n\n if (brief.gaps_to_watch.length > 0) {\n lines.push('### Gaps to watch');\n for (const gap of brief.gaps_to_watch) lines.push(`- ${gap}`);\n lines.push('');\n }\n\n if (brief.stop_criteria.length > 0) {\n lines.push('### Stop criteria');\n for (const c of brief.stop_criteria) lines.push(`- ${c}`);\n lines.push('');\n }\n\n lines.push('---');\n lines.push('');\n lines.push('Fire `first_call_sequence` now. After each smart scrape, harvest new terms from `## Follow-up signals`; after each raw scrape, inspect the full markdown/comments and seed the next search round. Stop when every gap is closed.');\n\n return lines.join('\\n');\n}\n", "import { z } from 'zod';\n\nexport const QUERY_REWRITE_PAIR_EXAMPLES = [\n 'Bad: `<feature> support` \u2192 Better: `site:<official-docs-domain> \"<feature>\" \"<platform-or-version>\"`',\n 'Bad: `<product> pricing` \u2192 Better: `site:<vendor-domain> \"<product>\" pricing \"enterprise\" OR \"free tier\"`',\n 'Bad: `<library> bug fix` \u2192 Better: `\"<exact error text>\" \"<library-or-package>\" \"<version>\" site:github.com`',\n 'Bad: `<tool> reviews` \u2192 Better: `site:reddit.com/r/<community>/comments \"<tool>\" \"migration\" OR \"regression\"`',\n] as const;\n\nexport const QUERY_REWRITE_PAIR_GUIDANCE = [\n 'Write Google retrieval probes, not topic labels.',\n 'For each broad idea, rewrite it into a query that names the evidence source class, discriminating anchor terms, and one useful operator when possible.',\n 'Use rewrite-pair thinking before searching:',\n ...QUERY_REWRITE_PAIR_EXAMPLES,\n 'Do not repeat the same noun phrase with adjectives changed; fan out by source type and evidence need.',\n] as const;\n\nexport const QUERY_REWRITE_PAIR_GUIDANCE_TEXT = QUERY_REWRITE_PAIR_GUIDANCE.join(' ');\n\nconst keywordSchema = z\n .string()\n .min(1, { message: 'search: Keyword cannot be empty' })\n .describe(\n `A single search keyword/query. Each item runs as a separate parallel search. ${QUERY_REWRITE_PAIR_GUIDANCE_TEXT}`,\n );\n\nconst keywordsSchema = z\n .array(keywordSchema)\n .min(1, { message: 'search: At least 1 keyword required' })\n .max(50, { message: 'search: At most 50 keywords allowed per call' })\n .describe(\n `Search keywords to run in parallel. Serper is primary when configured; Jina Search is fallback when Serper is missing, fails, or yields empty query results. ${QUERY_REWRITE_PAIR_GUIDANCE_TEXT} Think of keywords as retrieval probes, not topic labels. Pack distinct facets in one call: official docs, implementation, failures, comparisons, sentiment, changelog, CVE, pricing, or other source classes.`,\n );\n\nexport const rawWebSearchParamsSchema = z.object({\n keywords: keywordsSchema,\n extract: z.never().optional(),\n scope: z.never().optional(),\n verbose: z.never().optional(),\n}).strict();\n\nexport const smartWebSearchParamsSchema = z.object({\n keywords: keywordsSchema,\n extract: z\n .string()\n .min(1, { message: 'smart-web-search: extract cannot be empty' })\n .describe(\n 'Semantic instruction for the relevance classifier \u2014 what \"relevant\" means for THIS goal. This is the post-sort target, so name the evidence you need and the source-of-truth expectation: e.g. official docs/release notes for specs, issue/PR/error text for bugs, Reddit/HN/blogs for lived experience, vendor pricing pages for pricing, CVE databases for security. Drives tiering (HIGHLY_RELEVANT / MAYBE_RELEVANT / OTHER), synthesis, gap analysis, and refine-query suggestions. Be specific: \"OAuth 2.1 support in TypeScript MCP frameworks \u2014 runnable code, not marketing\", not \"MCP OAuth\".',\n ),\n scope: z\n .enum(['web', 'reddit', 'both'])\n .default('web')\n .describe(\n 'Search scope. \"web\" (default) = open web, no augmentation. \"reddit\" = server appends `site:reddit.com` to every keyword and filters results to post permalinks (`/r/.+/comments/[a-z0-9]+/`); subreddit homepages are dropped. \"both\" = runs every keyword twice (open web + reddit-scoped), merges the result set, and tags each row with its source. Use \"reddit\" for sentiment/migration/lived-experience research; use \"both\" when opinion-heavy AND official sources also matter.',\n ),\n verbose: z\n .boolean()\n .default(false)\n .describe(\n 'Include per-row scoring/coverage metadata, the trailing Signals block, and CONSENSUS labels even when they carry little signal. Default false.',\n ),\n}).strict();\n\nexport type RawWebSearchParams = z.infer<typeof rawWebSearchParamsSchema>;\nexport type SmartWebSearchParams = z.infer<typeof smartWebSearchParamsSchema>;\n\n// Internal alias retained for shared tests/helpers. Public tools register the\n// raw/smart schemas above, not this alias.\nexport const webSearchParamsSchema = smartWebSearchParamsSchema;\nexport type WebSearchParams = SmartWebSearchParams;\n\n// Search tools are markdown-only at the MCP boundary.\nexport type WebSearchOutput = Record<string, never>;\n", "/**\n * Server logging utility.\n *\n * This server is HTTP-only, so logging must never depend on a transport-bound\n * MCP server instance. All logs flow through mcp-use's Logger, which writes to\n * stderr in Node and console in the browser.\n */\n\nimport { Logger } from 'mcp-use';\n\nexport type LogLevel = 'debug' | 'info' | 'warning' | 'error';\n\nfunction getLogger(name: string) {\n return Logger.get(name);\n}\n\n/**\n * Structured log helper backed by mcp-use's Logger.\n *\n * @param level - Log level.\n * @param message - Message to emit.\n * @param loggerName - Tool/component name for context (falls back to \"research-powerpack\").\n */\nexport function mcpLog(level: LogLevel, message: string, loggerName?: string): void {\n const logger = getLogger(loggerName ?? 'research-powerpack');\n\n switch (level) {\n case 'debug':\n logger.debug(message);\n break;\n case 'info':\n logger.info(message);\n break;\n case 'warning':\n logger.warn(message);\n break;\n case 'error':\n logger.error(message);\n break;\n }\n}\n", "/**\n * Robust error handling utilities for MCP server\n * Ensures the server NEVER crashes and always returns structured responses\n */\n\nimport { mcpLog } from './logger.js';\n\n// ============================================================================\n// Error Codes (MCP-compliant)\n// ============================================================================\n\nexport const ErrorCode = {\n // Retryable errors\n RATE_LIMITED: 'RATE_LIMITED',\n TIMEOUT: 'TIMEOUT',\n NETWORK_ERROR: 'NETWORK_ERROR',\n SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',\n \n // Non-retryable errors\n AUTH_ERROR: 'AUTH_ERROR',\n INVALID_INPUT: 'INVALID_INPUT',\n NOT_FOUND: 'NOT_FOUND',\n QUOTA_EXCEEDED: 'QUOTA_EXCEEDED',\n UNSUPPORTED_BINARY_CONTENT: 'UNSUPPORTED_BINARY_CONTENT',\n\n // Internal errors\n INTERNAL_ERROR: 'INTERNAL_ERROR',\n PARSE_ERROR: 'PARSE_ERROR',\n UNKNOWN_ERROR: 'UNKNOWN_ERROR',\n} as const;\n\ntype ErrorCodeType = typeof ErrorCode[keyof typeof ErrorCode];\n\n// ============================================================================\n// Structured Error Types\n// ============================================================================\n\nexport interface StructuredError {\n code: ErrorCodeType;\n message: string;\n retryable: boolean;\n statusCode?: number;\n cause?: string;\n}\n\ninterface RetryOptions {\n readonly maxRetries: number;\n readonly baseDelayMs: number;\n readonly maxDelayMs: number;\n readonly retryableStatuses: readonly number[];\n readonly onRetry?: (attempt: number, error: StructuredError, delayMs: number) => void;\n}\n\nconst DEFAULT_RETRY_OPTIONS: RetryOptions = {\n maxRetries: 3,\n baseDelayMs: 1000,\n maxDelayMs: 30000,\n retryableStatuses: [408, 429, 500, 502, 503, 504, 510],\n};\n\n// ============================================================================\n// Error Classification \u2014 Atomic Classifiers\n// ============================================================================\n\n/**\n * Classify DOMException (AbortError from AbortController timeouts)\n */\nfunction classifyDomException(error: DOMException): StructuredError {\n if (error.name === 'AbortError') {\n return { code: ErrorCode.TIMEOUT, message: 'Request timed out', retryable: true };\n }\n return { code: ErrorCode.UNKNOWN_ERROR, message: error.message, retryable: false };\n}\n\n/**\n * Classify by Node.js error codes (ECONNREFUSED, ENOTFOUND, etc.)\n * Returns null if no matching code is found.\n */\nfunction classifyByErrorCode(error: { code?: string; message?: string }): StructuredError | null {\n const errCode = error.code;\n if (!errCode) return null;\n\n const networkErrorMessages: Record<string, string> = {\n ECONNREFUSED: 'Connection refused \u2014 service may be down',\n ECONNRESET: 'Connection was reset \u2014 please retry',\n ECONNABORTED: 'Connection aborted \u2014 please retry',\n ENOTFOUND: 'Service not reachable \u2014 check your network',\n EPIPE: 'Connection lost \u2014 please retry',\n EAI_AGAIN: 'DNS lookup failed \u2014 check your network',\n };\n\n if (errCode === 'ECONNREFUSED' || errCode === 'ENOTFOUND' || errCode === 'ECONNRESET') {\n return { code: ErrorCode.NETWORK_ERROR, message: networkErrorMessages[errCode] || 'Network connection failed', retryable: true, cause: error.message };\n }\n\n if (errCode === 'ECONNABORTED' || errCode === 'ETIMEDOUT') {\n return { code: ErrorCode.TIMEOUT, message: networkErrorMessages[errCode] || 'Request timed out', retryable: true, cause: error.message };\n }\n\n return null;\n}\n\n/**\n * Classify by HTTP status code extracted from error objects (axios-style, fetch-style, etc.)\n * Returns null if no status code is found.\n */\nfunction classifyByStatusCode(error: { status?: number; statusCode?: number; response?: { status?: number }; message?: string }): StructuredError | null {\n const status = error.response?.status || error.status || error.statusCode;\n if (!status) return null;\n return classifyHttpError(status, error.message || String(error));\n}\n\n/**\n * Classify by error message patterns (timeout, rate-limit, auth, parse errors)\n * Returns null if no pattern matches.\n */\nfunction classifyByMessage(message: string): StructuredError | null {\n const lower = message.toLowerCase();\n\n // Timeout patterns\n if (lower.includes('timeout') || lower.includes('timed out') || lower.includes('aborterror')) {\n return { code: ErrorCode.TIMEOUT, message: 'Request timed out', retryable: true, cause: message };\n }\n\n // Rate-limit patterns\n if (lower.includes('rate limit') || lower.includes('too many requests')) {\n return { code: ErrorCode.RATE_LIMITED, message: 'Rate limit exceeded', retryable: true, cause: message };\n }\n\n // API key errors\n if (message.includes('API_KEY') || message.includes('api_key') || message.includes('Invalid API')) {\n return { code: ErrorCode.AUTH_ERROR, message: 'API key missing or invalid', retryable: false, cause: message };\n }\n\n // Parse errors\n if (message.includes('JSON') || message.includes('parse') || message.includes('Unexpected token')) {\n return { code: ErrorCode.PARSE_ERROR, message: 'Failed to parse response', retryable: false, cause: message };\n }\n\n return null;\n}\n\n/**\n * Catch-all fallback classification when no other classifier matches.\n */\nfunction classifyFallback(message: string, cause?: unknown): StructuredError {\n return {\n code: ErrorCode.UNKNOWN_ERROR,\n message,\n retryable: false,\n cause: cause ? String(cause) : undefined,\n };\n}\n\n// ============================================================================\n// Main Error Classification Pipeline\n// ============================================================================\n\n/**\n * Classify any error into a structured format.\n * NEVER throws \u2014 always returns a valid StructuredError.\n */\nexport function classifyError(error: unknown): StructuredError {\n if (error == null) {\n return { code: ErrorCode.UNKNOWN_ERROR, message: 'An unknown error occurred', retryable: false };\n }\n\n if (error instanceof DOMException) return classifyDomException(error);\n\n if (!isErrorLike(error)) {\n return { code: ErrorCode.UNKNOWN_ERROR, message: String(error), retryable: false };\n }\n\n return classifyByErrorCode(error)\n ?? classifyByStatusCode(error)\n ?? classifyByMessage(error.message ?? String(error))\n ?? classifyFallback(error.message ?? String(error), error.cause);\n}\n\n/**\n * Type guard for error-like objects with common error properties\n */\nfunction isErrorLike(value: unknown): value is {\n message?: string;\n response?: { status?: number; data?: unknown };\n status?: number;\n statusCode?: number;\n code?: string;\n name?: string;\n cause?: unknown;\n} {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Classify HTTP status codes into structured errors.\n * Exhaustive switch with grouped default handling for unknown ranges.\n */\nfunction classifyHttpError(status: number, message: string): StructuredError {\n switch (status) {\n case 400:\n return { code: ErrorCode.INVALID_INPUT, message: 'Bad request', retryable: false, statusCode: status };\n case 401:\n return { code: ErrorCode.AUTH_ERROR, message: 'Invalid API key', retryable: false, statusCode: status };\n case 403:\n return { code: ErrorCode.QUOTA_EXCEEDED, message: 'Access forbidden or quota exceeded', retryable: false, statusCode: status };\n case 404:\n return { code: ErrorCode.NOT_FOUND, message: 'Resource not found', retryable: false, statusCode: status };\n case 408:\n return { code: ErrorCode.TIMEOUT, message: 'Request timeout', retryable: true, statusCode: status };\n case 429:\n return { code: ErrorCode.RATE_LIMITED, message: 'Rate limit exceeded', retryable: true, statusCode: status };\n case 500:\n return { code: ErrorCode.INTERNAL_ERROR, message: 'Server error', retryable: true, statusCode: status };\n case 502:\n return { code: ErrorCode.SERVICE_UNAVAILABLE, message: 'Bad gateway', retryable: true, statusCode: status };\n case 503:\n return { code: ErrorCode.SERVICE_UNAVAILABLE, message: 'Service unavailable', retryable: true, statusCode: status };\n case 504:\n return { code: ErrorCode.TIMEOUT, message: 'Gateway timeout', retryable: true, statusCode: status };\n case 510:\n return { code: ErrorCode.SERVICE_UNAVAILABLE, message: 'Request canceled', retryable: true, statusCode: status };\n default:\n if (status >= 500) {\n return { code: ErrorCode.SERVICE_UNAVAILABLE, message: `Server error: ${status}`, retryable: true, statusCode: status };\n }\n if (status >= 400) {\n return { code: ErrorCode.INVALID_INPUT, message: `Client error: ${status}`, retryable: false, statusCode: status };\n }\n return { code: ErrorCode.UNKNOWN_ERROR, message: `HTTP ${status}: ${message}`, retryable: false, statusCode: status };\n }\n}\n\n// ============================================================================\n// Retry Logic with Exponential Backoff\n// ============================================================================\n\n/**\n * Calculate delay with exponential backoff and jitter\n */\nfunction calculateBackoff(attempt: number, options: RetryOptions): number {\n const exponentialDelay = options.baseDelayMs * Math.pow(2, attempt);\n const jitter = Math.random() * 0.3 * exponentialDelay; // 0-30% jitter\n return Math.min(exponentialDelay + jitter, options.maxDelayMs);\n}\n\n/**\n * Sleep utility that respects abort signals\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(new DOMException('Aborted', 'AbortError'));\n return;\n }\n\n function onAbort() {\n clearTimeout(timeout);\n reject(new DOMException('Aborted', 'AbortError'));\n }\n\n const timeout = setTimeout(() => {\n if (signal) signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n\n signal?.addEventListener('abort', onAbort, { once: true });\n // Re-check: signal may have aborted between initial check and listener registration\n if (signal?.aborted) {\n onAbort();\n }\n });\n}\n\n/**\n * Wrap a fetch call with timeout via AbortController\n */\nexport function fetchWithTimeout(\n url: string,\n options: RequestInit & { timeoutMs?: number } = {}\n): Promise<Response> {\n const { timeoutMs = 30000, signal: externalSignal, ...fetchOptions } = options;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n let onExternalAbort: (() => void) | undefined;\n if (externalSignal) {\n onExternalAbort = () => controller.abort();\n externalSignal.addEventListener('abort', onExternalAbort, { once: true });\n if (externalSignal.aborted) {\n controller.abort();\n }\n }\n\n return fetch(url, { ...fetchOptions, signal: controller.signal }).finally(() => {\n clearTimeout(timeoutId);\n if (externalSignal && onExternalAbort) {\n externalSignal.removeEventListener('abort', onExternalAbort);\n }\n });\n}\n\n// ============================================================================\n// Stability Wrappers \u2014 Network resilience for LLM API calls\n// ============================================================================\n\n/**\n * Wrap a non-streaming API call with activity-based timeout detection.\n * If the call hasn't completed within `stallMs`, abort and retry.\n * This catches \"stuck\" connections where TCP stays open but no data flows.\n *\n * @param fn - Async function that accepts an AbortSignal\n * @param stallMs - Max milliseconds to wait for the call to complete before considering it stuck\n * @param maxAttempts - Max retry attempts for stalled requests\n * @param label - Label for log messages\n * @returns The result of the function\n */\nexport async function withStallProtection<T>(\n fn: (signal: AbortSignal) => Promise<T>,\n stallMs: number,\n maxAttempts: number = 2,\n label: string = 'request',\n): Promise<T> {\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const controller = new AbortController();\n let stallTimer: ReturnType<typeof setTimeout> | undefined;\n\n const stallPromise = new Promise<never>((_, reject) => {\n stallTimer = setTimeout(() => {\n controller.abort();\n reject(Object.assign(new Error(`Service temporarily unavailable \u2014 no response received (attempt ${attempt + 1}/${maxAttempts})`), {\n code: 'ESTALLED',\n retryable: attempt < maxAttempts - 1,\n }));\n }, stallMs);\n });\n\n let fnPromise: Promise<T> | undefined;\n try {\n fnPromise = fn(controller.signal);\n const result = await Promise.race([fnPromise, stallPromise]);\n clearTimeout(stallTimer);\n return result;\n } catch (err) {\n // Suppress unhandled rejection from the losing promise\n // (e.g. fnPromise rejects after stallPromise wins the race)\n fnPromise?.catch(() => {});\n clearTimeout(stallTimer);\n const isStall = err instanceof Error && (err as NodeJS.ErrnoException).code === 'ESTALLED';\n if (isStall && attempt < maxAttempts - 1) {\n const backoff = calculateBackoff(attempt, DEFAULT_RETRY_OPTIONS);\n mcpLog('warning', `${label} stalled, retrying in ${backoff}ms (attempt ${attempt + 1})`, 'stability');\n await sleep(backoff);\n continue;\n }\n throw err;\n }\n }\n // Should never reach here, but TypeScript needs it\n throw new Error(`${label} failed after ${maxAttempts} stall-protection attempts`);\n}\n", "/**\n * Scrape Links Tool Handler\n *\n * Scrapes many URLs in parallel. Reddit permalinks (reddit.com/r/.../comments/...)\n * are auto-detected and routed through the Reddit API; all other URLs go through\n * Jina Reader first, then Scrape.do proxy-mode retry, then optional Kernel\n * browser rendering. Smart mode feeds successful items to the LLM extractor.\n *\n * NEVER throws \u2014 every error is returned as a tool-level failure response.\n */\n\nimport type { MCPServer } from 'mcp-use/server';\nimport { Effect, Either } from 'effect';\n\nimport {\n SCRAPER,\n CONCURRENCY,\n getCapabilities,\n getMissingEnvMessage,\n parseEnv,\n} from '../config/index.js';\nimport {\n rawScrapeLinksParamsSchema,\n smartScrapeLinksParamsSchema,\n type RawScrapeLinksParams,\n type SmartScrapeLinksParams,\n} from '../schemas/scrape-links.js';\nimport { type PostResult } from '../clients/reddit.js';\nimport { buildScrapeDoProxyUrl, isTerminalReaderError, type JinaConvertResponse } from '../clients/jina.js';\nimport { MarkdownCleaner } from '../services/markdown-cleaner.js';\nimport { createLLMProcessor } from '../services/llm-processor.js';\nimport { removeMetaTags } from '../utils/markdown-formatter.js';\nimport { extractReadableContent } from '../utils/content-extractor.js';\nimport { classifyError, ErrorCode } from '../utils/errors.js';\nimport { isDocumentUrl } from '../utils/source-type.js';\nimport { assessMarkdownQuality } from '../utils/content-quality.js';\nimport { runExternalEffect } from '../effect/runtime.js';\nimport {\n JinaService,\n JinaServiceLive,\n KernelService,\n KernelServiceLive,\n LlmService,\n LlmServiceLive,\n RedditService,\n RedditServiceLive,\n} from '../effect/services.js';\nimport { ProviderTimeoutError } from '../effect/errors.js';\nimport {\n mcpLog,\n formatSuccess,\n formatError,\n formatBatchHeader,\n formatDuration,\n} from './utils.js';\nimport {\n createToolReporter,\n NOOP_REPORTER,\n toolFailure,\n toolSuccess,\n toToolResponse,\n type ToolExecutionResult,\n type ToolReporter,\n} from './mcp-helpers.js';\n\nconst markdownCleaner = new MarkdownCleaner();\n\ntype ScrapeToolOutput = Record<string, never>;\n\ninterface ScrapeHandlerParams {\n urls: string[];\n extract?: string;\n smart: boolean;\n toolName: 'raw-scrape-links' | 'smart-scrape-links';\n}\n\nfunction formatInputValidationError(\n toolName: string,\n issues: readonly { message: string; path: readonly PropertyKey[] }[],\n): string {\n const details = issues\n .map((issue) => {\n const path = issue.path.length > 0 ? issue.path.map(String).join('.') : '<root>';\n return `- ${path}: ${issue.message}`;\n })\n .join('\\n');\n return `Invalid ${toolName} input.\\n\\n${details}`;\n}\n\nfunction enhanceExtractionInstruction(instruction: string | undefined): string {\n const base = instruction || 'Extract the main content and key information from this page.';\n return `${SCRAPER.EXTRACTION_PREFIX}\\n\\n${base}\\n\\n${SCRAPER.EXTRACTION_SUFFIX}`;\n}\n\n// --- Types ---\n\ninterface ProcessedResult {\n url: string;\n content: string;\n index: number; // original position in params.urls[]\n /**\n * Cleaned markdown captured before LLM extraction. Preserved so the handler\n * can fall back to it when the LLM emits the terse \"Page did not load: X\"\n * escape line and would otherwise nuke the scraped body.\n */\n rawContent?: string;\n}\n\ninterface ScrapeMetrics {\n successful: number;\n failed: number;\n totalCredits: number;\n}\n\ninterface FailedContent {\n content: string;\n index: number;\n}\n\ninterface ScrapePhaseResult {\n successItems: ProcessedResult[];\n failedContents: FailedContent[];\n metrics: ScrapeMetrics;\n}\n\ninterface BranchInput {\n url: string;\n origIndex: number;\n}\n\ninterface KernelFallbackInput extends BranchInput {\n proxyError?: string;\n jinaError?: string;\n}\n\nfunction cleanFetchedContent(rawContent: string, url: string): string {\n try {\n const readable = extractReadableContent(rawContent, url);\n const sourceForCleaner = readable.extracted ? readable.content : rawContent;\n return markdownCleaner.processContent(sourceForCleaner);\n } catch {\n return rawContent;\n }\n}\n\nfunction effectErrorMessage(error: unknown): string {\n if (typeof error === 'object' && error !== null) {\n if ('error' in error) {\n const structured = (error as { error?: { message?: unknown } }).error;\n if (typeof structured?.message === 'string') return structured.message;\n }\n if ('message' in error && typeof (error as { message?: unknown }).message === 'string') {\n return (error as { message: string }).message;\n }\n if ('_tag' in error && typeof (error as { _tag?: unknown })._tag === 'string') {\n return (error as { _tag: string })._tag;\n }\n }\n return String(error);\n}\n\n// --- Reddit URL detection ---\n\nconst REDDIT_HOST = /(?:^|\\.)reddit\\.com$/i;\nconst REDDIT_POST_PERMALINK = /\\/r\\/[^/]+\\/comments\\/[a-z0-9]+/i;\n\nfunction isRedditUrl(url: string): boolean {\n try {\n const u = new URL(url);\n return REDDIT_HOST.test(u.hostname);\n } catch {\n return false;\n }\n}\n\nfunction isRedditPostPermalink(url: string): boolean {\n try {\n const u = new URL(url);\n return REDDIT_HOST.test(u.hostname) && REDDIT_POST_PERMALINK.test(u.pathname);\n } catch {\n return false;\n }\n}\n\n// --- Error helper ---\n\nfunction createScrapeErrorResponse(\n code: string,\n message: string,\n startTime: number,\n toolName: ScrapeHandlerParams['toolName'] = 'raw-scrape-links',\n retryable = false,\n alternatives?: string[],\n): ToolExecutionResult<ScrapeToolOutput> {\n return toolFailure(\n `${formatError({\n code,\n message,\n retryable,\n toolName,\n howToFix: code === 'NO_URLS' ? ['Provide at least one valid URL'] : undefined,\n alternatives,\n })}\\n\\nExecution time: ${formatDuration(Date.now() - startTime)}`,\n );\n}\n\n// --- URL partitioning ---\n\ninterface PartitionedUrls {\n webInputs: BranchInput[];\n redditInputs: BranchInput[];\n documentInputs: BranchInput[];\n invalidEntries: { url: string; origIndex: number }[];\n}\n\nfunction partitionUrls(urls: string[]): PartitionedUrls {\n const webInputs: BranchInput[] = [];\n const redditInputs: BranchInput[] = [];\n const documentInputs: BranchInput[] = [];\n const invalidEntries: { url: string; origIndex: number }[] = [];\n\n for (let i = 0; i < urls.length; i++) {\n const url = urls[i]!;\n try {\n new URL(url);\n } catch {\n invalidEntries.push({ url, origIndex: i });\n continue;\n }\n // Document URLs (.pdf/.docx/.pptx/.xlsx) go straight to Jina Reader \u2014\n // bypassing Scrape.do because it cannot decode binary bodies. Ordered\n // before the Reddit check so a hypothetical PDF on a reddit-adjacent host\n // still takes the document path.\n if (isDocumentUrl(url)) {\n documentInputs.push({ url, origIndex: i });\n } else if (isRedditUrl(url)) {\n redditInputs.push({ url, origIndex: i });\n } else {\n webInputs.push({ url, origIndex: i });\n }\n }\n\n return { webInputs, redditInputs, documentInputs, invalidEntries };\n}\n\n// --- Jina-first branch ---\n\n/**\n * Format a Jina-failure line. When Scrape.do proxy mode was attempted, surface\n * both direct and proxy layers so callers can see exactly where fallback ended.\n *\n * Exported for unit testing.\n */\nexport function formatJinaFailure(url: string, jinaError: string, proxyError?: string): string {\n if (proxyError) {\n return `## ${url}\\n\\n\u274C Jina Reader failed. Direct: ${jinaError}. Scrape.do proxy: ${proxyError}.`;\n }\n return `## ${url}\\n\\n\u274C Document conversion failed: ${jinaError}`;\n}\n\nfunction jinaResultError(result: JinaConvertResponse): string | null {\n if (result.error || result.statusCode < 200 || result.statusCode >= 300) {\n return result.error?.message || `HTTP ${result.statusCode}`;\n }\n const quality = assessMarkdownQuality(result.content);\n if (quality.weak) {\n return `Weak Jina markdown (${quality.reason})`;\n }\n return null;\n}\n\nfunction canTryKernel(input: BranchInput, directError?: JinaConvertResponse['error']): boolean {\n if (isDocumentUrl(input.url)) return false;\n if (!directError) return true;\n if (directError.code === ErrorCode.NOT_FOUND || directError.code === ErrorCode.INVALID_INPUT) {\n return false;\n }\n return true;\n}\n\ninterface JinaFirstInput extends BranchInput {\n directError?: string;\n directStructuredError?: JinaConvertResponse['error'];\n proxyError?: string;\n}\n\nfunction formatJinaFirstFailure(input: JinaFirstInput): string {\n if (input.proxyError) {\n return formatJinaFailure(input.url, input.directError ?? 'Unknown direct failure', input.proxyError);\n }\n return formatJinaFailure(input.url, input.directError ?? 'Unknown direct failure');\n}\n\nasync function fetchJinaFirstBranch(\n inputs: BranchInput[],\n kernelEnabled: boolean,\n scrapeDoProxyUrl: string | undefined,\n): Promise<ScrapePhaseResult> {\n if (inputs.length === 0) {\n return { successItems: [], failedContents: [], metrics: { successful: 0, failed: 0, totalCredits: 0 } };\n }\n\n mcpLog(\n 'info',\n `[concurrency] jina direct branch: converting ${inputs.length} URL(s) with limit=${CONCURRENCY.JINA_READER}`,\n 'scrape',\n );\n\n const directResults = await runExternalEffect(\n Effect.gen(function* () {\n const jina = yield* JinaService;\n return yield* Effect.forEach(\n inputs,\n (input) =>\n jina.convert({ url: input.url, timeoutSeconds: 15, allowProxyRetry: false }).pipe(\n Effect.timeoutFail({\n duration: '20 seconds',\n onTimeout: () => new ProviderTimeoutError({\n provider: 'jina',\n operation: 'convert:direct',\n durationMs: 20_000,\n }),\n }),\n Effect.either,\n ),\n { concurrency: CONCURRENCY.JINA_READER },\n );\n }),\n JinaServiceLive,\n );\n\n const successItems: ProcessedResult[] = [];\n const failedContents: FailedContent[] = [];\n const proxyInputs: JinaFirstInput[] = [];\n const kernelInputs: KernelFallbackInput[] = [];\n let successful = 0;\n let failed = 0;\n\n const enqueueFailure = (input: JinaFirstInput): void => {\n if (kernelEnabled && canTryKernel(input, input.directStructuredError)) {\n kernelInputs.push({\n url: input.url,\n origIndex: input.origIndex,\n proxyError: input.proxyError ? `Scrape.do proxy: ${input.proxyError}` : undefined,\n jinaError: input.directError,\n });\n return;\n }\n failed++;\n failedContents.push({ index: input.origIndex, content: formatJinaFirstFailure(input) });\n };\n\n for (let i = 0; i < directResults.length; i++) {\n const settled = directResults[i];\n const input = inputs[i]!;\n if (!settled) {\n proxyInputs.push({ ...input, directError: 'No result returned' });\n continue;\n }\n if (Either.isLeft(settled)) {\n const reason = effectErrorMessage(settled.left);\n proxyInputs.push({ ...input, directError: reason });\n continue;\n }\n\n const directError = jinaResultError(settled.right);\n if (!directError) {\n successful++;\n successItems.push({ url: input.url, content: settled.right.content, index: input.origIndex, rawContent: settled.right.content });\n continue;\n }\n\n const structuredError = settled.right.error;\n if (structuredError && isTerminalReaderError(structuredError)) {\n enqueueFailure({ ...input, directError, directStructuredError: structuredError });\n continue;\n }\n\n proxyInputs.push({ ...input, directError, directStructuredError: structuredError });\n }\n\n if (proxyInputs.length > 0) {\n if (!scrapeDoProxyUrl) {\n for (const input of proxyInputs) {\n enqueueFailure({ ...input, proxyError: 'SCRAPEDO_API_KEY is not configured' });\n }\n } else {\n mcpLog(\n 'info',\n `[concurrency] jina scrape.do proxy branch: retrying ${proxyInputs.length} URL(s) with limit=${CONCURRENCY.JINA_READER}`,\n 'scrape',\n );\n const proxyResults = await runExternalEffect(\n Effect.gen(function* () {\n const jina = yield* JinaService;\n return yield* Effect.forEach(\n proxyInputs,\n (input) =>\n jina.convert({\n url: input.url,\n timeoutSeconds: 15,\n proxyUrl: scrapeDoProxyUrl,\n noCache: true,\n allowProxyRetry: false,\n }).pipe(\n Effect.timeoutFail({\n duration: '20 seconds',\n onTimeout: () => new ProviderTimeoutError({\n provider: 'jina',\n operation: 'convert:proxy',\n durationMs: 20_000,\n }),\n }),\n Effect.either,\n ),\n { concurrency: CONCURRENCY.JINA_READER },\n );\n }),\n JinaServiceLive,\n );\n\n for (let i = 0; i < proxyResults.length; i++) {\n const settled = proxyResults[i];\n const input = proxyInputs[i]!;\n if (!settled) {\n enqueueFailure({ ...input, proxyError: 'No result returned' });\n continue;\n }\n if (Either.isLeft(settled)) {\n enqueueFailure({ ...input, proxyError: effectErrorMessage(settled.left) });\n continue;\n }\n\n const proxyError = jinaResultError(settled.right);\n if (!proxyError) {\n successful++;\n successItems.push({ url: input.url, content: settled.right.content, index: input.origIndex, rawContent: settled.right.content });\n continue;\n }\n\n enqueueFailure({ ...input, proxyError });\n }\n }\n }\n\n if (kernelInputs.length > 0 && kernelEnabled) {\n const kernelPhase = await fetchKernelBranch(kernelInputs);\n successItems.push(...kernelPhase.successItems);\n failedContents.push(...kernelPhase.failedContents);\n successful += kernelPhase.metrics.successful;\n failed += kernelPhase.metrics.failed;\n }\n\n return { successItems, failedContents, metrics: { successful, failed, totalCredits: 0 } };\n}\n\nfunction formatKernelFailure(\n url: string,\n kernelError: string,\n proxyError?: string,\n jinaError?: string,\n): string {\n const layers = [\n jinaError ? `Jina Reader: ${jinaError}` : undefined,\n proxyError ? proxyError : undefined,\n `Kernel: ${kernelError}`,\n ].filter((line): line is string => Boolean(line));\n return `## ${url}\\n\\n\u274C All scrape providers failed. ${layers.join('. ')}.`;\n}\n\nasync function fetchKernelBranch(\n inputs: KernelFallbackInput[],\n): Promise<ScrapePhaseResult> {\n if (inputs.length === 0) {\n return { successItems: [], failedContents: [], metrics: { successful: 0, failed: 0, totalCredits: 0 } };\n }\n\n mcpLog(\n 'info',\n `[concurrency] kernel branch: rendering ${inputs.length} URL(s) with limit=${CONCURRENCY.KERNEL}`,\n 'scrape',\n );\n\n const results = await runExternalEffect(\n Effect.gen(function* () {\n const kernel = yield* KernelService;\n return yield* Effect.forEach(\n inputs,\n (input) =>\n kernel.render({ url: input.url, timeoutSeconds: 15 }).pipe(\n Effect.timeoutFail({\n duration: '25 seconds',\n onTimeout: () => new ProviderTimeoutError({\n provider: 'kernel',\n operation: 'render',\n durationMs: 25_000,\n }),\n }),\n Effect.either,\n ),\n { concurrency: CONCURRENCY.KERNEL },\n );\n }),\n KernelServiceLive,\n );\n\n const successItems: ProcessedResult[] = [];\n const failedContents: FailedContent[] = [];\n let successful = 0;\n let failed = 0;\n\n for (let i = 0; i < results.length; i++) {\n const settled = results[i];\n const input = inputs[i]!;\n if (!settled) {\n failed++;\n failedContents.push({\n index: input.origIndex,\n content: formatKernelFailure(input.url, 'No result returned', input.proxyError, input.jinaError),\n });\n continue;\n }\n if (Either.isLeft(settled)) {\n failed++;\n const reason = effectErrorMessage(settled.left);\n failedContents.push({\n index: input.origIndex,\n content: formatKernelFailure(input.url, reason, input.proxyError, input.jinaError),\n });\n continue;\n }\n\n const result = settled.right;\n if (result.error || result.statusCode < 200 || result.statusCode >= 300) {\n failed++;\n const errorMsg = result.error?.message || `HTTP ${result.statusCode}`;\n failedContents.push({\n index: input.origIndex,\n content: formatKernelFailure(input.url, errorMsg, input.proxyError, input.jinaError),\n });\n continue;\n }\n\n const content = cleanFetchedContent(result.content, result.finalUrl ?? input.url);\n const quality = assessMarkdownQuality(content);\n if (quality.weak) {\n failed++;\n failedContents.push({\n index: input.origIndex,\n content: formatKernelFailure(\n input.url,\n `Weak Kernel markdown (${quality.reason})`,\n input.proxyError,\n input.jinaError,\n ),\n });\n continue;\n }\n\n successful++;\n const finalUrl = result.finalUrl ?? input.url;\n successItems.push({ url: finalUrl, content, index: input.origIndex, rawContent: content });\n }\n\n return { successItems, failedContents, metrics: { successful, failed, totalCredits: 0 } };\n}\n\n// --- Reddit branch ---\n\nfunction formatRedditPostAsMarkdown(result: PostResult): string {\n const { post, comments } = result;\n const lines: string[] = [];\n lines.push(`# ${post.title}`);\n lines.push('');\n lines.push(`**r/${post.subreddit}** \u2022 u/${post.author} \u2022 \u2B06\uFE0F ${post.score} \u2022 \uD83D\uDCAC ${post.commentCount} comments`);\n lines.push(`\uD83D\uDD17 ${post.url}`);\n lines.push('');\n if (post.body) {\n lines.push('## Post content');\n lines.push('');\n lines.push(post.body);\n lines.push('');\n }\n if (comments.length > 0) {\n lines.push(`## Top comments (${comments.length} total)`);\n lines.push('');\n for (const c of comments) {\n const indent = ' '.repeat(c.depth);\n const op = c.isOP ? ' **[OP]**' : '';\n const score = c.score >= 0 ? `+${c.score}` : `${c.score}`;\n lines.push(`${indent}- **u/${c.author}**${op} _(${score})_`);\n for (const line of c.body.split('\\n')) {\n lines.push(`${indent} ${line}`);\n }\n lines.push('');\n }\n }\n return lines.join('\\n');\n}\n\nasync function fetchRedditBranch(inputs: BranchInput[]): Promise<ScrapePhaseResult> {\n if (inputs.length === 0) {\n return { successItems: [], failedContents: [], metrics: { successful: 0, failed: 0, totalCredits: 0 } };\n }\n\n const env = parseEnv();\n if (!env.REDDIT_CLIENT_ID || !env.REDDIT_CLIENT_SECRET) {\n const failedContents = inputs.map(\n (i) => ({\n index: i.origIndex,\n content: `## ${i.url}\\n\\n\u274C Reddit URL detected, but Reddit API is not configured. Set \\`REDDIT_CLIENT_ID\\` and \\`REDDIT_CLIENT_SECRET\\` in the server env to enable threaded Reddit scraping.`,\n }),\n );\n return {\n successItems: [],\n failedContents,\n metrics: { successful: 0, failed: inputs.length, totalCredits: 0 },\n };\n }\n\n // Warn for non-permalink Reddit URLs (subreddit homepages, /new, /top, /hot,\n // user profiles). The Reddit API path we call requires /r/.../comments/... \u2014\n // reject upfront so the caller sees a helpful message instead of a 404.\n const [postInputs, nonPermalinks] = inputs.reduce<[BranchInput[], BranchInput[]]>(\n ([posts, rest], input) => {\n if (isRedditPostPermalink(input.url)) posts.push(input);\n else rest.push(input);\n return [posts, rest];\n },\n [[], []],\n );\n\n const nonPermalinkFailed = nonPermalinks.map(\n (i) => ({\n index: i.origIndex,\n content: `## ${i.url}\\n\\n\u274C Only Reddit post permalinks (/r/<sub>/comments/<id>/...) are supported. Use raw-web-search with explicit Reddit permalink probes or smart-web-search with scope:\"reddit\" to discover post permalinks first.`,\n }),\n );\n\n if (postInputs.length === 0) {\n return {\n successItems: [],\n failedContents: nonPermalinkFailed,\n metrics: { successful: 0, failed: nonPermalinks.length, totalCredits: 0 },\n };\n }\n\n mcpLog('info', `[concurrency] reddit branch: fetching ${postInputs.length} post(s) with limit=${CONCURRENCY.REDDIT}`, 'scrape');\n const urls = postInputs.map((i) => i.url);\n const batchResult = await runExternalEffect(\n Effect.gen(function* () {\n const reddit = yield* RedditService;\n return yield* reddit.batchGetPosts(urls, true).pipe(\n Effect.timeoutFail({\n duration: '60 seconds',\n onTimeout: () => new ProviderTimeoutError({\n provider: 'reddit',\n operation: 'batchGetPosts',\n durationMs: 60_000,\n }),\n }),\n );\n }),\n RedditServiceLive(env.REDDIT_CLIENT_ID, env.REDDIT_CLIENT_SECRET),\n );\n const urlToIndex = new Map(postInputs.map((i) => [i.url, i.origIndex]));\n\n const successItems: ProcessedResult[] = [];\n const failedContents: FailedContent[] = [...nonPermalinkFailed];\n let successful = 0;\n let failed = nonPermalinks.length;\n\n for (const [url, result] of batchResult.results) {\n const origIndex = urlToIndex.get(url) ?? -1;\n if (result instanceof Error) {\n failed++;\n failedContents.push({ index: origIndex, content: `## ${url}\\n\\n\u274C Reddit fetch failed: ${result.message}` });\n continue;\n }\n successful++;\n const md = formatRedditPostAsMarkdown(result);\n successItems.push({ url, content: md, index: origIndex, rawContent: md });\n }\n\n return { successItems, failedContents, metrics: { successful, failed, totalCredits: 0 } };\n}\n\n// --- Terse-LLM-escape detection + raw fallback merger ---\n\n/**\n * The LLM extraction prompt tells the model to emit a single terse line when\n * a page \"clearly failed to load\" (login walls, JS-render-empty, paywalls,\n * etc.). In practice the LLM over-triggers this on partially-rendered pages,\n * causing smart scrape to return a one-line verdict and discard the cleaned\n * markdown. This detector + merger keep the verdict but re-attach a capped\n * slice of the raw markdown so the caller always has something to work with.\n */\nconst TERSE_LLM_FAILURE_RE =\n /^\\s*##\\s*Matches\\s*\\n+\\s*_Page did not load:\\s*([a-z0-9_-]+)_\\s*\\.?\\s*$/i;\n\n/** Cap on the raw-markdown slice appended under \"## Raw content ...\" */\nexport const RAW_FALLBACK_CHAR_CAP = 4000;\n\n/**\n * If `llmOutput` is exactly the terse \"## Matches\\n_Page did not load: X_\"\n * line, return the reason token (e.g. \"login-wall\"). Otherwise null.\n */\nexport function detectTerseFailure(llmOutput: string): string | null {\n const m = llmOutput.trim().match(TERSE_LLM_FAILURE_RE);\n return m ? m[1]! : null;\n}\n\n/**\n * When the LLM emitted the terse escape line, append a capped slice of the\n * raw cleaned markdown under a `## Raw content (...)` section so the caller\n * still has the actual scraped body to inspect. No-op otherwise.\n */\nexport function mergeLlmWithRawFallback(\n llmOutput: string,\n rawContent: string | undefined,\n): string {\n const reason = detectTerseFailure(llmOutput);\n if (!reason) return llmOutput;\n const trimmed = rawContent?.trim();\n if (!trimmed) return llmOutput;\n const snippet =\n trimmed.length > RAW_FALLBACK_CHAR_CAP\n ? trimmed.slice(0, RAW_FALLBACK_CHAR_CAP) + '\\n\\n\u2026[raw truncated]'\n : trimmed;\n return `${llmOutput.trim()}\\n\\n## Raw content (LLM flagged page as ${reason})\\n\\n${snippet}`;\n}\n\n// --- LLM extraction (shared by both branches) ---\n\nasync function processItemsWithLlm(\n successItems: ProcessedResult[],\n enhancedInstruction: string | undefined,\n llmProcessor: ReturnType<typeof createLLMProcessor>,\n reporter: ToolReporter,\n): Promise<{ items: ProcessedResult[]; llmErrors: number; llmAttempted: number }> {\n let llmErrors = 0;\n\n // Raw-mode bypass: caller omitted `extract` \u2192 return cleaned markdown as-is.\n if (!enhancedInstruction) {\n if (successItems.length > 0) {\n mcpLog('info', 'Raw mode: extract omitted \u2014 returning cleaned scraped content without LLM pass', 'scrape');\n }\n return { items: successItems, llmErrors, llmAttempted: 0 };\n }\n\n if (!llmProcessor || successItems.length === 0) {\n if (!llmProcessor && successItems.length > 0) {\n mcpLog('warning', 'LLM unavailable (LLM_API_KEY not set). Returning raw scraped content.', 'scrape');\n void reporter.log('warning', 'llm_extractor_unreachable: planner not configured; raw scraped content returned');\n }\n return { items: successItems, llmErrors, llmAttempted: 0 };\n }\n\n mcpLog('info', `[concurrency] llm extraction: fanning out ${successItems.length} item(s) with limit=${CONCURRENCY.LLM_EXTRACTION}`, 'scrape');\n\n const llmResults = await runExternalEffect(\n Effect.gen(function* () {\n const llm = yield* LlmService;\n return yield* Effect.forEach(\n successItems,\n (item) =>\n llm.extractContent(\n item.content,\n { enabled: true, extract: enhancedInstruction, url: item.url },\n ).pipe(\n Effect.timeoutFail({\n duration: '155 seconds',\n onTimeout: () => new ProviderTimeoutError({\n provider: 'llm',\n operation: 'extractContent',\n durationMs: 155_000,\n }),\n }),\n Effect.either,\n Effect.map((result) => ({ item, result })),\n ),\n { concurrency: CONCURRENCY.LLM_EXTRACTION },\n );\n }),\n LlmServiceLive,\n );\n\n const processedItems = llmResults.map(({ item, result }) => {\n mcpLog('debug', `LLM extracting ${item.url}...`, 'scrape');\n\n if (Either.isLeft(result)) {\n llmErrors++;\n const errorMessage = effectErrorMessage(result.left);\n mcpLog('warning', `LLM extraction failed for ${item.url}: ${errorMessage}`, 'scrape');\n void reporter.log('warning', `llm_extractor_unreachable: ${item.url} \u2014 ${errorMessage}`);\n const raw = item.rawContent?.trim();\n const rawSnippet = raw\n ? `\\n\\n## Raw content (unextracted)\\n\\n${raw.length > RAW_FALLBACK_CHAR_CAP ? raw.slice(0, RAW_FALLBACK_CHAR_CAP) + '\\n\\n...[raw truncated]' : raw}`\n : '';\n return {\n ...item,\n content: `\u274C LLM extraction failed: ${errorMessage}${rawSnippet}`,\n };\n }\n\n const llmResult = result.right;\n\n if (llmResult.processed) {\n const merged = mergeLlmWithRawFallback(llmResult.content, item.rawContent);\n if (merged !== llmResult.content) {\n mcpLog('warning', `LLM emitted terse escape line for ${item.url} \u2014 preserved raw fallback`, 'scrape');\n void reporter.log('warning', `llm_terse_escape: ${item.url} \u2014 preserving raw fallback`);\n }\n return { ...item, content: merged };\n }\n\n llmErrors++;\n mcpLog('warning', `LLM extraction failed for ${item.url}: ${llmResult.error || 'unknown reason'}`, 'scrape');\n void reporter.log('warning', `llm_extractor_unreachable: ${item.url} \u2014 ${llmResult.error || 'unknown reason'}`);\n const raw = item.rawContent?.trim();\n const rawSnippet = raw\n ? `\\n\\n## Raw content (unextracted)\\n\\n${raw.length > RAW_FALLBACK_CHAR_CAP ? raw.slice(0, RAW_FALLBACK_CHAR_CAP) + '\\n\\n...[raw truncated]' : raw}`\n : '';\n return {\n ...item,\n content: `\u274C LLM extraction failed: ${llmResult.error || 'unknown reason'}${rawSnippet}`,\n };\n });\n\n return { items: processedItems, llmErrors, llmAttempted: successItems.length };\n}\n\n// --- Output assembly ---\n\ninterface ContentEntry {\n content: string;\n index: number;\n}\n\nexport function assembleContentEntries(successItems: ProcessedResult[], failedContents: FailedContent[]): string[] {\n const successEntries: ContentEntry[] = successItems.map((item) => {\n let content = item.content;\n try {\n content = removeMetaTags(content);\n } catch {\n // Use content as-is\n }\n return { index: item.index, content: `## ${item.url}\\n\\n${content}` };\n });\n\n return [...failedContents, ...successEntries]\n .sort((a, b) => a.index - b.index)\n .map((entry) => entry.content);\n}\n\nfunction buildScrapeResponse(\n params: ScrapeHandlerParams,\n contents: string[],\n metrics: ScrapeMetrics,\n llmErrors: number,\n executionTime: number,\n llmAccounting: { llmAttempted: number; llmSucceeded: boolean },\n): string {\n const llmExtras: Record<string, string | number> = {};\n if (llmAccounting.llmAttempted > 0) {\n const ok = llmAccounting.llmAttempted - llmErrors;\n llmExtras['LLM extraction'] = `${ok}/${llmAccounting.llmAttempted} succeeded`;\n if (!llmAccounting.llmSucceeded) {\n llmExtras['LLM credit'] = '0 charged (no extraction produced)';\n }\n } else if (llmErrors > 0) {\n llmExtras['LLM extraction failures'] = llmErrors;\n }\n\n const batchHeader = formatBatchHeader({\n title: `Scraped Content (${params.urls.length} URLs)`,\n totalItems: params.urls.length,\n successful: metrics.successful,\n failed: metrics.failed,\n extras: {\n 'Credits used': metrics.totalCredits,\n ...llmExtras,\n },\n });\n\n const formattedContent = formatSuccess({\n title: 'Scraping Complete',\n summary: batchHeader,\n data: contents.join('\\n\\n---\\n\\n'),\n metadata: {\n 'Execution time': formatDuration(executionTime),\n },\n });\n\n return formattedContent;\n}\n\n// --- Handler ---\n\nasync function handleScrapeLinksMode(\n params: ScrapeHandlerParams,\n reporter: ToolReporter = NOOP_REPORTER,\n): Promise<ToolExecutionResult<ScrapeToolOutput>> {\n const startTime = Date.now();\n\n if (!params.urls || params.urls.length === 0) {\n return createScrapeErrorResponse('NO_URLS', 'No URLs provided', startTime, params.toolName);\n }\n\n if (params.smart && !createLLMProcessor()) {\n return toolFailure(getMissingEnvMessage('llmExtraction'));\n }\n\n const { webInputs, redditInputs, documentInputs, invalidEntries } = partitionUrls(params.urls);\n const validCount = webInputs.length + redditInputs.length + documentInputs.length;\n\n await reporter.log(\n 'info',\n `Partitioned ${params.urls.length} URL(s): ${webInputs.length} web, ${redditInputs.length} reddit, ${documentInputs.length} document, ${invalidEntries.length} invalid`,\n );\n\n if (validCount === 0) {\n return createScrapeErrorResponse(\n 'INVALID_URLS',\n `All ${params.urls.length} URLs are invalid`,\n startTime,\n params.toolName,\n false,\n [\n 'raw-web-search(keywords=[...]) \u2014 search for valid URLs first, then scrape the results',\n ],\n );\n }\n\n mcpLog(\n 'info',\n `Starting ${params.toolName}: ${webInputs.length} web + ${redditInputs.length} reddit + ${documentInputs.length} document URL(s)`,\n 'scrape',\n );\n await reporter.progress(15, 100, 'Preparing scrape clients');\n\n let kernelEnabled: boolean;\n let scrapeDoProxyUrl: string | undefined;\n try {\n const env = parseEnv();\n kernelEnabled = getCapabilities().kernel;\n scrapeDoProxyUrl = env.SCRAPER_API_KEY ? buildScrapeDoProxyUrl(env.SCRAPER_API_KEY) : undefined;\n } catch (error) {\n const err = classifyError(error);\n return createScrapeErrorResponse(\n 'CLIENT_INIT_FAILED',\n `Failed to initialize scrape providers: ${err.message}`,\n startTime,\n params.toolName,\n false,\n [\n 'raw-web-search(keywords=[\"topic key findings\", \"topic summary\"]) \u2014 search instead of scraping',\n ],\n );\n }\n\n const enhancedInstruction = params.smart ? enhanceExtractionInstruction(params.extract) : undefined;\n\n await reporter.progress(35, 100, 'Fetching page content');\n\n const jinaInputs = [...webInputs, ...documentInputs];\n const [jinaPhase, redditPhase] = await Promise.all([\n fetchJinaFirstBranch(jinaInputs, kernelEnabled, scrapeDoProxyUrl),\n fetchRedditBranch(redditInputs),\n ]);\n\n const successItems = [\n ...jinaPhase.successItems,\n ...redditPhase.successItems,\n ];\n const invalidFailed = invalidEntries.map(\n ({ url, origIndex }) => ({ index: origIndex, content: `## ${url}\\n\\n\u274C Invalid URL format` }),\n );\n const failedContents = [\n ...invalidFailed,\n ...jinaPhase.failedContents,\n ...redditPhase.failedContents,\n ];\n const metrics: ScrapeMetrics = {\n successful:\n jinaPhase.metrics.successful\n + redditPhase.metrics.successful\n ,\n failed:\n invalidEntries.length\n + jinaPhase.metrics.failed\n + redditPhase.metrics.failed,\n totalCredits: 0,\n };\n\n await reporter.log('info', `Fetched ${metrics.successful} page(s), ${metrics.failed} failed`);\n\n if (successItems.length > 0) {\n await reporter.progress(80, 100, 'Running LLM extraction over fetched pages');\n }\n\n const { items: processedItems, llmErrors, llmAttempted } = await processItemsWithLlm(\n successItems,\n enhancedInstruction,\n createLLMProcessor(),\n reporter,\n );\n\n const contents = assembleContentEntries(processedItems, failedContents);\n const executionTime = Date.now() - startTime;\n\n mcpLog(\n 'info',\n `Completed: ${metrics.successful} successful, ${metrics.failed} failed, ${metrics.totalCredits} credits used`,\n 'scrape',\n );\n\n const llmSucceeded = llmAttempted > 0 && llmErrors < llmAttempted;\n const content = buildScrapeResponse(\n params,\n contents,\n metrics,\n llmErrors,\n executionTime,\n { llmAttempted, llmSucceeded },\n );\n\n if (metrics.successful === 0 && metrics.failed > 0) {\n return toolFailure(content);\n }\n\n if (params.smart && llmAttempted > 0 && llmErrors === llmAttempted) {\n return toolFailure(content);\n }\n\n return toolSuccess(content);\n}\n\nexport function handleRawScrapeLinks(\n params: RawScrapeLinksParams,\n reporter: ToolReporter = NOOP_REPORTER,\n): Promise<ToolExecutionResult<ScrapeToolOutput>> {\n return handleScrapeLinksMode({ ...params, smart: false, toolName: 'raw-scrape-links' }, reporter);\n}\n\nexport function handleSmartScrapeLinks(\n params: SmartScrapeLinksParams,\n reporter: ToolReporter = NOOP_REPORTER,\n): Promise<ToolExecutionResult<ScrapeToolOutput>> {\n return handleScrapeLinksMode({ ...params, smart: true, toolName: 'smart-scrape-links' }, reporter);\n}\n\nexport function registerScrapeLinksTools(server: MCPServer): void {\n server.tool(\n {\n name: 'raw-scrape-links',\n title: 'Raw Scrape Links',\n description:\n 'Fetch URLs in parallel and return full markdown directly. Input is only `urls` (1\u201350). Reddit post permalinks route through the Reddit API with threaded comments. Non-Reddit URLs use Jina Reader first, then Jina Reader with Scrape.do proxy mode when SCRAPEDO_API_KEY is configured, then optional Kernel browser rendering for web pages. Use this for full source capture, Reddit comment harvesting, and raw evidence before synthesis.',\n schema: rawScrapeLinksParamsSchema,\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: true,\n },\n },\n async (args, ctx) => {\n const parsed = rawScrapeLinksParamsSchema.safeParse(args);\n if (!parsed.success) {\n return toToolResponse(toolFailure(formatInputValidationError('raw-scrape-links', parsed.error.issues)));\n }\n\n const reporter = createToolReporter(ctx, 'raw-scrape-links');\n const result = await handleRawScrapeLinks(parsed.data, reporter);\n\n await reporter.progress(100, 100, result.isError ? 'Scrape failed' : 'Scrape complete');\n return toToolResponse(result);\n },\n );\n\n server.tool(\n {\n name: 'smart-scrape-links',\n title: 'Smart Scrape Links',\n description:\n 'Fetch URLs in parallel, then always run per-URL LLM extraction. Input is `urls` (1\u201350) plus required `extract`. Reddit post permalinks route through the Reddit API with threaded comments. Non-Reddit URLs use Jina Reader first, then Jina Reader with Scrape.do proxy mode when SCRAPEDO_API_KEY is configured, then optional Kernel browser rendering for web pages. Each extracted page returns markdown sections such as `## Source`, `## Matches`, `## Not found`, and `## Follow-up signals`.',\n schema: smartScrapeLinksParamsSchema,\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: true,\n },\n },\n async (args, ctx) => {\n const parsed = smartScrapeLinksParamsSchema.safeParse(args);\n if (!parsed.success) {\n return toToolResponse(toolFailure(formatInputValidationError('smart-scrape-links', parsed.error.issues)));\n }\n\n const reporter = createToolReporter(ctx, 'smart-scrape-links');\n const result = await handleSmartScrapeLinks(parsed.data, reporter);\n\n await reporter.progress(100, 100, result.isError ? 'Scrape failed' : 'Scrape complete');\n return toToolResponse(result);\n },\n );\n}\n", "import { z } from 'zod';\n\nconst urlSchema = z\n .string()\n .url({ message: 'scrape: Invalid URL format' })\n .refine(\n url => url.startsWith('http://') || url.startsWith('https://'),\n { message: 'scrape: URL must use http:// or https://' }\n )\n .describe('A fully-qualified HTTP or HTTPS URL to scrape.');\n\nconst urlsSchema = z\n .array(urlSchema)\n .min(1, { message: 'scrape: At least 1 URL required' })\n .max(50, { message: 'scrape: At most 50 URLs allowed per call' })\n .describe('URLs to fetch in parallel. Reddit post permalinks (`reddit.com/r/<sub>/comments/<id>/...`) are auto-detected and routed through the Reddit API (threaded post + comments). Non-Reddit URLs use Jina Reader first, then Jina Reader through Scrape.do proxy when configured, then optional Kernel browser rendering for web pages.');\n\nexport const rawScrapeLinksParamsSchema = z.object({\n urls: urlsSchema,\n extract: z.never().optional(),\n}).strict();\n\nexport const smartScrapeLinksParamsSchema = z.object({\n urls: z\n .array(urlSchema)\n .min(1, { message: 'scrape: At least 1 URL required' })\n .max(50, { message: 'scrape: At most 50 URLs allowed per call' })\n .describe('URLs to fetch and extract in parallel. Reddit post permalinks (`reddit.com/r/<sub>/comments/<id>/...`) are auto-detected and routed through the Reddit API (threaded post + comments). Non-Reddit URLs use Jina Reader first, then Jina Reader through Scrape.do proxy when configured, then optional Kernel browser rendering for web pages. Mix reddit + non-reddit URLs freely; branches run concurrently. Prefer contextually grouped batches \u2014 call this tool multiple times in parallel when URL sets are unrelated.'),\n extract: z\n .string()\n .min(1, { message: 'smart-scrape-links: extract cannot be empty' })\n .describe(\n 'Required semantic extraction instruction. Describe the SHAPE of what you want, separated by `|`. The extractor classifies each page (docs / github-thread / reddit / marketing / cve / paper / announcement / qa / blog / changelog / release-notes) and adjusts emphasis per type: preserves numbers/versions/stacktraces verbatim from docs and CVE pages, quotes Reddit/HN with attribution plus sentiment distribution, flags what the page did NOT answer in a \"Not found\" section, and surfaces referenced-but-unscraped URLs in a \"Follow-up signals\" section. Good examples: \"root cause | affected versions | fix | workarounds | timeline\"; \"pricing tiers | rate limits | enterprise contact | free-tier quotas\"; \"maintainer decisions | accepted fix commits | stacktraces | resolved version\".',\n ),\n}).strict();\n\nexport type RawScrapeLinksParams = z.infer<typeof rawScrapeLinksParamsSchema>;\nexport type SmartScrapeLinksParams = z.infer<typeof smartScrapeLinksParamsSchema>;\n\n// Internal alias retained for shared tests/helpers. Public tools register the\n// raw/smart schemas above, not this alias.\nexport const scrapeLinksParamsSchema = smartScrapeLinksParamsSchema;\nexport type ScrapeLinksParams = SmartScrapeLinksParams;\n\n// Scrape tools are markdown-only at the MCP boundary.\nexport type ScrapeLinksOutput = Record<string, never>;\n", "/**\n * Shared retry and backoff utilities\n */\n\n/** Jitter factor to prevent thundering herd */\nconst JITTER_FACTOR = 0.3 as const;\n\n/** Exponential base for backoff calculation */\nconst EXPONENTIAL_BASE = 2 as const;\n\n/** Default base delay for exponential backoff (ms) */\nconst DEFAULT_BASE_DELAY_MS = 1_000 as const;\n\n/** Default maximum backoff delay cap (ms) */\nconst DEFAULT_MAX_DELAY_MS = 30_000 as const;\n\n/**\n * Calculate exponential backoff delay with jitter.\n * Formula: min(base * 2^attempt + random_jitter, maxDelay)\n *\n * @param attempt - Zero-based retry attempt number\n * @param baseDelayMs - Base delay in milliseconds (default: 1000)\n * @param maxDelayMs - Maximum delay cap in milliseconds (default: 30000)\n * @returns Delay in milliseconds with jitter applied\n */\nexport function calculateBackoff(\n attempt: number,\n baseDelayMs: number = DEFAULT_BASE_DELAY_MS,\n maxDelayMs: number = DEFAULT_MAX_DELAY_MS,\n): number {\n const exponentialDelay = baseDelayMs * Math.pow(EXPONENTIAL_BASE, attempt);\n const jitter = JITTER_FACTOR * exponentialDelay * Math.random();\n return Math.min(exponentialDelay + jitter, maxDelayMs);\n}\n", "/**\n * Jina Reader Client\n *\n * Converts any URL (including PDFs, DOCX, PPTX, HTML) into clean markdown via\n * the public Jina Reader endpoint. Used by raw/smart scrape tools for full\n * markdown capture before optional LLM extraction.\n *\n * NEVER throws \u2014 every failure surfaces as a classified `StructuredError`\n * in the returned response, matching the shape of `ScraperClient.scrape`.\n *\n * Auth: optional `JINA_API_KEY` raises the rate limit from 20 RPM to 200+ RPM.\n * Without a key the endpoint still works; we just retry more aggressively on\n * 429 responses.\n */\n\nimport {\n classifyError,\n fetchWithTimeout,\n sleep,\n ErrorCode,\n type StructuredError,\n} from '../utils/errors.js';\nimport { calculateBackoff } from '../utils/retry.js';\nimport { mcpLog } from '../utils/logger.js';\nimport type { MultipleSearchResponse, QuerySearchResult } from './search.js';\n\n// \u2500\u2500 Constants \u2500\u2500\n\nconst JINA_READER_BASE = 'https://r.jina.ai/' as const;\nconst JINA_SEARCH_BASE = 'https://s.jina.ai/' as const;\nconst DEFAULT_TIMEOUT_SECONDS = 15 as const;\nconst DEFAULT_TIMEOUT_MS = DEFAULT_TIMEOUT_SECONDS * 1000;\nconst MAX_RETRIES = 2 as const;\nconst SEARCH_RESULTS_PER_QUERY = 10 as const;\n\nexport function buildJinaSearchUrl(query: string): string {\n const params = new URLSearchParams({ q: query });\n return `${JINA_SEARCH_BASE}?${params.toString()}`;\n}\n\nexport function buildScrapeDoProxyUrl(token: string, parameters = 'render=false'): string {\n const trimmed = token.trim();\n if (!trimmed) return '';\n return `http://${encodeURIComponent(trimmed)}:${parameters}@proxy.scrape.do:8080`;\n}\n\n// \u2500\u2500 Interfaces \u2500\u2500\n\nexport interface JinaConvertRequest {\n readonly url: string;\n readonly timeoutSeconds?: number;\n readonly proxyUrl?: string;\n readonly noCache?: boolean;\n readonly allowProxyRetry?: boolean;\n}\n\nexport interface JinaConvertResponse {\n readonly content: string;\n readonly statusCode: number;\n /** Always 0 \u2014 Jina is a separate service from Scrape.do's credit pool. */\n readonly credits: 0;\n readonly usageTokens?: number;\n readonly error?: StructuredError;\n}\n\n// \u2500\u2500 Client \u2500\u2500\n\nexport class JinaClient {\n private readonly apiKey: string | undefined;\n\n constructor(apiKey?: string) {\n const fromEnv = process.env.JINA_API_KEY?.trim();\n this.apiKey = apiKey?.trim() || fromEnv || undefined;\n }\n\n /**\n * Convert a URL to markdown via Jina Reader.\n * NEVER throws \u2014 always returns a JinaConvertResponse (possibly with error).\n */\n async convert(request: JinaConvertRequest): Promise<JinaConvertResponse> {\n const {\n url,\n timeoutSeconds = DEFAULT_TIMEOUT_SECONDS,\n proxyUrl,\n noCache = false,\n allowProxyRetry = false,\n } = request;\n\n try {\n new URL(url);\n } catch {\n return {\n content: `Invalid URL: ${url}`,\n statusCode: 400,\n credits: 0,\n error: { code: ErrorCode.INVALID_INPUT, message: `Invalid URL: ${url}`, retryable: false },\n };\n }\n\n const first = await this.convertOnce({\n url,\n timeoutSeconds,\n proxyUrl,\n noCache,\n });\n\n if (!first.error || !allowProxyRetry || proxyUrl || isTerminalReaderError(first.error)) {\n return first;\n }\n\n mcpLog('warning', `Jina Reader failed for ${url}; retrying with Jina proxy`, 'jina');\n return this.convertOnce({\n url,\n timeoutSeconds,\n proxyUrl: 'auto',\n noCache: true,\n });\n }\n\n async searchMultiple(queries: string[]): Promise<MultipleSearchResponse> {\n const startTime = Date.now();\n if (queries.length === 0) {\n return {\n searches: [],\n totalQueries: 0,\n executionTime: 0,\n error: { code: ErrorCode.INVALID_INPUT, message: 'No queries provided', retryable: false },\n };\n }\n if (!this.apiKey) {\n return {\n searches: [],\n totalQueries: queries.length,\n executionTime: Date.now() - startTime,\n error: { code: ErrorCode.AUTH_ERROR, message: 'Jina Search requires JINA_API_KEY', retryable: false },\n };\n }\n\n const searches = await Promise.all(queries.map((query) => this.searchOne(query)));\n const firstError = searches.find((search) => search.error)?.error;\n const allFailed = searches.every((search) => search.error);\n return {\n searches,\n totalQueries: queries.length,\n executionTime: Date.now() - startTime,\n ...(allFailed && firstError ? { error: firstError } : {}),\n };\n }\n\n private async convertOnce(request: Required<Pick<JinaConvertRequest, 'url' | 'timeoutSeconds' | 'noCache'>> & Pick<JinaConvertRequest, 'proxyUrl'>): Promise<JinaConvertResponse> {\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n };\n if (this.apiKey) headers['Authorization'] = `Bearer ${this.apiKey}`;\n if (request.proxyUrl && request.proxyUrl !== 'auto') {\n headers['X-Proxy-Url'] = request.proxyUrl;\n }\n\n const body: Record<string, unknown> = {\n url: request.url,\n respondWith: 'markdown',\n timeout: request.timeoutSeconds,\n base: 'final',\n removeOverlay: true,\n };\n if (request.proxyUrl === 'auto') body['proxy'] = 'auto';\n if (request.noCache) body['noCache'] = true;\n\n return this.fetchReader(body, headers, request.timeoutSeconds);\n }\n\n private async fetchReader(\n body: Record<string, unknown>,\n headers: Record<string, string>,\n timeoutSeconds: number,\n ): Promise<JinaConvertResponse> {\n let lastError: StructuredError | undefined;\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n const response = await fetchWithTimeout(JINA_READER_BASE, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n timeoutMs: (timeoutSeconds + 5) * 1000,\n });\n\n const raw = await response.text().catch((readError: unknown) =>\n `Failed to read Jina response: ${readError instanceof Error ? readError.message : String(readError)}`,\n );\n const usageHeader = response.headers.get('x-usage-tokens');\n const usageTokens = usageHeader ? Number(usageHeader) : undefined;\n const parsed = parseReaderContent(raw);\n\n if (response.ok) {\n if (!parsed.content.trim()) {\n return emptyReaderResponse(response.status, usageTokens);\n }\n return {\n content: parsed.content,\n statusCode: response.status,\n credits: 0,\n usageTokens: Number.isFinite(usageTokens) ? usageTokens : undefined,\n };\n }\n\n const terminal = terminalReaderResponse(response.status, parsed.content || raw);\n if (terminal) return terminal;\n\n lastError = classifyError({ status: response.status, message: raw.slice(0, 200) });\n if (lastError.retryable && attempt < MAX_RETRIES) {\n const delayMs = calculateBackoff(attempt);\n mcpLog(\n 'warning',\n `Jina ${response.status} on attempt ${attempt + 1}/${MAX_RETRIES + 1}. Retrying in ${delayMs}ms`,\n 'jina',\n );\n await sleep(delayMs);\n continue;\n }\n return {\n content: `Jina Reader error (${response.status}): ${raw.slice(0, 200)}`,\n statusCode: response.status,\n credits: 0,\n error: lastError,\n };\n } catch (error) {\n lastError = classifyError(error);\n if (lastError.retryable && attempt < MAX_RETRIES) {\n const delayMs = calculateBackoff(attempt);\n mcpLog(\n 'warning',\n `Jina ${lastError.code}: ${lastError.message}. Retry ${attempt + 1}/${MAX_RETRIES + 1} in ${delayMs}ms`,\n 'jina',\n );\n await sleep(delayMs);\n continue;\n }\n return {\n content: `Jina Reader failed: ${lastError.message}`,\n statusCode: lastError.statusCode ?? 500,\n credits: 0,\n error: lastError,\n };\n }\n }\n\n return {\n content: `Jina Reader failed after ${MAX_RETRIES + 1} attempts: ${lastError?.message ?? 'Unknown error'}`,\n statusCode: lastError?.statusCode ?? 500,\n credits: 0,\n error: lastError ?? { code: ErrorCode.UNKNOWN_ERROR, message: 'All retries exhausted', retryable: false },\n };\n }\n\n private async searchOne(query: string): Promise<QuerySearchResult> {\n const headers: Record<string, string> = {\n Accept: 'application/json',\n Authorization: `Bearer ${this.apiKey ?? ''}`,\n };\n\n try {\n const response = await fetchWithTimeout(buildJinaSearchUrl(query), {\n method: 'GET',\n headers,\n timeoutMs: DEFAULT_TIMEOUT_MS,\n });\n const raw = await response.text().catch((readError: unknown) =>\n `Failed to read Jina Search response: ${readError instanceof Error ? readError.message : String(readError)}`,\n );\n\n if (!response.ok) {\n return {\n query,\n results: [],\n totalResults: 0,\n related: [],\n error: classifyError({ status: response.status, message: raw.slice(0, 200) }),\n };\n }\n\n const results = parseSearchResults(raw);\n return { query, results, totalResults: results.length, related: [] };\n } catch (error) {\n return {\n query,\n results: [],\n totalResults: 0,\n related: [],\n error: classifyError(error),\n };\n }\n }\n}\n\nfunction parseReaderContent(raw: string): { content: string } {\n try {\n const parsed: unknown = JSON.parse(raw);\n const data = readRecord(parsed, 'data');\n const content = readString(data, 'content');\n if (content) return { content };\n } catch {\n // Jina may return text/plain on some degraded paths; use it as markdown.\n }\n return { content: raw };\n}\n\nfunction emptyReaderResponse(statusCode: number, usageTokens: number | undefined): JinaConvertResponse {\n return {\n content: 'Jina returned an empty body',\n statusCode,\n credits: 0,\n usageTokens: Number.isFinite(usageTokens) ? usageTokens : undefined,\n error: {\n code: ErrorCode.UNSUPPORTED_BINARY_CONTENT,\n message: 'Jina Reader returned empty content for this URL',\n retryable: false,\n },\n };\n}\n\nfunction terminalReaderResponse(statusCode: number, content: string): JinaConvertResponse | null {\n if (statusCode === 401 || statusCode === 403) {\n return {\n content: `Jina auth/quota error (${statusCode}): ${content.slice(0, 200)}`,\n statusCode,\n credits: 0,\n error: {\n code: statusCode === 401 ? ErrorCode.AUTH_ERROR : ErrorCode.QUOTA_EXCEEDED,\n message: statusCode === 401\n ? 'Jina Reader auth failed \u2014 check JINA_API_KEY'\n : 'Jina Reader quota exceeded',\n retryable: false,\n statusCode,\n },\n };\n }\n\n if (statusCode === 404) {\n return {\n content: 'Jina could not fetch the target URL (404)',\n statusCode: 404,\n credits: 0,\n error: {\n code: ErrorCode.NOT_FOUND,\n message: 'Target URL not reachable by Jina Reader',\n retryable: false,\n statusCode: 404,\n },\n };\n }\n\n if (statusCode >= 400 && statusCode < 500 && statusCode !== 429) {\n return {\n content: `Jina Reader error (${statusCode}): ${content.slice(0, 200)}`,\n statusCode,\n credits: 0,\n error: {\n code: ErrorCode.INVALID_INPUT,\n message: `Jina Reader returned ${statusCode}`,\n retryable: false,\n statusCode,\n },\n };\n }\n\n return null;\n}\n\nexport function isTerminalReaderError(error: StructuredError): boolean {\n return !error.retryable\n && (\n error.code === ErrorCode.AUTH_ERROR\n || error.code === ErrorCode.QUOTA_EXCEEDED\n || error.code === ErrorCode.NOT_FOUND\n || error.code === ErrorCode.INVALID_INPUT\n );\n}\n\nfunction parseSearchResults(raw: string): QuerySearchResult['results'] {\n let data: unknown;\n try {\n const parsed: unknown = JSON.parse(raw);\n data = readUnknown(parsed, 'data');\n } catch {\n data = parseMarkdownSearchResults(raw);\n }\n\n const items = Array.isArray(data) ? data : [];\n return items\n .map((item, index) => normalizeSearchItem(item, index))\n .filter((item): item is QuerySearchResult['results'][number] => item !== null)\n .slice(0, SEARCH_RESULTS_PER_QUERY);\n}\n\nfunction normalizeSearchItem(item: unknown, index: number): QuerySearchResult['results'][number] | null {\n const link = readString(item, 'url') ?? readString(item, 'link');\n if (!link) return null;\n return {\n title: readString(item, 'title') || link,\n link,\n snippet: (\n readString(item, 'snippet')\n || readString(item, 'description')\n || readString(item, 'content')\n || ''\n ).slice(0, 500),\n date: readString(item, 'date') ?? readString(item, 'publishedTime'),\n position: index + 1,\n };\n}\n\nfunction parseMarkdownSearchResults(raw: string): Array<Record<string, string>> {\n const items: Array<Record<string, string>> = [];\n const markdownLink = /\\[([^\\]]+)\\]\\((https?:\\/\\/[^)]+)\\)/g;\n let match: RegExpExecArray | null;\n while ((match = markdownLink.exec(raw)) !== null && items.length < SEARCH_RESULTS_PER_QUERY) {\n const title = match[1];\n const url = match[2];\n if (title && url) items.push({ title, url });\n }\n return items;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction readUnknown(value: unknown, key: string): unknown {\n return isRecord(value) ? value[key] : undefined;\n}\n\nfunction readRecord(value: unknown, key: string): Record<string, unknown> | undefined {\n const child = readUnknown(value, key);\n return isRecord(child) ? child : undefined;\n}\n\nfunction readString(value: unknown, key: string): string | undefined {\n const child = readUnknown(value, key);\n return typeof child === 'string' ? child : undefined;\n}\n", "/**\n * Markdown cleaner service using Turndown for HTML to Markdown conversion\n */\nimport { Logger } from 'mcp-use';\nimport TurndownService from 'turndown';\n\nconst logger = Logger.get('markdown-cleaner');\n\nconst turndown = new TurndownService({\n headingStyle: 'atx',\n codeBlockStyle: 'fenced',\n bulletListMarker: '-',\n});\n\n// Remove script, style, nav, footer, aside elements\nturndown.remove(['script', 'style', 'nav', 'footer', 'aside', 'noscript']);\n\n/** Maximum HTML content length in characters \u2014 prevents event loop blocking on huge pages */\nconst MAX_CONTENT_LENGTH = 524_288 as const; // 512 * 1024\n\n/**\n * Remove HTML comments using linear-time indexOf loop.\n * Avoids catastrophic backtracking from /<!--[\\s\\S]*?-->/g on malformed HTML.\n */\nfunction removeHtmlComments(html: string): string {\n const parts: string[] = [];\n let pos = 0;\n while (pos < html.length) {\n const start = html.indexOf('<!--', pos);\n if (start === -1) { parts.push(html.substring(pos)); break; }\n if (start > pos) parts.push(html.substring(pos, start));\n const end = html.indexOf('-->', start + 4);\n if (end === -1) {\n parts.push(html.substring(start)); // preserve unclosed comment + rest\n break;\n }\n pos = end + 3;\n }\n return parts.join('');\n}\n\nexport class MarkdownCleaner {\n /**\n * Process HTML content and convert to clean Markdown\n * NEVER throws - returns original content on any error for graceful degradation\n */\n processContent(htmlContent: string): string {\n try {\n // Handle null/undefined/non-string inputs gracefully\n if (!htmlContent || typeof htmlContent !== 'string') {\n return htmlContent || '';\n }\n\n // If already markdown (no HTML tags), return as-is\n if (!htmlContent.includes('<')) {\n return htmlContent.trim();\n }\n\n // Truncate oversized HTML to prevent blocking the event loop\n if (htmlContent.length > MAX_CONTENT_LENGTH) {\n htmlContent = htmlContent.substring(0, MAX_CONTENT_LENGTH);\n }\n\n // Remove HTML comments before conversion (linear-time)\n let content = removeHtmlComments(htmlContent);\n\n // Convert HTML to Markdown using Turndown\n content = turndown.turndown(content);\n\n // Clean up whitespace\n content = content.replace(/\\n{3,}/g, '\\n\\n');\n content = content.trim();\n\n return content;\n } catch (error) {\n // Log error but don't crash - return original content for graceful degradation\n logger.warn(\n `processContent failed: ${error instanceof Error ? error.message : String(error)} | Content length: ${htmlContent?.length ?? 0}`,\n );\n // Return original content if conversion fails\n return htmlContent || '';\n }\n }\n}\n", "/**\n * Markdown formatting utilities\n */\n\nexport function removeMetaTags(content: string): string {\n if (!content || typeof content !== 'string') {\n return content;\n }\n\n const lines = content.split('\\n');\n const filteredLines = lines.filter(line => {\n const trimmed = line.trim();\n return !trimmed.startsWith('- Meta:') && !trimmed.startsWith('Meta:');\n });\n\n return filteredLines.join('\\n');\n}\n", "/**\n * Content Extractor \u2014 strips HTML chrome (cookie banners, nav, footer,\n * repeated hero blocks) before scraped pages reach the LLM extractor or the\n * raw fallback path. Both paths benefit equally \u2014 the cleaner the content,\n * the less LLM tokens are spent on noise and the less raw HTML the agent has\n * to reason around.\n *\n * Implementation: Mozilla Readability over jsdom. Falls back to the original\n * HTML if Readability cannot identify an article body \u2014 never throws.\n *\n * See: docs/code-review/context/02-current-tool-surface.md (E5) for the\n * baseline 12,704-char Merge blog probe; this module's acceptance bar is\n * <8,000 chars on the same input.\n */\n\nimport { Readability } from '@mozilla/readability';\nimport { JSDOM, VirtualConsole } from 'jsdom';\nimport { mcpLog } from './logger.js';\n\nexport interface ExtractedContent {\n /** Page title \u2014 if Readability could identify one. */\n readonly title: string;\n /** Main article content (HTML, ready for HTML\u2192Markdown conversion). */\n readonly content: string;\n /** Author byline if extractable. */\n readonly byline?: string;\n /** True if Readability ran successfully; false on fallback. */\n readonly extracted: boolean;\n}\n\n/** Maximum HTML length we attempt to feed jsdom. Larger pages are passed\n * through unchanged \u2014 Readability + jsdom are O(n) but allocate a real DOM\n * per call which can balloon RSS on a 5MB SPA bundle. */\nconst MAX_READABILITY_BYTES = 1_500_000 as const;\n\nexport function extractReadableContent(html: string, url?: string): ExtractedContent {\n if (!html || typeof html !== 'string') {\n return { title: '', content: html ?? '', extracted: false };\n }\n\n if (html.length > MAX_READABILITY_BYTES) {\n return { title: '', content: html, extracted: false };\n }\n\n // Quick heuristic \u2014 if there's no HTML structure, skip Readability entirely.\n if (!html.includes('<')) {\n return { title: '', content: html, extracted: false };\n }\n\n // jsdom emits noisy \"could not parse CSS\" / network errors on real pages.\n // Silence them so they do not pollute server-side stderr.\n const virtualConsole = new VirtualConsole();\n virtualConsole.on('error', () => {});\n virtualConsole.on('warn', () => {});\n virtualConsole.on('jsdomError', () => {});\n\n let dom: JSDOM;\n try {\n dom = new JSDOM(html, {\n url: url && /^https?:/i.test(url) ? url : 'https://example.com/',\n virtualConsole,\n });\n } catch (err) {\n mcpLog('warning', `JSDOM construction failed: ${err instanceof Error ? err.message : String(err)}`, 'content-extractor');\n return { title: '', content: html, extracted: false };\n }\n\n try {\n const reader = new Readability(dom.window.document, {\n // Keep classes that downstream cleanup may need; Turndown ignores them.\n keepClasses: false,\n // Strip <script>/<style> already handled by Readability defaults.\n });\n const article = reader.parse();\n if (!article || !article.content) {\n return { title: article?.title ?? '', content: html, extracted: false };\n }\n return {\n title: article.title ?? '',\n content: article.content,\n byline: article.byline ?? undefined,\n extracted: true,\n };\n } catch (err) {\n mcpLog('warning', `Readability.parse failed: ${err instanceof Error ? err.message : String(err)}`, 'content-extractor');\n return { title: '', content: html, extracted: false };\n } finally {\n // jsdom retains references via global refs \u2014 close the window to free RSS.\n try { dom.window.close(); } catch { /* ignore */ }\n }\n}\n", "/**\n * Hostname/path-heuristic source-type tagging. Works without the LLM\n * classifier so degraded-mode search responses still carry a\n * `source_type` field per result. When the LLM classifier IS available,\n * its tag wins (the classifier sees title + snippet as well, not just URL).\n *\n * See: mcp-revisions/output-shaping/06-source-type-tagging-without-llm.md.\n */\n\nexport type SourceType =\n | 'reddit'\n | 'github'\n | 'docs'\n | 'blog'\n | 'paper'\n | 'qa'\n | 'cve'\n | 'news'\n | 'video'\n | 'web';\n\nconst RULES: Array<[RegExp, SourceType]> = [\n // Reddit post permalinks (subreddit homepages are filtered out upstream).\n [/(?:^|\\.)reddit\\.com\\//i, 'reddit'],\n [/(?:^|\\.)github\\.com\\//i, 'github'],\n [/(?:^|\\.)gitlab\\.com\\//i, 'github'],\n // CVE-prefixed paths are unambiguous regardless of host.\n [/\\/CVE-\\d{4}-\\d+/i, 'cve'],\n [/(?:^|\\.)nvd\\.nist\\.gov\\//i, 'cve'],\n [/(?:^|\\.)stackoverflow\\.com\\//i, 'qa'],\n [/(?:^|\\.)stackexchange\\.com\\//i, 'qa'],\n [/(?:^|\\.)arxiv\\.org\\//i, 'paper'],\n [/(?:^|\\.)medium\\.com\\//i, 'blog'],\n [/(?:^|\\.)dev\\.to\\//i, 'blog'],\n [/(?:^|\\.)substack\\.com\\//i, 'blog'],\n // Docs subdomains and /docs/ paths.\n [/^(?:[a-z0-9-]+\\.)*docs\\./i, 'docs'],\n [/\\/docs\\//i, 'docs'],\n [/(?:^|\\.)readthedocs\\.io\\//i, 'docs'],\n // Video.\n [/(?:^|\\.)youtube\\.com\\/watch/i, 'video'],\n [/(?:^|\\.)youtu\\.be\\//i, 'video'],\n // News / engineering blogs (last so it doesn't capture vendor docs).\n [/(?:^|\\.)(?:news|blog|engineering)\\.[a-z0-9-]+\\.[a-z]{2,}\\//i, 'news'],\n];\n\nexport function classifySourceByUrl(url: string): SourceType {\n let candidate: string;\n try {\n const u = new URL(url);\n // Match against `host + pathname` so rules can use either or both.\n candidate = `${u.hostname}${u.pathname}`;\n } catch {\n candidate = url;\n }\n for (const [re, type] of RULES) {\n if (re.test(candidate)) return type;\n }\n return 'web';\n}\n\n// \u2500\u2500 Document-format detection (PDF / Office) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Scrape.do + Readability + Turndown assume HTML input. Binary document\n// formats need a markdown-extraction service (Jina Reader) instead. These two\n// helpers give the scrape pipeline both a pre-fetch gate (URL suffix) and a\n// post-fetch gate (response Content-Type header).\n\nconst DOCUMENT_PATH_SUFFIXES = [\n '.pdf',\n '.doc', '.docx',\n '.ppt', '.pptx',\n '.xls', '.xlsx',\n] as const;\n\n/**\n * Pre-fetch gate: does this URL's path end in a known binary-document suffix?\n * Case-insensitive. Trailing query strings / fragments are ignored \u2014 only the\n * pathname is inspected. Invalid URLs return false (handled upstream).\n */\nexport function isDocumentUrl(url: string): boolean {\n let pathname: string;\n try {\n pathname = new URL(url).pathname.toLowerCase();\n } catch {\n return false;\n }\n for (const suffix of DOCUMENT_PATH_SUFFIXES) {\n if (pathname.endsWith(suffix)) return true;\n }\n return false;\n}\n\nconst BINARY_CONTENT_TYPE_PREFIXES = [\n 'application/pdf',\n 'application/msword',\n 'application/vnd.openxmlformats-officedocument.',\n 'application/vnd.ms-excel',\n 'application/vnd.ms-powerpoint',\n 'application/octet-stream',\n] as const;\n\n/**\n * Post-fetch gate: does this Content-Type header indicate a binary document\n * that our HTML pipeline cannot decode? Returns false for HTML/JSON/plain text\n * and for unknown/missing content-types (the upstream pipeline can still try).\n */\nexport function isBinaryDocumentContentType(contentType: string | null | undefined): boolean {\n if (!contentType) return false;\n const lower = contentType.toLowerCase();\n for (const prefix of BINARY_CONTENT_TYPE_PREFIXES) {\n if (lower.startsWith(prefix)) return true;\n }\n return false;\n}\n", "export interface ContentQuality {\n readonly weak: boolean;\n readonly reason: string;\n readonly charCount: number;\n readonly wordCount: number;\n readonly blockPhrase?: string;\n}\n\nconst MIN_MARKDOWN_CHARS = 800 as const;\nconst MIN_MARKDOWN_WORDS = 120 as const;\n\nconst BLOCK_PHRASES = [\n 'access denied',\n 'are you a human',\n 'captcha',\n 'checking your browser',\n 'enable javascript',\n 'just a moment',\n 'login required',\n 'please enable cookies',\n 'please verify you are human',\n 'sign in to continue',\n 'temporarily unavailable',\n] as const;\n\nfunction countWords(content: string): number {\n const matches = content.trim().match(/\\S+/g);\n return matches ? matches.length : 0;\n}\n\nfunction findBlockPhrase(content: string): string | undefined {\n const lower = content.toLowerCase();\n return BLOCK_PHRASES.find((phrase) => lower.includes(phrase));\n}\n\nexport function assessMarkdownQuality(content: string): ContentQuality {\n const trimmed = content.trim();\n const charCount = trimmed.length;\n const wordCount = countWords(trimmed);\n const blockPhrase = findBlockPhrase(trimmed);\n\n if (blockPhrase) {\n return {\n weak: true,\n reason: `blocked_or_interstitial:${blockPhrase}`,\n charCount,\n wordCount,\n blockPhrase,\n };\n }\n\n if (charCount < MIN_MARKDOWN_CHARS) {\n return {\n weak: true,\n reason: `too_few_chars:${charCount}<${MIN_MARKDOWN_CHARS}`,\n charCount,\n wordCount,\n };\n }\n\n if (wordCount < MIN_MARKDOWN_WORDS) {\n return {\n weak: true,\n reason: `too_few_words:${wordCount}<${MIN_MARKDOWN_WORDS}`,\n charCount,\n wordCount,\n };\n }\n\n return {\n weak: false,\n reason: 'ok',\n charCount,\n wordCount,\n };\n}\n", "import { Effect, Layer } from 'effect';\n\nimport { JinaServiceLive, LlmServiceLive, SearchServiceLive } from './services.js';\n\nexport const BaseExternalLive = Layer.mergeAll(\n JinaServiceLive,\n SearchServiceLive,\n LlmServiceLive,\n);\n\nexport function runExternalEffect<A>(\n effect: Effect.Effect<A, unknown, unknown>,\n layer: Layer.Layer<any>,\n): Promise<A> {\n return Effect.runPromise(effect.pipe(Effect.provide(layer)));\n}\n", "import { Context, Effect, Layer } from 'effect';\n\nimport { SearchClient, type MultipleSearchResponse } from '../clients/search.js';\nimport { JinaClient, type JinaConvertRequest, type JinaConvertResponse } from '../clients/jina.js';\nimport { KernelClient, type KernelRenderRequest, type KernelRenderResponse } from '../clients/kernel.js';\nimport { RedditClient, type BatchPostResult } from '../clients/reddit.js';\nimport {\n createLLMProcessor,\n processContentWithLLM,\n classifySearchResults,\n suggestRefineQueriesForRawMode,\n generateResearchBrief,\n type ClassificationResult,\n type RefineQuerySuggestion,\n type ResearchBrief,\n} from '../services/llm-processor.js';\nimport { parseEnv } from '../config/index.js';\nimport { type StructuredError } from '../utils/errors.js';\nimport { providerError, type ProviderRequestError } from './errors.js';\n\nexport interface SearchServiceShape {\n readonly serperSearchMultiple: (queries: readonly string[]) => Effect.Effect<MultipleSearchResponse, ProviderRequestError>;\n readonly jinaSearchMultiple: (queries: readonly string[]) => Effect.Effect<MultipleSearchResponse, ProviderRequestError>;\n}\n\nexport interface JinaServiceShape {\n readonly convert: (request: JinaConvertRequest) => Effect.Effect<JinaConvertResponse, ProviderRequestError>;\n}\n\nexport interface KernelServiceShape {\n readonly render: (request: KernelRenderRequest) => Effect.Effect<KernelRenderResponse, ProviderRequestError>;\n}\n\nexport interface RedditServiceShape {\n readonly batchGetPosts: (urls: readonly string[], includeComments: boolean) => Effect.Effect<BatchPostResult, ProviderRequestError>;\n}\n\nexport interface LlmServiceShape {\n readonly extractContent: (\n content: string,\n config: { readonly enabled: boolean; readonly extract: string | undefined; readonly url?: string },\n ) => Effect.Effect<Awaited<ReturnType<typeof processContentWithLLM>>, ProviderRequestError>;\n readonly classifySearchResults: (\n rankedUrls: Parameters<typeof classifySearchResults>[0],\n objective: string,\n totalQueries: number,\n processor: Parameters<typeof classifySearchResults>[3],\n previousQueries?: readonly string[],\n ) => Effect.Effect<{ result: ClassificationResult | null; error?: string }, ProviderRequestError>;\n readonly suggestRefineQueriesForRawMode: (\n rankedUrls: Parameters<typeof suggestRefineQueriesForRawMode>[0],\n objective: string,\n originalQueries: readonly string[],\n processor: Parameters<typeof suggestRefineQueriesForRawMode>[3],\n ) => Effect.Effect<{ result: RefineQuerySuggestion[]; error?: string }, ProviderRequestError>;\n readonly generateResearchBrief: (\n goal: string,\n processor: Parameters<typeof generateResearchBrief>[1],\n ) => Effect.Effect<ResearchBrief | null, ProviderRequestError>;\n}\n\nexport const SearchService = Context.GenericTag<SearchServiceShape>('SearchService');\nexport const JinaService = Context.GenericTag<JinaServiceShape>('JinaService');\nexport const KernelService = Context.GenericTag<KernelServiceShape>('KernelService');\nexport const RedditService = Context.GenericTag<RedditServiceShape>('RedditService');\nexport const LlmService = Context.GenericTag<LlmServiceShape>('LlmService');\n\nexport const SearchServiceLive = Layer.sync(SearchService, () => {\n const env = parseEnv();\n return {\n serperSearchMultiple: (queries) =>\n Effect.tryPromise({\n try: () => new SearchClient(env.SEARCH_API_KEY).searchMultiple([...queries]),\n catch: (error) => providerError('serper', 'searchMultiple', error),\n }),\n jinaSearchMultiple: (queries) =>\n Effect.tryPromise({\n try: () => new JinaClient(env.JINA_API_KEY).searchMultiple([...queries]),\n catch: (error) => providerError('jina', 'searchMultiple', error),\n }),\n };\n});\n\nexport const JinaServiceLive = Layer.sync(JinaService, () => {\n const env = parseEnv();\n const client = new JinaClient(env.JINA_API_KEY);\n return {\n convert: (request) =>\n Effect.tryPromise({\n try: () => client.convert(request),\n catch: (error) => providerError('jina', 'convert', error),\n }),\n };\n});\n\nexport const KernelServiceLive = Layer.sync(KernelService, () => {\n const client = new KernelClient();\n return {\n render: (request) =>\n Effect.tryPromise({\n try: () => client.render(request),\n catch: (error) => providerError('kernel', 'render', error),\n }),\n };\n});\n\nexport function RedditServiceLive(clientId: string, clientSecret: string) {\n return Layer.sync(RedditService, () => {\n const client = new RedditClient(clientId, clientSecret);\n return {\n batchGetPosts: (urls, includeComments) =>\n Effect.tryPromise({\n try: () => client.batchGetPosts([...urls], includeComments),\n catch: (error) => providerError('reddit', 'batchGetPosts', error),\n }),\n };\n });\n}\n\nexport const LlmServiceLive = Layer.succeed(LlmService, {\n extractContent: (content, config) =>\n Effect.tryPromise({\n try: () => processContentWithLLM(content, config, createLLMProcessor()),\n catch: (error) => providerError('llm', 'extractContent', error),\n }),\n classifySearchResults: (rankedUrls, objective, totalQueries, processor, previousQueries) =>\n Effect.tryPromise({\n try: () => classifySearchResults(rankedUrls, objective, totalQueries, processor, previousQueries),\n catch: (error) => providerError('llm', 'classifySearchResults', error),\n }),\n suggestRefineQueriesForRawMode: (rankedUrls, objective, originalQueries, processor) =>\n Effect.tryPromise({\n try: () => suggestRefineQueriesForRawMode(rankedUrls, objective, originalQueries, processor),\n catch: (error) => providerError('llm', 'suggestRefineQueriesForRawMode', error),\n }),\n generateResearchBrief: (goal, processor) =>\n Effect.tryPromise({\n try: () => generateResearchBrief(goal, processor),\n catch: (error) => providerError('llm', 'generateResearchBrief', error),\n }),\n});\n\nexport function structuredFromProviderError(error: ProviderRequestError): StructuredError {\n return error.error;\n}\n", "/**\n * Thin wrappers around the `p-map` library for bounded parallel execution.\n * Keeps the internal API stable (`pMap` / `pMapSettled`) while delegating\n * scheduling and AbortSignal handling to `p-map@7`.\n */\n\nimport pMapLib from 'p-map';\n\n/**\n * Run `mapper` over `items` with at most `concurrency` in flight.\n * Rejects with the first error the mapper throws \u2014 matches the prior\n * hand-rolled semantics. Results preserve input order.\n */\nexport async function pMap<T, R>(\n items: readonly T[],\n mapper: (item: T, index: number) => Promise<R>,\n concurrency: number = 6,\n signal?: AbortSignal,\n): Promise<R[]> {\n if (items.length === 0) return [];\n const limit = Math.max(1, Math.min(concurrency, items.length));\n return pMapLib(items, mapper, { concurrency: limit, signal });\n}\n\n/**\n * Like `pMap` but never rejects \u2014 mapper errors surface as\n * `{ status: 'rejected', reason }` entries. Useful when one per-item\n * failure must not cancel the others (e.g. per-URL scraping).\n */\nexport async function pMapSettled<T, R>(\n items: readonly T[],\n mapper: (item: T, index: number) => Promise<R>,\n concurrency: number = 6,\n signal?: AbortSignal,\n): Promise<PromiseSettledResult<R>[]> {\n if (items.length === 0) return [];\n const limit = Math.max(1, Math.min(concurrency, items.length));\n return pMapLib(\n items,\n async (item, index): Promise<PromiseSettledResult<R>> => {\n try {\n const value = await mapper(item, index);\n return { status: 'fulfilled', value };\n } catch (reason) {\n return { status: 'rejected', reason };\n }\n },\n { concurrency: limit, signal, stopOnError: false },\n );\n}\n", "/**\n * Web Search Client\n * Generic interface for web search via Google (Serper implementation)\n * Implements robust error handling that NEVER crashes\n */\n\nimport { parseEnv, CONCURRENCY } from '../config/index.js';\nimport {\n classifyError,\n fetchWithTimeout,\n sleep,\n ErrorCode,\n type StructuredError,\n} from '../utils/errors.js';\nimport { calculateBackoff } from '../utils/retry.js';\nimport { pMap } from '../utils/concurrency.js';\nimport { mcpLog } from '../utils/logger.js';\n\n// \u2500\u2500 Constants \u2500\u2500\n\nconst SERPER_API_URL = 'https://google.serper.dev/search' as const;\nconst DEFAULT_RESULTS_PER_QUERY = 10 as const;\nconst MAX_RETRIES = 3 as const;\n\n// \u2500\u2500 Data Interfaces \u2500\u2500\n\ninterface SearchResult {\n readonly title: string;\n readonly link: string;\n readonly snippet: string;\n readonly date?: string;\n readonly position: number;\n}\n\nexport interface QuerySearchResult {\n readonly query: string;\n readonly results: SearchResult[];\n readonly totalResults: number;\n readonly related: string[];\n readonly error?: StructuredError;\n}\n\nexport interface MultipleSearchResponse {\n readonly searches: QuerySearchResult[];\n readonly totalQueries: number;\n readonly executionTime: number;\n readonly error?: StructuredError;\n}\n\nexport interface RedditSearchResult {\n readonly title: string;\n readonly url: string;\n readonly snippet: string;\n readonly date?: string;\n}\n\n// \u2500\u2500 Retry Configuration \u2500\u2500\n\nconst SEARCH_RETRY_CONFIG = {\n maxRetries: MAX_RETRIES,\n baseDelayMs: 1000,\n maxDelayMs: 10000,\n timeoutMs: 30000,\n} as const;\n\nconst RETRYABLE_SEARCH_CODES = new Set([429, 500, 502, 503, 504]);\n\n// Pre-compiled regex patterns for Reddit search\nconst REDDIT_SITE_REGEX = /site:\\s*reddit\\.com/i;\nconst REDDIT_SUBREDDIT_SUFFIX_REGEX = / : r\\/\\w+$/;\nconst REDDIT_SUFFIX_REGEX = / - Reddit$/;\n\n// \u2500\u2500 Helper: Parse Serper search responses into structured results \u2500\u2500\n\nfunction parseSearchResponses(\n responses: Array<Record<string, unknown>>,\n queries: string[],\n): QuerySearchResult[] {\n return responses.map((resp, index) => {\n try {\n const organic = (resp.organic || []) as Array<Record<string, unknown>>;\n const results: SearchResult[] = organic.map((item, idx) => ({\n title: (item.title as string) || 'No title',\n link: (item.link as string) || '#',\n snippet: (item.snippet as string) || '',\n date: item.date as string | undefined,\n position: (item.position as number) || idx + 1,\n }));\n\n const searchInfo = resp.searchInformation as Record<string, unknown> | undefined;\n const totalResults = searchInfo?.totalResults\n ? parseInt(String(searchInfo.totalResults).replace(/,/g, ''), 10)\n : results.length;\n\n const relatedSearches = (resp.relatedSearches || []) as Array<Record<string, unknown>>;\n const related = relatedSearches.map((r) => (r.query as string) || '');\n\n return { query: queries[index] || '', results, totalResults, related };\n } catch {\n return { query: queries[index] || '', results: [], totalResults: 0, related: [] };\n }\n });\n}\n\n// \u2500\u2500 Helper: Execute search API call with retry \u2500\u2500\n\nasync function executeSearchWithRetry(\n apiKey: string,\n body: unknown,\n isRetryable: (status?: number, error?: unknown) => boolean,\n): Promise<{ data: unknown; error?: StructuredError }> {\n let lastError: StructuredError | undefined;\n\n for (let attempt = 0; attempt <= SEARCH_RETRY_CONFIG.maxRetries; attempt++) {\n try {\n if (attempt > 0) {\n mcpLog('warning', `Retry attempt ${attempt}/${SEARCH_RETRY_CONFIG.maxRetries}`, 'search');\n }\n\n const response = await fetchWithTimeout(SERPER_API_URL, {\n method: 'POST',\n headers: {\n 'X-API-KEY': apiKey,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(body),\n timeoutMs: SEARCH_RETRY_CONFIG.timeoutMs,\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => '');\n lastError = classifyError({ status: response.status, message: errorText });\n\n if (isRetryable(response.status) && attempt < SEARCH_RETRY_CONFIG.maxRetries) {\n const delayMs = calculateBackoff(attempt, SEARCH_RETRY_CONFIG.baseDelayMs, SEARCH_RETRY_CONFIG.maxDelayMs);\n mcpLog('warning', `API returned ${response.status}, retrying in ${delayMs}ms...`, 'search');\n await sleep(delayMs);\n continue;\n }\n\n return { data: undefined, error: lastError };\n }\n\n try {\n const data = await response.json();\n return { data };\n } catch {\n return {\n data: undefined,\n error: { code: ErrorCode.PARSE_ERROR, message: 'Failed to parse search response', retryable: false },\n };\n }\n } catch (error) {\n lastError = classifyError(error);\n\n if (isRetryable(undefined, error) && attempt < SEARCH_RETRY_CONFIG.maxRetries) {\n const delayMs = calculateBackoff(attempt, SEARCH_RETRY_CONFIG.baseDelayMs, SEARCH_RETRY_CONFIG.maxDelayMs);\n mcpLog('warning', `${lastError.code}: ${lastError.message}, retrying in ${delayMs}ms...`, 'search');\n await sleep(delayMs);\n continue;\n }\n\n return { data: undefined, error: lastError };\n }\n }\n\n return {\n data: undefined,\n error: lastError || { code: ErrorCode.UNKNOWN_ERROR, message: 'Search failed', retryable: false },\n };\n}\n\n// \u2500\u2500 SearchClient \u2500\u2500\n\nexport class SearchClient {\n private apiKey: string;\n\n constructor(apiKey?: string) {\n const env = parseEnv();\n this.apiKey = apiKey || env.SEARCH_API_KEY || '';\n\n if (!this.apiKey) {\n throw new Error('Web search capability is not configured. Please set up the required API credentials.');\n }\n }\n\n /**\n * Check if error is retryable\n */\n private isRetryable(status?: number, error?: unknown): boolean {\n if (status && RETRYABLE_SEARCH_CODES.has(status)) return true;\n\n if (error == null) return false;\n const message = (typeof error === 'object' && 'message' in error && typeof (error as { message?: string }).message === 'string')\n ? (error as { message: string }).message.toLowerCase()\n : '';\n return message.includes('timeout') || message.includes('rate limit') || message.includes('connection');\n }\n\n /**\n * Search multiple queries in parallel\n * NEVER throws - always returns a valid response\n */\n async searchMultiple(queries: string[]): Promise<MultipleSearchResponse> {\n const startTime = Date.now();\n\n if (queries.length === 0) {\n return {\n searches: [],\n totalQueries: 0,\n executionTime: 0,\n error: { code: ErrorCode.INVALID_INPUT, message: 'No queries provided', retryable: false },\n };\n }\n\n const searchQueries = queries.map(query => ({ q: query }));\n const { data, error } = await executeSearchWithRetry(\n this.apiKey,\n searchQueries,\n (status, err) => this.isRetryable(status, err),\n );\n\n if (error || data === undefined) {\n return {\n searches: [],\n totalQueries: queries.length,\n executionTime: Date.now() - startTime,\n error: error ?? { code: ErrorCode.UNKNOWN_ERROR, message: 'Search provider returned no data', retryable: false },\n };\n }\n\n const responses = Array.isArray(data) ? data : [data];\n const searches = parseSearchResponses(responses as Array<Record<string, unknown>>, queries);\n\n return { searches, totalQueries: queries.length, executionTime: Date.now() - startTime };\n }\n\n /**\n * Search Reddit via Google (adds site:reddit.com automatically)\n * NEVER throws - returns empty array on failure\n */\n async searchReddit(query: string, dateAfter?: string): Promise<RedditSearchResult[]> {\n if (!query?.trim()) {\n return [];\n }\n\n let q = query.replace(REDDIT_SITE_REGEX, '').trim() + ' site:reddit.com';\n\n if (dateAfter) {\n q += ` after:${dateAfter}`;\n }\n\n for (let attempt = 0; attempt <= SEARCH_RETRY_CONFIG.maxRetries; attempt++) {\n try {\n const res = await fetchWithTimeout(SERPER_API_URL, {\n method: 'POST',\n headers: { 'X-API-KEY': this.apiKey, 'Content-Type': 'application/json' },\n body: JSON.stringify({ q, num: DEFAULT_RESULTS_PER_QUERY }),\n timeoutMs: SEARCH_RETRY_CONFIG.timeoutMs,\n });\n\n if (!res.ok) {\n if (this.isRetryable(res.status) && attempt < SEARCH_RETRY_CONFIG.maxRetries) {\n const delayMs = calculateBackoff(attempt, SEARCH_RETRY_CONFIG.baseDelayMs, SEARCH_RETRY_CONFIG.maxDelayMs);\n mcpLog('warning', `Reddit search ${res.status}, retrying in ${delayMs}ms...`, 'search');\n await sleep(delayMs);\n continue;\n }\n mcpLog('error', `Reddit search failed with status ${res.status}`, 'search');\n return [];\n }\n\n const data = await res.json() as { organic?: Array<{ title: string; link: string; snippet: string; date?: string }> };\n return (data.organic || []).map((r) => ({\n title: (r.title || '').replace(REDDIT_SUBREDDIT_SUFFIX_REGEX, '').replace(REDDIT_SUFFIX_REGEX, ''),\n url: r.link || '',\n snippet: r.snippet || '',\n date: r.date,\n }));\n\n } catch (error) {\n const err = classifyError(error);\n if (this.isRetryable(undefined, error) && attempt < SEARCH_RETRY_CONFIG.maxRetries) {\n const delayMs = calculateBackoff(attempt, SEARCH_RETRY_CONFIG.baseDelayMs, SEARCH_RETRY_CONFIG.maxDelayMs);\n mcpLog('warning', `Reddit search ${err.code}, retrying in ${delayMs}ms...`, 'search');\n await sleep(delayMs);\n continue;\n }\n mcpLog('error', `Reddit search failed: ${err.message}`, 'search');\n return [];\n }\n }\n\n return [];\n }\n\n /**\n * Search Reddit with multiple queries (bounded concurrency)\n * NEVER throws - searchReddit never throws, pMap preserves order\n */\n async searchRedditMultiple(queries: string[], dateAfter?: string): Promise<Map<string, RedditSearchResult[]>> {\n if (queries.length === 0) {\n return new Map();\n }\n\n const results = await pMap(\n queries,\n q => this.searchReddit(q, dateAfter),\n CONCURRENCY.SEARCH\n );\n\n return new Map(queries.map((q, i) => [q, results[i] || []]));\n }\n}\n", "import Kernel from '@onkernel/sdk';\n\nimport { parseEnv } from '../config/index.js';\nimport { classifyError, ErrorCode, type StructuredError } from '../utils/errors.js';\nimport { mcpLog } from '../utils/logger.js';\n\nconst DEFAULT_RENDER_TIMEOUT_SECONDS = 15 as const;\nconst BROWSER_IDLE_TIMEOUT_SECONDS = 300 as const;\n\nexport interface KernelRenderRequest {\n readonly url: string;\n readonly timeoutSeconds?: number;\n}\n\nexport interface KernelRenderResponse {\n readonly content: string;\n readonly statusCode: number;\n readonly credits: 0;\n readonly finalUrl?: string;\n readonly title?: string;\n readonly text?: string;\n readonly error?: StructuredError;\n}\n\ninterface KernelRenderedPage {\n readonly html: string;\n readonly finalUrl: string;\n readonly title: string;\n readonly text: string;\n}\n\nexport class KernelClient {\n private readonly kernel: Kernel;\n\n constructor(apiKey?: string) {\n const env = parseEnv();\n const resolvedKey = apiKey?.trim() || env.KERNEL_API_KEY;\n if (!resolvedKey) {\n throw new Error('Kernel browser rendering is not configured. Set KERNEL_API_KEY.');\n }\n\n this.kernel = new Kernel({\n apiKey: resolvedKey,\n timeout: 30_000,\n maxRetries: 1,\n ...(env.KERNEL_PROJECT\n ? { defaultHeaders: { 'X-Kernel-Project-Id': env.KERNEL_PROJECT } }\n : {}),\n });\n }\n\n async render(request: KernelRenderRequest): Promise<KernelRenderResponse> {\n const { url, timeoutSeconds = DEFAULT_RENDER_TIMEOUT_SECONDS } = request;\n try {\n new URL(url);\n } catch {\n return {\n content: `Invalid URL: ${url}`,\n statusCode: 400,\n credits: 0,\n error: { code: ErrorCode.INVALID_INPUT, message: `Invalid URL: ${url}`, retryable: false },\n };\n }\n\n let sessionId: string | undefined;\n try {\n const session = await this.kernel.browsers.create({\n headless: true,\n stealth: true,\n timeout_seconds: BROWSER_IDLE_TIMEOUT_SECONDS,\n viewport: { width: 1280, height: 800 },\n });\n sessionId = session.session_id;\n\n const response = await this.kernel.browsers.playwright.execute(session.session_id, {\n code: buildRenderScript(url, timeoutSeconds),\n timeout_sec: Math.min(timeoutSeconds + 5, 300),\n });\n\n if (!response.success) {\n const message = response.error || response.stderr || 'Kernel Playwright execution failed';\n return {\n content: `Kernel render failed: ${message}`,\n statusCode: 500,\n credits: 0,\n error: { code: ErrorCode.SERVICE_UNAVAILABLE, message, retryable: true },\n };\n }\n\n const rendered = parseRenderedPage(response.result);\n if (!rendered) {\n return {\n content: 'Kernel render returned an invalid payload',\n statusCode: 500,\n credits: 0,\n error: {\n code: ErrorCode.PARSE_ERROR,\n message: 'Kernel render returned an invalid payload',\n retryable: false,\n },\n };\n }\n\n return {\n content: rendered.html,\n statusCode: 200,\n credits: 0,\n finalUrl: rendered.finalUrl,\n title: rendered.title,\n text: rendered.text,\n };\n } catch (error) {\n const err = formatKernelError(error);\n return {\n content: `Kernel render failed: ${err.message}`,\n statusCode: err.statusCode ?? 500,\n credits: 0,\n error: err,\n };\n } finally {\n if (sessionId) {\n try {\n await this.kernel.browsers.deleteByID(sessionId);\n } catch (deleteError) {\n const err = formatKernelError(deleteError);\n mcpLog('warning', `Kernel browser cleanup failed for ${sessionId}: ${err.message}`, 'kernel');\n }\n }\n }\n }\n}\n\nfunction buildRenderScript(url: string, timeoutSeconds: number): string {\n const timeoutMs = timeoutSeconds * 1000;\n return `\nconst targetUrl = ${JSON.stringify(url)};\nconst timeoutMs = ${timeoutMs};\nawait page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: timeoutMs });\nawait page.waitForLoadState('networkidle', { timeout: Math.min(5000, timeoutMs) }).catch(() => {});\nconst html = await page.content();\nconst text = await page.locator('body').innerText({ timeout: 2000 }).catch(() => '');\nreturn {\n html,\n text,\n title: await page.title(),\n finalUrl: page.url(),\n};\n`;\n}\n\nfunction parseRenderedPage(value: unknown): KernelRenderedPage | null {\n if (!isRecord(value)) return null;\n const html = readString(value, 'html');\n const finalUrl = readString(value, 'finalUrl');\n const title = readString(value, 'title');\n const text = readString(value, 'text');\n if (!html || !finalUrl || title === undefined || text === undefined) return null;\n return { html, finalUrl, title, text };\n}\n\nfunction formatKernelError(error: unknown): StructuredError {\n if (error instanceof Kernel.APIError) {\n return {\n ...classifyError({ status: error.status, message: error.message }),\n cause: error.message,\n };\n }\n return classifyError(error);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction readString(value: Record<string, unknown>, key: string): string | undefined {\n const child = value[key];\n return typeof child === 'string' ? child : undefined;\n}\n", "/**\n * Reddit OAuth API Client\n * Fetches posts and comments sorted by score (most upvoted first)\n * Implements robust error handling that NEVER crashes\n */\n\nimport { Logger } from 'mcp-use';\n\nimport { REDDIT, CONCURRENCY } from '../config/index.js';\nimport { USER_AGENT_VERSION } from '../version.js';\nimport { calculateBackoff } from '../utils/retry.js';\nimport {\n classifyError,\n fetchWithTimeout,\n sleep,\n ErrorCode,\n type StructuredError,\n} from '../utils/errors.js';\nimport { pMap, pMapSettled } from '../utils/concurrency.js';\nimport { mcpLog } from '../utils/logger.js';\n\n// \u2500\u2500 Constants \u2500\u2500\n\nconst REDDIT_TOKEN_URL = 'https://www.reddit.com/api/v1/access_token' as const;\nconst REDDIT_API_BASE = 'https://oauth.reddit.com' as const;\nconst TOKEN_EXPIRY_MS = 55_000 as const; // 55 second expiry (conservative)\n\n// \u2500\u2500 Data Interfaces \u2500\u2500\n\ninterface Post {\n readonly title: string;\n readonly author: string;\n readonly subreddit: string;\n readonly body: string;\n readonly score: number;\n readonly commentCount: number;\n readonly url: string;\n readonly created: Date;\n readonly flair?: string;\n readonly isNsfw: boolean;\n readonly isPinned: boolean;\n}\n\nexport interface Comment {\n readonly author: string;\n readonly body: string;\n readonly score: number;\n readonly depth: number;\n readonly isOP: boolean;\n}\n\nexport interface PostResult {\n readonly post: Post;\n readonly comments: Comment[];\n readonly actualComments: number;\n}\n\nexport interface BatchPostResult {\n readonly results: Map<string, PostResult | Error>;\n readonly batchesProcessed: number;\n readonly totalPosts: number;\n readonly rateLimitHits: number;\n}\n\n/** Reddit API \"Listing\" wrapper */\ninterface RedditListing<T> {\n readonly kind: string;\n readonly data: {\n readonly children: ReadonlyArray<{ readonly kind: string; readonly data: T }>;\n readonly after?: string;\n readonly before?: string;\n };\n}\n\n/** Reddit post data from API */\ninterface RedditPostData {\n readonly title: string;\n readonly selftext: string;\n readonly selftext_html?: string;\n readonly author: string;\n readonly subreddit: string;\n readonly score: number;\n readonly upvote_ratio: number;\n readonly num_comments: number;\n readonly created_utc: number;\n readonly url: string;\n readonly permalink: string;\n readonly is_self: boolean;\n readonly over_18: boolean;\n readonly stickied: boolean;\n readonly link_flair_text?: string;\n readonly [key: string]: unknown;\n}\n\n/** Reddit comment data from API */\ninterface RedditCommentData {\n readonly body?: string;\n readonly author?: string;\n readonly score?: number;\n readonly created_utc?: number;\n readonly replies?: RedditListing<RedditCommentData> | string;\n readonly [key: string]: unknown;\n}\n\ntype RedditPostResponse = [RedditListing<RedditPostData>, RedditListing<RedditCommentData>];\n\n/** Max comments to fetch per post from Reddit API */\n/** Reddit API caps at 500 comments per request */\nconst FETCH_LIMIT = 500;\n\n// ============================================================================\n// Module-Level Token Cache (shared across all RedditClient instances)\n// ============================================================================\nlet cachedToken: string | null = null;\nlet cachedTokenExpiry = 0;\n\n// Token cache logging only when DEBUG env is set\nconst DEBUG_TOKEN_CACHE = process.env.DEBUG_REDDIT === 'true';\nconst clientLogger = Logger.get('reddit-client');\n\n// Pending auth promise for deduplicating concurrent auth calls\nlet pendingAuthPromise: Promise<string | null> | null = null;\n\n// \u2500\u2500 Decomposed Helpers \u2500\u2500\n\n/**\n * Fetch a Reddit post's JSON from the API\n */\nasync function fetchRedditJson(\n sub: string,\n id: string,\n token: string,\n userAgent: string,\n): Promise<RedditPostResponse> {\n const limit = Math.min(FETCH_LIMIT, 500);\n const apiUrl = `${REDDIT_API_BASE}/r/${sub}/comments/${id}?sort=top&limit=${limit}&depth=10&raw_json=1`;\n\n const res = await fetchWithTimeout(apiUrl, {\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'User-Agent': userAgent,\n },\n timeoutMs: 30000,\n });\n\n if (res.status === 429) {\n const err = new Error('Rate limited by Reddit API');\n (err as Error & { status: number }).status = 429;\n throw err;\n }\n\n if (res.status === 404) {\n throw new Error(`Post not found: /r/${sub}/comments/${id}`);\n }\n\n if (!res.ok) {\n const err = new Error(`Reddit API error: ${res.status}`);\n (err as Error & { status: number }).status = res.status;\n throw err;\n }\n\n try {\n return await res.json() as RedditPostResponse;\n } catch {\n throw new Error('Failed to parse Reddit API response');\n }\n}\n\n/**\n * Extract structured post data from a Reddit listing\n */\nfunction parsePostData(\n postListing: RedditListing<RedditPostData>,\n sub: string,\n): Post {\n const p = postListing?.data?.children?.[0]?.data;\n if (!p) {\n throw new Error(`Post data not found in response for /r/${sub}`);\n }\n\n return {\n title: p.title || 'Untitled',\n author: p.author || '[deleted]',\n subreddit: p.subreddit || sub,\n body: formatBody(p),\n score: p.score || 0,\n commentCount: p.num_comments || 0,\n url: `https://reddit.com${p.permalink || ''}`,\n created: new Date((p.created_utc || 0) * 1000),\n flair: p.link_flair_text || undefined,\n isNsfw: p.over_18 || false,\n isPinned: p.stickied || false,\n };\n}\n\nfunction formatBody(p: RedditPostData): string {\n if (p.selftext?.trim()) return p.selftext;\n if (p.is_self) return '';\n if (p.url) return `**Link:** ${p.url}`;\n return '';\n}\n\n/** Safety cap on comment tree recursion depth */\nconst MAX_COMMENT_DEPTH = 15 as const;\n\n/**\n * Extract and sort comments from a Reddit comment listing\n */\nfunction parseCommentTree(\n commentListing: RedditListing<RedditCommentData>,\n opAuthor: string,\n): Comment[] {\n const result: Comment[] = [];\n\n const extract = (items: ReadonlyArray<{ readonly kind: string; readonly data: RedditCommentData }>, depth = 0): void => {\n if (depth > MAX_COMMENT_DEPTH) return;\n const sorted = [...items].sort((a, b) => (b.data?.score || 0) - (a.data?.score || 0));\n\n for (const c of sorted) {\n if (c.kind !== 't1' || !c.data?.author || c.data.author === '[deleted]') continue;\n\n result.push({\n author: c.data.author,\n body: c.data.body || '',\n score: c.data.score || 0,\n depth,\n isOP: c.data.author === opAuthor,\n });\n\n if (typeof c.data.replies === 'object' && c.data.replies?.data?.children) {\n extract(c.data.replies.data.children, depth + 1);\n }\n }\n };\n\n extract(commentListing?.data?.children || []);\n return result;\n}\n\n// \u2500\u2500 Batch Helpers \u2500\u2500\n\n/**\n * Process a single batch of Reddit URLs, returning results keyed by URL\n */\nasync function processBatch(\n client: RedditClient,\n batchUrls: string[],\n): Promise<{ results: Map<string, PostResult | Error>; rateLimitHits: number }> {\n const results = new Map<string, PostResult | Error>();\n let rateLimitHits = 0;\n\n const batchResults = await pMapSettled(\n batchUrls,\n url => client.getPost(url),\n CONCURRENCY.REDDIT,\n );\n\n for (let i = 0; i < batchResults.length; i++) {\n const result = batchResults[i];\n if (!result) continue;\n const url = batchUrls[i] ?? '';\n\n if (result.status === 'fulfilled') {\n results.set(url, result.value);\n } else {\n const errorMsg = result.reason?.message || String(result.reason);\n if (errorMsg.includes('429') || errorMsg.includes('rate')) rateLimitHits++;\n results.set(url, new Error(errorMsg));\n }\n }\n\n return { results, rateLimitHits };\n}\n\n// \u2500\u2500 RedditClient Class \u2500\u2500\n\nexport class RedditClient {\n private userAgent = `script:${USER_AGENT_VERSION} (by /u/research-powerpack)`;\n\n constructor(private clientId: string, private clientSecret: string) {}\n\n /**\n * Authenticate with Reddit API with retry logic\n * Uses module-level token cache and promise deduplication to prevent\n * concurrent auth calls from firing multiple token requests\n * Returns null on failure instead of throwing\n */\n private async auth(): Promise<string | null> {\n if (cachedToken && Date.now() < cachedTokenExpiry - TOKEN_EXPIRY_MS) {\n if (DEBUG_TOKEN_CACHE) clientLogger.debug('Token cache HIT');\n return cachedToken;\n }\n\n if (pendingAuthPromise) {\n if (DEBUG_TOKEN_CACHE) clientLogger.debug('Auth already in flight, awaiting...');\n return pendingAuthPromise;\n }\n\n pendingAuthPromise = this.performAuth();\n try {\n return await pendingAuthPromise;\n } finally {\n pendingAuthPromise = null;\n }\n }\n\n private async performAuth(): Promise<string | null> {\n if (DEBUG_TOKEN_CACHE) clientLogger.debug('Token cache MISS - authenticating');\n\n const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64');\n\n for (let attempt = 0; attempt < 3; attempt++) {\n try {\n const res = await fetchWithTimeout(REDDIT_TOKEN_URL, {\n method: 'POST',\n headers: {\n 'Authorization': `Basic ${credentials}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'User-Agent': this.userAgent,\n },\n body: 'grant_type=client_credentials',\n timeoutMs: 15000,\n });\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n mcpLog('error', `Auth failed (${res.status}): ${text}`, 'reddit');\n\n if (res.status === 401 || res.status === 403) {\n cachedToken = null;\n cachedTokenExpiry = 0;\n return null;\n }\n\n if (res.status >= 500 && attempt < 2) {\n await sleep(calculateBackoff(attempt));\n continue;\n }\n\n return null;\n }\n\n const data = await res.json() as { access_token?: string; expires_in?: number };\n if (!data.access_token) {\n mcpLog('error', 'Auth response missing access_token', 'reddit');\n return null;\n }\n\n cachedToken = data.access_token;\n cachedTokenExpiry = Date.now() + (data.expires_in || 3600) * 1000;\n return cachedToken;\n\n } catch (error) {\n const err = classifyError(error);\n mcpLog('error', `Auth error (attempt ${attempt + 1}): ${err.message}`, 'reddit');\n\n if (err.code === ErrorCode.AUTH_ERROR) {\n cachedToken = null;\n cachedTokenExpiry = 0;\n }\n\n if (attempt < 2 && err.retryable) {\n await sleep(calculateBackoff(attempt));\n continue;\n }\n\n return null;\n }\n }\n\n return null;\n }\n\n private parseUrl(url: string): { sub: string; id: string } | null {\n const m = url.match(/reddit\\.com\\/r\\/([^\\/]+)\\/comments\\/([a-z0-9]+)/i);\n return m ? { sub: m[1]!, id: m[2]! } : null;\n }\n\n /**\n * Get a single Reddit post with comments\n * Returns PostResult or throws Error (for use with Promise.allSettled)\n */\n async getPost(url: string): Promise<PostResult> {\n const parsed = this.parseUrl(url);\n if (!parsed) {\n throw new Error(`Invalid Reddit URL format: ${url}`);\n }\n\n const token = await this.auth();\n if (!token) {\n throw new Error('Reddit authentication failed - check credentials');\n }\n\n let lastError: StructuredError | null = null;\n\n for (let attempt = 0; attempt < REDDIT.RETRY_COUNT; attempt++) {\n try {\n const data = await fetchRedditJson(parsed.sub, parsed.id, token, this.userAgent);\n const [postListing, commentListing] = data;\n\n const post = parsePostData(postListing, parsed.sub);\n const comments = parseCommentTree(commentListing, post.author);\n\n return { post, comments, actualComments: post.commentCount };\n\n } catch (error) {\n lastError = classifyError(error);\n\n // Rate limited \u2014 always retry with backoff\n const status = (error as Error & { status?: number }).status;\n if (status === 429) {\n const delay = REDDIT.RETRY_DELAYS[attempt] || 32000;\n mcpLog('warning', `Rate limited. Retry ${attempt + 1}/${REDDIT.RETRY_COUNT} after ${delay}ms`, 'reddit');\n await sleep(delay);\n continue;\n }\n\n if (!lastError.retryable) {\n throw error instanceof Error ? error : new Error(lastError.message);\n }\n\n if (attempt < REDDIT.RETRY_COUNT - 1) {\n const delay = REDDIT.RETRY_DELAYS[attempt] || 2000;\n mcpLog('warning', `${lastError.code}: ${lastError.message}. Retry ${attempt + 1}/${REDDIT.RETRY_COUNT}`, 'reddit');\n await sleep(delay);\n }\n }\n }\n\n throw new Error(lastError?.message || 'Failed to fetch Reddit post after retries');\n }\n\n async batchGetPosts(\n urls: string[],\n fetchComments = true,\n onBatchComplete?: (batchNum: number, totalBatches: number, processed: number) => void,\n ): Promise<BatchPostResult> {\n const allResults = new Map<string, PostResult | Error>();\n let rateLimitHits = 0;\n\n const totalBatches = Math.ceil(urls.length / REDDIT.BATCH_SIZE);\n mcpLog('info', `Fetching ${urls.length} posts in ${totalBatches} batch(es), up to ${FETCH_LIMIT} comments/post`, 'reddit');\n\n for (let batchNum = 0; batchNum < totalBatches; batchNum++) {\n const startIdx = batchNum * REDDIT.BATCH_SIZE;\n const batchUrls = urls.slice(startIdx, startIdx + REDDIT.BATCH_SIZE);\n\n mcpLog('info', `Batch ${batchNum + 1}/${totalBatches} (${batchUrls.length} posts)`, 'reddit');\n\n const batchResult = await processBatch(this, batchUrls);\n for (const [url, result] of batchResult.results) {\n allResults.set(url, result);\n }\n rateLimitHits += batchResult.rateLimitHits;\n\n try {\n onBatchComplete?.(batchNum + 1, totalBatches, allResults.size);\n } catch (callbackError) {\n mcpLog('error', `onBatchComplete callback error: ${callbackError}`, 'reddit');\n }\n\n mcpLog('info', `Batch ${batchNum + 1} complete (${allResults.size}/${urls.length})`, 'reddit');\n\n if (batchNum < totalBatches - 1) {\n await sleep(500);\n }\n }\n\n return { results: allResults, batchesProcessed: totalBatches, totalPosts: urls.length, rateLimitHits };\n }\n}\n", "import { Data } from 'effect';\n\nimport { classifyError, ErrorCode, type StructuredError } from '../utils/errors.js';\n\nexport type ExternalProvider =\n | 'serper'\n | 'jina'\n | 'reddit'\n | 'kernel'\n | 'llm';\n\nexport class ProviderRequestError extends Data.TaggedError('ProviderRequestError')<{\n readonly provider: ExternalProvider;\n readonly operation: string;\n readonly error: StructuredError;\n}> {}\n\nexport class ProviderTimeoutError extends Data.TaggedError('ProviderTimeoutError')<{\n readonly provider: ExternalProvider;\n readonly operation: string;\n readonly durationMs: number;\n}> {}\n\nexport class WeakContentError extends Data.TaggedError('WeakContentError')<{\n readonly provider: ExternalProvider;\n readonly operation: string;\n readonly reason: string;\n}> {}\n\nexport class AllStrategiesExhaustedError extends Data.TaggedError('AllStrategiesExhaustedError')<{\n readonly url: string;\n readonly attempts: readonly ProviderAttemptFailure[];\n}> {}\n\nexport interface ProviderAttemptFailure {\n readonly provider: ExternalProvider;\n readonly strategy: string;\n readonly message: string;\n readonly code?: string;\n}\n\nexport type ExternalRequestError =\n | ProviderRequestError\n | ProviderTimeoutError\n | WeakContentError\n | AllStrategiesExhaustedError;\n\nexport function providerError(\n provider: ExternalProvider,\n operation: string,\n error: unknown,\n): ProviderRequestError {\n return new ProviderRequestError({\n provider,\n operation,\n error: classifyError(error),\n });\n}\n\nexport function providerResponseError(\n provider: ExternalProvider,\n operation: string,\n message: string,\n code: StructuredError['code'] = ErrorCode.UNKNOWN_ERROR,\n retryable = false,\n): ProviderRequestError {\n return new ProviderRequestError({\n provider,\n operation,\n error: { code, message, retryable },\n });\n}\n\nexport function attemptFromError(\n provider: ExternalProvider,\n strategy: string,\n error: ProviderRequestError | ProviderTimeoutError | WeakContentError,\n): ProviderAttemptFailure {\n if (error._tag === 'ProviderTimeoutError') {\n return {\n provider,\n strategy,\n code: ErrorCode.TIMEOUT,\n message: `${error.operation} timed out after ${error.durationMs}ms`,\n };\n }\n if (error._tag === 'WeakContentError') {\n return {\n provider,\n strategy,\n code: 'WEAK_CONTENT',\n message: error.reason,\n };\n }\n return {\n provider,\n strategy,\n code: error.error.code,\n message: error.error.message,\n };\n}\n", "/**\n * MCP Response Formatters\n */\n\n/** Duration thresholds in milliseconds */\nconst SECONDS_MS = 1_000 as const;\nconst MINUTES_MS = 60_000 as const;\n\n// ============================================================================\n// Success Response Formatter\n// ============================================================================\n\nexport interface SuccessOptions {\n /** Title/header for the response */\n readonly title: string;\n /** Summary section (70% of content) */\n readonly summary: string;\n /** Optional data section (20% of content) */\n readonly data?: string;\n /** Optional next steps (10% of content) */\n readonly nextSteps?: string[];\n /** Optional metadata footer */\n readonly metadata?: Record<string, string | number>;\n}\n\n/**\n * Format a successful response using 70/20/10 pattern\n */\nexport function formatSuccess(opts: SuccessOptions): string {\n const parts: string[] = [];\n\n // Title\n parts.push(`\u2713 ${opts.title}`);\n parts.push('');\n\n // Summary (70%)\n parts.push(opts.summary);\n\n // Data section (20%)\n if (opts.data) {\n parts.push('');\n parts.push('---');\n parts.push(opts.data);\n }\n\n // Next steps (10%)\n if (opts.nextSteps?.length) {\n parts.push('');\n parts.push('---');\n parts.push('**Next Steps:**');\n opts.nextSteps.forEach(step => parts.push(`\u2192 ${step}`));\n }\n\n // Metadata footer\n if (opts.metadata && Object.keys(opts.metadata).length > 0) {\n parts.push('');\n parts.push('---');\n const metaStr = Object.entries(opts.metadata)\n .map(([k, v]) => `${k}: ${v}`)\n .join(' | ');\n parts.push(`*${metaStr}*`);\n }\n\n return parts.join('\\n');\n}\n\n// ============================================================================\n// Error Response Formatter\n// ============================================================================\n\nexport interface ErrorOptions {\n /** Error code (e.g., RATE_LIMITED, TIMEOUT) */\n readonly code: string;\n /** Human-readable error message */\n readonly message: string;\n /** Is this error retryable? */\n readonly retryable?: boolean;\n /** How to fix the error */\n readonly howToFix?: string[];\n /** Alternative actions */\n readonly alternatives?: string[];\n /** Tool name for context */\n readonly toolName?: string;\n}\n\n/**\n * Format an error response with recovery guidance\n * Designed to keep agents moving \u2014 every error includes actionable alternatives\n */\nexport function formatError(opts: ErrorOptions): string {\n const parts: string[] = [];\n\n // Error header\n const prefix = opts.toolName ? `[${opts.toolName}] ` : '';\n parts.push(`\u274C ${prefix}${opts.code}: ${opts.message}`);\n\n // Retryable hint\n if (opts.retryable) {\n parts.push('*Retryable.*');\n }\n\n // How to fix\n if (opts.howToFix?.length) {\n parts.push('');\n parts.push('**How to Fix:**');\n opts.howToFix.forEach((step, i) => parts.push(`${i + 1}. ${step}`));\n }\n\n // Alternatives\n if (opts.alternatives?.length) {\n parts.push('');\n parts.push('**Alternatives:**');\n opts.alternatives.forEach((alt, i) => parts.push(`${i + 1}. ${alt}`));\n }\n\n return parts.join('\\n');\n}\n\n// ============================================================================\n// Batch Header Formatter\n// ============================================================================\n\nexport interface BatchHeaderOptions {\n /** Batch operation title */\n readonly title: string;\n /** Total items attempted */\n readonly totalItems: number;\n /** Successfully processed count */\n readonly successful: number;\n /** Failed count */\n readonly failed: number;\n /** Optional tokens per item */\n readonly tokensPerItem?: number;\n /** Optional batch count */\n readonly batches?: number;\n /** Extra stats to include */\n readonly extras?: Record<string, string | number>;\n}\n\n/**\n * Format a batch operation header with stats\n */\nexport function formatBatchHeader(opts: BatchHeaderOptions): string {\n const parts: string[] = [];\n\n // Title with emoji based on success rate\n const successRate = opts.totalItems > 0 ? opts.successful / opts.totalItems : 0;\n const emoji = successRate === 1 ? '\u2713' : successRate >= 0.5 ? '\u26A0\uFE0F' : '\u274C';\n parts.push(`${emoji} ${opts.title}`);\n parts.push('');\n\n // Stats\n parts.push(`\u2022 Total: ${opts.totalItems}`);\n parts.push(`\u2022 Successful: ${opts.successful}`);\n if (opts.failed > 0) {\n parts.push(`\u2022 Failed: ${opts.failed}`);\n }\n if (opts.tokensPerItem) {\n parts.push(`\u2022 Tokens/item: ~${opts.tokensPerItem.toLocaleString()}`);\n }\n if (opts.batches) {\n parts.push(`\u2022 Batches: ${opts.batches}`);\n }\n\n // Extra stats\n if (opts.extras) {\n Object.entries(opts.extras).forEach(([key, val]) => {\n parts.push(`\u2022 ${key}: ${val}`);\n });\n }\n\n return parts.join('\\n');\n}\n\n// ============================================================================\n// Duration Formatter\n// ============================================================================\n\n/**\n * Format duration in human-readable form\n */\nexport function formatDuration(ms: number): string {\n if (ms < SECONDS_MS) return `${ms}ms`;\n if (ms < MINUTES_MS) return `${(ms / SECONDS_MS).toFixed(1)}s`;\n return `${(ms / MINUTES_MS).toFixed(1)}m`;\n}\n\n", "import { error, markdown, type TypedCallToolResult } from 'mcp-use/server';\n\ntype ClientLogLevel =\n | 'debug'\n | 'info'\n | 'notice'\n | 'warning'\n | 'error'\n | 'critical'\n | 'alert'\n | 'emergency';\n\ninterface ReporterContext {\n log(level: ClientLogLevel, message: string, loggerName?: string): Promise<void>;\n reportProgress?: (loaded: number, total?: number, message?: string) => Promise<void>;\n}\n\nexport interface ToolExecutionSuccess<T extends Record<string, unknown>> {\n readonly isError: false;\n readonly content: string;\n readonly structuredContent?: T;\n}\n\nexport interface ToolExecutionFailure {\n readonly isError: true;\n readonly content: string;\n}\n\nexport type ToolExecutionResult<T extends Record<string, unknown>> =\n | ToolExecutionSuccess<T>\n | ToolExecutionFailure;\n\nexport interface ToolReporter {\n log(level: ClientLogLevel, message: string): Promise<void>;\n progress(loaded: number, total?: number, message?: string): Promise<void>;\n}\n\nexport const NOOP_REPORTER: ToolReporter = {\n async log() {},\n async progress() {},\n};\n\nexport function toolSuccess<T extends Record<string, unknown>>(\n content: string,\n structuredContent?: T,\n): ToolExecutionSuccess<T> {\n return {\n isError: false,\n content,\n structuredContent,\n };\n}\n\nexport function toolFailure(content: string): ToolExecutionFailure {\n return {\n isError: true,\n content,\n };\n}\n\nexport function createToolReporter(\n ctx: ReporterContext,\n loggerName: string,\n): ToolReporter {\n return {\n log(level, message) {\n return ctx.log(level, message, loggerName);\n },\n progress(loaded, total, message) {\n return ctx.reportProgress?.(loaded, total, message) ?? Promise.resolve();\n },\n };\n}\n\nexport function toToolResponse<T extends Record<string, unknown>>(\n result: ToolExecutionResult<T>,\n): TypedCallToolResult<T> | TypedCallToolResult<never> {\n if (result.isError) {\n return error(result.content);\n }\n\n if (result.structuredContent) {\n return {\n ...markdown(result.content),\n structuredContent: result.structuredContent,\n } as TypedCallToolResult<T>;\n }\n\n return markdown(result.content) as TypedCallToolResult<never>;\n}\n", "/**\n * Raw/smart web search tool handlers.\n * NEVER throws - always returns markdown tool responses for graceful degradation.\n */\n\nimport type { MCPServer } from 'mcp-use/server';\nimport { Effect } from 'effect';\n\nimport { getCapabilities, getMissingEnvMessage, parseEnv } from '../config/index.js';\nimport {\n QUERY_REWRITE_PAIR_GUIDANCE_TEXT,\n rawWebSearchParamsSchema,\n smartWebSearchParamsSchema,\n type RawWebSearchParams,\n type SmartWebSearchParams,\n} from '../schemas/web-search.js';\nimport { type MultipleSearchResponse } from '../clients/search.js';\nimport {\n aggregateAndRank,\n generateUnifiedOutput,\n} from '../utils/url-aggregator.js';\nimport {\n createLLMProcessor,\n type ClassificationEntry,\n type ClassificationResult,\n type RefineQuerySuggestion,\n} from '../services/llm-processor.js';\nimport { classifyError, ErrorCode, type StructuredError } from '../utils/errors.js';\nimport {\n mcpLog,\n formatError,\n formatDuration,\n} from './utils.js';\nimport {\n createToolReporter,\n NOOP_REPORTER,\n toolFailure,\n toolSuccess,\n toToolResponse,\n type ToolExecutionResult,\n type ToolReporter,\n} from './mcp-helpers.js';\nimport { sanitizeSuggestion } from '../utils/sanitize.js';\nimport {\n normalizeQueryForDispatch,\n relaxQueryForRetry,\n} from '../utils/query-relax.js';\nimport { runExternalEffect } from '../effect/runtime.js';\nimport { LlmService, LlmServiceLive, SearchService, SearchServiceLive } from '../effect/services.js';\n\n// --- Internal types ---\n\ntype SearchToolOutput = Record<string, never>;\n\ninterface SearchHandlerParams {\n keywords: string[];\n extract?: string;\n scope: 'web' | 'reddit' | 'both';\n verbose: boolean;\n smart: boolean;\n toolName: 'raw-web-search' | 'smart-web-search';\n}\n\ninterface SearchAggregation {\n readonly rankedUrls: ReturnType<typeof aggregateAndRank>['rankedUrls'];\n readonly totalUniqueUrls: number;\n readonly frequencyThreshold: number;\n readonly thresholdNote?: string;\n}\n\nexport type SearchResponse = MultipleSearchResponse;\nexport type SearchExecutor = (queries: string[]) => Promise<SearchResponse>;\n\ntype SearchFailurePhase = 'initial' | 'relax-retry';\ntype SearchResultScope = 'web' | 'reddit';\n\nfunction formatInputValidationError(\n toolName: string,\n issues: readonly { message: string; path: readonly PropertyKey[] }[],\n): string {\n const details = issues\n .map((issue) => {\n const path = issue.path.length > 0 ? issue.path.map(String).join('.') : '<root>';\n return `- ${path}: ${issue.message}`;\n })\n .join('\\n');\n return `Invalid ${toolName} input.\\n\\n${details}`;\n}\n\n// --- Helpers ---\n\n/** Reddit post permalink: /r/{sub}/comments/{id}/ \u2014 drops subreddit\n * homepages, /rising, /new, /top, etc. so only post URLs reach the agent.\n * See mcp-revisions/tool-surface/02-extend-web-search-with-reddit-scope.md. */\nconst REDDIT_POST_PERMALINK = /\\/r\\/[^/]+\\/comments\\/[a-z0-9]+\\//i;\nconst REDDIT_HOST = /(?:^|\\.)reddit\\.com$/i;\n\ninterface ScopedQuery {\n query: string;\n resultScope: SearchResultScope;\n dropSiteOnRetry: boolean;\n}\n\nfunction redditScopedQuery(query: string): string {\n return /\\bsite:reddit\\.com\\b/i.test(query) ? query : `${query} site:reddit.com`;\n}\n\nfunction buildScopedQueries(queries: string[], scope: 'web' | 'reddit' | 'both'): ScopedQuery[] {\n if (scope === 'web') {\n return queries.map((query) => ({ query, resultScope: 'web', dropSiteOnRetry: true }));\n }\n\n const reddited = queries.map((q) =>\n ({ query: redditScopedQuery(q), resultScope: 'reddit' as const, dropSiteOnRetry: false }),\n );\n\n if (scope === 'reddit') return reddited;\n\n return [\n ...queries.map((query) => ({ query, resultScope: 'web' as const, dropSiteOnRetry: true })),\n ...reddited,\n ];\n}\n\nasync function executeSearches(queries: string[]): Promise<SearchResponse> {\n const env = parseEnv();\n const hasSerper = Boolean(env.SEARCH_API_KEY);\n const hasJina = Boolean(env.JINA_API_KEY);\n\n if (!hasSerper && hasJina) {\n mcpLog('info', 'SERPER_API_KEY missing; using Jina Search fallback as primary search provider', 'search');\n return runExternalEffect(\n Effect.gen(function* () {\n const search = yield* SearchService;\n return yield* search.jinaSearchMultiple(queries);\n }),\n SearchServiceLive,\n );\n }\n\n if (!hasSerper) {\n return {\n searches: [],\n totalQueries: queries.length,\n executionTime: 0,\n error: {\n code: ErrorCode.AUTH_ERROR,\n message: 'No search provider configured. Set SERPER_API_KEY or JINA_API_KEY.',\n retryable: false,\n },\n };\n }\n\n const serperResponse = await runExternalEffect(\n Effect.gen(function* () {\n const search = yield* SearchService;\n return yield* search.serperSearchMultiple(queries);\n }),\n SearchServiceLive,\n );\n if (!hasJina) return serperResponse;\n\n if (serperResponse.error) {\n mcpLog('warning', `Serper failed (${serperResponse.error.message}); trying Jina Search fallback`, 'search');\n const jinaResponse = await runExternalEffect(\n Effect.gen(function* () {\n const search = yield* SearchService;\n return yield* search.jinaSearchMultiple(queries);\n }),\n SearchServiceLive,\n );\n return jinaResponse.error ? serperResponse : jinaResponse;\n }\n\n const emptyIndices = serperResponse.searches\n .map((search, index) => (search.results.length === 0 ? index : -1))\n .filter((index) => index !== -1);\n if (emptyIndices.length === 0) return serperResponse;\n\n const fallbackQueries = emptyIndices\n .map((index) => serperResponse.searches[index]?.query)\n .filter((query): query is string => typeof query === 'string' && query.length > 0);\n if (fallbackQueries.length === 0) return serperResponse;\n\n mcpLog('info', `${fallbackQueries.length} zero-result Serper query/queries; trying Jina Search fallback`, 'search');\n const jinaResponse = await runExternalEffect(\n Effect.gen(function* () {\n const search = yield* SearchService;\n return yield* search.jinaSearchMultiple(fallbackQueries);\n }),\n SearchServiceLive,\n );\n if (jinaResponse.error) return serperResponse;\n\n const fallbackByQuery = new Map(jinaResponse.searches.map((search) => [search.query, search]));\n const mergedSearches = serperResponse.searches.map((search) => {\n if (search.results.length > 0) return search;\n const fallback = fallbackByQuery.get(search.query);\n if (!fallback || fallback.results.length === 0) return search;\n return { ...fallback, query: search.query };\n });\n\n return {\n ...serperResponse,\n searches: mergedSearches,\n executionTime: serperResponse.executionTime + jinaResponse.executionTime,\n };\n}\n\ninterface QueryRewriteRecord {\n original: string;\n rewritten: string;\n rules: string[];\n}\n\ninterface RetriedQueryRecord {\n original: string;\n retried_with: string;\n rules: string[];\n recovered_results: number;\n}\n\n/** Run Serper, then for each query that returned 0 results build a relaxed\n * retry (Phase B) and reissue them in a single second batch. Replace the\n * empty slot with the retry's results when the retry recovered \u22651 hit, but\n * keep the original query string in the slot so downstream aggregation and\n * follow-up rendering stay consistent. */\nasync function executeWithRelaxRetry(\n dispatched: string[],\n reporter: ToolReporter,\n searchExecutor: SearchExecutor = executeSearches,\n retryOptions: { readonly dropSiteOnRetry?: readonly boolean[] } = {},\n): Promise<{\n response: SearchResponse;\n retried: RetriedQueryRecord[];\n failurePhase?: SearchFailurePhase;\n retryError?: StructuredError;\n}> {\n const initial = await searchExecutor(dispatched);\n\n if (initial.error) {\n return { response: initial, retried: [], failurePhase: 'initial' };\n }\n\n const emptyIndices = initial.searches\n .map((s, i) => (s.results.length === 0 ? i : -1))\n .filter((i) => i !== -1);\n\n if (emptyIndices.length === 0) {\n return { response: initial, retried: [] };\n }\n\n interface Plan { index: number; original: string; relaxed: string; rules: string[] }\n const plans: Plan[] = [];\n for (const idx of emptyIndices) {\n const dq = dispatched[idx];\n if (typeof dq !== 'string') continue;\n const r = relaxQueryForRetry(dq, { dropSite: retryOptions.dropSiteOnRetry?.[idx] ?? true });\n if (r.changed && r.rewritten !== dq) {\n plans.push({ index: idx, original: dq, relaxed: r.rewritten, rules: [...r.rules] });\n }\n }\n\n if (plans.length === 0) {\n return { response: initial, retried: [] };\n }\n\n mcpLog(\n 'info',\n `${plans.length}/${emptyIndices.length} empty-result queries eligible for relaxation retry`,\n 'search',\n );\n await reporter.log(\n 'info',\n `${plans.length} queries returned 0 results; retrying with relaxation`,\n );\n\n const retryResp = await searchExecutor(plans.map((p) => p.relaxed));\n const retried: RetriedQueryRecord[] = [];\n const retryByIndex = new Map<number, SearchResponse['searches'][number]>();\n\n plans.forEach((plan, i) => {\n const r = retryResp.searches[i];\n if (r) retryByIndex.set(plan.index, r);\n retried.push({\n original: plan.original,\n retried_with: plan.relaxed,\n rules: plan.rules,\n recovered_results: r?.results.length ?? 0,\n });\n });\n\n if (retryResp.error) {\n mcpLog(\n 'warning',\n `Relaxed retry batch failed; preserving initial search results: ${retryResp.error.message}`,\n 'search',\n );\n await reporter.log(\n 'warning',\n `search_relax_retry_failed: ${retryResp.error.message}`,\n );\n return {\n response: initial,\n retried,\n retryError: retryResp.error,\n };\n }\n\n const mergedSearches = initial.searches.map((s, idx) => {\n const r = retryByIndex.get(idx);\n if (r && r.results.length > 0) {\n return { ...r, query: s.query };\n }\n return s;\n });\n\n return {\n response: { ...initial, searches: mergedSearches },\n retried,\n };\n}\n\nfunction filterScopedSearches(\n response: SearchResponse,\n scope: 'web' | 'reddit' | 'both',\n resultScopes: readonly SearchResultScope[] = [],\n): SearchResponse {\n if (scope === 'web') return response;\n const filtered = response.searches.map((search, index) => {\n const resultScope = resultScopes[index] ?? (scope === 'reddit' ? 'reddit' : 'web');\n return {\n ...search,\n results: search.results.filter((r) => {\n let host: string;\n try { host = new URL(r.link).hostname; } catch { return true; }\n if (resultScope === 'reddit') {\n return REDDIT_HOST.test(host) && REDDIT_POST_PERMALINK.test(r.link);\n }\n // Web-side results pass through; reddit URLs still must be post permalinks.\n if (!REDDIT_HOST.test(host)) return true;\n return REDDIT_POST_PERMALINK.test(r.link);\n }),\n };\n });\n return { ...response, searches: filtered };\n}\n\nfunction processResults(response: SearchResponse): {\n aggregation: SearchAggregation;\n} {\n const aggregation = aggregateAndRank(response.searches, 5);\n return { aggregation };\n}\n\n// --- Raw output (traditional unified ranked list) ---\n\nfunction buildRawOutput(\n queries: string[],\n aggregation: SearchAggregation,\n searches: SearchResponse['searches'],\n verbose: boolean = false,\n): string {\n return generateUnifiedOutput(\n aggregation.rankedUrls, queries, searches,\n aggregation.totalUniqueUrls,\n aggregation.frequencyThreshold, aggregation.thresholdNote,\n verbose,\n );\n}\n\nfunction buildSignalsSection(\n aggregation: SearchAggregation,\n searches: SearchResponse['searches'],\n totalQueries: number,\n): string {\n const coverageCount = searches.filter((search) => search.results.length >= 3).length;\n const lowYield = searches\n .filter((search) => search.results.length <= 1)\n .map((search) => `\"${search.query}\"`);\n const consensusCount = aggregation.rankedUrls.filter((url) => url.isConsensus).length;\n\n const lines = [\n '**Signals**',\n `- Coverage: ${coverageCount}/${totalQueries} queries returned \u22653 results`,\n `- Consensus URLs: ${consensusCount}`,\n ];\n\n if (lowYield.length > 0) {\n lines.push(`- Low-yield: ${lowYield.join(', ')}`);\n }\n\n return lines.join('\\n');\n}\n\nexport function buildSuggestedFollowUpsSection(\n refineQueries: Array<{ query: string; rationale?: string; gap_id?: number; gap_description?: string }> | undefined,\n): string {\n if (!refineQueries || refineQueries.length === 0) {\n return '';\n }\n\n const lines = ['## Suggested follow-up searches', ''];\n\n for (const item of refineQueries) {\n const query = sanitizeSuggestion(item.query ?? '');\n if (!query) continue;\n const rationale = sanitizeSuggestion(item.rationale ?? '');\n const gapTag = typeof item.gap_id === 'number'\n ? ` _(closes gap [${item.gap_id}])_`\n : item.gap_description\n ? ` _(${sanitizeSuggestion(item.gap_description)})_`\n : '';\n lines.push(rationale\n ? `- ${query} \u2014 ${rationale}${gapTag}`\n : `- ${query}${gapTag}`,\n );\n }\n\n return lines.length === 2 ? '' : lines.join('\\n');\n}\n\nexport function appendSignalsAndFollowUps(\n markdown: string,\n signalsSection: string,\n refineQueries: RefineQuerySuggestion[] | undefined,\n options: { includeSignals?: boolean } = {},\n): string {\n const includeSignals = options.includeSignals ?? false;\n const sections = [markdown];\n if (includeSignals && signalsSection) {\n sections.push('', '---', signalsSection);\n }\n const followUps = buildSuggestedFollowUpsSection(refineQueries);\n if (followUps) {\n sections.push('', followUps);\n }\n return sections.join('\\n');\n}\n\n// --- \"Start here\" section ---\n//\n// Surfaces the best 3-5 URLs at the top of the classified response so an agent\n// skimming the first screen sees them before tier tables. Deterministic: uses\n// existing `tier` + `rank` + `reason` from the classifier, no extra LLM call.\n//\n// Algorithm: take HIGHLY_RELEVANT by rank up to MAX_START_HERE; if fewer than\n// MIN_START_HERE, pad from top MAYBE_RELEVANT; skip entirely if no entries\n// above OTHER.\n\nconst MIN_START_HERE = 3;\nconst MAX_START_HERE = 5;\n\n/** Minimal structural shape \u2014 avoids coupling to private `RankedUrl` type. */\ninterface StartHereCandidate {\n readonly rank: number;\n readonly url: string;\n readonly title: string;\n}\n\ninterface StartHereTiers {\n readonly high: readonly StartHereCandidate[];\n readonly maybe: readonly StartHereCandidate[];\n}\n\nexport function buildStartHereSection(\n tiers: StartHereTiers,\n entryByRank: Map<number, ClassificationEntry>,\n opts: { min?: number; max?: number } = {},\n): string {\n const min = opts.min ?? MIN_START_HERE;\n const max = opts.max ?? MAX_START_HERE;\n\n const picks: Array<{ candidate: StartHereCandidate; tier: 'HIGHLY_RELEVANT' | 'MAYBE_RELEVANT' }> = [];\n\n for (const candidate of tiers.high) {\n if (picks.length >= max) break;\n picks.push({ candidate, tier: 'HIGHLY_RELEVANT' });\n }\n\n if (picks.length < min) {\n const target = Math.min(min, max);\n for (const candidate of tiers.maybe) {\n if (picks.length >= target) break;\n picks.push({ candidate, tier: 'MAYBE_RELEVANT' });\n }\n }\n\n if (picks.length === 0) return '';\n\n const lines: string[] = [];\n lines.push('## Start here \u2014 best candidates for your extract');\n picks.forEach((pick, i) => {\n const entry = entryByRank.get(pick.candidate.rank);\n const reason = entry?.reason && entry.reason.trim().length > 0 ? entry.reason : '\u2014';\n let domain: string;\n try {\n domain = new URL(pick.candidate.url).hostname.replace(/^www\\./, '');\n } catch {\n domain = pick.candidate.url;\n }\n lines.push(\n `${i + 1}. **[${pick.candidate.title}](${pick.candidate.url})** \u2014 ${domain} \u2014 ${reason} *(${pick.tier}, rank ${pick.candidate.rank})*`,\n );\n });\n return lines.join('\\n');\n}\n\n// --- Classified output (3-tier LLM-classified table) ---\n\nfunction buildClassifiedOutput(\n classification: ClassificationResult,\n aggregation: SearchAggregation,\n extract: string,\n searches: SearchResponse['searches'],\n totalQueries: number,\n verbose: boolean = false,\n): string {\n const rankedUrls = aggregation.rankedUrls;\n\n // Build tier \u2192 entries mapping (keep url data alongside classifier metadata)\n const entryByRank = new Map(classification.results.map((r) => [r.rank, r]));\n\n const tiers = {\n high: [] as typeof rankedUrls,\n maybe: [] as typeof rankedUrls,\n other: [] as typeof rankedUrls,\n };\n\n for (const url of rankedUrls) {\n const entry = entryByRank.get(url.rank);\n const tier = entry?.tier;\n if (tier === 'HIGHLY_RELEVANT') {\n tiers.high.push(url);\n } else if (tier === 'MAYBE_RELEVANT') {\n tiers.maybe.push(url);\n } else {\n tiers.other.push(url);\n }\n }\n\n const lines: string[] = [];\n\n // Header with generated title, synthesis, and confidence\n lines.push(`## ${classification.title}`);\n lines.push(`> Looking for: ${extract}`);\n lines.push(`> ${totalQueries} queries \u2192 ${rankedUrls.length} URLs \u2192 ${tiers.high.length} highly relevant, ${tiers.maybe.length} possibly relevant`);\n if (classification.confidence) {\n const confReason = classification.confidence_reason ? ` \u2014 ${classification.confidence_reason}` : '';\n lines.push(`> Confidence: \\`${classification.confidence}\\`${confReason}`);\n }\n lines.push('');\n\n // \"Start here\" block: surface the top 3-5 URLs above the synthesis so an\n // agent skimming the first screen sees scrape candidates before prose.\n const startHere = buildStartHereSection(\n { high: tiers.high, maybe: tiers.maybe },\n entryByRank,\n );\n if (startHere) {\n lines.push(startHere);\n lines.push('');\n }\n\n lines.push(`**Summary:** ${classification.synthesis}`);\n lines.push('');\n\n // Helper: render one row with optional source_type + reason\n const renderRichRow = (url: typeof rankedUrls[number]): string => {\n const entry = entryByRank.get(url.rank);\n const coveragePct = Math.round(url.coverageRatio * 100);\n const seenIn = `${url.frequency}/${totalQueries} (${coveragePct}%)`;\n const sourceType = entry?.source_type ? `\\`${entry.source_type}\\`` : '\u2014';\n const reason = entry?.reason ? entry.reason.replace(/\\|/g, '\\\\|') : '\u2014';\n return `| ${url.rank} | [${url.title}](${url.url}) | ${sourceType} | ${seenIn} | ${reason} |`;\n };\n\n // Highly Relevant tier\n if (tiers.high.length > 0) {\n lines.push(`### Highly Relevant (${tiers.high.length})`);\n lines.push('| # | URL | Source | Seen in | Why |');\n lines.push('|---|-----|--------|---------|-----|');\n for (const url of tiers.high) lines.push(renderRichRow(url));\n lines.push('');\n }\n\n // Maybe Relevant tier\n if (tiers.maybe.length > 0) {\n lines.push(`### Maybe Relevant (${tiers.maybe.length})`);\n lines.push('| # | URL | Source | Seen in | Why |');\n lines.push('|---|-----|--------|---------|-----|');\n for (const url of tiers.maybe) lines.push(renderRichRow(url));\n lines.push('');\n }\n\n // Other tier \u2014 with query attribution\n if (tiers.other.length > 0) {\n lines.push(`### Other Results (${tiers.other.length})`);\n lines.push('| # | URL | Source | Score | Queries |');\n lines.push('|---|-----|--------|-------|---------|');\n for (const url of tiers.other) {\n const entry = entryByRank.get(url.rank);\n const queryList = url.queries.map((q) => `\"${q}\"`).join(', ');\n const sourceType = entry?.source_type ? `\\`${entry.source_type}\\`` : '\u2014';\n let domain: string;\n try {\n domain = new URL(url.url).hostname.replace(/^www\\./, '');\n } catch {\n domain = url.url;\n }\n lines.push(`| ${url.rank} | ${domain} | ${sourceType} | ${url.score.toFixed(1)} | ${queryList} |`);\n }\n lines.push('');\n }\n\n // Signals block is gated behind verbose \u2014 it duplicates info already\n // present in the per-row metadata for callers who care.\n // See: docs/code-review/context/05-output-formatting-patterns.md.\n if (verbose) {\n lines.push(buildSignalsSection(aggregation, searches, totalQueries));\n }\n\n // Gaps section \u2014 what the current results don't answer\n if (classification.gaps && classification.gaps.length > 0) {\n lines.push('');\n lines.push('## Gaps');\n for (const gap of classification.gaps) {\n lines.push(`- **[${gap.id}]** ${gap.description}`);\n }\n }\n\n const followUps = buildSuggestedFollowUpsSection(classification.refine_queries);\n if (followUps) {\n lines.push('');\n lines.push(followUps);\n }\n\n return lines.join('\\n');\n}\n\n// --- Error builder ---\n\nfunction formatSearchFailureMessage(\n error: StructuredError,\n phase?: SearchFailurePhase,\n): string {\n if (phase === 'initial') {\n return `Search provider failed during initial batch: ${error.message}`;\n }\n\n if (phase === 'relax-retry') {\n return `Search provider failed during relaxed retry batch: ${error.message}`;\n }\n\n return error.message;\n}\n\nfunction buildWebSearchError(\n error: StructuredError,\n params: SearchHandlerParams,\n startTime: number,\n phase?: SearchFailurePhase,\n): ToolExecutionResult<SearchToolOutput> {\n const message = formatSearchFailureMessage(error, phase);\n const executionTime = Date.now() - startTime;\n\n mcpLog('error', `${params.toolName}: ${message}`, 'search');\n\n const errorContent = formatError({\n code: error.code,\n message,\n retryable: error.retryable,\n toolName: params.toolName,\n howToFix: ['Verify SERPER_API_KEY or JINA_API_KEY is set correctly'],\n alternatives: [\n 'raw-web-search(keywords=[\"topic recommendations site:reddit.com\"]) \u2014 unclassified search results',\n 'raw-scrape-links(urls=[...]) \u2014 if you already have URLs, fetch their markdown now',\n ],\n });\n\n return toolFailure(\n `${errorContent}\\n\\nExecution time: ${formatDuration(executionTime)}\\nKeywords: ${params.keywords.length}`,\n );\n}\n\n// --- Main handler ---\n\nasync function handleSearch(\n params: SearchHandlerParams,\n reporter: ToolReporter = NOOP_REPORTER,\n searchExecutor: SearchExecutor = executeSearches,\n): Promise<ToolExecutionResult<SearchToolOutput>> {\n const startTime = Date.now();\n\n try {\n if (params.smart && !createLLMProcessor()) {\n return toolFailure(getMissingEnvMessage('llmExtraction'));\n }\n\n const scopedQueries = buildScopedQueries(params.keywords, params.scope);\n const effectiveQueries = scopedQueries.map((entry) => entry.query);\n if (params.scope !== 'web') {\n mcpLog('info', `Searching scope=${params.scope}: ${params.keywords.length} input keywords \u2192 ${effectiveQueries.length} dispatched`, 'search');\n } else {\n mcpLog('info', `Searching for ${params.keywords.length} keyword(s)`, 'search');\n }\n await reporter.log('info', `Searching for ${effectiveQueries.length} query/queries (scope=${params.scope})`);\n await reporter.progress(15, 100, 'Submitting search queries');\n\n // Phase A \u2014 pre-dispatch normalizer. Rewrites the small fraction of\n // queries Google was statistically going to mis-handle (3+ phrase AND,\n // operator chars in quotes, paths in quotes). See src/utils/query-relax.ts.\n const dispatchPlan = effectiveQueries.map((q) => {\n const r = normalizeQueryForDispatch(q);\n return { original: q, dispatched: r.rewritten, rules: [...r.rules], changed: r.changed };\n });\n const dispatchedQueries = dispatchPlan.map((p) => p.dispatched);\n const resultScopes = scopedQueries.map((entry) => entry.resultScope);\n const dropSiteOnRetry = scopedQueries.map((entry) => entry.dropSiteOnRetry);\n const queryRewrites: QueryRewriteRecord[] = dispatchPlan\n .filter((p) => p.changed)\n .map((p) => ({ original: p.original, rewritten: p.dispatched, rules: p.rules }));\n\n if (queryRewrites.length > 0) {\n mcpLog(\n 'info',\n `Pre-dispatch normalized ${queryRewrites.length}/${effectiveQueries.length} queries`,\n 'search',\n );\n await reporter.log(\n 'info',\n `Normalized ${queryRewrites.length} queries pre-dispatch`,\n );\n }\n\n // Phase B \u2014 on-empty retry: any query returning 0 results gets one\n // relaxed retry (drop quotes, drop site:). Recovered hits replace the\n // empty slot transparently.\n const {\n response: rawResponse,\n retried: retriedQueries,\n failurePhase,\n retryError,\n } = await executeWithRelaxRetry(\n dispatchedQueries,\n reporter,\n searchExecutor,\n { dropSiteOnRetry },\n );\n\n if (rawResponse.error) {\n await reporter.log('error', `search_provider_failed: ${rawResponse.error.message}`);\n return buildWebSearchError(rawResponse.error, params, startTime, failurePhase);\n }\n\n const response = filterScopedSearches(rawResponse, params.scope, resultScopes);\n await reporter.progress(50, 100, 'Collected search results');\n\n const { aggregation } = processResults(response);\n await reporter.log(\n 'info',\n `Collected ${aggregation.totalUniqueUrls} unique URLs across ${response.totalQueries} queries`,\n );\n\n let markdown: string;\n\n if (!params.smart) {\n markdown = appendSignalsAndFollowUps(\n buildRawOutput(params.keywords, aggregation, response.searches, false),\n buildSignalsSection(aggregation, response.searches, response.totalQueries),\n undefined,\n { includeSignals: false },\n );\n await reporter.progress(80, 100, 'Ranking search results');\n } else {\n const llmProcessor = createLLMProcessor();\n if (!llmProcessor) {\n return toolFailure(getMissingEnvMessage('llmExtraction'));\n }\n\n await reporter.progress(65, 100, 'Classifying results by relevance');\n const classification = await runExternalEffect(\n Effect.gen(function* () {\n const llm = yield* LlmService;\n return yield* llm.classifySearchResults(\n aggregation.rankedUrls,\n params.extract ?? '',\n response.totalQueries,\n llmProcessor,\n params.keywords,\n );\n }),\n LlmServiceLive,\n );\n\n if (classification.result) {\n markdown = buildClassifiedOutput(\n classification.result, aggregation, params.extract ?? '', response.searches, response.totalQueries, params.verbose,\n );\n await reporter.progress(85, 100, 'Formatted classified results');\n } else {\n const llmError = classification.error ?? 'Unknown classification error';\n mcpLog('warning', `Classification failed for smart-web-search: ${llmError}`, 'search');\n await reporter.log('warning', `llm_classifier_failed: ${llmError}`);\n return toolFailure(\n `${formatError({\n code: ErrorCode.SERVICE_UNAVAILABLE,\n message: `LLM classification failed: ${llmError}`,\n retryable: true,\n toolName: params.toolName,\n alternatives: ['raw-web-search(keywords=[...]) \u2014 return unclassified search results'],\n })}\\n\\nExecution time: ${formatDuration(Date.now() - startTime)}`,\n );\n }\n }\n\n const executionTime = Date.now() - startTime;\n\n mcpLog('info', `Search completed: ${aggregation.rankedUrls.length} URLs, smart=${params.smart}`, 'search');\n await reporter.log('info', `Search completed with ${aggregation.rankedUrls.length} URLs (smart: ${params.smart})`);\n\n const footerParts = [\n formatDuration(executionTime),\n `${aggregation.totalUniqueUrls} unique URLs`,\n params.smart ? 'LLM classified' : 'raw search',\n ];\n if (queryRewrites.length > 0) footerParts.push(`${queryRewrites.length} normalized`);\n if (retriedQueries.length > 0) footerParts.push(`${retriedQueries.length} retried`);\n if (retryError) footerParts.push(`retry warning: ${retryError.code}`);\n const footer = `\\n---\\n*${footerParts.join(' | ')}*`;\n const fullMarkdown = markdown + footer;\n\n return toolSuccess(fullMarkdown);\n } catch (error) {\n return buildWebSearchError(classifyError(error), params, startTime);\n }\n}\n\nexport function handleRawWebSearch(\n params: RawWebSearchParams,\n reporter: ToolReporter = NOOP_REPORTER,\n searchExecutor: SearchExecutor = executeSearches,\n): Promise<ToolExecutionResult<SearchToolOutput>> {\n return handleSearch(\n {\n ...params,\n scope: 'web',\n verbose: false,\n smart: false,\n toolName: 'raw-web-search',\n },\n reporter,\n searchExecutor,\n );\n}\n\nexport function handleSmartWebSearch(\n params: SmartWebSearchParams,\n reporter: ToolReporter = NOOP_REPORTER,\n searchExecutor: SearchExecutor = executeSearches,\n): Promise<ToolExecutionResult<SearchToolOutput>> {\n return handleSearch(\n {\n ...params,\n smart: true,\n toolName: 'smart-web-search',\n },\n reporter,\n searchExecutor,\n );\n}\n\nexport function registerWebSearchTools(server: MCPServer): void {\n server.tool(\n {\n name: 'raw-web-search',\n title: 'Raw Web Search',\n description:\n `Fan out raw search keywords in parallel and return the ranked markdown list directly. Serper is primary when configured; Jina Search is fallback when Serper is missing, fails, or yields empty query results. Input is only \\`keywords\\` (1\u201350 items). ${QUERY_REWRITE_PAIR_GUIDANCE_TEXT} Use this when you need unclassified result data, Reddit permalink discovery via explicit \\`site:reddit.com/r/.../comments\\` keywords, or broad reconnaissance before synthesis.`,\n schema: rawWebSearchParamsSchema,\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: true,\n },\n },\n async (args, ctx) => {\n const parsed = rawWebSearchParamsSchema.safeParse(args);\n if (!parsed.success) {\n return toToolResponse(toolFailure(formatInputValidationError('raw-web-search', parsed.error.issues)));\n }\n\n if (!getCapabilities().search) {\n return toToolResponse(toolFailure(getMissingEnvMessage('search')));\n }\n\n const reporter = createToolReporter(ctx, 'raw-web-search');\n const result = await handleRawWebSearch(parsed.data, reporter);\n\n await reporter.progress(100, 100, result.isError ? 'Search failed' : 'Search complete');\n return toToolResponse(result);\n },\n );\n\n server.tool(\n {\n name: 'smart-web-search',\n title: 'Smart Web Search',\n description:\n `Fan out search keywords in parallel, then always run LLM classification and synthesis against \\`extract\\`. Serper is primary when configured; Jina Search is fallback when Serper is missing, fails, or yields empty query results. Input carries 1\u201350 \\`keywords\\`, required \\`extract\\`, optional \\`scope: \"web\" | \"reddit\" | \"both\"\\`, and optional \\`verbose\\`. ${QUERY_REWRITE_PAIR_GUIDANCE_TEXT} Use this for comprehensive research passes where you need HIGHLY_RELEVANT/MAYBE/OTHER tiers, a grounded synthesis with rank citations, gaps, and suggested follow-up searches.`,\n schema: smartWebSearchParamsSchema,\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: true,\n },\n },\n async (args, ctx) => {\n const parsed = smartWebSearchParamsSchema.safeParse(args);\n if (!parsed.success) {\n return toToolResponse(toolFailure(formatInputValidationError('smart-web-search', parsed.error.issues)));\n }\n\n if (!getCapabilities().search) {\n return toToolResponse(toolFailure(getMissingEnvMessage('search')));\n }\n\n const reporter = createToolReporter(ctx, 'smart-web-search');\n const result = await handleSmartWebSearch(parsed.data, reporter);\n\n await reporter.progress(100, 100, result.isError ? 'Search failed' : 'Search complete');\n return toToolResponse(result);\n },\n );\n}\n", "/**\n * URL Aggregator Utility\n * Aggregates search results across multiple queries, calculates CTR-weighted scores,\n * and generates consensus-based rankings.\n */\n\nimport { CTR_WEIGHTS } from '../config/index.js';\nimport type { QuerySearchResult } from '../clients/search.js';\n\n/** Minimum frequency for web search consensus marking */\nconst WEB_CONSENSUS_THRESHOLD = 3 as const;\n\n\n/** Minimum weight assigned to positions beyond top 10 */\nconst MIN_BEYOND_TOP10_WEIGHT = 0 as const;\n\n/** Weight decay per position beyond top 10 */\nconst BEYOND_TOP10_DECAY = 0.5 as const;\n\n/** Base position for beyond-top-10 weight calculation */\nconst BEYOND_TOP10_BASE = 10 as const;\n\n/** Default minimum consensus URLs before lowering threshold (web search) */\nconst DEFAULT_MIN_CONSENSUS_URLS = 5 as const;\n\n/** High consensus frequency threshold for enhanced output labeling */\nconst HIGH_CONSENSUS_THRESHOLD = 4 as const;\n\n/** Maximum number of alternative snippets to retain per URL */\nconst MAX_ALT_SNIPPETS = 3 as const;\n\n/** Consistency penalty cap \u2014 bounds the impact of position variance */\nconst MAX_CONSISTENCY_PENALTY = 0.15 as const;\n\n/** Standard deviation normalizer \u2014 stdDev of 5+ gets full penalty */\nconst CONSISTENCY_STDDEV_SCALE = 5 as const;\n\n/**\n * Aggregated URL data structure\n */\ninterface AggregatedUrl {\n readonly url: string;\n title: string;\n snippet: string;\n readonly allSnippets: string[];\n frequency: number;\n readonly positions: number[];\n readonly queries: string[];\n bestPosition: number;\n totalScore: number;\n}\n\n/**\n * Compute position statistics for consistency scoring\n */\nfunction computePositionStats(positions: number[]): { mean: number; stdDev: number; consistencyMultiplier: number } {\n if (positions.length <= 1) {\n return { mean: positions[0] ?? 0, stdDev: 0, consistencyMultiplier: 1.0 };\n }\n const mean = positions.reduce((a, b) => a + b, 0) / positions.length;\n const variance = positions.reduce((sum, p) => sum + (p - mean) ** 2, 0) / (positions.length - 1);\n const stdDev = Math.sqrt(variance);\n const consistencyMultiplier = 1.0 - MAX_CONSISTENCY_PENALTY * Math.min(stdDev / CONSISTENCY_STDDEV_SCALE, 1.0);\n return { mean, stdDev, consistencyMultiplier };\n}\n\n/**\n * Ranked URL with normalized score and enriched signals\n */\ninterface RankedUrl {\n readonly url: string;\n readonly title: string;\n readonly snippet: string;\n readonly allSnippets: string[];\n readonly rank: number;\n readonly score: number;\n readonly frequency: number;\n readonly positions: number[];\n readonly queries: string[];\n readonly bestPosition: number;\n readonly isConsensus: boolean;\n readonly coverageRatio: number;\n readonly positionStdDev: number;\n readonly consistencyMultiplier: number;\n}\n\n/**\n * Aggregation result containing all processed data\n */\ninterface AggregationResult {\n readonly rankedUrls: RankedUrl[];\n readonly totalUniqueUrls: number;\n readonly totalQueries: number;\n readonly frequencyThreshold: number;\n readonly thresholdNote?: string;\n}\n\n/**\n * Get CTR weight for a position (1-10)\n * Positions beyond 10 get minimal weight\n */\nfunction getCtrWeight(position: number): number {\n if (position >= 1 && position <= 10) {\n return CTR_WEIGHTS[position] ?? 0;\n }\n // Positions beyond 10 get diminishing returns\n return Math.max(MIN_BEYOND_TOP10_WEIGHT, BEYOND_TOP10_BASE - (position - BEYOND_TOP10_BASE) * BEYOND_TOP10_DECAY);\n}\n\n/**\n * Aggregate results from multiple searches\n * Flattens all results, deduplicates by URL, and tracks frequency/positions\n */\nfunction aggregateResults(searches: QuerySearchResult[]): Map<string, AggregatedUrl> {\n const urlMap = new Map<string, AggregatedUrl>();\n\n for (const search of searches) {\n for (const result of search.results) {\n const normalizedUrl = normalizeUrl(result.link);\n const existing = urlMap.get(normalizedUrl);\n\n if (existing) {\n existing.frequency += 1;\n existing.positions.push(result.position);\n existing.queries.push(search.query);\n const prevBest = existing.bestPosition;\n existing.bestPosition = Math.min(existing.bestPosition, result.position);\n existing.totalScore += getCtrWeight(result.position);\n // Collect distinct snippets (up to MAX_ALT_SNIPPETS)\n if (\n result.snippet &&\n existing.allSnippets.length < MAX_ALT_SNIPPETS &&\n !existing.allSnippets.some(s => s === result.snippet)\n ) {\n existing.allSnippets.push(result.snippet);\n }\n // Keep best title/snippet (from highest ranking position)\n if (result.position < prevBest) {\n existing.title = result.title;\n existing.snippet = result.snippet;\n }\n } else {\n urlMap.set(normalizedUrl, {\n url: result.link,\n title: result.title,\n snippet: result.snippet,\n allSnippets: result.snippet ? [result.snippet] : [],\n frequency: 1,\n positions: [result.position],\n queries: [search.query],\n bestPosition: result.position,\n totalScore: getCtrWeight(result.position),\n });\n }\n }\n }\n\n return urlMap;\n}\n\n/**\n * Normalize URL for deduplication\n * Removes trailing slashes, www prefix, and normalizes protocol\n */\nfunction normalizeUrl(url: string): string {\n try {\n const parsed = new URL(url);\n let host = parsed.hostname.replace(/^www\\./, '');\n let path = parsed.pathname.replace(/\\/$/, '') || '/';\n return `${host}${path}${parsed.search}`.toLowerCase();\n } catch {\n return url.toLowerCase().replace(/\\/$/, '');\n }\n}\n\n/**\n * Count URLs meeting a frequency threshold\n */\nfunction countByFrequency(\n urlMap: Map<string, AggregatedUrl>,\n minFrequency: number\n): number {\n let count = 0;\n for (const url of urlMap.values()) {\n if (url.frequency >= minFrequency) count++;\n }\n return count;\n}\n\n/**\n * Calculate weighted scores with consistency multiplier, normalize to 100.0.\n * Returns ALL URLs sorted by composite score with rank assignments and consensus marking.\n */\nfunction calculateWeightedScores(urls: AggregatedUrl[], consensusThreshold: number, totalQueries: number): RankedUrl[] {\n if (urls.length === 0) return [];\n\n // Compute composite scores (base CTR \u00D7 consistency multiplier)\n const scored = urls.map(url => {\n const stats = computePositionStats(url.positions);\n const compositeScore = url.totalScore * stats.consistencyMultiplier;\n return { url, compositeScore, stats };\n });\n\n // Sort by composite score descending\n scored.sort((a, b) => b.compositeScore - a.compositeScore);\n\n // Find max for normalization\n const maxScore = scored[0]!.compositeScore;\n\n // Map to ranked URLs with all signals\n return scored.map(({ url, compositeScore, stats }, index) => ({\n url: url.url,\n title: url.title,\n snippet: url.snippet,\n allSnippets: url.allSnippets,\n rank: index + 1,\n score: maxScore > 0 ? (compositeScore / maxScore) * 100 : 0,\n frequency: url.frequency,\n positions: url.positions,\n queries: url.queries,\n bestPosition: url.bestPosition,\n isConsensus: url.frequency >= consensusThreshold,\n coverageRatio: totalQueries > 0 ? url.frequency / totalQueries : 0,\n positionStdDev: stats.stdDev,\n consistencyMultiplier: stats.consistencyMultiplier,\n }));\n}\n\n/** Maximum queries to show in the coverage table before collapsing */\nconst COVERAGE_TABLE_MAX_ROWS = 20 as const;\n\n/**\n * Consistency label based on position standard deviation\n */\nfunction consistencyLabel(stdDev: number, frequency: number): string {\n if (frequency <= 1) return 'n/a';\n if (stdDev < 1.5) return 'high';\n if (stdDev < 3.5) return 'medium';\n return 'variable';\n}\n\n/**\n * Generate a unified output where every URL appears exactly once.\n * Replaces the old generateEnhancedOutput + per-query section combo.\n */\nexport function generateUnifiedOutput(\n rankedUrls: RankedUrl[],\n allQueries: string[],\n queryResults: QuerySearchResult[],\n totalUniqueUrls: number,\n frequencyThreshold: number,\n thresholdNote?: string,\n verbose: boolean = false,\n): string {\n const lines: string[] = [];\n const consensusCount = rankedUrls.filter(u => u.isConsensus).length;\n\n // Header\n lines.push(`## Web Search Results (${allQueries.length} queries, ${totalUniqueUrls} unique URLs)`);\n lines.push('');\n if (thresholdNote) {\n lines.push(`> ${thresholdNote}`);\n lines.push('');\n }\n\n // Ranked URL list \u2014 every URL exactly once.\n //\n // Per-row metadata is gated:\n // - CONSENSUS labels only appear when the effective threshold is >1 (a\n // threshold of 1 means *every* row gets the label, so it carries no\n // signal). See: docs/code-review/context/02-current-tool-surface.md.\n // - The Score/Seen/Consistency line is suppressed for rows that were\n // seen in exactly one query in a multi-query call (Seen=1/N is common\n // and Consistency is always \"n/a\" in that case).\n // - Verbose mode restores both for callers that explicitly want them.\n const consensusActive = frequencyThreshold > 1;\n\n for (const url of rankedUrls) {\n const consensusTag = consensusActive && url.frequency >= HIGH_CONSENSUS_THRESHOLD\n ? ' CONSENSUS+++'\n : consensusActive && url.isConsensus\n ? ' CONSENSUS'\n : '';\n const coveragePct = Math.round(url.coverageRatio * 100);\n const consistency = consistencyLabel(url.positionStdDev, url.frequency);\n\n lines.push(`**${url.rank}. [${url.title}](${url.url})**${consensusTag}`);\n\n const showRowMetadata = verbose\n || (allQueries.length > 1 && url.frequency > 1)\n || allQueries.length === 1;\n if (showRowMetadata) {\n const parts = [\n `Score: ${url.score.toFixed(1)}`,\n `Seen in: ${url.frequency}/${allQueries.length} queries (${coveragePct}%)`,\n `Best pos: #${url.bestPosition}`,\n ];\n if (url.frequency > 1) {\n parts.push(`Consistency: ${consistency}`);\n }\n lines.push(parts.join(' | '));\n }\n if (url.queries.length > 1 || verbose) {\n lines.push(`Queries: ${url.queries.map(q => `\"${q}\"`).join(', ')}`);\n }\n lines.push(`> ${url.snippet}`);\n\n // Alt snippets (if multiple distinct snippets were collected)\n if (url.allSnippets.length > 1) {\n const alts = url.allSnippets\n .filter(s => s !== url.snippet)\n .slice(0, 3)\n .map(s => s.length > 100 ? s.slice(0, 97) + '...' : s);\n if (alts.length > 0) {\n lines.push(`Alt: ${alts.map(s => `\"${s}\"`).join(' | ')}`);\n }\n }\n\n lines.push('');\n }\n\n // Keyword coverage section\n lines.push('---');\n\n if (allQueries.length <= COVERAGE_TABLE_MAX_ROWS) {\n // Full table for \u226420 queries\n lines.push('### Query Coverage');\n lines.push('| Query | Results | Top URL | Top Pos |');\n lines.push('|---------|---------|---------|---------|');\n\n for (const search of queryResults) {\n const topResult = search.results[0];\n let topDomain = '';\n if (topResult) {\n try {\n topDomain = new URL(topResult.link).hostname.replace(/^www\\./, '');\n } catch {\n topDomain = topResult.link;\n }\n }\n lines.push(`| \"${search.query}\" | ${search.results.length} | ${topDomain || '\u2014'} | ${topResult ? `#${topResult.position}` : '\u2014'} |`);\n }\n lines.push('');\n } else {\n // Collapsed summary for >20 queries\n const goodCount = queryResults.filter(s => s.results.length >= 3).length;\n lines.push(`### Query Coverage: ${goodCount}/${allQueries.length} queries returned 3+ results`);\n lines.push('');\n }\n\n // Low-yield queries\n const lowYield = queryResults.filter(s => s.results.length <= 1);\n if (lowYield.length > 0) {\n lines.push(`**Low-yield queries** (0-1 results): ${lowYield.map(s => `\\`${s.query}\\``).join(', ')}`);\n lines.push('');\n }\n\n // Related searches (merged and deduplicated)\n const allRelated = new Set<string>();\n for (const search of queryResults) {\n if (search.related) {\n for (const r of search.related) {\n allRelated.add(r);\n }\n }\n }\n if (allRelated.size > 0) {\n const related = [...allRelated].slice(0, 10);\n lines.push(`**Related searches:** ${related.map(r => `\\`${r}\\``).join(', ')}`);\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Full aggregation pipeline \u2014 returns ALL URLs ranked by CTR score.\n * Determines a consensus threshold (\u22653, \u22652, or \u22651) for labeling, but never\n * drops URLs below the threshold. Every collected URL appears in the output.\n */\nexport function aggregateAndRank(\n searches: QuerySearchResult[],\n minConsensusUrls: number = DEFAULT_MIN_CONSENSUS_URLS\n): AggregationResult {\n const urlMap = aggregateResults(searches);\n const totalUniqueUrls = urlMap.size;\n const totalQueries = searches.length;\n\n // Determine consensus threshold for labeling (not filtering)\n const thresholds = [3, 2, 1];\n let usedThreshold = 1;\n let thresholdNote: string | undefined;\n\n for (const threshold of thresholds) {\n const count = countByFrequency(urlMap, threshold);\n if (count >= minConsensusUrls || threshold === 1) {\n usedThreshold = threshold;\n if (threshold < 3) {\n thresholdNote = `Note: Consensus threshold set to \u2265${threshold} due to result diversity.`;\n }\n break;\n }\n }\n\n // Rank ALL URLs, marking consensus based on determined threshold\n const allUrls = [...urlMap.values()];\n const rankedUrls = calculateWeightedScores(allUrls, usedThreshold, totalQueries);\n\n return {\n rankedUrls,\n totalUniqueUrls,\n totalQueries,\n frequencyThreshold: usedThreshold,\n thresholdNote,\n };\n}\n\n", "const CONTROL_CHARS = /[\\x00-\\x1f\\x7f]/g;\nconst URLS = /https?:\\/\\/\\S+/gi;\nconst MARKDOWN_LINKS = /\\[([^\\]]+)\\]\\([^)]+\\)/g;\n\nexport function sanitizeSuggestion(input: string): string {\n return input\n .replace(CONTROL_CHARS, ' ')\n .replace(MARKDOWN_LINKS, '$1')\n .replace(URLS, '')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n", "/**\n * Query relaxation for search fan-out.\n *\n * Two-phase rewriter that addresses systematic Google query failures observed\n * in the Serper log:\n * - 3+ back-to-back quoted phrases get implicit-AND'd by Google \u2192 no page\n * contains all rare tokens \u2192 0 results.\n * - Quoted phrases with operator chars (parens, colons, brackets) \u2014 Google\n * strips them inside quotes, so the quotes only impose a pointless AND.\n * - Quoted paths/URLs (`/`, `~/`, leading `@`, 3+ dots) \u2014 same reason.\n * - Tiny `site:` corpus that returns 0 \u2014 drop site filter on retry.\n * - Verbatim long phrases that don't appear word-for-word \u2014 strip quotes on retry.\n *\n * Phase A (`normalizeQueryForDispatch`) is always-on, deterministic, lossless.\n * Phase B (`relaxQueryForRetry`) is aggressive; only invoke when Phase A's\n * dispatched form returned zero results.\n */\n\nconst QUOTED_PHRASE_RE = /\"([^\"]*)\"/g;\nconst HAS_BOOLEAN_GROUPING = /\\b(?:OR|AND)\\b|[()]/;\nconst OPERATOR_CHAR_IN_PHRASE = /[():[\\]]/;\nconst OPERATOR_CHAR_GLOBAL = /[():[\\]]/g;\nconst PATH_LIKE_IN_PHRASE = /\\/|~\\/|^@|\\.{3,}/;\nconst URI_SCHEME_IN_PHRASE = /^[a-z][a-z0-9+.-]*:/i;\nconst HAS_SITE_OPERATOR = /\\bsite:\\S+/i;\nconst SITE_OPERATOR_GLOBAL = /\\bsite:\\S+/gi;\n\nexport interface RewriteResult {\n rewritten: string;\n changed: boolean;\n rules: string[];\n}\n\ninterface PhraseSeg { type: 'phrase'; text: string; quoted: boolean }\ninterface RawSeg { type: 'raw'; text: string }\ntype Seg = PhraseSeg | RawSeg;\n\nfunction renderSeg(seg: Seg): string {\n return seg.type === 'raw' ? seg.text : seg.quoted ? `\"${seg.text}\"` : seg.text;\n}\n\nfunction tokenize(query: string): Seg[] {\n const segs: Seg[] = [];\n let last = 0;\n for (const m of query.matchAll(QUOTED_PHRASE_RE)) {\n const start = m.index ?? 0;\n const end = start + m[0].length;\n if (start > last) segs.push({ type: 'raw', text: query.slice(last, start) });\n segs.push({ type: 'phrase', text: m[1] ?? '', quoted: true });\n last = end;\n }\n if (last < query.length) segs.push({ type: 'raw', text: query.slice(last) });\n return segs;\n}\n\nfunction rebuild(segs: Seg[]): string {\n return segs\n .map(renderSeg)\n .join('')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction onlyWhitespaceBetween(segs: Seg[], fromIndex: number, toIndex: number): boolean {\n for (let i = fromIndex + 1; i < toIndex; i += 1) {\n const seg = segs[i];\n if (seg === undefined) continue;\n if (seg.type !== 'raw' || seg.text.trim() !== '') {\n return false;\n }\n }\n return true;\n}\n\nfunction buildAnchoredOrGroup(segs: Seg[], quotedIndices: number[]): string | null {\n const groupStart = quotedIndices[1];\n const groupEnd = quotedIndices[quotedIndices.length - 1];\n if (groupStart === undefined || groupEnd === undefined) {\n return null;\n }\n\n for (let i = 2; i < quotedIndices.length; i += 1) {\n const previous = quotedIndices[i - 1];\n const current = quotedIndices[i];\n if (\n previous === undefined\n || current === undefined\n || !onlyWhitespaceBetween(segs, previous, current)\n ) {\n return null;\n }\n }\n\n const groupIndices = new Set(quotedIndices.slice(1));\n const parts: string[] = [];\n for (let i = 0; i < segs.length; i += 1) {\n const seg = segs[i];\n if (seg === undefined) continue;\n\n if (seg.type === 'raw') {\n if (i > groupStart && i < groupEnd) {\n continue;\n }\n parts.push(seg.text);\n continue;\n }\n\n if (groupIndices.has(i)) {\n parts.push(i === groupStart ? ' (' : ' OR ');\n parts.push(renderSeg(seg));\n if (i === groupEnd) {\n parts.push(')');\n }\n continue;\n }\n\n parts.push(renderSeg(seg));\n }\n\n return parts.join('').replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * Phase A \u2014 pre-dispatch normalizer. Runs on every query before it leaves the\n * server. Three rules apply in order; each only fires when the original query\n * was statistically going to mis-handle in Google anyway.\n *\n * Rule A1: phrase contains `(`, `)`, `:`, `[`, `]` \u2192 drop quotes; replace those\n * chars with space (Google strips them inside quotes anyway).\n * Rule A2: phrase contains a URI scheme, `/`, `~/`, leading `@`, or 3+ dots\n * \u2192 drop quotes only (Google tokenizes path separators in or out of quotes).\n * Rule A3: \u22653 still-quoted phrases AND no existing `OR`/`AND`/parens \u2192 keep\n * first phrase as anchor; group consecutive subsequent phrases with\n * ` OR `. Bare words and `site:`/`filetype:` operators are preserved\n * verbatim.\n */\nexport function normalizeQueryForDispatch(query: string): RewriteResult {\n const original = query.trim().replace(/\\s+/g, ' ');\n if (!original) {\n return { rewritten: original, changed: false, rules: [] };\n }\n const segs = tokenize(query);\n const rules: string[] = [];\n\n // A1 \u2014 operator chars inside quotes are pointless AND constraints.\n for (const s of segs) {\n if (\n s.type === 'phrase'\n && s.quoted\n && OPERATOR_CHAR_IN_PHRASE.test(s.text)\n && !URI_SCHEME_IN_PHRASE.test(s.text)\n ) {\n s.quoted = false;\n s.text = s.text.replace(OPERATOR_CHAR_GLOBAL, ' ');\n if (!rules.includes('A1')) rules.push('A1');\n }\n }\n\n // A2 \u2014 path/URL inside quotes; quoting doesn't help recall.\n for (const s of segs) {\n if (\n s.type === 'phrase'\n && s.quoted\n && (URI_SCHEME_IN_PHRASE.test(s.text) || PATH_LIKE_IN_PHRASE.test(s.text))\n ) {\n s.quoted = false;\n if (!rules.includes('A2')) rules.push('A2');\n }\n }\n\n // A3 \u2014 phrase-AND collapse. Trigger requires \u22653 phrases that survive A1+A2,\n // and no existing boolean grouping in the raw (non-quoted) part of the query.\n const stillQuotedIndices: number[] = [];\n for (let i = 0; i < segs.length; i += 1) {\n const seg = segs[i];\n if (seg?.type === 'phrase' && seg.quoted) {\n stillQuotedIndices.push(i);\n }\n }\n const rawJoined = segs\n .filter((s): s is RawSeg => s.type === 'raw')\n .map((s) => s.text)\n .join(' ');\n\n if (stillQuotedIndices.length >= 3 && !HAS_BOOLEAN_GROUPING.test(rawJoined)) {\n const grouped = buildAnchoredOrGroup(segs, stillQuotedIndices);\n if (grouped !== null) {\n rules.push('A3');\n return { rewritten: grouped, changed: grouped !== original, rules };\n }\n }\n\n const rewritten = rebuild(segs);\n return { rewritten, changed: rewritten !== original, rules };\n}\n\n/**\n * Phase B \u2014 on-empty retry. Only invoked for queries whose Phase-A dispatched\n * form returned zero results from Serper. Strips ALL remaining quotes and the\n * `site:` operator (if present). Caller should skip the retry when the\n * relaxed form equals the dispatched form.\n *\n * Rule B1: strip every `\"` (turns each phrase into a bag of words; Google\n * ranks by token co-occurrence instead of forcing verbatim match).\n * Rule B2: drop `site:operator` (broadens to open web; catches \"tiny corpus\"\n * and \"site path doesn't exist\" cases at once).\n */\nexport function relaxQueryForRetry(\n query: string,\n options: { dropSite?: boolean } = {},\n): RewriteResult {\n const original = query.trim().replace(/\\s+/g, ' ');\n if (!original) {\n return { rewritten: original, changed: false, rules: [] };\n }\n const dropSite = options.dropSite ?? true;\n const rules: string[] = [];\n let result = query;\n\n if (result.includes('\"')) {\n result = result.replace(/\"/g, '');\n rules.push('B1');\n }\n\n if (dropSite && HAS_SITE_OPERATOR.test(result)) {\n result = result.replace(SITE_OPERATOR_GLOBAL, ' ');\n rules.push('B2');\n }\n\n result = result.replace(/\\s+/g, ' ').trim();\n return { rewritten: result, changed: result !== original, rules };\n}\n", "import type { MCPServer } from 'mcp-use/server';\nimport { Effect } from 'effect';\n\nimport {\n startResearchParamsSchema,\n type StartResearchOutput,\n type StartResearchParams,\n} from '../schemas/start-research.js';\nimport { QUERY_REWRITE_PAIR_GUIDANCE_TEXT } from '../schemas/web-search.js';\nimport {\n createLLMProcessor,\n getLLMHealth,\n renderResearchBrief,\n type LLMHealthSnapshot,\n} from '../services/llm-processor.js';\nimport { runExternalEffect } from '../effect/runtime.js';\nimport { LlmService, LlmServiceLive } from '../effect/services.js';\nimport { classifyError } from '../utils/errors.js';\nimport { mcpLog } from '../utils/logger.js';\nimport { toolFailure, toolSuccess, toToolResponse, type ToolExecutionResult } from './mcp-helpers.js';\nimport { formatError } from './utils.js';\n\nconst SKILL_INSTALL_HINT = [\n '> \uD83D\uDCA1 **Pair this server with the `run-research` skill** for the full agentic playbook',\n '> (single-agent loop, multi-agent orchestrator, mission-prompt templates, output discipline).',\n '> Install once per machine \u2014 the skill is what teaches the agent how to spend these tools well:',\n '>',\n '> ```bash',\n '> npx -y skills add -y -g https://github.com/yigitkonur/skills-by-yigitkonur --skill /run-research',\n '> ```',\n '>',\n '> Already installed? Skip this \u2014 the skill auto-loads on relevant prompts. The full pack',\n '> ships ~50 sibling skills: `npx -y skills add -y -g https://github.com/yigitkonur/skills-by-yigitkonur`.',\n].join('\\n');\n\n/**\n * Full research-loop playbook. Teaches the 5-tool mental model\n * (start-research, raw/smart search, raw/smart scrape), the aggressive multi-call\n * discipline, parallel-callability, and the cite-from-scrape rule.\n *\n * Emitted when the LLM planner is healthy OR `include_playbook: true`.\n */\nexport function buildStaticScaffolding(goal?: string, opts: { plannerAvailable?: boolean } = {}): string {\n const plannerAvailable = opts.plannerAvailable ?? true;\n const focusLine = goal\n ? `> Focus for this session: ${goal}`\n : '> Focus for this session: not yet specified \u2014 set one on the next pass';\n\n const classifierLoopStep = plannerAvailable\n ? '3. If you used `smart-web-search`, treat its output as a prioritization layer over your keyword fan-out: read `Start here`, HIGHLY/MAYBE tiers, `gaps[]`, and `refine_queries[]`, then decide what to scrape. Do not treat smart search as evidence; it only sees titles/snippets. If you used `raw-web-search`, use the ranked URLs/snippets as the candidate pool and do your own prioritization.'\n : '3. Classifier output is NOT available (LLM planner offline). Use `raw-web-search` and synthesize the terrain yourself from titles + snippets; then scrape before making claims.';\n\n return [\n SKILL_INSTALL_HINT,\n '',\n '# Research session started',\n '',\n focusLine,\n '',\n 'You are running a research LOOP, not answering from memory. Training data is stale; the web is authoritative for anything dated, versioned, priced, or contested. Every non-trivial claim in your final answer must be traceable to a raw or smart scrape excerpt you read. Never cite a URL from a search snippet alone.',\n '',\n '## The 5 tools',\n '',\n '**1. `start-research`** \u2014 you just called me. I plan this session and return the brief below. Call me again only if the goal materially shifts.',\n '',\n '**2. `raw-web-search`** \u2014 fan out search keywords in parallel and return the ranked markdown list directly. Serper is primary when configured; Jina Search is fallback when Serper is missing, fails, or yields empty query results. One call carries **up to 50 keywords** in a flat `keywords` array. Use raw search when recall matters more than interpretation: broad discovery, audit trails, exact candidate URLs, cheap second/third passes, and Reddit permalink discovery.',\n '',\n '**3. `smart-web-search`** \u2014 fan out search keywords, then always run LLM classification/synthesis with required `extract`. Its value is **prioritization, not evidence**: it ranks title/snippet results into HIGHLY_RELEVANT / MAYBE / OTHER, surfaces `## Gaps`, and suggests sharper follow-up keywords. Use smart search after you have a clear goal and a deliberately diverse keyword set; use raw search when you mainly need all candidate URLs or maximum distinct keyword coverage without classifier cost. Call search **aggressively** \u2014 2\u20134 rounds per session is normal, not 1. **Parallel-safe**: run multiple search calls in the same turn for orthogonal subtopics. `scope` values:',\n '- `\"reddit\"` \u2192 server appends `site:reddit.com` and filters to post permalinks. Use for sentiment / migration / lived experience.',\n '- `\"web\"` (default) \u2192 open web. Use for spec / bug / pricing / CVE / API / primary-source hunts.',\n '- `\"both\"` \u2192 fans each query across both. Use when the topic is opinion-heavy AND needs official sources.',\n '',\n `**Query rewrite discipline** \u2014 ${QUERY_REWRITE_PAIR_GUIDANCE_TEXT}`,\n '',\n '## Raw vs smart choice',\n '',\n '- Prefer **raw search** for exploration and breadth: maximize distinct keywords, source classes, domains, and exact-result capture. Prefer **smart search** for triage: give it the same diverse keyword fan-out plus a precise `extract` goal so it can prioritize which URLs deserve scraping.',\n '- Prefer **raw scrape** when the page/comments are the research object, when you need complete context, when extraction shape is unclear, or when preserving Reddit threads matters. Prefer **smart scrape** when you know the fields/facets you need and want compact evidence with `## Matches`, `## Not found`, and `## Follow-up signals`.',\n '- Smart search is allowed to steer attention, but final claims must come from raw or smart scrape content. Smart scrape is usually the highest-value smart tool for answer construction because it reads the page body, not just snippets.',\n '',\n '**4. `raw-scrape-links`** \u2014 fetch URLs in parallel and return full markdown directly. **Auto-detects** `reddit.com/r/.../comments/` permalinks and routes them through the Reddit API (threaded post + comments). Non-Reddit URLs use Jina Reader first, then Jina Reader through Scrape.do proxy mode when configured, then optional Kernel browser rendering for web pages. Use raw scrape when you need complete markdown/comments, when the extraction shape is not yet known, or before narrowing a long/important source.',\n '',\n '**5. `smart-scrape-links`** \u2014 fetch URLs with the same provider stack, then always run per-URL LLM extraction with required `extract`. Use smart scrape for focused extraction, final evidence packs, long pages where only specific facts matter, and cross-source comparison. Describe extraction SHAPE in `extract`, facets separated by `|`: `root cause | affected versions | fix | workarounds | timeline`. It should preserve numbers/versions/errors/quotes and explicitly say what the source did not answer.',\n '',\n '## The loop',\n '',\n '1. Read the brief below (if present). Note `primary_branch`, `keyword_seeds`, `gaps_to_watch`, `stop_criteria`.',\n '2. Fire `first_call_sequence` in order. For `primary_branch: reddit`, lead with `raw-web-search` using Reddit permalink probes, then `raw-scrape-links` on the best post permalinks for full comments. For `web`, use `raw-web-search` for maximum candidate breadth or `smart-web-search scope:\"web\"` to prioritize a strong keyword fan-out, then `smart-scrape-links` on selected URLs when the extraction shape is known. For `both`, issue parallel search calls split by source need.',\n classifierLoopStep,\n '4. Scrape every HIGHLY_RELEVANT plus the 2\u20133 best MAYBE_RELEVANT. Use `raw-scrape-links` first for full documents/comments and ambiguous sources; use `smart-scrape-links` when you can name the extraction facets precisely.',\n '5. Harvest from smart scrape `## Follow-up signals` and from raw scrape full markdown/comments \u2014 new terms, version numbers, vendor names, failure modes, referenced URLs. These seed your next search round.',\n '6. Fire the next search round with harvested terms plus any `refine_queries[]` the classifier suggested. Do NOT paraphrase queries already run \u2014 the classifier tracks them.',\n '7. **Stop** when every `gaps_to_watch` item is closed AND the last search pass surfaced no new terms, OR when you have completed 4 full passes. State remaining gaps explicitly if you hit the cap.',\n '',\n '## Output discipline',\n '',\n '- Cite URL (or Reddit permalink) for every non-trivial claim \u2014 only from a raw or smart scrape excerpt you read.',\n '- Quote verbatim: numbers, versions, API names, prices, error messages, stacktraces, people\\'s words.',\n '- Separate documented facts from inferred conclusions explicitly.',\n '- Include the scrape date for time-sensitive claims.',\n '- If you could not verify something, say so \u2014 do not paper over gaps.',\n '- Never cite a URL from a search snippet alone.',\n '',\n '## Post-cutoff discipline',\n '',\n 'For anything released / changed after your training cutoff \u2014 new products, versions, prices, benchmarks \u2014 treat your own query suggestions as hypotheses until a scraped first-party page confirms them. Include `site:<vendor-domain>` keywords in your first search call when the goal names a vendor or product.',\n ].join('\\n');\n}\n\n/**\n * Compact stub emitted when the LLM planner is offline AND the caller did\n * not opt into the full playbook. Names the 5 tools, the loop, parallel-safety,\n * Reddit branch, and cite-from-scrape \u2014 enough to keep an agent moving.\n */\nexport function buildDegradedStub(goal?: string): string {\n const focusLine = goal\n ? `> Focus for this session: ${goal}`\n : '> Focus for this session: not specified \u2014 set one on the next pass.';\n return [\n SKILL_INSTALL_HINT,\n '',\n '# Research session started (LLM planner offline \u2014 compact stub)',\n '',\n focusLine,\n '',\n '**5 tools**: `start-research` (plans), `raw-web-search` (keywords-only raw search), `smart-web-search` (search + required LLM prioritization over snippets, `scope: web|reddit|both`), `raw-scrape-links` (urls-only full markdown/comments), `smart-scrape-links` (scrape + required LLM extraction over page bodies). All are **parallel-callable** \u2014 fire multiple in the same turn when subtopics are orthogonal.',\n '',\n '**Loop**: search \u2192 scrape \u2192 harvest terms/signals \u2192 next search round \u2192 stop when gaps close OR after 4 passes. Use raw search for breadth, smart search for prioritization, raw scrape for full context, and smart scrape for focused extraction.',\n '',\n '**Reddit branch**: use `raw-web-search` with Reddit permalink probes for sentiment / migration / lived experience, then `raw-scrape-links` to capture full threaded comments. Use smart search only to prioritize many candidate posts; use smart scrape only after you know the extraction facets.',\n '',\n '**Cite**: every non-trivial claim must trace to a raw or smart scrape excerpt, never a search snippet. Quote verbatim for numbers, versions, stacktraces, people\\'s words.',\n '',\n 'Pass `include_playbook: true` to `start-research` for the full tactic reference.',\n ].join('\\n');\n}\n\n/**\n * Backward-compat alias \u2014 older call sites import `buildOrientation` directly.\n */\nexport const buildOrientation = buildStaticScaffolding;\n\n// ============================================================================\n// Planner-offline gate.\n//\n// The problem we are guarding against: a single transient LLM failure (one bad\n// 429, one malformed JSON response from the classifier) used to poison the\n// gate forever and force every subsequent `start-research` call into the\n// compact stub \u2014 even when env was fine and the next call would have\n// succeeded. That created a deadlock where the very tool that could reset\n// the health flag was the tool being blocked.\n//\n// The safer semantics implemented here:\n// 1. If env is not configured, we are offline. Hard stop.\n// 2. Otherwise, require **two consecutive failures** before gating (one\n// blip is tolerated).\n// 3. Even then, the gate only holds for PLANNER_FAILURE_TTL_MS after the\n// most recent failure. After that window we give the planner another\n// chance regardless of the counter \u2014 if it is still broken the next\n// call's failure will re-arm the gate.\n// 4. Any success resets the counter to 0, so the gate opens immediately\n// on recovery.\n// ============================================================================\n\n/** Minimum consecutive failures before the gate closes. */\nexport const PLANNER_FAILURE_THRESHOLD = 2;\n\n/** How long a recent failure burst keeps the gate closed, in ms. */\nexport const PLANNER_FAILURE_TTL_MS = 60_000;\n\ntype PlannerGateHealth = Pick<\n LLMHealthSnapshot,\n 'plannerConfigured' | 'consecutivePlannerFailures' | 'lastPlannerCheckedAt'\n>;\n\n/**\n * Pure predicate \u2014 returns true when the planner should be treated as\n * offline for the purposes of `start-research`. Kept exported and\n * dependency-free so tests can drive it without touching the LLM.\n */\nexport function isPlannerKnownOffline(\n health: PlannerGateHealth,\n nowMs: number = Date.now(),\n): boolean {\n if (!health.plannerConfigured) {\n return true;\n }\n if (health.consecutivePlannerFailures < PLANNER_FAILURE_THRESHOLD) {\n return false;\n }\n if (health.lastPlannerCheckedAt === null) {\n return false;\n }\n const lastMs = Date.parse(health.lastPlannerCheckedAt);\n if (Number.isNaN(lastMs)) {\n return false;\n }\n return nowMs - lastMs < PLANNER_FAILURE_TTL_MS;\n}\n\nasync function buildGoalAwareBrief(\n goal: string,\n signal?: AbortSignal,\n): Promise<string> {\n const processor = createLLMProcessor();\n if (!processor) {\n mcpLog('info', 'start-research: LLM unavailable, returning static orientation only', 'start-research');\n return '';\n }\n\n void signal;\n const brief = await runExternalEffect(\n Effect.gen(function* () {\n const llm = yield* LlmService;\n return yield* llm.generateResearchBrief(goal, processor);\n }),\n LlmServiceLive,\n );\n if (!brief) {\n mcpLog('warning', 'start-research: brief generation failed, returning static orientation only', 'start-research');\n return '';\n }\n\n return renderResearchBrief(brief);\n}\n\nasync function handleStartResearch(\n params: StartResearchParams,\n signal?: AbortSignal,\n): Promise<ToolExecutionResult<StartResearchOutput>> {\n try {\n const llmHealth = getLLMHealth();\n const plannerKnownOffline = isPlannerKnownOffline(llmHealth);\n\n if (plannerKnownOffline && !params.include_playbook) {\n const stub = buildDegradedStub(params.goal);\n return toolSuccess(stub);\n }\n\n const scaffolding = buildStaticScaffolding(params.goal, {\n plannerAvailable: !plannerKnownOffline,\n });\n\n let brief = '';\n if (params.goal) {\n brief = await buildGoalAwareBrief(params.goal, signal);\n }\n\n const briefFallbackNote = params.goal && !brief\n ? '\\n\\n---\\n\\n> _Goal-tailored brief unavailable: LLM planner is not configured or failed this call. The static playbook above still applies; you can proceed with it, or retry `start-research` after verifying `LLM_API_KEY`._'\n : '';\n\n const content = brief\n ? `${scaffolding}\\n\\n---\\n\\n${brief}`\n : `${scaffolding}${briefFallbackNote}`;\n\n return toolSuccess(content);\n } catch (err: unknown) {\n const structuredError = classifyError(err);\n mcpLog('error', `start-research: ${structuredError.message}`, 'start-research');\n return toolFailure(\n formatError({\n code: structuredError.code,\n message: structuredError.message,\n retryable: structuredError.retryable,\n toolName: 'start-research',\n howToFix: ['Retry start-research. If the failure persists, verify LLM_API_KEY / LLM_BASE_URL / LLM_MODEL.'],\n }),\n );\n }\n}\n\nexport function registerStartResearchTool(server: MCPServer): void {\n server.tool(\n {\n name: 'start-research',\n title: 'Start Research Session',\n description:\n `Call this FIRST every research session. Provide a \\`goal\\`; I return a goal-tailored brief naming (a) \\`primary_branch\\` (reddit for sentiment/migration, web for spec/bug/pricing, both when opinion-heavy AND needs official sources), (b) the exact \\`first_call_sequence\\` of raw/smart search and scrape calls to fire, (c) 25\u201350 keyword seeds for your first search call, (d) iteration hints, (e) gaps to watch, (f) stop criteria. ${QUERY_REWRITE_PAIR_GUIDANCE_TEXT} No goal? You still get the generic 5-tool playbook. Other tools work without calling this, but you will use them worse.`,\n schema: startResearchParamsSchema,\n annotations: {\n readOnlyHint: true,\n idempotentHint: true,\n destructiveHint: false,\n openWorldHint: false,\n },\n },\n async (args) => toToolResponse(await handleStartResearch(args)),\n );\n}\n", "import { z } from 'zod';\n\nexport const startResearchParamsSchema = z.object({\n goal: z\n .string()\n .min(1, { message: 'start-research: goal cannot be empty' })\n .optional()\n .describe(\n 'Research goal for this session. When provided AND the LLM planner is configured (LLM_API_KEY + LLM_BASE_URL + LLM_MODEL all set), the server returns a goal-tailored brief: classified goal type (spec | bug | migration | sentiment | pricing | security | synthesis | product_launch), a `primary_branch` recommendation (reddit for sentiment/migration; web for spec/bug/pricing; both when opinion-heavy AND needs official sources), the exact `first_call_sequence` of raw/smart search and scrape calls to fire, 25\u201350 keyword seeds for the first search call, iteration hints, gaps to watch, and stop criteria. The goal also sets the post-sort relevance target, so state the evidence you need and what \"done\" means. No goal \u2192 the generic 5-tool playbook (no tailored brief). Write the goal as you would to a human researcher \u2014 one or two sentences, specific about what \"done\" looks like.',\n ),\n include_playbook: z\n .boolean()\n .default(false)\n .describe(\n 'Include the full 5-tool research playbook (toolbelt overview, the loop, output discipline). Default false \u2014 when the LLM planner is offline the server emits a compact stub that already names the tools and the loop. Pass true only if the agent needs the verbose tactic reference, or to override the degraded-mode shrink.',\n ),\n}).strict();\n\nexport type StartResearchParams = z.infer<typeof startResearchParamsSchema>;\n\n// `start-research` is text-only: the tool registration deliberately omits\n// `outputSchema`, and successful calls omit `structuredContent`.\nexport type StartResearchOutput = Record<string, never>;\n", "import type { MCPServer } from 'mcp-use/server';\n\nimport { registerScrapeLinksTools } from './scrape.js';\nimport { registerWebSearchTools } from './search.js';\nimport { registerStartResearchTool } from './start-research.js';\n\nexport function registerAllTools(server: MCPServer): void {\n // 5 research tools. Raw tools return provider markdown directly; smart tools\n // always run the LLM extraction/classification layer.\n registerStartResearchTool(server);\n registerWebSearchTools(server);\n registerScrapeLinksTools(server);\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["Logger", "logger", "Logger", "logger", "error", "error", "Effect", "z", "calculateBackoff", "calculateBackoff", "error", "Logger", "error", "Effect", "Layer", "MAX_RETRIES", "calculateBackoff", "error", "error", "isRecord", "readString", "Logger", "Logger", "calculateBackoff", "error", "error", "error", "Layer", "Effect", "error", "Effect", "failedContents", "raw", "rawSnippet", "Effect", "formatInputValidationError", "REDDIT_POST_PERMALINK", "REDDIT_HOST", "Effect", "jinaResponse", "markdown", "error", "Effect", "z", "Effect", "llmHealth", "Logger", "error"]
7
7
  }