@lincoln504/pi-research 0.1.12 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +64 -115
  3. package/dist/cli.mjs +23211 -0
  4. package/dist/prompts/research-knowledge-relevance-triage.md +35 -0
  5. package/dist/prompts/research-knowledge-search-extractor.md +41 -0
  6. package/dist/prompts/research-tool-usage.md +92 -0
  7. package/dist/prompts/researcher.md +89 -0
  8. package/dist/prompts/system-coordinator.md +75 -0
  9. package/dist/prompts/system-lead-evaluator.md +118 -0
  10. package/dist/thread-worker.mjs +2611 -0
  11. package/dist/webgpu-probe.mjs +80 -0
  12. package/docs/AGENT-SKILL.md +102 -0
  13. package/docs/ARCHITECTURE.md +306 -80
  14. package/docs/CONFIGURATION.md +249 -0
  15. package/docs/KNOWLEDGE-STORE.md +213 -0
  16. package/docs/PI-EXTENSION.md +91 -0
  17. package/docs/SDK.md +113 -0
  18. package/docs/THIRD-PARTY-NOTICES.md +64 -0
  19. package/package.json +102 -36
  20. package/scripts/build.cjs +140 -0
  21. package/scripts/cleanup.cjs +149 -0
  22. package/scripts/prepare.cjs +28 -0
  23. package/scripts/setup.cjs +134 -0
  24. package/skills/pi-research/README.md +90 -0
  25. package/skills/pi-research/SKILL.md +102 -0
  26. package/skills/pi-research/references/configuration.md +77 -0
  27. package/skills/pi-research/scripts/run.mjs +237 -0
  28. package/src/cleanup/research-cleanup.ts +140 -0
  29. package/src/cli.ts +1044 -0
  30. package/src/config.ts +1036 -147
  31. package/src/constants.ts +115 -84
  32. package/src/core/health-check-service.ts +265 -0
  33. package/src/core/interfaces/health-check-interfaces.ts +50 -0
  34. package/src/core/interfaces/knowledge-interfaces.ts +190 -0
  35. package/src/core/interfaces/observer-interfaces.ts +67 -0
  36. package/src/core/interfaces/orchestration-interfaces.ts +86 -0
  37. package/src/core/interfaces/planning-interfaces.ts +100 -0
  38. package/src/core/interfaces/process-interfaces.ts +37 -0
  39. package/src/core/interfaces/research-plan-types.ts +52 -0
  40. package/src/core/interfaces/scheduler-interfaces.ts +91 -0
  41. package/src/core/interfaces/service-names.ts +39 -0
  42. package/src/core/interfaces/state-manager-interfaces.ts +29 -0
  43. package/src/core/interfaces/steering-interfaces.ts +31 -0
  44. package/src/core/llm/agentic-repair.ts +169 -0
  45. package/src/core/llm/inject-date.ts +52 -0
  46. package/src/core/llm/llm-timeout.ts +64 -0
  47. package/src/core/llm/llm-utils.ts +86 -0
  48. package/src/core/llm/model-registry-factory.ts +287 -0
  49. package/src/core/llm/pi-ai-completion.ts +29 -0
  50. package/src/core/llm/prompts.ts +50 -0
  51. package/src/core/llm/research-model-resolver.ts +91 -0
  52. package/src/core/planning-constants.ts +13 -0
  53. package/src/core/planning-service.ts +649 -0
  54. package/src/core/planning-utils.ts +299 -0
  55. package/src/core/scheduler-factory.ts +48 -0
  56. package/src/core/scheduler-service.ts +123 -0
  57. package/src/core/service-initialization.ts +194 -0
  58. package/src/core/service-interfaces.ts +48 -0
  59. package/src/core/service-registry.ts +659 -0
  60. package/src/healthcheck/index.ts +271 -265
  61. package/src/healthcheck/registry.ts +130 -0
  62. package/src/index.ts +639 -50
  63. package/src/infrastructure/browser/browser-cleanup.ts +309 -0
  64. package/src/infrastructure/browser/browser-client.ts +199 -0
  65. package/src/infrastructure/browser/browser-error-utils.ts +160 -0
  66. package/src/infrastructure/browser/browser-lifecycle.ts +120 -0
  67. package/src/infrastructure/browser/browser-server.ts +174 -0
  68. package/src/infrastructure/browser/browser-task-scheduler.ts +461 -0
  69. package/src/infrastructure/browser/cleanup-utils.ts +155 -0
  70. package/src/infrastructure/browser/client-agent.ts +31 -0
  71. package/src/infrastructure/browser/config.ts +288 -0
  72. package/src/infrastructure/browser/ensure-browser.ts +205 -0
  73. package/src/infrastructure/browser/index.ts +37 -0
  74. package/src/infrastructure/browser/priority-task-queue.ts +226 -0
  75. package/src/infrastructure/browser/scheduler-factory.ts +354 -0
  76. package/src/infrastructure/browser/task-execution-service.ts +209 -0
  77. package/src/infrastructure/browser/thread-worker-browser.ts +282 -0
  78. package/src/infrastructure/browser/thread-worker-lifecycle.ts +323 -0
  79. package/src/infrastructure/browser/thread-worker-messaging.ts +662 -0
  80. package/src/infrastructure/browser/thread-worker-types.ts +25 -0
  81. package/src/infrastructure/browser/thread-worker.mjs +2611 -0
  82. package/src/infrastructure/browser/thread-worker.ts +168 -0
  83. package/src/infrastructure/browser/worker-pool-manager.ts +449 -0
  84. package/src/infrastructure/embedding/embedding-client.ts +205 -0
  85. package/src/infrastructure/embedding/embedding-factory.ts +324 -0
  86. package/src/infrastructure/embedding/embedding-server.ts +535 -0
  87. package/src/infrastructure/file-lock-service.ts +592 -0
  88. package/src/infrastructure/gpu-resource-service.ts +191 -0
  89. package/src/infrastructure/knowledge-store-service.ts +475 -0
  90. package/src/infrastructure/metrics-service.ts +129 -0
  91. package/src/infrastructure/process-lifecycle-service.ts +253 -0
  92. package/src/infrastructure/scheduler-factory-service.ts +58 -0
  93. package/src/infrastructure/service-initialization.ts +292 -0
  94. package/src/infrastructure/state/state-backup-manager.ts +290 -0
  95. package/src/infrastructure/state/state-browser-api.ts +62 -0
  96. package/src/infrastructure/state/state-browser-manager.ts +70 -0
  97. package/src/infrastructure/state/state-manager-service.ts +264 -0
  98. package/src/infrastructure/state/state-manager.ts +547 -0
  99. package/src/infrastructure/state/state-metrics.ts +92 -0
  100. package/src/infrastructure/state/state-migration.ts +103 -0
  101. package/src/infrastructure/state/state-path-configuration.ts +119 -0
  102. package/src/infrastructure/state/state-session-api.ts +94 -0
  103. package/src/infrastructure/state/state-session-manager.ts +127 -0
  104. package/src/infrastructure/state/state-validator.ts +93 -0
  105. package/src/infrastructure/types/state-types.ts +85 -0
  106. package/src/knowledge/chunker.ts +157 -0
  107. package/src/knowledge/embedder-init.ts +413 -0
  108. package/src/knowledge/embedder-utils.ts +296 -0
  109. package/src/knowledge/embedder.ts +592 -0
  110. package/src/knowledge/index.ts +126 -0
  111. package/src/knowledge/lancedb-loader.ts +35 -0
  112. package/src/knowledge/migration.ts +17 -0
  113. package/src/knowledge/model-config.ts +132 -0
  114. package/src/knowledge/onnx-env.ts +48 -0
  115. package/src/knowledge/store-operations.ts +291 -0
  116. package/src/knowledge/store-schema.ts +79 -0
  117. package/src/knowledge/store.ts +1251 -0
  118. package/src/knowledge/transformers-loader.ts +31 -0
  119. package/src/knowledge/webgpu-probe.mjs +80 -0
  120. package/src/knowledge/webgpu-viability.ts +255 -0
  121. package/src/knowledge/writer-queue.ts +252 -0
  122. package/src/logger.ts +316 -122
  123. package/src/observers/research-observer-impl.ts +459 -0
  124. package/src/orchestration/deep-research-orchestrator.ts +524 -773
  125. package/src/orchestration/deep-research-types.ts +47 -35
  126. package/src/orchestration/headless-observer.ts +114 -0
  127. package/src/orchestration/orchestration-types.ts +33 -0
  128. package/src/orchestration/quick-research-orchestrator.ts +463 -0
  129. package/src/orchestration/research-orchestration-service.ts +549 -0
  130. package/src/orchestration/research-session-service.ts +178 -0
  131. package/src/orchestration/research-synthesis-service.ts +348 -0
  132. package/src/orchestration/researcher-executor.ts +394 -0
  133. package/src/orchestration/researcher.ts +115 -42
  134. package/src/orchestration/service-initialization.ts +109 -0
  135. package/src/orchestration/session-context.ts +7 -3
  136. package/src/orchestration/session-state.ts +672 -0
  137. package/src/prompts/research-knowledge-relevance-triage.md +35 -0
  138. package/src/prompts/research-knowledge-search-extractor.md +41 -0
  139. package/src/prompts/research-tool-usage.md +63 -16
  140. package/src/prompts/researcher.md +59 -136
  141. package/src/prompts/system-coordinator.md +64 -13
  142. package/src/prompts/system-lead-evaluator.md +82 -189
  143. package/src/research-config.ts +872 -0
  144. package/src/sdk.ts +847 -0
  145. package/src/security/cisa-kev.ts +72 -9
  146. package/src/security/cvss.ts +92 -0
  147. package/src/security/ecosystem.ts +45 -0
  148. package/src/security/github-advisories.ts +168 -428
  149. package/src/security/github-advisory-types.ts +282 -0
  150. package/src/security/index.ts +63 -177
  151. package/src/security/nvd-client.ts +550 -0
  152. package/src/security/nvd-types.ts +179 -0
  153. package/src/security/osv-client.ts +246 -0
  154. package/src/security/osv-types.ts +285 -0
  155. package/src/security/types.ts +67 -53
  156. package/src/skill-install/skill-installer.ts +519 -0
  157. package/src/stackexchange/index.ts +81 -27
  158. package/src/stackexchange/output/compact.ts +7 -6
  159. package/src/stackexchange/output/table.ts +11 -7
  160. package/src/stackexchange/queries.ts +0 -10
  161. package/src/stackexchange/rest-client.ts +141 -66
  162. package/src/stackexchange/types.ts +0 -17
  163. package/src/tool.ts +15 -423
  164. package/src/tools/grep.ts +32 -310
  165. package/src/tools/health-tool-definition.ts +101 -0
  166. package/src/tools/index.ts +38 -9
  167. package/src/tools/research-knowledge-search.ts +974 -0
  168. package/src/tools/research-knowledge-types.ts +77 -0
  169. package/src/tools/research-tool-definition.ts +494 -0
  170. package/src/tools/scrape.ts +286 -388
  171. package/src/tools/search.ts +98 -153
  172. package/src/tools/security.ts +107 -80
  173. package/src/tools/stackexchange.ts +58 -35
  174. package/src/tools/youtube-transcript.ts +163 -0
  175. package/src/tui/knowledge-search-panel.ts +86 -0
  176. package/src/tui/pulse.ts +86 -0
  177. package/src/tui/research-health.ts +124 -0
  178. package/src/tui/research-panel-color-utils.ts +340 -0
  179. package/src/tui/research-panel-format.ts +54 -0
  180. package/src/tui/research-panel-state.ts +404 -0
  181. package/src/tui/research-panel-wave.ts +280 -0
  182. package/src/tui/research-panel.ts +339 -435
  183. package/src/tui/research-tui-manager.ts +233 -0
  184. package/src/tui/tui-controller.ts +137 -0
  185. package/src/types/extension-context.ts +18 -49
  186. package/src/types/index.ts +61 -0
  187. package/src/types/llm.ts +68 -29
  188. package/src/types/research-panel-types.ts +61 -0
  189. package/src/utils/circuit-breaker.ts +146 -0
  190. package/src/utils/citation-utils.ts +97 -0
  191. package/src/utils/disk-space-checker.ts +55 -0
  192. package/src/utils/error-tracker.ts +265 -0
  193. package/src/utils/host-config.ts +45 -0
  194. package/src/utils/input-validation.ts +8 -10
  195. package/src/utils/json-utils.ts +150 -16
  196. package/src/utils/llm-usage.ts +54 -0
  197. package/src/utils/log-rotation.ts +125 -0
  198. package/src/utils/log-utils.ts +159 -0
  199. package/src/utils/make-resource-loader.ts +3 -1
  200. package/src/utils/metrics-summary.ts +459 -0
  201. package/src/utils/metrics.ts +302 -0
  202. package/src/utils/research-export.ts +28 -12
  203. package/src/utils/safe-unref.ts +31 -0
  204. package/src/utils/shared-links.ts +195 -90
  205. package/src/utils/shutdown-manager.ts +65 -0
  206. package/src/utils/stdio-capture.ts +407 -0
  207. package/src/utils/text-utils.ts +264 -7
  208. package/src/utils/tool-usage-tracker.ts +83 -16
  209. package/src/utils/url-utils.ts +255 -0
  210. package/src/utils/user-agent.ts +28 -0
  211. package/src/web-research/browser-search.ts +179 -0
  212. package/src/web-research/retry-utils.ts +173 -77
  213. package/src/web-research/scraper-types.ts +72 -0
  214. package/src/web-research/scraper-utils.ts +434 -0
  215. package/src/web-research/search.ts +78 -218
  216. package/src/web-research/types.ts +9 -24
  217. package/src/web-research/utils.ts +2 -143
  218. package/src/web-research/web-scraper.ts +477 -0
  219. package/src/youtube/index.ts +154 -0
  220. package/src/youtube/potoken.ts +259 -0
  221. package/src/youtube/transcript-client.ts +390 -0
  222. package/src/youtube/video-id.ts +118 -0
  223. package/config/searxng/default-settings.yml +0 -189
  224. package/config/searxng/limiter.toml +0 -34
  225. package/config/tooling/eslint.config.js +0 -73
  226. package/config/tooling/vitest.config.integration.ts +0 -17
  227. package/config/tooling/vitest.config.ts +0 -40
  228. package/config/tooling/vitest.config.unit.ts +0 -12
  229. package/docs/assets/README-banner.jpg +0 -0
  230. package/scripts/setup.js +0 -174
  231. package/src/infrastructure/searxng-lifecycle.ts +0 -577
  232. package/src/infrastructure/searxng-manager.ts +0 -1183
  233. package/src/infrastructure/state-manager.ts +0 -918
  234. package/src/orchestration/deep-research-reducer.ts +0 -129
  235. package/src/orchestration/id-utils.ts +0 -63
  236. package/src/security/nvd.ts +0 -532
  237. package/src/security/osv.ts +0 -487
  238. package/src/stackexchange/cache.ts +0 -111
  239. package/src/stackexchange/output/index.ts +0 -7
  240. package/src/stackexchange/output/json.ts +0 -21
  241. package/src/tui/searxng-status.ts +0 -80
  242. package/src/utils/inject-date.ts +0 -29
  243. package/src/utils/research-error.ts +0 -205
  244. package/src/utils/searxng-config.ts +0 -94
  245. package/src/utils/session-state.ts +0 -295
  246. package/src/web-research/dependencies.ts +0 -20
  247. package/src/web-research/scrapers.ts +0 -935
  248. package/tsconfig.json +0 -40
@@ -1,577 +0,0 @@
1
- /**
2
- * SearXNG Lifecycle Manager
3
- *
4
- * Refactored to use dependency injection for testability.
5
- * Maintains backward compatibility with original API.
6
- *
7
- * All agents (coordinator + researchers) share same container.
8
- * Status states: 'starting_up' | 'active' | 'inactive' | 'error'
9
- *
10
- * Lifecycle: Container lives for duration of pi process.
11
- * Cleanup is triggered via pi lifecycle hooks rather than process signal ownership.
12
- * Use shutdownLifecycle() for extension-owned cleanup paths.
13
- */
14
-
15
- import type { ExtensionContext } from '@mariozechner/pi-coding-agent';
16
- import {
17
- DOCKER_HOST_INTERNAL_HOSTNAME,
18
- DockerSearxngManager,
19
- type SearxngManagerConfig,
20
- verifyDockerInstalled,
21
- } from './searxng-manager.ts';
22
- import { logger, type ILogger } from '../logger.ts';
23
- import * as path from 'node:path';
24
- import * as fs from 'node:fs';
25
- import * as os from 'node:os';
26
- import { fileURLToPath } from 'node:url';
27
- import { PROXY_URL, BRAVE_SEARCH_API_KEY } from '../config.ts';
28
- import { shutdownManager } from '../utils/shutdown-manager.ts';
29
-
30
- /**
31
- * Extension directory for pi-research
32
- */
33
- const __filename = fileURLToPath(import.meta.url);
34
- const __dirname = path.dirname(__filename);
35
-
36
- function hasExtensionAssets(dir: string): boolean {
37
- return (
38
- fs.existsSync(path.join(dir, 'config', 'searxng', 'default-settings.yml')) &&
39
- fs.existsSync(path.join(dir, 'config', 'searxng', 'limiter.toml'))
40
- );
41
- }
42
-
43
- function resolveExtensionDir(): string {
44
- const envOverride = process.env['PI_RESEARCH_EXTENSION_DIR']?.trim();
45
- if (envOverride && hasExtensionAssets(envOverride)) {
46
- return envOverride;
47
- }
48
-
49
- const installedDir = path.join(
50
- os.homedir(),
51
- '.pi',
52
- 'agent',
53
- 'extensions',
54
- 'pi-research',
55
- );
56
- if (hasExtensionAssets(installedDir)) {
57
- return installedDir;
58
- }
59
-
60
- const repoDir = path.resolve(__dirname, '..', '..');
61
- if (hasExtensionAssets(repoDir)) {
62
- return repoDir;
63
- }
64
-
65
- return installedDir;
66
- }
67
-
68
- const EXTENSION_DIR = resolveExtensionDir();
69
- const LOCALHOST_PROXY_HOST_PATTERN = /^([a-z][a-z0-9+.-]*:\/\/(?:[^@/?#]*@)?)(localhost|127\.0\.0\.1)(?=[:/?#]|$)/i;
70
-
71
- export function rewriteLocalhostProxyForContainer(proxyUrl: string): string {
72
- return proxyUrl.replace(
73
- LOCALHOST_PROXY_HOST_PATTERN,
74
- `$1${DOCKER_HOST_INTERNAL_HOSTNAME}`,
75
- );
76
- }
77
-
78
- /**
79
- * Track status for TUI display (minimal: starting_up | active | inactive | error)
80
- */
81
- export type SearxngLifecycleStatus = {
82
- state: 'starting_up' | 'active' | 'inactive' | 'error';
83
- url: string;
84
- isFunctional: boolean;
85
- };
86
-
87
- /**
88
- * Status callback type
89
- */
90
- type StatusCallback = (_status: SearxngLifecycleStatus) => void;
91
-
92
- /**
93
- * Searxng Lifecycle Manager Configuration
94
- */
95
- export interface SearxngLifecycleConfig {
96
- extensionDir?: string;
97
- logger?: ILogger;
98
- manager?: DockerSearxngManager | null;
99
- proxyUrl?: string;
100
- braveApiKey?: string;
101
- }
102
-
103
- type ResolvedConfig = {
104
- extensionDir: string;
105
- logger: ILogger;
106
- manager: DockerSearxngManager | null;
107
- proxyUrl: string | undefined;
108
- braveApiKey: string | undefined;
109
- };
110
-
111
- /**
112
- * Manager interface for dependency injection
113
- */
114
- export interface ISearxngLifecycleManager {
115
- init(ctx: ExtensionContext): Promise<void>;
116
- ensureRunning(): Promise<string>;
117
- getManager(): DockerSearxngManager | null;
118
- getStatus(): SearxngLifecycleStatus;
119
- setFunctional(ok: boolean): void;
120
- isFunctional(): boolean;
121
- onStatusChange(callback: StatusCallback): () => void;
122
- shutdown(): Promise<void>;
123
- isInitialized(): boolean;
124
- }
125
-
126
- /**
127
- * Default configuration
128
- */
129
- const DEFAULT_CONFIG: ResolvedConfig = {
130
- extensionDir: EXTENSION_DIR,
131
- logger,
132
- manager: null,
133
- proxyUrl: PROXY_URL,
134
- braveApiKey: BRAVE_SEARCH_API_KEY,
135
- };
136
-
137
- /**
138
- * Searxng Lifecycle Manager Class
139
- *
140
- * Manages the lifecycle of the SearXNG container.
141
- * Uses dependency injection for testability.
142
- */
143
- export class SearxngLifecycleManager implements ISearxngLifecycleManager {
144
- private manager: DockerSearxngManager | null = null;
145
- private sessionId: string | null = null;
146
- private initialized = false;
147
- private currentStatus: SearxngLifecycleStatus = {
148
- state: 'inactive',
149
- url: '',
150
- isFunctional: false,
151
- };
152
- private statusCallbacks: StatusCallback[] = [];
153
- private readonly config: ResolvedConfig;
154
- private proxySettingsPath: string | null = null;
155
-
156
- constructor(config: Partial<SearxngLifecycleConfig> = {}) {
157
- this.config = { ...DEFAULT_CONFIG, ...config } as ResolvedConfig;
158
- // Set manager immediately if provided (for testing)
159
- if (this.config.manager) {
160
- this.manager = this.config.manager;
161
- }
162
- }
163
-
164
- /**
165
- * Get manager instance (for passing to web-research module)
166
- */
167
- getManager(): DockerSearxngManager | null {
168
- return this.manager;
169
- }
170
-
171
- setFunctional(ok: boolean): void {
172
- this.currentStatus.isFunctional = ok;
173
- this.notifyStatusChange();
174
- }
175
-
176
- isFunctional(): boolean {
177
- return this.currentStatus.isFunctional;
178
- }
179
-
180
- /**
181
- * Subscribe to status changes
182
- */
183
- onStatusChange(callback: StatusCallback): () => void {
184
- this.statusCallbacks.push(callback);
185
- // Immediately call with current status (handle errors gracefully)
186
- try {
187
- callback(this.currentStatus);
188
- } catch (error) {
189
- // Log errors but don't fail registration
190
- this.config.logger.error('[searxng-lifecycle] Error in status callback:', error);
191
- }
192
- // Return unsubscribe function
193
- return () => {
194
- const index = this.statusCallbacks.indexOf(callback);
195
- if (index !== -1) {
196
- this.statusCallbacks.splice(index, 1);
197
- }
198
- };
199
- }
200
-
201
- private notifyStatusChange(): void {
202
- for (const callback of this.statusCallbacks) {
203
- try {
204
- callback(this.currentStatus);
205
- } catch (error) {
206
- // Log errors but don't fail the entire notification
207
- this.config.logger.error('[searxng-lifecycle] Error in status callback:', error);
208
- }
209
- }
210
- }
211
-
212
- private extractSessionId(ctx: ExtensionContext): string {
213
- if ('sessionId' in ctx && typeof ctx.sessionId === 'string') {
214
- return ctx.sessionId;
215
- }
216
- // Fallback: generate unique ID
217
- return `pi-research-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
218
- }
219
-
220
- /**
221
- * Generate a runtime settings file with optional proxy and/or Brave Search API key injected.
222
- * Localhost proxy addresses are rewritten to Docker's host alias automatically.
223
- */
224
- private async generateRuntimeSettings(proxyUrl?: string, braveApiKey?: string): Promise<string> {
225
- const defaultSettingsPath = path.join(
226
- this.config.extensionDir,
227
- 'config',
228
- 'default-settings.yml'
229
- );
230
- const uniqueId = this.sessionId?.substring(0, 8) || Math.random().toString(36).substring(2, 8);
231
- const runtimeSettingsPath = path.join(
232
- this.config.extensionDir,
233
- 'config',
234
- `runtime-settings-${uniqueId}.yml`
235
- );
236
-
237
- try {
238
- const defaultSettings = await fs.promises.readFile(defaultSettingsPath, 'utf-8');
239
- const yaml = await import('js-yaml');
240
- const settings = yaml.load(defaultSettings) as any;
241
-
242
- if (proxyUrl) {
243
- const containerProxyUrl = rewriteLocalhostProxyForContainer(proxyUrl);
244
- if (containerProxyUrl !== proxyUrl) {
245
- logger.log(`[pi-research] Converting localhost proxy to Docker host alias: ${proxyUrl} -> ${containerProxyUrl}`);
246
- }
247
- settings.outgoing = settings.outgoing || {};
248
- settings.outgoing.proxies = { 'all://': [containerProxyUrl] };
249
- settings.outgoing.extra_proxy_timeout = 10;
250
- logger.log(`[pi-research] Runtime settings: proxy configured (${containerProxyUrl})`);
251
- }
252
-
253
- if (braveApiKey) {
254
- settings.engines = settings.engines || [];
255
- settings.engines.push({
256
- name: 'braveapi',
257
- engine: 'braveapi',
258
- api_key: braveApiKey,
259
- shortcut: 'bapi',
260
- weight: 1.2,
261
- inactive: false,
262
- });
263
- logger.log('[pi-research] Runtime settings: braveapi engine enabled');
264
- }
265
-
266
- await fs.promises.writeFile(runtimeSettingsPath, yaml.dump(settings), 'utf-8');
267
- this.proxySettingsPath = runtimeSettingsPath;
268
-
269
- return runtimeSettingsPath;
270
- } catch (error) {
271
- logger.error('[pi-research] Failed to generate runtime settings:', error);
272
- throw error;
273
- }
274
- }
275
-
276
- async init(ctx: ExtensionContext): Promise<void> {
277
- if (this.initialized) {
278
- logger.log('[pi-research] SearXNG already initialized, skipping');
279
- return;
280
- }
281
-
282
- // Skip real Docker verification when a test/dummy manager is injected.
283
- if (!this.config.manager) {
284
- const dockerCheck = await verifyDockerInstalled();
285
- if (!dockerCheck.running) {
286
- this.currentStatus = {
287
- state: 'error',
288
- url: '',
289
- isFunctional: false,
290
- };
291
- this.notifyStatusChange();
292
- const errorMsg = `Docker health check failed: ${dockerCheck.error}`;
293
- logger.error(`[pi-research] ${errorMsg}`);
294
- throw new Error(errorMsg);
295
- }
296
- }
297
-
298
- // Set state to starting_up
299
- this.currentStatus = {
300
- state: 'starting_up',
301
- url: '',
302
- isFunctional: false,
303
- };
304
- this.notifyStatusChange();
305
-
306
- // Use injected manager or create new one
307
- if (this.config.manager) {
308
- this.manager = this.config.manager;
309
- } else {
310
- // Clean up old manager if exists
311
- if (this.manager !== null) {
312
- try {
313
- this.manager.stopHeartbeat();
314
- if (this.sessionId !== null) {
315
- await this.manager.release(this.sessionId).catch(() => {});
316
- }
317
- } catch (error) {
318
- this.config.logger.warn('[pi-research] Error cleaning up old manager:', error);
319
- }
320
- }
321
-
322
- // Generate runtime settings if proxy or Brave API key is configured
323
- let settingsPath: string;
324
- if (this.config.proxyUrl || this.config.braveApiKey) {
325
- try {
326
- if (this.config.proxyUrl) logger.log('[pi-research] Proxy configured, generating runtime settings...');
327
- if (this.config.braveApiKey) logger.log('[pi-research] BRAVE_SEARCH_API_KEY set, enabling braveapi engine...');
328
- settingsPath = await this.generateRuntimeSettings(this.config.proxyUrl, this.config.braveApiKey);
329
- } catch (error) {
330
- const errorMsg = error instanceof Error ? error.message : String(error);
331
- logger.error('[pi-research] Failed to generate runtime settings:', errorMsg);
332
- const configError = new Error(
333
- `Runtime settings generation failed: ${errorMsg}\n\n` +
334
- `To fix:\n` +
335
- `1. Check PROXY_URL format: socks5://host:port or http://host:port\n` +
336
- `2. Check BRAVE_SEARCH_API_KEY is a valid key\n` +
337
- `3. Unset these env vars to use default settings`
338
- );
339
- configError.cause = error;
340
- throw configError;
341
- }
342
- } else {
343
- settingsPath = path.join(
344
- this.config.extensionDir,
345
- 'config',
346
- 'searxng',
347
- 'default-settings.yml'
348
- );
349
- }
350
-
351
- // Create new manager instance
352
- const managerConfig: SearxngManagerConfig = { settingsPath };
353
- this.manager = new DockerSearxngManager(this.config.extensionDir, managerConfig);
354
- }
355
-
356
- this.manager.setContext(ctx);
357
-
358
- const ctxSessionId = this.extractSessionId(ctx);
359
- this.sessionId = ctxSessionId;
360
-
361
- try {
362
- await this.manager.acquire(ctxSessionId);
363
- this.manager.startHeartbeat();
364
-
365
- // Get current status
366
- const searxngStatus = await this.manager.getStatus();
367
-
368
- // Update internal status
369
- this.currentStatus = {
370
- state: 'active',
371
- url: searxngStatus.url || '',
372
- isFunctional: false, // Reset on every re-init, must re-verify
373
- };
374
-
375
- this.initialized = true;
376
-
377
- logger.log('[pi-research] SearXNG lifecycle initialized');
378
- logger.log('[pi-research] SearXNG container:', searxngStatus.url);
379
-
380
- this.notifyStatusChange();
381
-
382
- } catch (error) {
383
- logger.error('[pi-research] Failed to initialize SearXNG:', error);
384
-
385
- this.currentStatus = {
386
- state: 'error',
387
- url: '',
388
- isFunctional: false,
389
- };
390
-
391
- this.initialized = true;
392
- this.notifyStatusChange();
393
-
394
- throw error;
395
- }
396
- }
397
-
398
- async ensureRunning(): Promise<string> {
399
- if (!this.manager) {
400
- throw new Error('SearXNG not initialized. Call initLifecycle() first.');
401
- }
402
-
403
- // ensureReady waits for container to be healthy
404
- await this.manager.ensureReady();
405
-
406
- return this.manager.getSearxngUrl();
407
- }
408
-
409
- getStatus(): SearxngLifecycleStatus {
410
- return { ...this.currentStatus };
411
- }
412
-
413
- async shutdown(): Promise<void> {
414
- if (!this.manager || !this.initialized) {
415
- logger.log('[pi-research] SearXNG not active, skipping shutdown');
416
- return;
417
- }
418
-
419
- try {
420
- this.manager.stopHeartbeat();
421
-
422
- if (this.sessionId !== null) {
423
- await this.manager.release(this.sessionId);
424
- }
425
-
426
- // Cleanup temporary proxy settings file if it exists
427
- if (this.proxySettingsPath && fs.existsSync(this.proxySettingsPath)) {
428
- try {
429
- await fs.promises.unlink(this.proxySettingsPath);
430
- logger.log(`[pi-research] Deleted temporary proxy settings: ${this.proxySettingsPath}`);
431
- } catch (error) {
432
- logger.warn('[pi-research] Failed to delete temporary proxy settings:', error);
433
- }
434
- this.proxySettingsPath = null;
435
- }
436
-
437
- this.currentStatus = {
438
- state: 'inactive',
439
- url: '',
440
- isFunctional: false,
441
- };
442
-
443
- this.initialized = false;
444
-
445
- logger.log('[pi-research] SearXNG lifecycle shut down');
446
- this.notifyStatusChange();
447
-
448
- } catch (error) {
449
- logger.error('[pi-research] Error during shutdown:', error);
450
- } finally {
451
- this.manager = null;
452
- this.sessionId = null;
453
- }
454
- }
455
-
456
- isInitialized(): boolean {
457
- return this.initialized && this.manager !== null;
458
- }
459
- }
460
-
461
- // ============================================================================
462
- // GLOBAL STATE FOR BACKWARD COMPATIBILITY
463
- // ============================================================================
464
-
465
- let globalManager: SearxngLifecycleManager | null = null;
466
-
467
- /**
468
- * Create a new lifecycle manager instance
469
- */
470
- export function createSearxngLifecycleManager(
471
- config: Partial<SearxngLifecycleConfig> = {}
472
- ): SearxngLifecycleManager {
473
- return new SearxngLifecycleManager(config);
474
- }
475
-
476
- /**
477
- * Get the global lifecycle manager instance
478
- */
479
- export function getLifecycleManager(): SearxngLifecycleManager {
480
- if (!globalManager) {
481
- globalManager = new SearxngLifecycleManager();
482
- }
483
- return globalManager;
484
- }
485
-
486
- /**
487
- * Set the global lifecycle manager instance (for testing)
488
- */
489
- export function setLifecycleManager(manager: SearxngLifecycleManager | null): void {
490
- globalManager = manager;
491
- }
492
-
493
- /**
494
- * Reset the global lifecycle manager instance (for testing)
495
- */
496
- export function resetLifecycleManager(): void {
497
- globalManager = null;
498
- }
499
-
500
- // ============================================================================
501
- // BACKWARD COMPATIBILITY EXPORTS
502
- // ============================================================================
503
-
504
- /**
505
- * Backward compatible: Get manager instance
506
- */
507
- export function getManager(): DockerSearxngManager | null {
508
- return getLifecycleManager().getManager();
509
- }
510
-
511
- /**
512
- * Backward compatible: Initialize lifecycle
513
- */
514
- export async function initLifecycle(ctx: ExtensionContext): Promise<void> {
515
- await getLifecycleManager().init(ctx);
516
- // Register extension-owned cleanup once; repeated initLifecycle() calls are safe.
517
- shutdownManager.register(shutdownLifecycle);
518
- }
519
-
520
- /**
521
- * Backward compatible: Ensure container is running
522
- */
523
- export async function ensureRunning(): Promise<string> {
524
- return getLifecycleManager().ensureRunning();
525
- }
526
-
527
- /**
528
- * Backward compatible: Get status (renamed type to avoid conflict)
529
- */
530
- export function getStatus(): SearxngLifecycleStatus {
531
- return getLifecycleManager().getStatus();
532
- }
533
-
534
- /**
535
- * Backward compatible: Set functional state
536
- */
537
- export function setFunctional(ok: boolean): void {
538
- getLifecycleManager().setFunctional(ok);
539
- }
540
-
541
- /**
542
- * Backward compatible: Get functional state
543
- */
544
- export function isFunctional(): boolean {
545
- return getLifecycleManager().isFunctional();
546
- }
547
-
548
- /**
549
- * Backward compatible: Subscribe to status changes
550
- */
551
- export function onStatusChange(callback: StatusCallback): () => void {
552
- return getLifecycleManager().onStatusChange(callback);
553
- }
554
-
555
- /**
556
- * Backward compatible: Shutdown lifecycle
557
- */
558
- export async function shutdownLifecycle(): Promise<void> {
559
- await getLifecycleManager().shutdown();
560
- }
561
-
562
- /**
563
- * Backward compatible: Check if initialized
564
- */
565
- export function isInitialized(): boolean {
566
- return getLifecycleManager().isInitialized();
567
- }
568
-
569
- /**
570
- * Check if Docker is installed and running
571
- */
572
- export async function checkDockerAvailability(): Promise<{ installed: boolean; running: boolean; error?: string }> {
573
- return verifyDockerInstalled();
574
- }
575
-
576
- // Export original type for compatibility
577
- export type SearxngStatus = SearxngLifecycleStatus;