@lincoln504/pi-research 1.7.7 → 1.7.9
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/agent-skill/pi-research/README.md +3 -3
- package/agent-skill/pi-research/SKILL.md +1 -1
- package/dist/cli-bin.mjs +10 -1
- package/dist/cli.mjs +692 -387
- package/dist/pdf-extract-worker.mjs +12 -3
- package/dist/thread-worker.mjs +41 -12
- package/docs/AGENT-SKILL.md +3 -3
- package/docs/ARCHITECTURE.md +64 -50
- package/docs/CHANGELOG.md +31 -0
- package/docs/CONFIGURATION.md +2 -2
- package/docs/SDK.md +7 -6
- package/docs/THIRD-PARTY-NOTICES.md +11 -7
- package/package.json +10 -8
- package/scripts/camoufox-layout.cjs +455 -0
- package/scripts/cleanup.cjs +72 -29
- package/scripts/setup.cjs +72 -111
- package/src/cli-bin.mjs +10 -1
- package/src/cli.ts +27 -15
- package/src/healthcheck/index.ts +1 -1
- package/src/infrastructure/browser/browser-cleanup.ts +1 -1
- package/src/infrastructure/browser/browser-error-utils.ts +5 -4
- package/src/infrastructure/browser/camoufox-layout.ts +450 -0
- package/src/infrastructure/browser/config.ts +46 -103
- package/src/infrastructure/browser/ensure-browser.ts +63 -49
- package/src/infrastructure/browser/thread-worker-browser.ts +38 -42
- package/src/infrastructure/browser/thread-worker.mjs +41 -12
- package/src/infrastructure/knowledge-store-service.ts +6 -6
- package/src/knowledge/store.ts +1 -1
- package/src/orchestration/researcher-executor.ts +47 -1
- package/src/tui/research-health.ts +1 -1
- package/src/utils/log-utils.ts +19 -4
- package/src/utils/research-export.ts +1 -1
- package/src/web-research/browser-search.ts +8 -10
- package/src/web-research/pdf-extract-worker.mjs +12 -3
- package/src/web-research/scraper-utils.ts +42 -10
- package/src/web-research/web-scraper.ts +1 -1
|
@@ -323,20 +323,18 @@ export async function performSearch(
|
|
|
323
323
|
|
|
324
324
|
// A missing native addon is a BROKEN INSTALL, and the generic advice below
|
|
325
325
|
// actively misleads: it sends the reader to check their network and their
|
|
326
|
-
// machine load when neither is involved and neither can be acted on.
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
326
|
+
// machine load when neither is involved and neither can be acted on. A
|
|
327
|
+
// dropped platform package (npm/cli#4828) or an install that skipped a native
|
|
328
|
+
// dependency's binding makes every browser worker die identically. Say that
|
|
329
|
+
// instead, and say what fixes it.
|
|
330
330
|
if (isNativeBindingError(sampleWorkerError)) {
|
|
331
331
|
metrics.increment('browser_search_total_failures_total', 1, { cause: 'native_binding' });
|
|
332
332
|
throw new Error(
|
|
333
333
|
`Search completely failed: ${reason}, all with a missing native module. ` +
|
|
334
|
-
`This is an incomplete install, not a network problem:
|
|
335
|
-
`ships prebuilt
|
|
336
|
-
`
|
|
337
|
-
`
|
|
338
|
-
`modern npm skips the needless recompile that fails on toolchain-less ` +
|
|
339
|
-
`machines, and the prebuilt binding then loads as-is.` +
|
|
334
|
+
`This is an incomplete install, not a network problem: the browser launcher's ` +
|
|
335
|
+
`native dependency (impit) ships a prebuilt binding per platform as a separate ` +
|
|
336
|
+
`package, and npm sometimes drops it (npm/cli#4828). ` +
|
|
337
|
+
`Reinstall this package (npm install, or pi install again) to restore it.` +
|
|
340
338
|
(sampleWorkerError ? ` Last worker error: ${sampleWorkerError}` : '')
|
|
341
339
|
);
|
|
342
340
|
}
|
|
@@ -213,11 +213,15 @@ var COOKIE_HEADER_PATTERN = new RegExp(
|
|
|
213
213
|
"gi"
|
|
214
214
|
);
|
|
215
215
|
var SENSITIVE_KV_PATTERN = new RegExp(
|
|
216
|
-
`${B_LEFT}(api[_-]?key|apikey|access[_-]?token|auth[_-]?token|refresh[_-]?token|secret|client[_-]?secret|password|passwd|pwd|authorization|bearer|set[_-]?cookie|cookie|session[_-]?id|session|csrf[_-]?token|xsrf[_-]?token|private[_-]?key|key)${B_RIGHT}(["']?\\s*[:=]\\s*["']?
|
|
216
|
+
`${B_LEFT}(api[_-]?key|apikey|access[_-]?token|auth[_-]?token|refresh[_-]?token|secret|client[_-]?secret|password|passwd|pwd|authorization|bearer|set[_-]?cookie|cookie|session[_-]?id|session|csrf[_-]?token|xsrf[_-]?token|private[_-]?key|token|key)${B_RIGHT}(["']?\\s*[:=]\\s*)("[^"\\r\\n]{0,1024}"|'[^'\\r\\n]{0,1024}'|["']?[^\\s"',&)]+)`,
|
|
217
|
+
"gi"
|
|
218
|
+
);
|
|
219
|
+
var AUTH_SCHEME_PATTERN = new RegExp(
|
|
220
|
+
`${B_LEFT}(authorization)${B_RIGHT}(["']?\\s*[:=]\\s*["']?)([A-Za-z][A-Za-z0-9_-]{0,31})[ \\t]+[^\\s"',&)]+`,
|
|
217
221
|
"gi"
|
|
218
222
|
);
|
|
219
223
|
var KNOWN_TOKEN_PATTERN = new RegExp(
|
|
220
|
-
`${B_LEFT}(sk-[A-Za-z0-9_-]{16,}|gh[posru]_[A-Za-z0-9]{16,}|github_pat_[A-Za-z0-9_]{20,}|AKIA[0-9A-Z]{16}|xox[baprs]-[A-Za-z0-9-]{10,})${B_RIGHT}`,
|
|
224
|
+
`${B_LEFT}(sk-[A-Za-z0-9_-]{16,}|gh[posru]_[A-Za-z0-9]{16,}|github_pat_[A-Za-z0-9_]{20,}|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35}|npm_[A-Za-z0-9]{36}|hf_[A-Za-z0-9]{30,}|xox[baprs]-[A-Za-z0-9-]{10,})${B_RIGHT}`,
|
|
221
225
|
"g"
|
|
222
226
|
);
|
|
223
227
|
var JWT_PATTERN = new RegExp(
|
|
@@ -251,7 +255,12 @@ function redactSecrets(message) {
|
|
|
251
255
|
out = out.replace(BASIC_AUTH_PATTERN, "Basic [REDACTED]");
|
|
252
256
|
out = out.replace(BEARER_AUTH_PATTERN, "Bearer [REDACTED]");
|
|
253
257
|
out = out.replace(COOKIE_HEADER_PATTERN, (_m, key, sep) => `${key}${sep}[REDACTED]`);
|
|
254
|
-
out = out.replace(
|
|
258
|
+
out = out.replace(AUTH_SCHEME_PATTERN, (_m, key, sep, scheme) => `${key}${sep}${scheme} [REDACTED]`);
|
|
259
|
+
out = out.replace(SENSITIVE_KV_PATTERN, (_m, key, sep, val) => {
|
|
260
|
+
const q = val[0] === '"' || val[0] === "'" ? val[0] : "";
|
|
261
|
+
const closed = q !== "" && val.length > 1 && val.endsWith(q);
|
|
262
|
+
return `${key}${sep}${q}[REDACTED]${closed ? q : ""}`;
|
|
263
|
+
});
|
|
255
264
|
out = out.replace(KNOWN_TOKEN_PATTERN, "[REDACTED]");
|
|
256
265
|
out = out.replace(PROVIDER_TOKEN_PATTERN, "[REDACTED]");
|
|
257
266
|
out = out.replace(LONG_HEX_SECRET_PATTERN, "[REDACTED]");
|
|
@@ -382,18 +382,27 @@ export function formatErrorWithCause(err: unknown): string {
|
|
|
382
382
|
function isPrivateIp(ip: string): boolean {
|
|
383
383
|
const parts = ip.split('.').map(Number);
|
|
384
384
|
if (parts.length !== 4 || parts.some(p => isNaN(p))) return false;
|
|
385
|
-
const [a, b] = parts;
|
|
385
|
+
const [a, b, c] = parts;
|
|
386
386
|
if (a === undefined || b === undefined) return false;
|
|
387
387
|
// 0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10 (RFC 6598 CGNAT / shared address space,
|
|
388
388
|
// routinely routed to internal infra by cloud/hosting providers), 127.0.0.0/8,
|
|
389
389
|
// 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16, 224.0.0.0/4 (multicast),
|
|
390
|
-
// 240.0.0.0/4 (reserved)
|
|
390
|
+
// 240.0.0.0/4 (reserved).
|
|
391
|
+
// Also blocked: 198.18.0.0/15 (RFC 2544 benchmarking, sometimes routed by
|
|
392
|
+
// broken tooling), 192.0.0.0/24 (RFC 6890 IETF protocol assignments) and the
|
|
393
|
+
// three documentation ranges 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24
|
|
394
|
+
// (TEST-NET-1/2/3, RFC 5737). None is a legitimate scrape target, and a
|
|
395
|
+
// mis-issued NAT or a hostile page's SSRF probe can name them.
|
|
391
396
|
return (
|
|
392
397
|
a === 0 || a === 10 || a === 127 ||
|
|
393
398
|
(a === 100 && b >= 64 && b <= 127) ||
|
|
394
399
|
(a === 169 && b === 254) ||
|
|
395
400
|
(a === 172 && b >= 16 && b <= 31) ||
|
|
396
401
|
(a === 192 && b === 168) ||
|
|
402
|
+
(a === 198 && (b === 18 || b === 19)) ||
|
|
403
|
+
(a === 192 && b === 0 && (c === 0 || c === 2)) ||
|
|
404
|
+
(a === 198 && b === 51 && c === 100) ||
|
|
405
|
+
(a === 203 && b === 0 && c === 113) ||
|
|
397
406
|
a >= 224
|
|
398
407
|
);
|
|
399
408
|
}
|
|
@@ -551,20 +560,43 @@ export function trimFrontMatter(markdown: string): string {
|
|
|
551
560
|
* leaves extra blank lines, so they are collapsed again.
|
|
552
561
|
*/
|
|
553
562
|
export function cleanConvertedMarkdown(markdown: string): string {
|
|
554
|
-
return stripEmptyFragmentLinks(stripImageLinks(trimFrontMatter(markdown)))
|
|
555
|
-
|
|
556
|
-
|
|
563
|
+
return collapseBlankLines(stripEmptyFragmentLinks(stripImageLinks(trimFrontMatter(markdown)))).trim();
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/** Opening/closing fence of a fenced code block (CommonMark: up to 3 spaces, 3+ backticks or tildes). */
|
|
567
|
+
const CODE_FENCE = /^ {0,3}(`{3,}|~{3,})/;
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Collapse runs of blank lines to one, EXCEPT inside fenced code blocks, where
|
|
571
|
+
* blank lines are content (PEP 8 spacing, Makefiles, ASCII art). An unclosed
|
|
572
|
+
* fence runs to the end of the document, as in CommonMark.
|
|
573
|
+
*/
|
|
574
|
+
export function collapseBlankLines(markdown: string): string {
|
|
575
|
+
if (!markdown.includes('\n\n\n')) return markdown;
|
|
576
|
+
const out: string[] = [];
|
|
577
|
+
let fence: string | null = null;
|
|
578
|
+
for (const line of markdown.split('\n')) {
|
|
579
|
+
const m = CODE_FENCE.exec(line);
|
|
580
|
+
if (fence === null) {
|
|
581
|
+
if (m) fence = m[1]!;
|
|
582
|
+
} else if (m && m[1]![0] === fence[0] && m[1]!.length >= fence.length && line.slice(m[0].length).trim() === '') {
|
|
583
|
+
fence = null;
|
|
584
|
+
}
|
|
585
|
+
if (fence === null && line === '' && out.length > 0 && out[out.length - 1] === '') continue;
|
|
586
|
+
out.push(line);
|
|
587
|
+
}
|
|
588
|
+
return out.join('\n');
|
|
557
589
|
}
|
|
558
590
|
|
|
559
591
|
/**
|
|
560
592
|
* Strip image links from markdown
|
|
561
593
|
*/
|
|
562
594
|
export function stripImageLinks(markdown: string): string {
|
|
563
|
-
return
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
595
|
+
return collapseBlankLines(
|
|
596
|
+
markdown
|
|
597
|
+
.replace(MARKDOWN_IMAGE_PATTERN, '')
|
|
598
|
+
.replace(IMAGE_LINK_PATTERN, '$1'),
|
|
599
|
+
).trim();
|
|
568
600
|
}
|
|
569
601
|
|
|
570
602
|
/**
|
|
@@ -41,7 +41,7 @@ let playwrightAvailable: boolean = false;
|
|
|
41
41
|
let markdownConverterPromise: Promise<(html: string) => Promise<string>> | null = null;
|
|
42
42
|
|
|
43
43
|
export function initScraperDependencies(): void {
|
|
44
|
-
playwrightAvailable = checkModule('playwright-core') && checkModule('camoufox
|
|
44
|
+
playwrightAvailable = checkModule('playwright-core') && checkModule('@camoufox/camoufox');
|
|
45
45
|
}
|
|
46
46
|
initScraperDependencies();
|
|
47
47
|
|