@lincoln504/pi-research 0.1.12 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +64 -115
  3. package/dist/cli.mjs +23211 -0
  4. package/dist/prompts/research-knowledge-relevance-triage.md +35 -0
  5. package/dist/prompts/research-knowledge-search-extractor.md +41 -0
  6. package/dist/prompts/research-tool-usage.md +92 -0
  7. package/dist/prompts/researcher.md +89 -0
  8. package/dist/prompts/system-coordinator.md +75 -0
  9. package/dist/prompts/system-lead-evaluator.md +118 -0
  10. package/dist/thread-worker.mjs +2611 -0
  11. package/dist/webgpu-probe.mjs +80 -0
  12. package/docs/AGENT-SKILL.md +102 -0
  13. package/docs/ARCHITECTURE.md +306 -80
  14. package/docs/CONFIGURATION.md +249 -0
  15. package/docs/KNOWLEDGE-STORE.md +213 -0
  16. package/docs/PI-EXTENSION.md +91 -0
  17. package/docs/SDK.md +113 -0
  18. package/docs/THIRD-PARTY-NOTICES.md +64 -0
  19. package/package.json +102 -36
  20. package/scripts/build.cjs +140 -0
  21. package/scripts/cleanup.cjs +149 -0
  22. package/scripts/prepare.cjs +28 -0
  23. package/scripts/setup.cjs +134 -0
  24. package/skills/pi-research/README.md +90 -0
  25. package/skills/pi-research/SKILL.md +102 -0
  26. package/skills/pi-research/references/configuration.md +77 -0
  27. package/skills/pi-research/scripts/run.mjs +237 -0
  28. package/src/cleanup/research-cleanup.ts +140 -0
  29. package/src/cli.ts +1044 -0
  30. package/src/config.ts +1036 -147
  31. package/src/constants.ts +115 -84
  32. package/src/core/health-check-service.ts +265 -0
  33. package/src/core/interfaces/health-check-interfaces.ts +50 -0
  34. package/src/core/interfaces/knowledge-interfaces.ts +190 -0
  35. package/src/core/interfaces/observer-interfaces.ts +67 -0
  36. package/src/core/interfaces/orchestration-interfaces.ts +86 -0
  37. package/src/core/interfaces/planning-interfaces.ts +100 -0
  38. package/src/core/interfaces/process-interfaces.ts +37 -0
  39. package/src/core/interfaces/research-plan-types.ts +52 -0
  40. package/src/core/interfaces/scheduler-interfaces.ts +91 -0
  41. package/src/core/interfaces/service-names.ts +39 -0
  42. package/src/core/interfaces/state-manager-interfaces.ts +29 -0
  43. package/src/core/interfaces/steering-interfaces.ts +31 -0
  44. package/src/core/llm/agentic-repair.ts +169 -0
  45. package/src/core/llm/inject-date.ts +52 -0
  46. package/src/core/llm/llm-timeout.ts +64 -0
  47. package/src/core/llm/llm-utils.ts +86 -0
  48. package/src/core/llm/model-registry-factory.ts +287 -0
  49. package/src/core/llm/pi-ai-completion.ts +29 -0
  50. package/src/core/llm/prompts.ts +50 -0
  51. package/src/core/llm/research-model-resolver.ts +91 -0
  52. package/src/core/planning-constants.ts +13 -0
  53. package/src/core/planning-service.ts +649 -0
  54. package/src/core/planning-utils.ts +299 -0
  55. package/src/core/scheduler-factory.ts +48 -0
  56. package/src/core/scheduler-service.ts +123 -0
  57. package/src/core/service-initialization.ts +194 -0
  58. package/src/core/service-interfaces.ts +48 -0
  59. package/src/core/service-registry.ts +659 -0
  60. package/src/healthcheck/index.ts +271 -265
  61. package/src/healthcheck/registry.ts +130 -0
  62. package/src/index.ts +639 -50
  63. package/src/infrastructure/browser/browser-cleanup.ts +309 -0
  64. package/src/infrastructure/browser/browser-client.ts +199 -0
  65. package/src/infrastructure/browser/browser-error-utils.ts +160 -0
  66. package/src/infrastructure/browser/browser-lifecycle.ts +120 -0
  67. package/src/infrastructure/browser/browser-server.ts +174 -0
  68. package/src/infrastructure/browser/browser-task-scheduler.ts +461 -0
  69. package/src/infrastructure/browser/cleanup-utils.ts +155 -0
  70. package/src/infrastructure/browser/client-agent.ts +31 -0
  71. package/src/infrastructure/browser/config.ts +288 -0
  72. package/src/infrastructure/browser/ensure-browser.ts +205 -0
  73. package/src/infrastructure/browser/index.ts +37 -0
  74. package/src/infrastructure/browser/priority-task-queue.ts +226 -0
  75. package/src/infrastructure/browser/scheduler-factory.ts +354 -0
  76. package/src/infrastructure/browser/task-execution-service.ts +209 -0
  77. package/src/infrastructure/browser/thread-worker-browser.ts +282 -0
  78. package/src/infrastructure/browser/thread-worker-lifecycle.ts +323 -0
  79. package/src/infrastructure/browser/thread-worker-messaging.ts +662 -0
  80. package/src/infrastructure/browser/thread-worker-types.ts +25 -0
  81. package/src/infrastructure/browser/thread-worker.mjs +2611 -0
  82. package/src/infrastructure/browser/thread-worker.ts +168 -0
  83. package/src/infrastructure/browser/worker-pool-manager.ts +449 -0
  84. package/src/infrastructure/embedding/embedding-client.ts +205 -0
  85. package/src/infrastructure/embedding/embedding-factory.ts +324 -0
  86. package/src/infrastructure/embedding/embedding-server.ts +535 -0
  87. package/src/infrastructure/file-lock-service.ts +592 -0
  88. package/src/infrastructure/gpu-resource-service.ts +191 -0
  89. package/src/infrastructure/knowledge-store-service.ts +475 -0
  90. package/src/infrastructure/metrics-service.ts +129 -0
  91. package/src/infrastructure/process-lifecycle-service.ts +253 -0
  92. package/src/infrastructure/scheduler-factory-service.ts +58 -0
  93. package/src/infrastructure/service-initialization.ts +292 -0
  94. package/src/infrastructure/state/state-backup-manager.ts +290 -0
  95. package/src/infrastructure/state/state-browser-api.ts +62 -0
  96. package/src/infrastructure/state/state-browser-manager.ts +70 -0
  97. package/src/infrastructure/state/state-manager-service.ts +264 -0
  98. package/src/infrastructure/state/state-manager.ts +547 -0
  99. package/src/infrastructure/state/state-metrics.ts +92 -0
  100. package/src/infrastructure/state/state-migration.ts +103 -0
  101. package/src/infrastructure/state/state-path-configuration.ts +119 -0
  102. package/src/infrastructure/state/state-session-api.ts +94 -0
  103. package/src/infrastructure/state/state-session-manager.ts +127 -0
  104. package/src/infrastructure/state/state-validator.ts +93 -0
  105. package/src/infrastructure/types/state-types.ts +85 -0
  106. package/src/knowledge/chunker.ts +157 -0
  107. package/src/knowledge/embedder-init.ts +413 -0
  108. package/src/knowledge/embedder-utils.ts +296 -0
  109. package/src/knowledge/embedder.ts +592 -0
  110. package/src/knowledge/index.ts +126 -0
  111. package/src/knowledge/lancedb-loader.ts +35 -0
  112. package/src/knowledge/migration.ts +17 -0
  113. package/src/knowledge/model-config.ts +132 -0
  114. package/src/knowledge/onnx-env.ts +48 -0
  115. package/src/knowledge/store-operations.ts +291 -0
  116. package/src/knowledge/store-schema.ts +79 -0
  117. package/src/knowledge/store.ts +1251 -0
  118. package/src/knowledge/transformers-loader.ts +31 -0
  119. package/src/knowledge/webgpu-probe.mjs +80 -0
  120. package/src/knowledge/webgpu-viability.ts +255 -0
  121. package/src/knowledge/writer-queue.ts +252 -0
  122. package/src/logger.ts +316 -122
  123. package/src/observers/research-observer-impl.ts +459 -0
  124. package/src/orchestration/deep-research-orchestrator.ts +524 -773
  125. package/src/orchestration/deep-research-types.ts +47 -35
  126. package/src/orchestration/headless-observer.ts +114 -0
  127. package/src/orchestration/orchestration-types.ts +33 -0
  128. package/src/orchestration/quick-research-orchestrator.ts +463 -0
  129. package/src/orchestration/research-orchestration-service.ts +549 -0
  130. package/src/orchestration/research-session-service.ts +178 -0
  131. package/src/orchestration/research-synthesis-service.ts +348 -0
  132. package/src/orchestration/researcher-executor.ts +394 -0
  133. package/src/orchestration/researcher.ts +115 -42
  134. package/src/orchestration/service-initialization.ts +109 -0
  135. package/src/orchestration/session-context.ts +7 -3
  136. package/src/orchestration/session-state.ts +672 -0
  137. package/src/prompts/research-knowledge-relevance-triage.md +35 -0
  138. package/src/prompts/research-knowledge-search-extractor.md +41 -0
  139. package/src/prompts/research-tool-usage.md +63 -16
  140. package/src/prompts/researcher.md +59 -136
  141. package/src/prompts/system-coordinator.md +64 -13
  142. package/src/prompts/system-lead-evaluator.md +82 -189
  143. package/src/research-config.ts +872 -0
  144. package/src/sdk.ts +847 -0
  145. package/src/security/cisa-kev.ts +72 -9
  146. package/src/security/cvss.ts +92 -0
  147. package/src/security/ecosystem.ts +45 -0
  148. package/src/security/github-advisories.ts +168 -428
  149. package/src/security/github-advisory-types.ts +282 -0
  150. package/src/security/index.ts +63 -177
  151. package/src/security/nvd-client.ts +550 -0
  152. package/src/security/nvd-types.ts +179 -0
  153. package/src/security/osv-client.ts +246 -0
  154. package/src/security/osv-types.ts +285 -0
  155. package/src/security/types.ts +67 -53
  156. package/src/skill-install/skill-installer.ts +519 -0
  157. package/src/stackexchange/index.ts +81 -27
  158. package/src/stackexchange/output/compact.ts +7 -6
  159. package/src/stackexchange/output/table.ts +11 -7
  160. package/src/stackexchange/queries.ts +0 -10
  161. package/src/stackexchange/rest-client.ts +141 -66
  162. package/src/stackexchange/types.ts +0 -17
  163. package/src/tool.ts +15 -423
  164. package/src/tools/grep.ts +32 -310
  165. package/src/tools/health-tool-definition.ts +101 -0
  166. package/src/tools/index.ts +38 -9
  167. package/src/tools/research-knowledge-search.ts +974 -0
  168. package/src/tools/research-knowledge-types.ts +77 -0
  169. package/src/tools/research-tool-definition.ts +494 -0
  170. package/src/tools/scrape.ts +286 -388
  171. package/src/tools/search.ts +98 -153
  172. package/src/tools/security.ts +107 -80
  173. package/src/tools/stackexchange.ts +58 -35
  174. package/src/tools/youtube-transcript.ts +163 -0
  175. package/src/tui/knowledge-search-panel.ts +86 -0
  176. package/src/tui/pulse.ts +86 -0
  177. package/src/tui/research-health.ts +124 -0
  178. package/src/tui/research-panel-color-utils.ts +340 -0
  179. package/src/tui/research-panel-format.ts +54 -0
  180. package/src/tui/research-panel-state.ts +404 -0
  181. package/src/tui/research-panel-wave.ts +280 -0
  182. package/src/tui/research-panel.ts +339 -435
  183. package/src/tui/research-tui-manager.ts +233 -0
  184. package/src/tui/tui-controller.ts +137 -0
  185. package/src/types/extension-context.ts +18 -49
  186. package/src/types/index.ts +61 -0
  187. package/src/types/llm.ts +68 -29
  188. package/src/types/research-panel-types.ts +61 -0
  189. package/src/utils/circuit-breaker.ts +146 -0
  190. package/src/utils/citation-utils.ts +97 -0
  191. package/src/utils/disk-space-checker.ts +55 -0
  192. package/src/utils/error-tracker.ts +265 -0
  193. package/src/utils/host-config.ts +45 -0
  194. package/src/utils/input-validation.ts +8 -10
  195. package/src/utils/json-utils.ts +150 -16
  196. package/src/utils/llm-usage.ts +54 -0
  197. package/src/utils/log-rotation.ts +125 -0
  198. package/src/utils/log-utils.ts +159 -0
  199. package/src/utils/make-resource-loader.ts +3 -1
  200. package/src/utils/metrics-summary.ts +459 -0
  201. package/src/utils/metrics.ts +302 -0
  202. package/src/utils/research-export.ts +28 -12
  203. package/src/utils/safe-unref.ts +31 -0
  204. package/src/utils/shared-links.ts +195 -90
  205. package/src/utils/shutdown-manager.ts +65 -0
  206. package/src/utils/stdio-capture.ts +407 -0
  207. package/src/utils/text-utils.ts +264 -7
  208. package/src/utils/tool-usage-tracker.ts +83 -16
  209. package/src/utils/url-utils.ts +255 -0
  210. package/src/utils/user-agent.ts +28 -0
  211. package/src/web-research/browser-search.ts +179 -0
  212. package/src/web-research/retry-utils.ts +173 -77
  213. package/src/web-research/scraper-types.ts +72 -0
  214. package/src/web-research/scraper-utils.ts +434 -0
  215. package/src/web-research/search.ts +78 -218
  216. package/src/web-research/types.ts +9 -24
  217. package/src/web-research/utils.ts +2 -143
  218. package/src/web-research/web-scraper.ts +477 -0
  219. package/src/youtube/index.ts +154 -0
  220. package/src/youtube/potoken.ts +259 -0
  221. package/src/youtube/transcript-client.ts +390 -0
  222. package/src/youtube/video-id.ts +118 -0
  223. package/config/searxng/default-settings.yml +0 -189
  224. package/config/searxng/limiter.toml +0 -34
  225. package/config/tooling/eslint.config.js +0 -73
  226. package/config/tooling/vitest.config.integration.ts +0 -17
  227. package/config/tooling/vitest.config.ts +0 -40
  228. package/config/tooling/vitest.config.unit.ts +0 -12
  229. package/docs/assets/README-banner.jpg +0 -0
  230. package/scripts/setup.js +0 -174
  231. package/src/infrastructure/searxng-lifecycle.ts +0 -577
  232. package/src/infrastructure/searxng-manager.ts +0 -1183
  233. package/src/infrastructure/state-manager.ts +0 -918
  234. package/src/orchestration/deep-research-reducer.ts +0 -129
  235. package/src/orchestration/id-utils.ts +0 -63
  236. package/src/security/nvd.ts +0 -532
  237. package/src/security/osv.ts +0 -487
  238. package/src/stackexchange/cache.ts +0 -111
  239. package/src/stackexchange/output/index.ts +0 -7
  240. package/src/stackexchange/output/json.ts +0 -21
  241. package/src/tui/searxng-status.ts +0 -80
  242. package/src/utils/inject-date.ts +0 -29
  243. package/src/utils/research-error.ts +0 -205
  244. package/src/utils/searxng-config.ts +0 -94
  245. package/src/utils/session-state.ts +0 -295
  246. package/src/web-research/dependencies.ts +0 -20
  247. package/src/web-research/scrapers.ts +0 -935
  248. package/tsconfig.json +0 -40
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
- /** Flash duration for UI slice animations in milliseconds */
10
- export const FLASH_DURATION_MS = 500;
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
- /** Delay between API requests in milliseconds (e.g., NVD rate limit) */
13
- export const REQUEST_DELAY_MS = 6500;
15
+ /** Duration (ms) for a green (success) flash on a researcher column */
16
+ export const FLASH_GREEN_DURATION_MS = 400;
14
17
 
15
- // ==================== Research Constraints ====================
18
+ /** Duration (ms) for a red (failure) flash on a researcher column */
19
+ export const FLASH_RED_DURATION_MS = 700;
16
20
 
17
- /** Maximum gathering (tool) calls per researcher */
18
- export const MAX_GATHERING_CALLS = 4;
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
- /** Maximum scrape tool calls per researcher (1 handshake + up to 3 execution batches) */
21
- export const MAX_SCRAPE_CALLS = 4;
24
+ /** Minimum gap (ms) between queued flashes for the same researcher column */
25
+ export const FLASH_QUEUE_GAP_MS = 150;
22
26
 
23
- /** Maximum URLs to scrape per batch */
24
- export const MAX_SCRAPE_URLS = 3;
27
+ // ==================== Research Constraints ====================
25
28
 
26
- /** Maximum siblings (researchers) allowed to run simultaneously across all rounds */
27
- export const MAX_CONCURRENT_RESEARCHERS = 3;
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
- // ==================== Complexity Levels ====================
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
- * 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)
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
- /** Initial researcher count for complexity 1 (Normal) */
40
- export const INITIAL_RESEARCHERS_LEVEL_1 = 2;
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
- /** Initial researcher count for complexity 3 (Ultra) */
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 for complexity 1 (Normal) */
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
- * 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).
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 MAX_EXTRA_ROUNDS = 2;
100
+ export const MAX_EXTRA_ROUNDS_WITH_STEERING = 2;
64
101
 
65
102
 
66
103
  // ==================== Timeout Constants ====================
67
104
 
68
- /** Default timeout for SearXNG requests in milliseconds */
69
- export const SEARXNG_TIMEOUT_MS = 30000;
105
+ export const OSV_TIMEOUT_MS = 10000;
70
106
 
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;
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 MAX_QUERY_LENGTH = 50;
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
- // ==================== Context-Aware Scraping Constants ====================
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
- * 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;
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
- * Default maxConcurrency for the second scrape batch.
124
- * Higher than batch 1 because batch 2 URLs are already prioritized.
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 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;
169
+ export const MAX_SCRAPE_CONTENT_CHARS_PER_DOC = 500_000;
135
170
 
136
171
  // ==================== Orchestrator Constants ====================
137
172
 
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 */
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
- /** 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;
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
+ }