@lincoln504/pi-research 1.3.10 → 1.3.12
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/README.md +1 -1
- package/dist/cli.mjs +264 -61
- package/dist/prompts/system-coordinator.md +1 -1
- package/dist/prompts/system-lead-evaluator.md +1 -1
- package/dist/thread-worker.mjs +31 -21
- package/docs/ARCHITECTURE.md +60 -22
- package/docs/CHANGELOG.md +103 -1
- package/docs/CONFIGURATION.md +6 -5
- package/package.json +18 -10
- package/scripts/cleanup.cjs +26 -5
- package/scripts/setup.cjs +25 -1
- package/skills/pi-research/SKILL.md +1 -1
- package/src/cli.ts +94 -13
- package/src/config.ts +6 -15
- package/src/constants.ts +13 -0
- package/src/core/service-registry.ts +19 -3
- package/src/infrastructure/browser/config.ts +36 -5
- package/src/infrastructure/browser/task-execution-service.ts +12 -2
- package/src/infrastructure/browser/thread-worker-browser.ts +34 -18
- package/src/infrastructure/browser/thread-worker.mjs +31 -21
- package/src/infrastructure/gpu-resource-service.ts +15 -1
- package/src/knowledge/embedder-init.ts +34 -2
- package/src/knowledge/store.ts +75 -6
- package/src/knowledge/writer-queue.ts +28 -0
- package/src/orchestration/research-orchestration-service.ts +35 -1
- package/src/orchestration/research-synthesis-service.ts +4 -2
- package/src/orchestration/researcher.ts +2 -13
- package/src/prompts/system-coordinator.md +1 -1
- package/src/prompts/system-lead-evaluator.md +1 -1
- package/src/sdk.ts +10 -1
- package/src/security/nvd-client.ts +55 -4
- package/src/security/osv-client.ts +13 -1
- package/src/tools/research-knowledge-search.ts +18 -1
- package/src/tools/research-tool-definition.ts +8 -1
- package/src/utils/citation-utils.ts +4 -2
- package/src/utils/log-utils.ts +16 -1
- package/src/utils/text-utils.ts +17 -3
- package/src/web-research/scraper-utils.ts +55 -14
|
@@ -277,10 +277,18 @@ export interface SsrfSafeFetcher {
|
|
|
277
277
|
dispatcher: unknown;
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
// Cached after first construction. `undefined` = not yet attempted
|
|
281
|
-
// undici unavailable (fall back to the global fetch
|
|
282
|
-
// validation only).
|
|
283
|
-
|
|
280
|
+
// Cached after first construction. `undefined` = not yet attempted; the promise
|
|
281
|
+
// resolves to `null` when undici is unavailable (fall back to the global fetch
|
|
282
|
+
// with request-time validation only).
|
|
283
|
+
//
|
|
284
|
+
// The PROMISE is cached, not the resolved value. Caching the value left a window
|
|
285
|
+
// between the `undefined` check and the assignment — an `await import('undici')`
|
|
286
|
+
// wide — during which every concurrent first caller built its OWN Agent. Only the
|
|
287
|
+
// last assignment survived in the cache, so the others were handed live Agents
|
|
288
|
+
// that nothing tracked: they opened keep-alive sockets and no shutdown path could
|
|
289
|
+
// ever close them, which is precisely the leak disposeSsrfSafeFetcher exists to
|
|
290
|
+
// prevent. Scrapes run concurrently by design, so the very first burst hit this.
|
|
291
|
+
let cachedSsrfFetcher: Promise<SsrfSafeFetcher | null> | undefined = undefined;
|
|
284
292
|
|
|
285
293
|
/**
|
|
286
294
|
* Build (once) an undici Agent whose connector resolves via ssrfSafeLookup, so
|
|
@@ -304,19 +312,52 @@ let cachedSsrfFetcher: SsrfSafeFetcher | null | undefined = undefined;
|
|
|
304
312
|
* Returns null if undici can't be loaded; callers then use the global fetch with
|
|
305
313
|
* no dispatcher (request-time validation still applies).
|
|
306
314
|
*/
|
|
307
|
-
export
|
|
308
|
-
|
|
315
|
+
export function getSsrfSafeFetcher(): Promise<SsrfSafeFetcher | null> {
|
|
316
|
+
// Assigned before the first await, so concurrent callers all join this one
|
|
317
|
+
// construction rather than each starting their own.
|
|
318
|
+
cachedSsrfFetcher ??= (async () => {
|
|
319
|
+
try {
|
|
320
|
+
const { Agent, fetch: undiciFetch } = await import('undici');
|
|
321
|
+
return {
|
|
322
|
+
fetch: undiciFetch as unknown as SsrfSafeFetcher['fetch'],
|
|
323
|
+
dispatcher: new Agent({ connect: { lookup: ssrfSafeLookup } }),
|
|
324
|
+
};
|
|
325
|
+
} catch (e) {
|
|
326
|
+
logger.warn(`[Scrapers] undici unavailable; connect-time SSRF pinning disabled (request-time validation still active): ${e instanceof Error ? e.message : String(e)}`);
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
})();
|
|
330
|
+
return cachedSsrfFetcher;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Close the cached SSRF Agent and drop the cache.
|
|
335
|
+
*
|
|
336
|
+
* The Agent is a process-lifetime singleton holding keep-alive sockets to every
|
|
337
|
+
* host scraped so far. The CLI hard-exits so it never notices, but an embedding
|
|
338
|
+
* host (the pi extension) keeps the process alive across SDK
|
|
339
|
+
* init/shutdown cycles, where those sockets would linger past a teardown that
|
|
340
|
+
* claims to have released everything.
|
|
341
|
+
*
|
|
342
|
+
* Clearing the cache is not optional: the SDK can be re-initialized in the same
|
|
343
|
+
* process, and a closed Agent left in the cache would fail every subsequent
|
|
344
|
+
* scrape. Resetting to `undefined` (not `null`) makes the next call rebuild it.
|
|
345
|
+
*/
|
|
346
|
+
export async function disposeSsrfSafeFetcher(): Promise<void> {
|
|
347
|
+
const pending = cachedSsrfFetcher;
|
|
348
|
+
cachedSsrfFetcher = undefined;
|
|
349
|
+
if (pending === undefined) return;
|
|
350
|
+
// Await the in-flight construction rather than skipping it: a dispose racing
|
|
351
|
+
// the first getSsrfSafeFetcher() would otherwise leave the Agent it is still
|
|
352
|
+
// building unreferenced and unclosed.
|
|
353
|
+
const cached = await pending.catch(() => null);
|
|
354
|
+
const closable = cached?.dispatcher as { close?: () => Promise<void> } | undefined;
|
|
355
|
+
if (!closable?.close) return;
|
|
309
356
|
try {
|
|
310
|
-
|
|
311
|
-
cachedSsrfFetcher = {
|
|
312
|
-
fetch: undiciFetch as unknown as SsrfSafeFetcher['fetch'],
|
|
313
|
-
dispatcher: new Agent({ connect: { lookup: ssrfSafeLookup } }),
|
|
314
|
-
};
|
|
357
|
+
await closable.close();
|
|
315
358
|
} catch (e) {
|
|
316
|
-
logger.
|
|
317
|
-
cachedSsrfFetcher = null;
|
|
359
|
+
logger.debug(`[Scrapers] Error closing SSRF dispatcher: ${e instanceof Error ? e.message : String(e)}`);
|
|
318
360
|
}
|
|
319
|
-
return cachedSsrfFetcher;
|
|
320
361
|
}
|
|
321
362
|
|
|
322
363
|
/**
|