@lincoln504/pi-research 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +131 -0
- package/config/searxng/default-settings.yml +189 -0
- package/config/searxng/limiter.toml +34 -0
- package/config/tooling/eslint.config.js +73 -0
- package/config/tooling/vitest.config.integration.ts +17 -0
- package/config/tooling/vitest.config.ts +40 -0
- package/config/tooling/vitest.config.unit.ts +12 -0
- package/docs/ARCHITECTURE.md +109 -0
- package/docs/assets/README-banner.jpg +0 -0
- package/package.json +92 -0
- package/scripts/setup.js +174 -0
- package/src/config.ts +206 -0
- package/src/constants.ts +148 -0
- package/src/healthcheck/index.ts +309 -0
- package/src/index.ts +71 -0
- package/src/infrastructure/searxng-lifecycle.ts +577 -0
- package/src/infrastructure/searxng-manager.ts +1173 -0
- package/src/infrastructure/state-manager.ts +918 -0
- package/src/logger.ts +246 -0
- package/src/orchestration/deep-research-orchestrator.ts +848 -0
- package/src/orchestration/deep-research-reducer.ts +129 -0
- package/src/orchestration/deep-research-types.ts +54 -0
- package/src/orchestration/id-utils.ts +63 -0
- package/src/orchestration/researcher.ts +104 -0
- package/src/orchestration/session-context.ts +43 -0
- package/src/prompts/research-tool-usage.md +45 -0
- package/src/prompts/researcher.md +166 -0
- package/src/prompts/system-coordinator.md +24 -0
- package/src/prompts/system-lead-evaluator.md +225 -0
- package/src/security/cisa-kev.ts +242 -0
- package/src/security/github-advisories.ts +607 -0
- package/src/security/index.ts +406 -0
- package/src/security/nvd.ts +532 -0
- package/src/security/osv.ts +487 -0
- package/src/security/types.ts +138 -0
- package/src/stackexchange/cache.ts +111 -0
- package/src/stackexchange/index.ts +352 -0
- package/src/stackexchange/output/compact.ts +64 -0
- package/src/stackexchange/output/index.ts +7 -0
- package/src/stackexchange/output/json.ts +21 -0
- package/src/stackexchange/output/table.ts +123 -0
- package/src/stackexchange/queries.ts +168 -0
- package/src/stackexchange/rest-client.ts +127 -0
- package/src/stackexchange/types.ts +129 -0
- package/src/tool.ts +430 -0
- package/src/tools/grep.ts +335 -0
- package/src/tools/index.ts +77 -0
- package/src/tools/scrape.ts +451 -0
- package/src/tools/search.ts +188 -0
- package/src/tools/security.ts +292 -0
- package/src/tools/stackexchange.ts +101 -0
- package/src/tui/research-panel.ts +576 -0
- package/src/tui/searxng-status.ts +80 -0
- package/src/types/extension-context.ts +60 -0
- package/src/types/llm.ts +111 -0
- package/src/utils/inject-date.ts +29 -0
- package/src/utils/input-validation.ts +103 -0
- package/src/utils/json-utils.ts +262 -0
- package/src/utils/make-resource-loader.ts +43 -0
- package/src/utils/research-error.ts +205 -0
- package/src/utils/research-export.ts +199 -0
- package/src/utils/searxng-config.ts +94 -0
- package/src/utils/session-state.ts +295 -0
- package/src/utils/shared-links.ts +123 -0
- package/src/utils/shutdown-manager.ts +54 -0
- package/src/utils/text-utils.ts +83 -0
- package/src/utils/tool-usage-tracker.ts +135 -0
- package/src/web-research/dependencies.ts +20 -0
- package/src/web-research/retry-utils.ts +232 -0
- package/src/web-research/scrapers.ts +935 -0
- package/src/web-research/search.ts +235 -0
- package/src/web-research/types.ts +47 -0
- package/src/web-research/utils.ts +158 -0
- package/tsconfig.json +40 -0
package/package.json
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lincoln504/pi-research",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Multi-agent research Deep Mode orchestration for pi. State-driven parallel researchers with web search, scraping, security databases, and Stack Exchange integration.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": false,
|
|
7
|
+
"main": "src/index.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"src/",
|
|
10
|
+
"config/",
|
|
11
|
+
"docs/",
|
|
12
|
+
"scripts/",
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"README.md",
|
|
15
|
+
"tsconfig.json"
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/Lincoln504/pi-research.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/Lincoln504/pi-research#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/Lincoln504/pi-research/issues"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"pi",
|
|
27
|
+
"pi-package",
|
|
28
|
+
"research",
|
|
29
|
+
"agent",
|
|
30
|
+
"orchestration",
|
|
31
|
+
"web-search",
|
|
32
|
+
"scraping",
|
|
33
|
+
"security",
|
|
34
|
+
"stack-exchange"
|
|
35
|
+
],
|
|
36
|
+
"author": "Lincolndeen",
|
|
37
|
+
"contributors": [
|
|
38
|
+
"MuratAitov"
|
|
39
|
+
],
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": "^22.13.0 || >=24.0.0"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"lint": "npx eslint --config config/tooling/eslint.config.js src/",
|
|
46
|
+
"lint:fix": "npx eslint --config config/tooling/eslint.config.js src/ --fix",
|
|
47
|
+
"type-check": "tsc --noEmit",
|
|
48
|
+
"test": "vitest run --config config/tooling/vitest.config.unit.ts",
|
|
49
|
+
"test:unit": "vitest run --config config/tooling/vitest.config.unit.ts",
|
|
50
|
+
"test:integration": "vitest run --config config/tooling/vitest.config.integration.ts",
|
|
51
|
+
"test:watch": "vitest --config config/tooling/vitest.config.unit.ts",
|
|
52
|
+
"test:coverage": "vitest run --config config/tooling/vitest.config.ts --coverage",
|
|
53
|
+
"postinstall": "node scripts/setup.js",
|
|
54
|
+
"install:browsers": "npx playwright install chromium",
|
|
55
|
+
"install:deps": "npx playwright install-deps",
|
|
56
|
+
"setup": "npm install && npm run install:browsers"
|
|
57
|
+
},
|
|
58
|
+
"pi": {
|
|
59
|
+
"extensions": [
|
|
60
|
+
"src/index.ts"
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"@kreuzberg/html-to-markdown-node": "^2.30.0",
|
|
65
|
+
"dockerode": "^4.0.10",
|
|
66
|
+
"js-yaml": "^4.1.1",
|
|
67
|
+
"node-html-markdown": "^2.0.0",
|
|
68
|
+
"playwright": "^1.52.0"
|
|
69
|
+
},
|
|
70
|
+
"optionalDependencies": {
|
|
71
|
+
"@kreuzberg/html-to-markdown-node-darwin-arm64": "^2.30.0",
|
|
72
|
+
"@kreuzberg/html-to-markdown-node-darwin-x64": "^2.30.0",
|
|
73
|
+
"@kreuzberg/html-to-markdown-node-win32-x64-msvc": "^2.30.0"
|
|
74
|
+
},
|
|
75
|
+
"peerDependencies": {
|
|
76
|
+
"@mariozechner/pi-coding-agent": ">=0.65.0",
|
|
77
|
+
"@sinclair/typebox": ">=0.34.0"
|
|
78
|
+
},
|
|
79
|
+
"devDependencies": {
|
|
80
|
+
"@eslint/js": "^10.0.1",
|
|
81
|
+
"@types/dockerode": "^4.0.1",
|
|
82
|
+
"@types/js-yaml": "^4.0.9",
|
|
83
|
+
"@typescript-eslint/eslint-plugin": "^8.58.0",
|
|
84
|
+
"@typescript-eslint/parser": "^8.58.0",
|
|
85
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
86
|
+
"eslint": "^10.1.0",
|
|
87
|
+
"eslint-config-prettier": "^10.1.0",
|
|
88
|
+
"testcontainers": "^11.13.0",
|
|
89
|
+
"typescript": "^6.0.2",
|
|
90
|
+
"vitest": "^4.1.2"
|
|
91
|
+
}
|
|
92
|
+
}
|
package/scripts/setup.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* pi-research Postinstall Setup Script
|
|
5
|
+
*
|
|
6
|
+
* This script runs automatically after `npm install` and ensures:
|
|
7
|
+
* 1. Playwright browser binaries are installed (Chromium, Firefox, WebKit)
|
|
8
|
+
* 2. System dependencies are available (if run with --system-deps flag)
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* npm run setup # Full setup: install deps + browsers
|
|
12
|
+
* npm run install:browsers # Install only browser binaries
|
|
13
|
+
* npm run install:deps # Install system dependencies only
|
|
14
|
+
*
|
|
15
|
+
* Environment Variables:
|
|
16
|
+
* PLAYWRIGHT_BROWSERS_PATH - Custom path for browser cache
|
|
17
|
+
* PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 - Skip browser download
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { execSync } from 'child_process';
|
|
21
|
+
import { existsSync, readdirSync, statSync } from 'fs';
|
|
22
|
+
import { join, dirname } from 'path';
|
|
23
|
+
import { fileURLToPath } from 'url';
|
|
24
|
+
import { homedir, platform } from 'os';
|
|
25
|
+
|
|
26
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
27
|
+
const __dirname = dirname(__filename);
|
|
28
|
+
|
|
29
|
+
console.log('='.repeat(60));
|
|
30
|
+
console.log('🔧 pi-research Setup');
|
|
31
|
+
console.log('='.repeat(60));
|
|
32
|
+
|
|
33
|
+
// Check Node.js version
|
|
34
|
+
const nodeVersion = process.version.replace('v', '');
|
|
35
|
+
const [major] = nodeVersion.split('.').map(Number);
|
|
36
|
+
|
|
37
|
+
console.log(`\nℹ️ Node.js version: v${nodeVersion}`);
|
|
38
|
+
|
|
39
|
+
const requiredVersions = {
|
|
40
|
+
min: 22,
|
|
41
|
+
preferred: [22, 24]
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
if (major < requiredVersions.min) {
|
|
45
|
+
console.warn(
|
|
46
|
+
`⚠️ WARNING: Node.js ${nodeVersion} is below the minimum required version ${requiredVersions.min}.`
|
|
47
|
+
);
|
|
48
|
+
console.warn(' Please upgrade to Node.js 22.13.0+ or 24.x+');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(` Required: >= ${requiredVersions.min}.0.0`);
|
|
52
|
+
|
|
53
|
+
// Check if playwright is installed
|
|
54
|
+
const playwrightPath = join(process.cwd(), 'node_modules', 'playwright');
|
|
55
|
+
|
|
56
|
+
if (!existsSync(playwrightPath)) {
|
|
57
|
+
console.error('\n❌ ERROR: playwright is not installed.');
|
|
58
|
+
console.error(' Run `npm install` first, then `npm run setup`.');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log('✅ Playwright package installed');
|
|
63
|
+
|
|
64
|
+
// Detect OS for browser installation
|
|
65
|
+
const isLinux = platform() === 'linux';
|
|
66
|
+
const isDarwin = platform() === 'darwin';
|
|
67
|
+
const isWindows = platform() === 'win32';
|
|
68
|
+
|
|
69
|
+
console.log(`\n📦 Detected platform: ${platform()}`);
|
|
70
|
+
|
|
71
|
+
// Install browsers
|
|
72
|
+
console.log('\n🌐 Installing Playwright browsers...');
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
// Check if user wants to install system dependencies
|
|
76
|
+
const installDeps = process.argv.includes('--system-deps') ||
|
|
77
|
+
process.env.PLAYWRIGHT_INSTALL_DEPS === 'true';
|
|
78
|
+
|
|
79
|
+
if (installDeps && isLinux) {
|
|
80
|
+
console.log('⚙️ Installing Linux system dependencies for Playwright...');
|
|
81
|
+
try {
|
|
82
|
+
execSync('npx playwright install-deps', {
|
|
83
|
+
stdio: 'inherit',
|
|
84
|
+
env: { ...process.env, PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '0' }
|
|
85
|
+
});
|
|
86
|
+
console.log('✅ System dependencies installed');
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.warn('⚠️ Could not install system dependencies automatically.');
|
|
89
|
+
console.warn(' Try running: sudo apt-get install $(npx playwright install-deps --print-deps)');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Install browser binaries
|
|
94
|
+
// Only Chromium is needed for scraping (as per scrapers.ts implementation)
|
|
95
|
+
console.log(' Installing Chromium browser...');
|
|
96
|
+
execSync('npx playwright install chromium', {
|
|
97
|
+
stdio: 'inherit',
|
|
98
|
+
env: {
|
|
99
|
+
...process.env,
|
|
100
|
+
PLAYWRIGHT_BROWSERS_PATH: process.env.PLAYWRIGHT_BROWSERS_PATH || ''
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
console.log('✅ Browsers installed successfully');
|
|
105
|
+
|
|
106
|
+
// Verify installation
|
|
107
|
+
console.log('\n📋 Verifying browser installation...');
|
|
108
|
+
const browsers = ['chromium']; // Only Chromium is required for scraping
|
|
109
|
+
const cacheDir = process.env['PLAYWRIGHT_BROWSERS_PATH'] ||
|
|
110
|
+
(process.platform === 'win32'
|
|
111
|
+
? join(process.env['LOCALAPPDATA'] || join(homedir(), 'AppData', 'Local'), 'ms-playwright')
|
|
112
|
+
: join(homedir(), '.cache', 'ms-playwright'));
|
|
113
|
+
|
|
114
|
+
browsers.forEach(browser => {
|
|
115
|
+
let browserDirs = [];
|
|
116
|
+
try {
|
|
117
|
+
browserDirs = readdirSync(cacheDir).filter(f =>
|
|
118
|
+
f.startsWith(browser) && statSync(join(cacheDir, f)).isDirectory()
|
|
119
|
+
);
|
|
120
|
+
} catch {
|
|
121
|
+
// cache dir may not exist yet or may be at a different path
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (browserDirs.length > 0) {
|
|
125
|
+
console.log(` ✅ ${browser}: ${browserDirs.sort().join(', ')}`);
|
|
126
|
+
} else {
|
|
127
|
+
console.warn(` ⚠️ ${browser}: Not found in cache`);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Show browser versions
|
|
132
|
+
console.log('\n🔍 Browser versions:');
|
|
133
|
+
try {
|
|
134
|
+
const playwrightVersion = execSync('npx playwright --version', { encoding: 'utf8' }).trim();
|
|
135
|
+
console.log(` Playwright: ${playwrightVersion}`);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.warn(' Could not determine Playwright version');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error('\n❌ ERROR: Failed to install browsers');
|
|
142
|
+
console.error(' Error:', error.message);
|
|
143
|
+
console.error('\n💡 Troubleshooting:');
|
|
144
|
+
console.error(' 1. Ensure you have internet connectivity');
|
|
145
|
+
console.error(' 2. Try running: npm run install:browsers');
|
|
146
|
+
console.error(' 3. Check Node.js version: node --version');
|
|
147
|
+
console.error(' 4. For Linux: sudo apt-get install -y $(npx playwright install-deps --print-deps)');
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log('\n' + '='.repeat(60));
|
|
152
|
+
console.log('🎉 Setup complete! Chromium browser installed. pi-research is ready to use.');
|
|
153
|
+
console.log('='.repeat(60));
|
|
154
|
+
|
|
155
|
+
// Additional info
|
|
156
|
+
console.log('\n📚 Quick start:');
|
|
157
|
+
console.log(' • npm test - Run unit tests');
|
|
158
|
+
console.log(' • npm run type-check - Type check TypeScript');
|
|
159
|
+
console.log(' • npm run lint - Run ESLint');
|
|
160
|
+
console.log(' • npm run setup - Full setup with system deps');
|
|
161
|
+
|
|
162
|
+
// Platform-specific tips
|
|
163
|
+
if (isLinux) {
|
|
164
|
+
console.log('\n💡 Linux tip: If you encounter issues, install system deps:');
|
|
165
|
+
console.log(' sudo apt-get install -y libgbm1 libnss3 libatk1.0-0');
|
|
166
|
+
console.log(' libatk-bridge2.0-0 libcups2 libxkbcommon0 libxcomposite1');
|
|
167
|
+
} else if (isDarwin) {
|
|
168
|
+
console.log('\n💡 macOS: Ensure Xcode Command Line Tools are installed:');
|
|
169
|
+
console.log(' xcode-select --install');
|
|
170
|
+
} else if (isWindows) {
|
|
171
|
+
console.log('\n💡 Windows: Ensure you have the latest Visual C++ redistributables');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log('\n');
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Module (Refactored for Testability)
|
|
3
|
+
*
|
|
4
|
+
* Uses factory pattern instead of module-level caching.
|
|
5
|
+
* Allows for different configurations in tests.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { logger } from './logger.ts';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Configuration values
|
|
12
|
+
*/
|
|
13
|
+
export interface Config {
|
|
14
|
+
/** Per-researcher timeout in milliseconds (default: 240000) */
|
|
15
|
+
RESEARCHER_TIMEOUT_MS: number;
|
|
16
|
+
/** Maximum researchers allowed to run simultaneously (default: 3) */
|
|
17
|
+
MAX_CONCURRENT_RESEARCHERS: number;
|
|
18
|
+
/** Proxy URL for SearXNG searches (optional) */
|
|
19
|
+
PROXY_URL?: string;
|
|
20
|
+
/** Brave Search API key — enables the braveapi engine in SearXNG when set (optional) */
|
|
21
|
+
BRAVE_SEARCH_API_KEY?: string;
|
|
22
|
+
/** Health check timeout in milliseconds (default: 15000) */
|
|
23
|
+
HEALTH_CHECK_TIMEOUT_MS?: number;
|
|
24
|
+
/** Global TUI refresh debounce in milliseconds (default: 10) */
|
|
25
|
+
TUI_REFRESH_DEBOUNCE_MS: number;
|
|
26
|
+
/** Console restore delay after research in milliseconds (default: 15000) */
|
|
27
|
+
CONSOLE_RESTORE_DELAY_MS: number;
|
|
28
|
+
/** BCP 47 language tag sent to SearXNG to filter search results (default: 'en-US') */
|
|
29
|
+
SEARCH_LANGUAGE: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Configuration defaults
|
|
34
|
+
*/
|
|
35
|
+
const DEFAULTS: Config = {
|
|
36
|
+
RESEARCHER_TIMEOUT_MS: 240000,
|
|
37
|
+
MAX_CONCURRENT_RESEARCHERS: 3,
|
|
38
|
+
PROXY_URL: undefined,
|
|
39
|
+
BRAVE_SEARCH_API_KEY: undefined,
|
|
40
|
+
HEALTH_CHECK_TIMEOUT_MS: 15000,
|
|
41
|
+
TUI_REFRESH_DEBOUNCE_MS: 10,
|
|
42
|
+
CONSOLE_RESTORE_DELAY_MS: 15000,
|
|
43
|
+
SEARCH_LANGUAGE: 'en-US',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Parse environment variable to number with default
|
|
48
|
+
*/
|
|
49
|
+
function parseEnvNumber(
|
|
50
|
+
env: Record<string, string | undefined>,
|
|
51
|
+
key: string,
|
|
52
|
+
defaultValue: number,
|
|
53
|
+
base: number = 10
|
|
54
|
+
): number {
|
|
55
|
+
const value = env[key];
|
|
56
|
+
if (value === undefined) {
|
|
57
|
+
return defaultValue;
|
|
58
|
+
}
|
|
59
|
+
const parsed = parseInt(value, base);
|
|
60
|
+
if (isNaN(parsed)) {
|
|
61
|
+
logger.warn(`[config] Invalid value for ${key}, using default: ${defaultValue}`);
|
|
62
|
+
return defaultValue;
|
|
63
|
+
}
|
|
64
|
+
return parsed;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get optional string environment variable
|
|
69
|
+
*/
|
|
70
|
+
function parseEnvString(
|
|
71
|
+
env: Record<string, string | undefined>,
|
|
72
|
+
key: string
|
|
73
|
+
): string | undefined {
|
|
74
|
+
const value = env[key];
|
|
75
|
+
return value === undefined ? undefined : value;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create configuration from environment variables
|
|
80
|
+
* @param env Optional environment object (for testing)
|
|
81
|
+
* @returns Configuration object
|
|
82
|
+
*/
|
|
83
|
+
export function createConfig(env: Record<string, string | undefined> = process.env): Config {
|
|
84
|
+
return {
|
|
85
|
+
RESEARCHER_TIMEOUT_MS: parseEnvNumber(
|
|
86
|
+
env,
|
|
87
|
+
'PI_RESEARCH_RESEARCHER_TIMEOUT_MS',
|
|
88
|
+
DEFAULTS.RESEARCHER_TIMEOUT_MS
|
|
89
|
+
),
|
|
90
|
+
MAX_CONCURRENT_RESEARCHERS: parseEnvNumber(
|
|
91
|
+
env,
|
|
92
|
+
'PI_RESEARCH_MAX_CONCURRENT_RESEARCHERS',
|
|
93
|
+
DEFAULTS.MAX_CONCURRENT_RESEARCHERS
|
|
94
|
+
),
|
|
95
|
+
PROXY_URL: parseEnvString(env, 'PROXY_URL'),
|
|
96
|
+
BRAVE_SEARCH_API_KEY: parseEnvString(env, 'BRAVE_SEARCH_API_KEY'),
|
|
97
|
+
HEALTH_CHECK_TIMEOUT_MS: parseEnvNumber(
|
|
98
|
+
env,
|
|
99
|
+
'PI_RESEARCH_HEALTH_CHECK_TIMEOUT_MS',
|
|
100
|
+
DEFAULTS.HEALTH_CHECK_TIMEOUT_MS as number,
|
|
101
|
+
),
|
|
102
|
+
TUI_REFRESH_DEBOUNCE_MS: parseEnvNumber(
|
|
103
|
+
env,
|
|
104
|
+
'PI_RESEARCH_TUI_REFRESH_DEBOUNCE_MS',
|
|
105
|
+
DEFAULTS.TUI_REFRESH_DEBOUNCE_MS
|
|
106
|
+
),
|
|
107
|
+
CONSOLE_RESTORE_DELAY_MS: parseEnvNumber(
|
|
108
|
+
env,
|
|
109
|
+
'PI_RESEARCH_CONSOLE_RESTORE_DELAY_MS',
|
|
110
|
+
DEFAULTS.CONSOLE_RESTORE_DELAY_MS
|
|
111
|
+
),
|
|
112
|
+
SEARCH_LANGUAGE: parseEnvString(env, 'PI_RESEARCH_SEARCH_LANGUAGE') ?? DEFAULTS.SEARCH_LANGUAGE,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Global configuration instance (for backward compatibility)
|
|
118
|
+
*/
|
|
119
|
+
let globalConfig: Config | null = null;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get global configuration
|
|
123
|
+
* Creates from environment if not yet initialized
|
|
124
|
+
*/
|
|
125
|
+
export function getConfig(): Config {
|
|
126
|
+
if (!globalConfig) {
|
|
127
|
+
globalConfig = createConfig();
|
|
128
|
+
}
|
|
129
|
+
return globalConfig;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Set global configuration (for testing or manual override)
|
|
134
|
+
*/
|
|
135
|
+
export function setConfig(config: Config): void {
|
|
136
|
+
globalConfig = config;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Reset global configuration to environment defaults
|
|
141
|
+
*/
|
|
142
|
+
export function resetConfig(): void {
|
|
143
|
+
globalConfig = null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Validate configuration values
|
|
148
|
+
* @param config Configuration to validate
|
|
149
|
+
* @throws {Error} if configuration is invalid
|
|
150
|
+
*/
|
|
151
|
+
export function validateConfig(config: Config = getConfig()): void {
|
|
152
|
+
if (config.RESEARCHER_TIMEOUT_MS < 30000 || config.RESEARCHER_TIMEOUT_MS > 600000) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`PI_RESEARCH_RESEARCHER_TIMEOUT_MS must be between 30000ms (30s) and 600000ms (10m), got ${config.RESEARCHER_TIMEOUT_MS}ms`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (config.MAX_CONCURRENT_RESEARCHERS < 1 || config.MAX_CONCURRENT_RESEARCHERS > 10) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`PI_RESEARCH_MAX_CONCURRENT_RESEARCHERS must be between 1 and 10, got ${config.MAX_CONCURRENT_RESEARCHERS}`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Warn if proxy is configured
|
|
165
|
+
if (config.PROXY_URL) {
|
|
166
|
+
logger.warn('[config] Proxy configured - searches will be routed through:', config.PROXY_URL);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
logger.debug('[config] Configuration validated:', {
|
|
170
|
+
RESEARCHER_TIMEOUT_MS: config.RESEARCHER_TIMEOUT_MS,
|
|
171
|
+
PROXY_URL: config.PROXY_URL,
|
|
172
|
+
HEALTH_CHECK_TIMEOUT_MS: config.HEALTH_CHECK_TIMEOUT_MS,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ============================================================================
|
|
177
|
+
// Backward Compatibility Exports
|
|
178
|
+
// ============================================================================
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @deprecated Use getConfig().RESEARCHER_TIMEOUT_MS instead
|
|
182
|
+
* Returns current config value - reflects environment changes if config is reset
|
|
183
|
+
*/
|
|
184
|
+
export function getResearcherTimeoutMs(): number {
|
|
185
|
+
return getConfig().RESEARCHER_TIMEOUT_MS;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @deprecated Use getConfig().PROXY_URL instead
|
|
190
|
+
* Returns current config value - reflects environment changes if config is reset
|
|
191
|
+
*/
|
|
192
|
+
export function getProxyUrl(): string | undefined {
|
|
193
|
+
return getConfig().PROXY_URL;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @deprecated Use getConfig().BRAVE_SEARCH_API_KEY instead
|
|
198
|
+
*/
|
|
199
|
+
export function getBraveSearchApiKey(): string | undefined {
|
|
200
|
+
return getConfig().BRAVE_SEARCH_API_KEY;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Legacy variable exports (for backward compatibility, but reflect module load time values)
|
|
204
|
+
export const RESEARCHER_TIMEOUT_MS = getResearcherTimeoutMs();
|
|
205
|
+
export const PROXY_URL = getProxyUrl();
|
|
206
|
+
export const BRAVE_SEARCH_API_KEY = getBraveSearchApiKey();
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Constants
|
|
3
|
+
*
|
|
4
|
+
* Centralized constants for magic numbers and configuration values.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ==================== Time Constants ====================
|
|
8
|
+
|
|
9
|
+
/** Flash duration for UI slice animations in milliseconds */
|
|
10
|
+
export const FLASH_DURATION_MS = 500;
|
|
11
|
+
|
|
12
|
+
/** Delay between API requests in milliseconds (e.g., NVD rate limit) */
|
|
13
|
+
export const REQUEST_DELAY_MS = 6500;
|
|
14
|
+
|
|
15
|
+
// ==================== Research Constraints ====================
|
|
16
|
+
|
|
17
|
+
/** Maximum gathering (tool) calls per researcher */
|
|
18
|
+
export const MAX_GATHERING_CALLS = 4;
|
|
19
|
+
|
|
20
|
+
/** Maximum scrape tool calls per researcher (1 handshake + up to 3 execution batches) */
|
|
21
|
+
export const MAX_SCRAPE_CALLS = 4;
|
|
22
|
+
|
|
23
|
+
/** Maximum URLs to scrape per batch */
|
|
24
|
+
export const MAX_SCRAPE_URLS = 3;
|
|
25
|
+
|
|
26
|
+
/** Maximum siblings (researchers) allowed to run simultaneously across all rounds */
|
|
27
|
+
export const MAX_CONCURRENT_RESEARCHERS = 3;
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
// ==================== Complexity Levels ====================
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Complexity 0: Quick mode (no orchestrator, 1 researcher, 1 round)
|
|
34
|
+
* Complexity 1: Normal (2 researchers, 2 rounds)
|
|
35
|
+
* Complexity 2: Deep (3 researchers, 3 rounds)
|
|
36
|
+
* Complexity 3: Ultra (5 researchers, 5 rounds)
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/** Initial researcher count for complexity 1 (Normal) */
|
|
40
|
+
export const INITIAL_RESEARCHERS_LEVEL_1 = 2;
|
|
41
|
+
|
|
42
|
+
/** Initial researcher count for complexity 2 (Deep) */
|
|
43
|
+
export const INITIAL_RESEARCHERS_LEVEL_2 = 3;
|
|
44
|
+
|
|
45
|
+
/** Initial researcher count for complexity 3 (Ultra) */
|
|
46
|
+
export const INITIAL_RESEARCHERS_LEVEL_3 = 5;
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
/** Maximum rounds for complexity 1 (Normal) */
|
|
50
|
+
export const MAX_ROUNDS_LEVEL_1 = 2;
|
|
51
|
+
|
|
52
|
+
/** Maximum rounds for complexity 2 (Deep) */
|
|
53
|
+
export const MAX_ROUNDS_LEVEL_2 = 3;
|
|
54
|
+
|
|
55
|
+
/** Maximum rounds for complexity 3 (Ultra) */
|
|
56
|
+
export const MAX_ROUNDS_LEVEL_3 = 5;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Extra rounds the lead evaluator may earn beyond targetRounds when it judges
|
|
60
|
+
* critical gaps still exist. Rounds targetRounds+1 through targetRounds+MAX_EXTRA_ROUNDS
|
|
61
|
+
* are "bonus" territory; round targetRounds+MAX_EXTRA_ROUNDS+1 is unreachable (hard limit).
|
|
62
|
+
*/
|
|
63
|
+
export const MAX_EXTRA_ROUNDS = 2;
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
// ==================== Timeout Constants ====================
|
|
67
|
+
|
|
68
|
+
/** Default timeout for SearXNG requests in milliseconds */
|
|
69
|
+
export const SEARXNG_TIMEOUT_MS = 30000;
|
|
70
|
+
|
|
71
|
+
/** Default timeout for OSV API requests in milliseconds */
|
|
72
|
+
export const OSV_TIMEOUT_MS = 30000;
|
|
73
|
+
|
|
74
|
+
/** Default timeout for NVD API requests in milliseconds */
|
|
75
|
+
export const NVD_TIMEOUT_MS = 30000;
|
|
76
|
+
|
|
77
|
+
// ==================== Export Constants ====================
|
|
78
|
+
|
|
79
|
+
/** Maximum query length for filename sanitization */
|
|
80
|
+
export const MAX_QUERY_LENGTH = 50;
|
|
81
|
+
|
|
82
|
+
/** Maximum retry attempts for export file collision */
|
|
83
|
+
export const MAX_EXPORT_RETRIES = 3;
|
|
84
|
+
|
|
85
|
+
// ==================== Retry Constants ====================
|
|
86
|
+
|
|
87
|
+
/** Default maximum retry attempts for transient errors */
|
|
88
|
+
export const DEFAULT_MAX_RETRIES = 3;
|
|
89
|
+
|
|
90
|
+
/** Default initial delay for exponential backoff in milliseconds */
|
|
91
|
+
export const DEFAULT_INITIAL_DELAY_MS = 1000;
|
|
92
|
+
|
|
93
|
+
/** Default maximum delay for exponential backoff in milliseconds */
|
|
94
|
+
export const DEFAULT_MAX_DELAY_MS = 10000;
|
|
95
|
+
|
|
96
|
+
// ==================== Context-Aware Scraping Constants ====================
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Fraction of context window consumed beyond which all scraping is blocked.
|
|
100
|
+
* At or above this threshold, researchers skip batches and move to synthesis.
|
|
101
|
+
*/
|
|
102
|
+
export const MAX_CONTEXT_FRACTION_FOR_SCRAPING = 0.55;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Estimated tokens consumed per scrape call (handshake + content).
|
|
106
|
+
* Used to project whether another batch is feasible given remaining context.
|
|
107
|
+
*/
|
|
108
|
+
export const AVG_TOKENS_PER_SCRAPE = 15000;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Fallback context window size (tokens) when the model does not advertise one.
|
|
112
|
+
* Corresponds to typical large-context models (Sonnet, Gemini 1.5 Pro, etc.).
|
|
113
|
+
*/
|
|
114
|
+
export const DEFAULT_MODEL_CONTEXT_WINDOW = 200000;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Maximum URLs per second-scrape batch (more targeted than batch 1).
|
|
118
|
+
* Kept lower to preserve context for synthesis after focused follow-up.
|
|
119
|
+
*/
|
|
120
|
+
export const BATCH_2_MAX_SCRAPE_URLS = 2;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Default maxConcurrency for the second scrape batch.
|
|
124
|
+
* Higher than batch 1 because batch 2 URLs are already prioritized.
|
|
125
|
+
*/
|
|
126
|
+
export const BATCH_2_DEFAULT_CONCURRENCY = 15;
|
|
127
|
+
|
|
128
|
+
// ==================== UI Constants ====================
|
|
129
|
+
|
|
130
|
+
/** Maximum characters for final report before truncation */
|
|
131
|
+
export const MAX_REPORT_LENGTH = 200000;
|
|
132
|
+
|
|
133
|
+
/** Visual width of the ====------ progress bar in the TUI header */
|
|
134
|
+
export const PROGRESS_BAR_WIDTH = 18;
|
|
135
|
+
|
|
136
|
+
// ==================== Orchestrator Constants ====================
|
|
137
|
+
|
|
138
|
+
/** Maximum characters per researcher report when sent to lead evaluator (truncated with link preservation) */
|
|
139
|
+
export const MAX_EVALUATOR_REPORT_LENGTH = 50000;
|
|
140
|
+
|
|
141
|
+
/** Delay in milliseconds between launching concurrent researchers to stagger startup */
|
|
142
|
+
export const RESEARCHER_LAUNCH_DELAY_MS = 1500;
|
|
143
|
+
|
|
144
|
+
/** Delay in milliseconds after state update to ensure propagation before next action */
|
|
145
|
+
export const STATE_PROPAGATION_DELAY_MS = 50;
|
|
146
|
+
|
|
147
|
+
/** Minimum streaming text length before updating UI with estimated tokens */
|
|
148
|
+
export const STREAMING_UPDATE_THRESHOLD_CHARS = 200;
|