@lincoln504/pi-research 0.1.12 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +64 -115
- package/dist/cli.mjs +23211 -0
- package/dist/prompts/research-knowledge-relevance-triage.md +35 -0
- package/dist/prompts/research-knowledge-search-extractor.md +41 -0
- package/dist/prompts/research-tool-usage.md +92 -0
- package/dist/prompts/researcher.md +89 -0
- package/dist/prompts/system-coordinator.md +75 -0
- package/dist/prompts/system-lead-evaluator.md +118 -0
- package/dist/thread-worker.mjs +2611 -0
- package/dist/webgpu-probe.mjs +80 -0
- package/docs/AGENT-SKILL.md +102 -0
- package/docs/ARCHITECTURE.md +306 -80
- package/docs/CONFIGURATION.md +249 -0
- package/docs/KNOWLEDGE-STORE.md +213 -0
- package/docs/PI-EXTENSION.md +91 -0
- package/docs/SDK.md +113 -0
- package/docs/THIRD-PARTY-NOTICES.md +64 -0
- package/package.json +102 -36
- package/scripts/build.cjs +140 -0
- package/scripts/cleanup.cjs +149 -0
- package/scripts/prepare.cjs +28 -0
- package/scripts/setup.cjs +134 -0
- package/skills/pi-research/README.md +90 -0
- package/skills/pi-research/SKILL.md +102 -0
- package/skills/pi-research/references/configuration.md +77 -0
- package/skills/pi-research/scripts/run.mjs +237 -0
- package/src/cleanup/research-cleanup.ts +140 -0
- package/src/cli.ts +1044 -0
- package/src/config.ts +1036 -147
- package/src/constants.ts +115 -84
- package/src/core/health-check-service.ts +265 -0
- package/src/core/interfaces/health-check-interfaces.ts +50 -0
- package/src/core/interfaces/knowledge-interfaces.ts +190 -0
- package/src/core/interfaces/observer-interfaces.ts +67 -0
- package/src/core/interfaces/orchestration-interfaces.ts +86 -0
- package/src/core/interfaces/planning-interfaces.ts +100 -0
- package/src/core/interfaces/process-interfaces.ts +37 -0
- package/src/core/interfaces/research-plan-types.ts +52 -0
- package/src/core/interfaces/scheduler-interfaces.ts +91 -0
- package/src/core/interfaces/service-names.ts +39 -0
- package/src/core/interfaces/state-manager-interfaces.ts +29 -0
- package/src/core/interfaces/steering-interfaces.ts +31 -0
- package/src/core/llm/agentic-repair.ts +169 -0
- package/src/core/llm/inject-date.ts +52 -0
- package/src/core/llm/llm-timeout.ts +64 -0
- package/src/core/llm/llm-utils.ts +86 -0
- package/src/core/llm/model-registry-factory.ts +287 -0
- package/src/core/llm/pi-ai-completion.ts +29 -0
- package/src/core/llm/prompts.ts +50 -0
- package/src/core/llm/research-model-resolver.ts +91 -0
- package/src/core/planning-constants.ts +13 -0
- package/src/core/planning-service.ts +649 -0
- package/src/core/planning-utils.ts +299 -0
- package/src/core/scheduler-factory.ts +48 -0
- package/src/core/scheduler-service.ts +123 -0
- package/src/core/service-initialization.ts +194 -0
- package/src/core/service-interfaces.ts +48 -0
- package/src/core/service-registry.ts +659 -0
- package/src/healthcheck/index.ts +271 -265
- package/src/healthcheck/registry.ts +130 -0
- package/src/index.ts +639 -50
- package/src/infrastructure/browser/browser-cleanup.ts +309 -0
- package/src/infrastructure/browser/browser-client.ts +199 -0
- package/src/infrastructure/browser/browser-error-utils.ts +160 -0
- package/src/infrastructure/browser/browser-lifecycle.ts +120 -0
- package/src/infrastructure/browser/browser-server.ts +174 -0
- package/src/infrastructure/browser/browser-task-scheduler.ts +461 -0
- package/src/infrastructure/browser/cleanup-utils.ts +155 -0
- package/src/infrastructure/browser/client-agent.ts +31 -0
- package/src/infrastructure/browser/config.ts +288 -0
- package/src/infrastructure/browser/ensure-browser.ts +205 -0
- package/src/infrastructure/browser/index.ts +37 -0
- package/src/infrastructure/browser/priority-task-queue.ts +226 -0
- package/src/infrastructure/browser/scheduler-factory.ts +354 -0
- package/src/infrastructure/browser/task-execution-service.ts +209 -0
- package/src/infrastructure/browser/thread-worker-browser.ts +282 -0
- package/src/infrastructure/browser/thread-worker-lifecycle.ts +323 -0
- package/src/infrastructure/browser/thread-worker-messaging.ts +662 -0
- package/src/infrastructure/browser/thread-worker-types.ts +25 -0
- package/src/infrastructure/browser/thread-worker.mjs +2611 -0
- package/src/infrastructure/browser/thread-worker.ts +168 -0
- package/src/infrastructure/browser/worker-pool-manager.ts +449 -0
- package/src/infrastructure/embedding/embedding-client.ts +205 -0
- package/src/infrastructure/embedding/embedding-factory.ts +324 -0
- package/src/infrastructure/embedding/embedding-server.ts +535 -0
- package/src/infrastructure/file-lock-service.ts +592 -0
- package/src/infrastructure/gpu-resource-service.ts +191 -0
- package/src/infrastructure/knowledge-store-service.ts +475 -0
- package/src/infrastructure/metrics-service.ts +129 -0
- package/src/infrastructure/process-lifecycle-service.ts +253 -0
- package/src/infrastructure/scheduler-factory-service.ts +58 -0
- package/src/infrastructure/service-initialization.ts +292 -0
- package/src/infrastructure/state/state-backup-manager.ts +290 -0
- package/src/infrastructure/state/state-browser-api.ts +62 -0
- package/src/infrastructure/state/state-browser-manager.ts +70 -0
- package/src/infrastructure/state/state-manager-service.ts +264 -0
- package/src/infrastructure/state/state-manager.ts +547 -0
- package/src/infrastructure/state/state-metrics.ts +92 -0
- package/src/infrastructure/state/state-migration.ts +103 -0
- package/src/infrastructure/state/state-path-configuration.ts +119 -0
- package/src/infrastructure/state/state-session-api.ts +94 -0
- package/src/infrastructure/state/state-session-manager.ts +127 -0
- package/src/infrastructure/state/state-validator.ts +93 -0
- package/src/infrastructure/types/state-types.ts +85 -0
- package/src/knowledge/chunker.ts +157 -0
- package/src/knowledge/embedder-init.ts +413 -0
- package/src/knowledge/embedder-utils.ts +296 -0
- package/src/knowledge/embedder.ts +592 -0
- package/src/knowledge/index.ts +126 -0
- package/src/knowledge/lancedb-loader.ts +35 -0
- package/src/knowledge/migration.ts +17 -0
- package/src/knowledge/model-config.ts +132 -0
- package/src/knowledge/onnx-env.ts +48 -0
- package/src/knowledge/store-operations.ts +291 -0
- package/src/knowledge/store-schema.ts +79 -0
- package/src/knowledge/store.ts +1251 -0
- package/src/knowledge/transformers-loader.ts +31 -0
- package/src/knowledge/webgpu-probe.mjs +80 -0
- package/src/knowledge/webgpu-viability.ts +255 -0
- package/src/knowledge/writer-queue.ts +252 -0
- package/src/logger.ts +316 -122
- package/src/observers/research-observer-impl.ts +459 -0
- package/src/orchestration/deep-research-orchestrator.ts +524 -773
- package/src/orchestration/deep-research-types.ts +47 -35
- package/src/orchestration/headless-observer.ts +114 -0
- package/src/orchestration/orchestration-types.ts +33 -0
- package/src/orchestration/quick-research-orchestrator.ts +463 -0
- package/src/orchestration/research-orchestration-service.ts +549 -0
- package/src/orchestration/research-session-service.ts +178 -0
- package/src/orchestration/research-synthesis-service.ts +348 -0
- package/src/orchestration/researcher-executor.ts +394 -0
- package/src/orchestration/researcher.ts +115 -42
- package/src/orchestration/service-initialization.ts +109 -0
- package/src/orchestration/session-context.ts +7 -3
- package/src/orchestration/session-state.ts +672 -0
- package/src/prompts/research-knowledge-relevance-triage.md +35 -0
- package/src/prompts/research-knowledge-search-extractor.md +41 -0
- package/src/prompts/research-tool-usage.md +63 -16
- package/src/prompts/researcher.md +59 -136
- package/src/prompts/system-coordinator.md +64 -13
- package/src/prompts/system-lead-evaluator.md +82 -189
- package/src/research-config.ts +872 -0
- package/src/sdk.ts +847 -0
- package/src/security/cisa-kev.ts +72 -9
- package/src/security/cvss.ts +92 -0
- package/src/security/ecosystem.ts +45 -0
- package/src/security/github-advisories.ts +168 -428
- package/src/security/github-advisory-types.ts +282 -0
- package/src/security/index.ts +63 -177
- package/src/security/nvd-client.ts +550 -0
- package/src/security/nvd-types.ts +179 -0
- package/src/security/osv-client.ts +246 -0
- package/src/security/osv-types.ts +285 -0
- package/src/security/types.ts +67 -53
- package/src/skill-install/skill-installer.ts +519 -0
- package/src/stackexchange/index.ts +81 -27
- package/src/stackexchange/output/compact.ts +7 -6
- package/src/stackexchange/output/table.ts +11 -7
- package/src/stackexchange/queries.ts +0 -10
- package/src/stackexchange/rest-client.ts +141 -66
- package/src/stackexchange/types.ts +0 -17
- package/src/tool.ts +15 -423
- package/src/tools/grep.ts +32 -310
- package/src/tools/health-tool-definition.ts +101 -0
- package/src/tools/index.ts +38 -9
- package/src/tools/research-knowledge-search.ts +974 -0
- package/src/tools/research-knowledge-types.ts +77 -0
- package/src/tools/research-tool-definition.ts +494 -0
- package/src/tools/scrape.ts +286 -388
- package/src/tools/search.ts +98 -153
- package/src/tools/security.ts +107 -80
- package/src/tools/stackexchange.ts +58 -35
- package/src/tools/youtube-transcript.ts +163 -0
- package/src/tui/knowledge-search-panel.ts +86 -0
- package/src/tui/pulse.ts +86 -0
- package/src/tui/research-health.ts +124 -0
- package/src/tui/research-panel-color-utils.ts +340 -0
- package/src/tui/research-panel-format.ts +54 -0
- package/src/tui/research-panel-state.ts +404 -0
- package/src/tui/research-panel-wave.ts +280 -0
- package/src/tui/research-panel.ts +339 -435
- package/src/tui/research-tui-manager.ts +233 -0
- package/src/tui/tui-controller.ts +137 -0
- package/src/types/extension-context.ts +18 -49
- package/src/types/index.ts +61 -0
- package/src/types/llm.ts +68 -29
- package/src/types/research-panel-types.ts +61 -0
- package/src/utils/circuit-breaker.ts +146 -0
- package/src/utils/citation-utils.ts +97 -0
- package/src/utils/disk-space-checker.ts +55 -0
- package/src/utils/error-tracker.ts +265 -0
- package/src/utils/host-config.ts +45 -0
- package/src/utils/input-validation.ts +8 -10
- package/src/utils/json-utils.ts +150 -16
- package/src/utils/llm-usage.ts +54 -0
- package/src/utils/log-rotation.ts +125 -0
- package/src/utils/log-utils.ts +159 -0
- package/src/utils/make-resource-loader.ts +3 -1
- package/src/utils/metrics-summary.ts +459 -0
- package/src/utils/metrics.ts +302 -0
- package/src/utils/research-export.ts +28 -12
- package/src/utils/safe-unref.ts +31 -0
- package/src/utils/shared-links.ts +195 -90
- package/src/utils/shutdown-manager.ts +65 -0
- package/src/utils/stdio-capture.ts +407 -0
- package/src/utils/text-utils.ts +264 -7
- package/src/utils/tool-usage-tracker.ts +83 -16
- package/src/utils/url-utils.ts +255 -0
- package/src/utils/user-agent.ts +28 -0
- package/src/web-research/browser-search.ts +179 -0
- package/src/web-research/retry-utils.ts +173 -77
- package/src/web-research/scraper-types.ts +72 -0
- package/src/web-research/scraper-utils.ts +434 -0
- package/src/web-research/search.ts +78 -218
- package/src/web-research/types.ts +9 -24
- package/src/web-research/utils.ts +2 -143
- package/src/web-research/web-scraper.ts +477 -0
- package/src/youtube/index.ts +154 -0
- package/src/youtube/potoken.ts +259 -0
- package/src/youtube/transcript-client.ts +390 -0
- package/src/youtube/video-id.ts +118 -0
- package/config/searxng/default-settings.yml +0 -189
- package/config/searxng/limiter.toml +0 -34
- package/config/tooling/eslint.config.js +0 -73
- package/config/tooling/vitest.config.integration.ts +0 -17
- package/config/tooling/vitest.config.ts +0 -40
- package/config/tooling/vitest.config.unit.ts +0 -12
- package/docs/assets/README-banner.jpg +0 -0
- package/scripts/setup.js +0 -174
- package/src/infrastructure/searxng-lifecycle.ts +0 -577
- package/src/infrastructure/searxng-manager.ts +0 -1183
- package/src/infrastructure/state-manager.ts +0 -918
- package/src/orchestration/deep-research-reducer.ts +0 -129
- package/src/orchestration/id-utils.ts +0 -63
- package/src/security/nvd.ts +0 -532
- package/src/security/osv.ts +0 -487
- package/src/stackexchange/cache.ts +0 -111
- package/src/stackexchange/output/index.ts +0 -7
- package/src/stackexchange/output/json.ts +0 -21
- package/src/tui/searxng-status.ts +0 -80
- package/src/utils/inject-date.ts +0 -29
- package/src/utils/research-error.ts +0 -205
- package/src/utils/searxng-config.ts +0 -94
- package/src/utils/session-state.ts +0 -295
- package/src/web-research/dependencies.ts +0 -20
- package/src/web-research/scrapers.ts +0 -935
- package/tsconfig.json +0 -40
package/src/constants.ts
CHANGED
|
@@ -4,80 +4,126 @@
|
|
|
4
4
|
* Centralized constants for magic numbers and configuration values.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { getConfig, type Config } from './config.ts';
|
|
8
|
+
|
|
7
9
|
// ==================== Time Constants ====================
|
|
8
10
|
|
|
9
|
-
/**
|
|
10
|
-
export const
|
|
11
|
+
/** Delay between API requests in milliseconds */
|
|
12
|
+
export const REQUEST_DELAY_MS_NVD = 6000;
|
|
13
|
+
export const REQUEST_DELAY_MS_OTHER = 1000;
|
|
11
14
|
|
|
12
|
-
/**
|
|
13
|
-
export const
|
|
15
|
+
/** Duration (ms) for a green (success) flash on a researcher column */
|
|
16
|
+
export const FLASH_GREEN_DURATION_MS = 400;
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
/** Duration (ms) for a red (failure) flash on a researcher column */
|
|
19
|
+
export const FLASH_RED_DURATION_MS = 700;
|
|
16
20
|
|
|
17
|
-
/**
|
|
18
|
-
export const
|
|
21
|
+
/** Duration (ms) for a tool status 'pop' (word flash) in a researcher column */
|
|
22
|
+
export const FLASH_STATUS_DURATION_MS = 800;
|
|
19
23
|
|
|
20
|
-
/**
|
|
21
|
-
export const
|
|
24
|
+
/** Minimum gap (ms) between queued flashes for the same researcher column */
|
|
25
|
+
export const FLASH_QUEUE_GAP_MS = 150;
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
export const MAX_SCRAPE_URLS = 3;
|
|
27
|
+
// ==================== Research Constraints ====================
|
|
25
28
|
|
|
26
|
-
/** Maximum
|
|
27
|
-
|
|
29
|
+
/** Maximum gathering (tool) calls per researcher (search, security_search, stackexchange, youtube_transcript — shared web-API budget).
|
|
30
|
+
* This is the schema default; use getMaxGatheringCalls(config) for the configured value. */
|
|
31
|
+
export const MAX_GATHERING_CALLS = 12;
|
|
28
32
|
|
|
33
|
+
/** Maximum local grep (ripgrep) calls per researcher. grep is local/free and does not hit
|
|
34
|
+
* rate-limited web APIs, so it has its own generous budget instead of sharing the gathering
|
|
35
|
+
* pool — but it is still bounded to prevent a pathological search loop. */
|
|
36
|
+
export const MAX_GREP_CALLS = 30;
|
|
29
37
|
|
|
30
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Get the maximum gathering calls from config (env/config-file driven, mirrors getMaxScrapeBatches).
|
|
40
|
+
* Not surfaced in the TUI — advanced knob only.
|
|
41
|
+
*/
|
|
42
|
+
export function getMaxGatheringCalls(config?: Config): number {
|
|
43
|
+
try {
|
|
44
|
+
return (config || getConfig()).MAX_GATHERING_CALLS;
|
|
45
|
+
} catch {
|
|
46
|
+
return MAX_GATHERING_CALLS; // Fallback to default
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get the maximum scrape batches from config.
|
|
52
|
+
* This function should be used instead of MAX_SCRAPE_CALLS to support dynamic configuration.
|
|
53
|
+
*/
|
|
54
|
+
export function getMaxScrapeBatches(config?: Config): number {
|
|
55
|
+
try {
|
|
56
|
+
const batches = (config || getConfig()).MAX_SCRAPE_BATCHES;
|
|
57
|
+
return batches === 0 || batches > 99 ? 999999 : batches;
|
|
58
|
+
} catch {
|
|
59
|
+
return 2; // Fallback to default
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Maximum URLs to scrape per batch */
|
|
64
|
+
export const MAX_SCRAPE_URLS = 6;
|
|
31
65
|
|
|
32
66
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* Complexity 3: Ultra (5 researchers, 5 rounds)
|
|
67
|
+
* Get the units per researcher for the progress bar.
|
|
68
|
+
* Units = 1 (for start/search) + number of scrape batches.
|
|
69
|
+
* When batches are unlimited (MAX_SCRAPE_BATCHES=0), caps at 4 for a sane progress estimate.
|
|
37
70
|
*/
|
|
71
|
+
export function getUnitsPerResearcher(config?: Config): number {
|
|
72
|
+
const batches = getMaxScrapeBatches(config);
|
|
73
|
+
return 1 + Math.min(batches, 4);
|
|
74
|
+
}
|
|
38
75
|
|
|
39
|
-
/**
|
|
40
|
-
export const
|
|
76
|
+
/** Progress bar weight per lead evaluator round */
|
|
77
|
+
export const LEAD_EVAL_UNITS = 2;
|
|
41
78
|
|
|
42
|
-
/** Initial researcher count for complexity 2 (Deep) */
|
|
43
|
-
export const INITIAL_RESEARCHERS_LEVEL_2 = 3;
|
|
44
79
|
|
|
45
|
-
|
|
46
|
-
export const INITIAL_RESEARCHERS_LEVEL_3 = 5;
|
|
80
|
+
// ==================== Complexity Levels ====================
|
|
47
81
|
|
|
82
|
+
/** Maximum researchers (siblings) in any single round per complexity level */
|
|
83
|
+
export const MAX_TEAM_SIZE_LEVEL_1 = 2;
|
|
84
|
+
export const MAX_TEAM_SIZE_LEVEL_2 = 3;
|
|
85
|
+
export const MAX_TEAM_SIZE_LEVEL_3 = 5;
|
|
48
86
|
|
|
49
|
-
/** Maximum rounds
|
|
87
|
+
/** Maximum research rounds per complexity level */
|
|
50
88
|
export const MAX_ROUNDS_LEVEL_1 = 2;
|
|
51
|
-
|
|
52
|
-
/** Maximum rounds for complexity 2 (Deep) */
|
|
53
89
|
export const MAX_ROUNDS_LEVEL_2 = 3;
|
|
54
|
-
|
|
55
|
-
/** Maximum rounds for complexity 3 (Ultra) */
|
|
56
|
-
export const MAX_ROUNDS_LEVEL_3 = 5;
|
|
90
|
+
export const MAX_ROUNDS_LEVEL_3 = 3;
|
|
57
91
|
|
|
58
92
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
93
|
+
* Additional research rounds allowed past MAX_ROUNDS_LEVEL_n when queued
|
|
94
|
+
* steering messages exist at the end of the last planned round. Each
|
|
95
|
+
* consumed steering message unlocks one more round, up to this cap per
|
|
96
|
+
* session. This lets the user push research deeper via Alt+Enter even
|
|
97
|
+
* after the normal round budget is exhausted, without letting steering
|
|
98
|
+
* spin research forever.
|
|
62
99
|
*/
|
|
63
|
-
export const
|
|
100
|
+
export const MAX_EXTRA_ROUNDS_WITH_STEERING = 2;
|
|
64
101
|
|
|
65
102
|
|
|
66
103
|
// ==================== Timeout Constants ====================
|
|
67
104
|
|
|
68
|
-
|
|
69
|
-
export const SEARXNG_TIMEOUT_MS = 30000;
|
|
105
|
+
export const OSV_TIMEOUT_MS = 10000;
|
|
70
106
|
|
|
71
|
-
/**
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Hard cap for the out-of-process WebGPU viability probe when the embedding model
|
|
109
|
+
* is ALREADY cached on disk. A real WebGPU init on a capable host completes in
|
|
110
|
+
* ~1-3s; this bounds the fall-back-to-CPU on a GPU-less host (headless VM/CI/
|
|
111
|
+
* software-Vulkan) to seconds instead of the multi-minute model-init budget. The
|
|
112
|
+
* first-ever run (model not yet downloaded) keeps the full init budget — see
|
|
113
|
+
* resolveEmbeddingDevice. Mirrors the cached-pipeline 30s reuse in embedder.ts.
|
|
114
|
+
*/
|
|
115
|
+
export const WEBGPU_PROBE_TIMEOUT_MS = 30_000;
|
|
76
116
|
|
|
77
117
|
// ==================== Export Constants ====================
|
|
78
118
|
|
|
119
|
+
/** Maximum query length for validation */
|
|
120
|
+
export const MAX_QUERY_LENGTH = 100000;
|
|
121
|
+
|
|
79
122
|
/** Maximum query length for filename sanitization */
|
|
80
|
-
export const
|
|
123
|
+
export const MAX_FILENAME_QUERY_LENGTH = 150;
|
|
124
|
+
|
|
125
|
+
/** Minimum query length */
|
|
126
|
+
export const MIN_QUERY_LENGTH = 3;
|
|
81
127
|
|
|
82
128
|
/** Maximum retry attempts for export file collision */
|
|
83
129
|
export const MAX_EXPORT_RETRIES = 3;
|
|
@@ -93,56 +139,41 @@ export const DEFAULT_INITIAL_DELAY_MS = 1000;
|
|
|
93
139
|
/** Default maximum delay for exponential backoff in milliseconds */
|
|
94
140
|
export const DEFAULT_MAX_DELAY_MS = 10000;
|
|
95
141
|
|
|
96
|
-
// ====================
|
|
142
|
+
// ==================== Scraping Constants ====================
|
|
97
143
|
|
|
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
144
|
export const DEFAULT_MODEL_CONTEXT_WINDOW = 200000;
|
|
115
145
|
|
|
116
|
-
/**
|
|
117
|
-
*
|
|
118
|
-
|
|
119
|
-
*/
|
|
120
|
-
export const BATCH_2_MAX_SCRAPE_URLS = 2;
|
|
146
|
+
/** Upper bound on batch-2+ scrape concurrency; the caller additionally caps this
|
|
147
|
+
* to the real browser-pool capacity (WORKER_THREADS × WORKER_CONCURRENCY). */
|
|
148
|
+
export const BATCH_2_DEFAULT_CONCURRENCY = 15;
|
|
121
149
|
|
|
122
150
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
151
|
+
* Per-document cap (in characters) on scraped content that flows into LLM
|
|
152
|
+
* context — the scrape tool result and the session scraped-content cache
|
|
153
|
+
* (shared-links), whose entries are re-served into researcher sessions and
|
|
154
|
+
* embedded by the knowledge store.
|
|
155
|
+
*
|
|
156
|
+
* The scraper itself caps only RAW bytes (25MB HTML / 100MB PDF — see
|
|
157
|
+
* scraper-types.ts), and the pre-scrape context gate bills a flat
|
|
158
|
+
* AVG_TOKENS_PER_SCRAPE (~2500 tokens) per URL, so without this cap a single
|
|
159
|
+
* huge PDF/page could inject millions of tokens into a researcher session,
|
|
160
|
+
* overflow its context window, and kill the run.
|
|
161
|
+
*
|
|
162
|
+
* 500k chars ≈ 125k tokens: a deliberately permissive ceiling — real articles
|
|
163
|
+
* and long-form docs are never cut; only pathological pages (multi-hundred-page
|
|
164
|
+
* PDFs, dumps) hit it. This is a backstop against the multi-million-token
|
|
165
|
+
* blowout, not a tuning knob: the post-batch context gate handles ordinary
|
|
166
|
+
* budget pressure. Truncation appends an explicit "[content truncated: …]"
|
|
167
|
+
* marker (see truncateWithMarker).
|
|
125
168
|
*/
|
|
126
|
-
export const
|
|
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;
|
|
169
|
+
export const MAX_SCRAPE_CONTENT_CHARS_PER_DOC = 500_000;
|
|
135
170
|
|
|
136
171
|
// ==================== Orchestrator Constants ====================
|
|
137
172
|
|
|
138
|
-
/**
|
|
139
|
-
export const MAX_EVALUATOR_REPORT_LENGTH = 50000;
|
|
140
|
-
|
|
141
|
-
/** Delay in milliseconds between launching concurrent researchers to stagger startup */
|
|
173
|
+
/** Delay in milliseconds between launching concurrent researchers to stagger browser pool startup */
|
|
142
174
|
export const RESEARCHER_LAUNCH_DELAY_MS = 1500;
|
|
143
175
|
|
|
144
|
-
/**
|
|
145
|
-
export const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
export const STREAMING_UPDATE_THRESHOLD_CHARS = 200;
|
|
176
|
+
/** Hard cap on search queries per researcher, enforced after LLM planning */
|
|
177
|
+
export const MAX_QUERIES_PER_RESEARCHER_LEVEL_1 = 10;
|
|
178
|
+
export const MAX_QUERIES_PER_RESEARCHER_LEVEL_2 = 15;
|
|
179
|
+
export const MAX_QUERIES_PER_RESEARCHER_LEVEL_3 = 20;
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health Check Service
|
|
3
|
+
*
|
|
4
|
+
* Manages health check cache state and backoff logic.
|
|
5
|
+
* Provides thread-safe access to health check state and prevents
|
|
6
|
+
* thundering herd problems during health checks.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { IHealthCheckService, HealthCheckResult } from './service-interfaces.ts';
|
|
10
|
+
import { ServiceNames } from './service-interfaces.ts';
|
|
11
|
+
import { ServiceLifecycle } from './service-registry.ts';
|
|
12
|
+
import { logger } from '../logger.ts';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Default backoff configuration
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_BACKOFF_BASE_MS = 1000; // 1 second
|
|
18
|
+
const DEFAULT_BACKOFF_MAX_MS = 60000; // 1 minute
|
|
19
|
+
const DEFAULT_BACKOFF_MULTIPLIER = 2;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Health Check Service Implementation
|
|
23
|
+
*/
|
|
24
|
+
export class HealthCheckService implements IHealthCheckService {
|
|
25
|
+
readonly name = ServiceNames.HEALTH_CHECK_CACHE;
|
|
26
|
+
lifecycle = ServiceLifecycle.UNINITIALIZED;
|
|
27
|
+
|
|
28
|
+
// Thread-safe state management using atomic operations
|
|
29
|
+
private _pendingCheck: Promise<HealthCheckResult> | null = null;
|
|
30
|
+
private _failureCount: number = 0;
|
|
31
|
+
private _backoffUntil: number = 0;
|
|
32
|
+
|
|
33
|
+
// Backoff configuration
|
|
34
|
+
private readonly _backoffBaseMs: number;
|
|
35
|
+
private readonly _backoffMaxMs: number;
|
|
36
|
+
private readonly _backoffMultiplier: number;
|
|
37
|
+
|
|
38
|
+
constructor(options?: {
|
|
39
|
+
backoffBaseMs?: number;
|
|
40
|
+
backoffMaxMs?: number;
|
|
41
|
+
backoffMultiplier?: number;
|
|
42
|
+
}) {
|
|
43
|
+
this._backoffBaseMs = options?.backoffBaseMs ?? DEFAULT_BACKOFF_BASE_MS;
|
|
44
|
+
this._backoffMaxMs = options?.backoffMaxMs ?? DEFAULT_BACKOFF_MAX_MS;
|
|
45
|
+
this._backoffMultiplier = options?.backoffMultiplier ?? DEFAULT_BACKOFF_MULTIPLIER;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async initialize(): Promise<void> {
|
|
49
|
+
if (this.lifecycle === ServiceLifecycle.INITIALIZED) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.lifecycle = ServiceLifecycle.INITIALIZING;
|
|
54
|
+
logger.debug('[HealthCheckService] Initializing...');
|
|
55
|
+
|
|
56
|
+
// Initialize with default state
|
|
57
|
+
this._pendingCheck = null;
|
|
58
|
+
this._failureCount = 0;
|
|
59
|
+
this._backoffUntil = 0;
|
|
60
|
+
|
|
61
|
+
this.lifecycle = ServiceLifecycle.INITIALIZED;
|
|
62
|
+
logger.debug('[HealthCheckService] Initialized');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async dispose(): Promise<void> {
|
|
66
|
+
if (this.lifecycle === ServiceLifecycle.DISPOSED) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.lifecycle = ServiceLifecycle.DISPOSING;
|
|
71
|
+
logger.debug('[HealthCheckService] Disposing...');
|
|
72
|
+
|
|
73
|
+
// Clear state
|
|
74
|
+
this.clear();
|
|
75
|
+
|
|
76
|
+
this.lifecycle = ServiceLifecycle.DISPOSED;
|
|
77
|
+
logger.debug('[HealthCheckService] Disposed');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get the cached pending health check promise
|
|
82
|
+
*/
|
|
83
|
+
getPendingCheck(): Promise<HealthCheckResult> | null {
|
|
84
|
+
return this._pendingCheck;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Set a pending health check promise
|
|
89
|
+
* This should only be called from orchestration code that coordinates health checks
|
|
90
|
+
*/
|
|
91
|
+
setPendingCheck(promise: Promise<HealthCheckResult> | null): void {
|
|
92
|
+
this._pendingCheck = promise;
|
|
93
|
+
|
|
94
|
+
// Clean up the promise when it settles to avoid memory leaks
|
|
95
|
+
if (promise) {
|
|
96
|
+
promise.finally(() => {
|
|
97
|
+
// Only clear if this is still the same promise
|
|
98
|
+
if (this._pendingCheck === promise) {
|
|
99
|
+
this._pendingCheck = null;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get the failure count
|
|
107
|
+
*/
|
|
108
|
+
getFailureCount(): number {
|
|
109
|
+
return this._failureCount;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Increment the failure count
|
|
114
|
+
*/
|
|
115
|
+
incrementFailureCount(): void {
|
|
116
|
+
this._failureCount++;
|
|
117
|
+
logger.debug(`[HealthCheckService] Failure count incremented to ${this._failureCount}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Reset the failure count
|
|
122
|
+
*/
|
|
123
|
+
resetFailureCount(): void {
|
|
124
|
+
if (this._failureCount > 0) {
|
|
125
|
+
logger.debug(`[HealthCheckService] Failure count reset from ${this._failureCount} to 0`);
|
|
126
|
+
}
|
|
127
|
+
this._failureCount = 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get the backoff timestamp
|
|
132
|
+
*/
|
|
133
|
+
getBackoffUntil(): number {
|
|
134
|
+
return this._backoffUntil;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Set the backoff timestamp
|
|
139
|
+
*/
|
|
140
|
+
setBackoffUntil(timestamp: number): void {
|
|
141
|
+
this._backoffUntil = timestamp;
|
|
142
|
+
const remainingMs = Math.max(0, timestamp - Date.now());
|
|
143
|
+
if (remainingMs > 0) {
|
|
144
|
+
logger.debug(`[HealthCheckService] Backoff set: ${remainingMs}ms remaining`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Clear all cache state
|
|
150
|
+
*/
|
|
151
|
+
clear(): void {
|
|
152
|
+
this._pendingCheck = null;
|
|
153
|
+
this._failureCount = 0;
|
|
154
|
+
this._backoffUntil = 0;
|
|
155
|
+
logger.debug('[HealthCheckService] Cache cleared');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Check if a backoff is currently active
|
|
160
|
+
*/
|
|
161
|
+
isBackoffActive(): boolean {
|
|
162
|
+
return Date.now() < this._backoffUntil;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get remaining backoff time in milliseconds
|
|
167
|
+
*/
|
|
168
|
+
getBackoffRemainingMs(): number {
|
|
169
|
+
return Math.max(0, this._backoffUntil - Date.now());
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Calculate the next backoff duration based on failure count
|
|
174
|
+
* Uses exponential backoff with jitter
|
|
175
|
+
*/
|
|
176
|
+
calculateNextBackoffMs(): number {
|
|
177
|
+
// Exponential backoff: base * 2^failures, capped at max
|
|
178
|
+
const backoffMs = Math.min(
|
|
179
|
+
this._backoffBaseMs * Math.pow(this._backoffMultiplier, this._failureCount),
|
|
180
|
+
this._backoffMaxMs
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Add jitter (±25%) to prevent thundering herd
|
|
184
|
+
const jitter = backoffMs * 0.25 * (Math.random() * 2 - 1);
|
|
185
|
+
|
|
186
|
+
return Math.floor(backoffMs + jitter);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Set backoff based on current failure count
|
|
191
|
+
*/
|
|
192
|
+
setBackoff(): void {
|
|
193
|
+
const backoffMs = this.calculateNextBackoffMs();
|
|
194
|
+
this._backoffUntil = Date.now() + backoffMs;
|
|
195
|
+
logger.debug(`[HealthCheckService] Backoff set: ${backoffMs}ms (${this._failureCount} failures)`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Record a health check failure
|
|
200
|
+
* Increments failure count and sets backoff
|
|
201
|
+
*/
|
|
202
|
+
recordFailure(): void {
|
|
203
|
+
this.incrementFailureCount();
|
|
204
|
+
this.setBackoff();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Record a health check success
|
|
209
|
+
* Resets failure count and clears backoff
|
|
210
|
+
*/
|
|
211
|
+
recordSuccess(): void {
|
|
212
|
+
this.resetFailureCount();
|
|
213
|
+
this._backoffUntil = 0;
|
|
214
|
+
logger.debug('[HealthCheckService] Health check success recorded');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Get the current backoff state as an object
|
|
219
|
+
*/
|
|
220
|
+
getBackoffState(): {
|
|
221
|
+
isActive: boolean;
|
|
222
|
+
remainingMs: number;
|
|
223
|
+
failureCount: number;
|
|
224
|
+
nextBackoffMs: number;
|
|
225
|
+
} {
|
|
226
|
+
return {
|
|
227
|
+
isActive: this.isBackoffActive(),
|
|
228
|
+
remainingMs: this.getBackoffRemainingMs(),
|
|
229
|
+
failureCount: this._failureCount,
|
|
230
|
+
nextBackoffMs: this.calculateNextBackoffMs(),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Wait for backoff to complete if active
|
|
236
|
+
* Returns immediately if no backoff is active
|
|
237
|
+
*/
|
|
238
|
+
async waitForBackoff(): Promise<void> {
|
|
239
|
+
const remainingMs = this.getBackoffRemainingMs();
|
|
240
|
+
if (remainingMs > 0) {
|
|
241
|
+
logger.debug(`[HealthCheckService] Waiting for backoff: ${remainingMs}ms`);
|
|
242
|
+
await new Promise(resolve => setTimeout(resolve, remainingMs));
|
|
243
|
+
logger.debug('[HealthCheckService] Backoff complete');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Check if a new health check should be allowed
|
|
249
|
+
* Returns false if backoff is active or a check is already pending
|
|
250
|
+
*/
|
|
251
|
+
shouldAllowCheck(): boolean {
|
|
252
|
+
if (this._pendingCheck !== null) {
|
|
253
|
+
logger.debug('[HealthCheckService] Check not allowed: already pending');
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (this.isBackoffActive()) {
|
|
258
|
+
const remainingMs = this.getBackoffRemainingMs();
|
|
259
|
+
logger.debug(`[HealthCheckService] Check not allowed: backoff active (${remainingMs}ms remaining)`);
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health Check Service Interfaces
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { IService } from '../service-registry.ts';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Health check result
|
|
9
|
+
*/
|
|
10
|
+
export interface HealthCheckResult {
|
|
11
|
+
success: boolean;
|
|
12
|
+
searchOk: boolean;
|
|
13
|
+
scrapeOk: boolean;
|
|
14
|
+
error?: string;
|
|
15
|
+
timestamp: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Health check cache state
|
|
20
|
+
*/
|
|
21
|
+
export interface HealthCheckCache {
|
|
22
|
+
pending: Promise<HealthCheckResult> | null;
|
|
23
|
+
failureCount: number;
|
|
24
|
+
backoffUntil: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Health check service interface
|
|
29
|
+
*/
|
|
30
|
+
export interface IHealthCheckService extends IService {
|
|
31
|
+
getPendingCheck(): Promise<HealthCheckResult> | null;
|
|
32
|
+
setPendingCheck(promise: Promise<HealthCheckResult> | null): void;
|
|
33
|
+
getFailureCount(): number;
|
|
34
|
+
incrementFailureCount(): void;
|
|
35
|
+
resetFailureCount(): void;
|
|
36
|
+
getBackoffUntil(): number;
|
|
37
|
+
setBackoffUntil(timestamp: number): void;
|
|
38
|
+
clear(): void;
|
|
39
|
+
isBackoffActive(): boolean;
|
|
40
|
+
getBackoffRemainingMs(): number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Health check registry interface
|
|
45
|
+
*/
|
|
46
|
+
export interface IHealthRegistryService extends IService {
|
|
47
|
+
register(name: string, check: (options?: { force?: boolean }) => Promise<{ healthy: boolean; error?: string; diagnostic?: Record<string, any> }>, options?: { timeoutMs?: number; critical?: boolean }): void;
|
|
48
|
+
isCritical(componentName: string): boolean;
|
|
49
|
+
runAll(options?: { force?: boolean }): Promise<{ status: 'healthy' | 'degraded' | 'unhealthy'; components: any[]; timestamp: string }>;
|
|
50
|
+
}
|