@lincoln504/pi-research 1.1.2 → 1.3.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/README.md +3 -3
- package/dist/cli.mjs +2599 -1268
- package/dist/thread-worker.mjs +224 -147
- package/docs/AGENT-SKILL.md +11 -4
- package/docs/ARCHITECTURE.md +20 -10
- package/docs/CHANGELOG.md +195 -17
- package/docs/CONFIGURATION.md +9 -7
- package/docs/KNOWLEDGE-STORE.md +4 -3
- package/docs/PI-EXTENSION.md +1 -1
- package/docs/SDK.md +55 -0
- package/docs/THIRD-PARTY-NOTICES.md +1 -1
- package/package.json +5 -4
- package/scripts/cleanup.cjs +114 -12
- package/scripts/prepare.cjs +15 -5
- package/scripts/setup.cjs +24 -4
- package/skills/pi-research/README.md +9 -2
- package/skills/pi-research/SKILL.md +6 -3
- package/skills/pi-research/references/configuration.md +6 -2
- package/skills/pi-research/scripts/run.mjs +24 -2
- package/src/cleanup/research-cleanup.ts +12 -4
- package/src/cli.ts +372 -86
- package/src/config.ts +134 -17
- package/src/constants.ts +31 -0
- package/src/core/interfaces/health-check-interfaces.ts +3 -39
- package/src/core/interfaces/knowledge-interfaces.ts +6 -1
- package/src/core/interfaces/planning-interfaces.ts +8 -0
- package/src/core/interfaces/service-names.ts +0 -1
- package/src/core/interfaces/state-manager-interfaces.ts +11 -3
- package/src/core/llm/agentic-repair.ts +14 -0
- package/src/core/pi-version.ts +136 -0
- package/src/core/planning-service.ts +43 -1
- package/src/core/service-initialization.ts +6 -14
- package/src/core/service-interfaces.ts +1 -1
- package/src/core/service-registry.ts +117 -8
- package/src/healthcheck/index.ts +7 -2
- package/src/index.ts +11 -18
- package/src/infrastructure/browser/browser-client.ts +80 -13
- package/src/infrastructure/browser/browser-error-utils.ts +6 -1
- package/src/infrastructure/browser/browser-server.ts +41 -12
- package/src/infrastructure/browser/browser-task-scheduler.ts +159 -35
- package/src/infrastructure/browser/cleanup-utils.ts +95 -9
- package/src/infrastructure/browser/client-agent.ts +6 -5
- package/src/infrastructure/browser/config.ts +14 -3
- package/src/infrastructure/browser/priority-task-queue.ts +10 -3
- package/src/infrastructure/browser/scheduler-factory.ts +44 -4
- package/src/infrastructure/browser/task-execution-service.ts +92 -28
- package/src/infrastructure/browser/thread-worker-lifecycle.ts +50 -13
- package/src/infrastructure/browser/thread-worker-messaging.ts +54 -2
- package/src/infrastructure/browser/thread-worker-types.ts +3 -1
- package/src/infrastructure/browser/thread-worker.mjs +224 -147
- package/src/infrastructure/browser/worker-pool-manager.ts +18 -5
- package/src/infrastructure/embedding/embedding-client.ts +22 -8
- package/src/infrastructure/embedding/embedding-factory.ts +61 -15
- package/src/infrastructure/embedding/embedding-server.ts +105 -11
- package/src/infrastructure/file-lock-service.ts +295 -28
- package/src/infrastructure/knowledge-store-service.ts +48 -3
- package/src/infrastructure/process-lifecycle-service.ts +134 -34
- package/src/infrastructure/research-run-semaphore.ts +37 -3
- package/src/infrastructure/state/state-backup-manager.ts +5 -7
- package/src/infrastructure/state/state-browser-api.ts +5 -2
- package/src/infrastructure/state/state-browser-manager.ts +15 -1
- package/src/infrastructure/state/state-manager-service.ts +5 -5
- package/src/infrastructure/state/state-manager.ts +34 -23
- package/src/infrastructure/types/state-types.ts +9 -0
- package/src/knowledge/chunker.ts +88 -1
- package/src/knowledge/embedder-init.ts +1 -1
- package/src/knowledge/embedder.ts +188 -46
- package/src/knowledge/index.ts +1 -1
- package/src/knowledge/store.ts +101 -21
- package/src/knowledge/webgpu-viability.ts +9 -4
- package/src/knowledge/writer-queue.ts +88 -46
- package/src/logger.ts +5 -1
- package/src/observers/research-observer-impl.ts +31 -0
- package/src/orchestration/abort-utils.ts +60 -0
- package/src/orchestration/deep-research-orchestrator.ts +25 -4
- package/src/orchestration/headless-observer.ts +8 -0
- package/src/orchestration/quick-research-orchestrator.ts +2 -2
- package/src/orchestration/research-orchestration-service.ts +65 -15
- package/src/orchestration/research-session-service.ts +37 -11
- package/src/orchestration/research-synthesis-service.ts +33 -11
- package/src/orchestration/researcher-executor.ts +100 -20
- package/src/orchestration/researcher.ts +39 -20
- package/src/orchestration/session-state.ts +11 -0
- package/src/sdk.ts +125 -19
- package/src/security/cisa-kev.ts +10 -3
- package/src/security/github-advisories.ts +18 -7
- package/src/security/index.ts +51 -30
- package/src/security/nvd-client.ts +24 -8
- package/src/security/osv-client.ts +14 -4
- package/src/skill-install/skill-installer.ts +153 -11
- package/src/stackexchange/index.ts +20 -5
- package/src/stackexchange/output/table.ts +7 -2
- package/src/stackexchange/rest-client.ts +41 -3
- package/src/tools/research-knowledge-search.ts +15 -0
- package/src/tools/research-tool-definition.ts +8 -2
- package/src/tools/scrape.ts +48 -25
- package/src/tools/search.ts +16 -2
- package/src/tools/security.ts +68 -7
- package/src/tui/research-health.ts +18 -2
- package/src/utils/atomic-replace.ts +101 -0
- package/src/utils/circuit-breaker.ts +9 -2
- package/src/utils/citation-utils.ts +3 -1
- package/src/utils/disk-space-checker.ts +11 -6
- package/src/utils/http-body.ts +176 -0
- package/src/utils/json-utils.ts +116 -76
- package/src/utils/llm-usage.ts +17 -1
- package/src/utils/log-rotation.ts +42 -10
- package/src/utils/log-utils.ts +40 -0
- package/src/utils/metrics-summary.ts +27 -9
- package/src/utils/stdio-capture.ts +63 -22
- package/src/utils/text-utils.ts +8 -1
- package/src/utils/tool-usage-tracker.ts +9 -2
- package/src/web-research/browser-search.ts +70 -5
- package/src/web-research/scraper-utils.ts +13 -0
- package/src/web-research/search.ts +25 -6
- package/src/web-research/types.ts +19 -4
- package/src/web-research/web-scraper.ts +72 -54
- package/src/youtube/transcript-client.ts +5 -1
- package/src/core/health-check-service.ts +0 -265
package/dist/thread-worker.mjs
CHANGED
|
@@ -51,13 +51,17 @@ function redactSecrets(message) {
|
|
|
51
51
|
out = out.replace(JWT_PATTERN, "[REDACTED]");
|
|
52
52
|
out = out.replace(BASIC_AUTH_PATTERN, "Basic [REDACTED]");
|
|
53
53
|
out = out.replace(BEARER_AUTH_PATTERN, "Bearer [REDACTED]");
|
|
54
|
+
out = out.replace(COOKIE_HEADER_PATTERN, (_m, key, sep) => `${key}${sep}[REDACTED]`);
|
|
54
55
|
out = out.replace(SENSITIVE_KV_PATTERN, (_m, key, sep) => `${key}${sep}[REDACTED]`);
|
|
55
56
|
out = out.replace(KNOWN_TOKEN_PATTERN, "[REDACTED]");
|
|
56
57
|
out = out.replace(PROVIDER_TOKEN_PATTERN, "[REDACTED]");
|
|
57
58
|
out = out.replace(LONG_HEX_SECRET_PATTERN, "[REDACTED]");
|
|
58
59
|
return out;
|
|
59
60
|
}
|
|
60
|
-
|
|
61
|
+
function stripTerminalEscapes(message) {
|
|
62
|
+
return message.replace(TERMINAL_ESCAPE_PATTERN, "").replace(CONTROL_CHARS_PATTERN, " ");
|
|
63
|
+
}
|
|
64
|
+
var logContextStorage, MAX_LOG_MESSAGE_LENGTH, ANSI_PATTERN, URL_CREDENTIALS_PATTERN, COOKIE_HEADER_PATTERN, SENSITIVE_KV_PATTERN, KNOWN_TOKEN_PATTERN, JWT_PATTERN, BASIC_AUTH_PATTERN, BEARER_AUTH_PATTERN, PROVIDER_TOKEN_PATTERN, LONG_HEX_SECRET_PATTERN, CONTROL_CHARS_PATTERN, TERMINAL_ESCAPE_PATTERN;
|
|
61
65
|
var init_log_utils = __esm({
|
|
62
66
|
"src/utils/log-utils.ts"() {
|
|
63
67
|
"use strict";
|
|
@@ -65,6 +69,7 @@ var init_log_utils = __esm({
|
|
|
65
69
|
MAX_LOG_MESSAGE_LENGTH = 1e4;
|
|
66
70
|
ANSI_PATTERN = /\x1b\[[0-9;]*[A-Za-z]/g;
|
|
67
71
|
URL_CREDENTIALS_PATTERN = /([a-z][a-z0-9+.-]*:\/\/)[^/\s:@]+:[^/\s:@]+@/gi;
|
|
72
|
+
COOKIE_HEADER_PATTERN = /\b(set[_-]?cookie|cookie)\b(["']?\s*[:=]\s*["']?)([^\r\n]+)/gi;
|
|
68
73
|
SENSITIVE_KV_PATTERN = /\b(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)\b(["']?\s*[:=]\s*["']?)([^\s"',&)]+)/gi;
|
|
69
74
|
KNOWN_TOKEN_PATTERN = /\b(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/g;
|
|
70
75
|
JWT_PATTERN = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g;
|
|
@@ -72,6 +77,106 @@ var init_log_utils = __esm({
|
|
|
72
77
|
BEARER_AUTH_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/-]+=*/gi;
|
|
73
78
|
PROVIDER_TOKEN_PATTERN = /\b(AIza[0-9A-Za-z_-]{35}|ya29\.[0-9A-Za-z_-]{20,}|[sp]k_(?:live|test)_[0-9A-Za-z]{16,})\b/g;
|
|
74
79
|
LONG_HEX_SECRET_PATTERN = /\b[0-9a-fA-F]{32,}\b/g;
|
|
80
|
+
CONTROL_CHARS_PATTERN = /[\x00-\x1f\x7f]/g;
|
|
81
|
+
TERMINAL_ESCAPE_PATTERN = new RegExp(
|
|
82
|
+
"\\x1b\\[[0-?]*[ -/]*[@-~]?|\\x1b\\][^\\x07\\x1b]*(?:\\x07|\\x1b\\\\)?|\\x1b[PX^_][^\\x1b]*(?:\\x1b\\\\)?|\\x1b.?",
|
|
83
|
+
"g"
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// src/utils/user-agent.ts
|
|
89
|
+
var USER_AGENTS;
|
|
90
|
+
var init_user_agent = __esm({
|
|
91
|
+
"src/utils/user-agent.ts"() {
|
|
92
|
+
"use strict";
|
|
93
|
+
USER_AGENTS = [
|
|
94
|
+
// Chrome on Windows (updated 2025)
|
|
95
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
|
|
96
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
|
|
97
|
+
// Chrome on macOS
|
|
98
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
|
|
99
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
|
|
100
|
+
// Chrome on Linux
|
|
101
|
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
|
|
102
|
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
|
|
103
|
+
// Firefox on Windows (updated 2025)
|
|
104
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0",
|
|
105
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:138.0) Gecko/20100101 Firefox/138.0",
|
|
106
|
+
// Firefox on macOS
|
|
107
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0",
|
|
108
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0"
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// src/web-research/scraper-types.ts
|
|
114
|
+
var MAX_HTML_SIZE, MAX_PDF_SIZE, INTERNAL_NETWORK_PATTERNS, FILTERED_TAGS, BOT_PATTERNS, IMAGE_LINK_PATTERN, MARKDOWN_IMAGE_PATTERN;
|
|
115
|
+
var init_scraper_types = __esm({
|
|
116
|
+
"src/web-research/scraper-types.ts"() {
|
|
117
|
+
"use strict";
|
|
118
|
+
init_user_agent();
|
|
119
|
+
MAX_HTML_SIZE = 25 * 1024 * 1024;
|
|
120
|
+
MAX_PDF_SIZE = 100 * 1024 * 1024;
|
|
121
|
+
INTERNAL_NETWORK_PATTERNS = [
|
|
122
|
+
/^127\./,
|
|
123
|
+
// IPv4 loopback
|
|
124
|
+
/^0\./,
|
|
125
|
+
// IPv4 "this" network
|
|
126
|
+
/^::1$/,
|
|
127
|
+
// IPv6 loopback
|
|
128
|
+
/^fe80::/i,
|
|
129
|
+
// IPv6 link-local
|
|
130
|
+
/^f[cd][0-9a-f]{2}:/i,
|
|
131
|
+
// IPv6 unique local — full fc00::/7 block (fc00::–fdff::)
|
|
132
|
+
/^169\.254\./,
|
|
133
|
+
// IPv4 link-local (and metadata)
|
|
134
|
+
/^10\./,
|
|
135
|
+
// RFC 1918 Class A private
|
|
136
|
+
/^192\.168\./,
|
|
137
|
+
// RFC 1918 Class C private
|
|
138
|
+
/^172\.(1[6-9]|2[0-9]|3[01])\./,
|
|
139
|
+
// RFC 1918 Class B private
|
|
140
|
+
/^::ffff:(127\.|0\.|169\.254\.|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.)/i
|
|
141
|
+
// IPv4-mapped IPv6
|
|
142
|
+
];
|
|
143
|
+
FILTERED_TAGS = [
|
|
144
|
+
"nav",
|
|
145
|
+
"header",
|
|
146
|
+
"footer",
|
|
147
|
+
"aside",
|
|
148
|
+
"script",
|
|
149
|
+
"style",
|
|
150
|
+
"noscript",
|
|
151
|
+
"form",
|
|
152
|
+
"input",
|
|
153
|
+
"select",
|
|
154
|
+
"textarea",
|
|
155
|
+
"button",
|
|
156
|
+
"object",
|
|
157
|
+
"embed",
|
|
158
|
+
"svg",
|
|
159
|
+
"symbol",
|
|
160
|
+
"use",
|
|
161
|
+
"defs",
|
|
162
|
+
"path",
|
|
163
|
+
"circle",
|
|
164
|
+
"rect",
|
|
165
|
+
"line",
|
|
166
|
+
"polygon",
|
|
167
|
+
"img",
|
|
168
|
+
"iframe"
|
|
169
|
+
];
|
|
170
|
+
BOT_PATTERNS = [
|
|
171
|
+
["_cf_chl_", "Cloudflare challenge"],
|
|
172
|
+
["cf_chl_opt", "Cloudflare challenge opt"],
|
|
173
|
+
["cdn-cgi/challenge-platform", "Cloudflare challenge platform"],
|
|
174
|
+
["ddos-guard", "DDoS-Guard challenge"],
|
|
175
|
+
["Just a moment...", "Cloudflare interstitial"],
|
|
176
|
+
["Checking your browser before accessing", "Cloudflare interstitial"]
|
|
177
|
+
];
|
|
178
|
+
IMAGE_LINK_PATTERN = /\[([^\]]*)\]\((data:image\/[^)]+|[^)\s]+\.(?:svg|png|jpe?g|gif|webp|bmp|ico)(?:\?[^)]*)?)\)/gi;
|
|
179
|
+
MARKDOWN_IMAGE_PATTERN = /!\[[^\]]*]\((?:data:image\/[^)]+|[^)\s]+)\)/gi;
|
|
75
180
|
}
|
|
76
181
|
});
|
|
77
182
|
|
|
@@ -265,6 +370,14 @@ var init_log_rotation = __esm({
|
|
|
265
370
|
try {
|
|
266
371
|
if (fs2.existsSync(logFile)) {
|
|
267
372
|
fs2.unlinkSync(logFile);
|
|
373
|
+
try {
|
|
374
|
+
fs2.closeSync(fs2.openSync(logFile, "a", 384));
|
|
375
|
+
} catch {
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
fs2.chmodSync(logFile, 384);
|
|
379
|
+
} catch {
|
|
380
|
+
}
|
|
268
381
|
}
|
|
269
382
|
const files = fs2.readdirSync(logDir);
|
|
270
383
|
const baseName = path2.basename(logFile);
|
|
@@ -297,12 +410,16 @@ var init_log_rotation = __esm({
|
|
|
297
410
|
try {
|
|
298
411
|
fs2.renameSync(logFile, archivePath);
|
|
299
412
|
} catch (renameErr) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
413
|
+
this.logger.log("[Logger] Log rotation skipped (rename failed; file likely in use):", renameErr);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
fs2.closeSync(fs2.openSync(logFile, "a", 384));
|
|
418
|
+
} catch {
|
|
419
|
+
}
|
|
420
|
+
try {
|
|
421
|
+
fs2.chmodSync(logFile, 384);
|
|
422
|
+
} catch {
|
|
306
423
|
}
|
|
307
424
|
try {
|
|
308
425
|
const files = fs2.readdirSync(logDir);
|
|
@@ -372,10 +489,13 @@ var init_disk_space_checker = __esm({
|
|
|
372
489
|
const availableBytes = stats.bavail * stats.bsize;
|
|
373
490
|
if (availableBytes < this.MIN_DISK_SPACE_BYTES) {
|
|
374
491
|
this.hasDiskSpace = false;
|
|
375
|
-
|
|
376
|
-
|
|
492
|
+
try {
|
|
493
|
+
process.stderr.write(
|
|
494
|
+
`[pi-research] Insufficient disk space for logging: ${Math.round(availableBytes / 1024 / 1024)}MB available, minimum ${this.MIN_DISK_SPACE_BYTES / 1024 / 1024}MB required
|
|
377
495
|
`
|
|
378
|
-
|
|
496
|
+
);
|
|
497
|
+
} catch {
|
|
498
|
+
}
|
|
379
499
|
} else {
|
|
380
500
|
this.hasDiskSpace = true;
|
|
381
501
|
}
|
|
@@ -482,7 +602,7 @@ async function captureStdio(logFile, hasSufficientDiskSpace, task, sessionId) {
|
|
|
482
602
|
if (shouldRedirectStderr(stat)) {
|
|
483
603
|
savedFd2 = fs4.openSync("/dev/fd/2", "a");
|
|
484
604
|
fs4.closeSync(2);
|
|
485
|
-
const newFd = fs4.openSync(logFile, "a");
|
|
605
|
+
const newFd = fs4.openSync(logFile, "a", 384);
|
|
486
606
|
if (newFd === 2) {
|
|
487
607
|
fd2Redirected = true;
|
|
488
608
|
} else {
|
|
@@ -614,42 +734,52 @@ async function captureStdio(logFile, hasSufficientDiskSpace, task, sessionId) {
|
|
|
614
734
|
}
|
|
615
735
|
} catch (_e) {
|
|
616
736
|
}
|
|
737
|
+
const frame = {
|
|
738
|
+
retired: false,
|
|
739
|
+
restore: () => {
|
|
740
|
+
process.stderr.write = originalStderrWrite;
|
|
741
|
+
process.stdout.write = originalStdoutWrite;
|
|
742
|
+
try {
|
|
743
|
+
fs4.writeSync = originalFsWriteSync;
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
console.log = originalConsole.log;
|
|
747
|
+
console.info = originalConsole.info;
|
|
748
|
+
console.warn = originalConsole.warn;
|
|
749
|
+
console.error = originalConsole.error;
|
|
750
|
+
console.debug = originalConsole.debug;
|
|
751
|
+
if (fd2Redirected && savedFd2 >= 0) {
|
|
752
|
+
try {
|
|
753
|
+
fs4.closeSync(2);
|
|
754
|
+
const r = fs4.openSync(`/dev/fd/${savedFd2}`, "a");
|
|
755
|
+
if (r !== 2 && r >= 0) {
|
|
756
|
+
}
|
|
757
|
+
try {
|
|
758
|
+
fs4.closeSync(savedFd2);
|
|
759
|
+
} catch {
|
|
760
|
+
}
|
|
761
|
+
} catch {
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
captureStack.push(frame);
|
|
617
767
|
try {
|
|
618
768
|
return await task();
|
|
619
769
|
} finally {
|
|
620
|
-
process.stderr.write = originalStderrWrite;
|
|
621
|
-
process.stdout.write = originalStdoutWrite;
|
|
622
|
-
try {
|
|
623
|
-
fs4.writeSync = originalFsWriteSync;
|
|
624
|
-
} catch {
|
|
625
|
-
}
|
|
626
|
-
console.log = originalConsole.log;
|
|
627
|
-
console.info = originalConsole.info;
|
|
628
|
-
console.warn = originalConsole.warn;
|
|
629
|
-
console.error = originalConsole.error;
|
|
630
|
-
console.debug = originalConsole.debug;
|
|
631
770
|
if (sessionId) {
|
|
632
771
|
setSessionCapturing(sessionId, false);
|
|
633
772
|
} else {
|
|
634
773
|
unkeyedCaptureDepth = Math.max(0, unkeyedCaptureDepth - 1);
|
|
635
774
|
recomputeGlobalCapturing();
|
|
636
775
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
const r = fs4.openSync(`/dev/fd/${savedFd2}`, "a");
|
|
641
|
-
if (r !== 2 && r >= 0) {
|
|
642
|
-
}
|
|
643
|
-
try {
|
|
644
|
-
fs4.closeSync(savedFd2);
|
|
645
|
-
} catch {
|
|
646
|
-
}
|
|
647
|
-
} catch {
|
|
648
|
-
}
|
|
776
|
+
frame.retired = true;
|
|
777
|
+
while (captureStack.length > 0 && captureStack[captureStack.length - 1].retired) {
|
|
778
|
+
captureStack.pop().restore();
|
|
649
779
|
}
|
|
650
780
|
}
|
|
651
781
|
}
|
|
652
|
-
var fs4, sessionCaptureStates, unkeyedCaptureDepth, isAnyLoggerCapturingOutput, NATIVE_LOG_PATTERNS, COMPLEX_TUI_PATTERNS, BOX_DRAWING_CHARS;
|
|
782
|
+
var fs4, sessionCaptureStates, unkeyedCaptureDepth, isAnyLoggerCapturingOutput, captureStack, NATIVE_LOG_PATTERNS, COMPLEX_TUI_PATTERNS, BOX_DRAWING_CHARS;
|
|
653
783
|
var init_stdio_capture = __esm({
|
|
654
784
|
"src/utils/stdio-capture.ts"() {
|
|
655
785
|
"use strict";
|
|
@@ -658,6 +788,7 @@ var init_stdio_capture = __esm({
|
|
|
658
788
|
sessionCaptureStates = /* @__PURE__ */ new Map();
|
|
659
789
|
unkeyedCaptureDepth = 0;
|
|
660
790
|
isAnyLoggerCapturingOutput = false;
|
|
791
|
+
captureStack = [];
|
|
661
792
|
NATIVE_LOG_PATTERNS = [
|
|
662
793
|
"Warning:",
|
|
663
794
|
"Error:",
|
|
@@ -699,7 +830,7 @@ var init_stdio_capture = __esm({
|
|
|
699
830
|
});
|
|
700
831
|
|
|
701
832
|
// src/logger.ts
|
|
702
|
-
import { appendFileSync, existsSync as existsSync2, mkdirSync, openSync, closeSync } from "node:fs";
|
|
833
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync, openSync as openSync2, closeSync as closeSync2 } from "node:fs";
|
|
703
834
|
import { appendFile as appendFile2 } from "node:fs/promises";
|
|
704
835
|
import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
|
|
705
836
|
import * as path3 from "node:path";
|
|
@@ -859,7 +990,7 @@ var init_logger = __esm({
|
|
|
859
990
|
mkdirSync(this.logDir, { recursive: true, mode: 448 });
|
|
860
991
|
}
|
|
861
992
|
try {
|
|
862
|
-
|
|
993
|
+
closeSync2(openSync2(this.logFile, "wx", 384));
|
|
863
994
|
} catch {
|
|
864
995
|
}
|
|
865
996
|
} catch {
|
|
@@ -894,7 +1025,7 @@ var init_logger = __esm({
|
|
|
894
1025
|
if (this.consoleLog) {
|
|
895
1026
|
const color = level === "ERROR" /* ERROR */ ? "\x1B[31m" : level === "WARN" /* WARN */ ? "\x1B[33m" : level === "DEBUG" /* DEBUG */ ? "\x1B[90m" : "\x1B[36m";
|
|
896
1027
|
const reset = "\x1B[0m";
|
|
897
|
-
const msg = message.replace(/[\r\n]/g, "");
|
|
1028
|
+
const msg = stripTerminalEscapes(message.replace(/[\r\n]/g, ""));
|
|
898
1029
|
const rawSid = this.sessionId ?? "";
|
|
899
1030
|
const sid = rawSid.replace(/[\r\n]/g, "");
|
|
900
1031
|
const prefix = sid ? `[${sid}] ` : "";
|
|
@@ -1133,101 +1264,6 @@ var init_metrics = __esm({
|
|
|
1133
1264
|
}
|
|
1134
1265
|
});
|
|
1135
1266
|
|
|
1136
|
-
// src/utils/user-agent.ts
|
|
1137
|
-
var USER_AGENTS;
|
|
1138
|
-
var init_user_agent = __esm({
|
|
1139
|
-
"src/utils/user-agent.ts"() {
|
|
1140
|
-
"use strict";
|
|
1141
|
-
USER_AGENTS = [
|
|
1142
|
-
// Chrome on Windows (updated 2025)
|
|
1143
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
|
|
1144
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
|
|
1145
|
-
// Chrome on macOS
|
|
1146
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
|
|
1147
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
|
|
1148
|
-
// Chrome on Linux
|
|
1149
|
-
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
|
|
1150
|
-
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
|
|
1151
|
-
// Firefox on Windows (updated 2025)
|
|
1152
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0",
|
|
1153
|
-
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:138.0) Gecko/20100101 Firefox/138.0",
|
|
1154
|
-
// Firefox on macOS
|
|
1155
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:139.0) Gecko/20100101 Firefox/139.0",
|
|
1156
|
-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:138.0) Gecko/20100101 Firefox/138.0"
|
|
1157
|
-
];
|
|
1158
|
-
}
|
|
1159
|
-
});
|
|
1160
|
-
|
|
1161
|
-
// src/web-research/scraper-types.ts
|
|
1162
|
-
var MAX_HTML_SIZE, MAX_PDF_SIZE, INTERNAL_NETWORK_PATTERNS, FILTERED_TAGS, BOT_PATTERNS, IMAGE_LINK_PATTERN, MARKDOWN_IMAGE_PATTERN;
|
|
1163
|
-
var init_scraper_types = __esm({
|
|
1164
|
-
"src/web-research/scraper-types.ts"() {
|
|
1165
|
-
"use strict";
|
|
1166
|
-
init_user_agent();
|
|
1167
|
-
MAX_HTML_SIZE = 25 * 1024 * 1024;
|
|
1168
|
-
MAX_PDF_SIZE = 100 * 1024 * 1024;
|
|
1169
|
-
INTERNAL_NETWORK_PATTERNS = [
|
|
1170
|
-
/^127\./,
|
|
1171
|
-
// IPv4 loopback
|
|
1172
|
-
/^0\./,
|
|
1173
|
-
// IPv4 "this" network
|
|
1174
|
-
/^::1$/,
|
|
1175
|
-
// IPv6 loopback
|
|
1176
|
-
/^fe80::/i,
|
|
1177
|
-
// IPv6 link-local
|
|
1178
|
-
/^f[cd][0-9a-f]{2}:/i,
|
|
1179
|
-
// IPv6 unique local — full fc00::/7 block (fc00::–fdff::)
|
|
1180
|
-
/^169\.254\./,
|
|
1181
|
-
// IPv4 link-local (and metadata)
|
|
1182
|
-
/^10\./,
|
|
1183
|
-
// RFC 1918 Class A private
|
|
1184
|
-
/^192\.168\./,
|
|
1185
|
-
// RFC 1918 Class C private
|
|
1186
|
-
/^172\.(1[6-9]|2[0-9]|3[01])\./,
|
|
1187
|
-
// RFC 1918 Class B private
|
|
1188
|
-
/^::ffff:(127\.|0\.|169\.254\.|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.)/i
|
|
1189
|
-
// IPv4-mapped IPv6
|
|
1190
|
-
];
|
|
1191
|
-
FILTERED_TAGS = [
|
|
1192
|
-
"nav",
|
|
1193
|
-
"header",
|
|
1194
|
-
"footer",
|
|
1195
|
-
"aside",
|
|
1196
|
-
"script",
|
|
1197
|
-
"style",
|
|
1198
|
-
"noscript",
|
|
1199
|
-
"form",
|
|
1200
|
-
"input",
|
|
1201
|
-
"select",
|
|
1202
|
-
"textarea",
|
|
1203
|
-
"button",
|
|
1204
|
-
"object",
|
|
1205
|
-
"embed",
|
|
1206
|
-
"svg",
|
|
1207
|
-
"symbol",
|
|
1208
|
-
"use",
|
|
1209
|
-
"defs",
|
|
1210
|
-
"path",
|
|
1211
|
-
"circle",
|
|
1212
|
-
"rect",
|
|
1213
|
-
"line",
|
|
1214
|
-
"polygon",
|
|
1215
|
-
"img",
|
|
1216
|
-
"iframe"
|
|
1217
|
-
];
|
|
1218
|
-
BOT_PATTERNS = [
|
|
1219
|
-
["_cf_chl_", "Cloudflare challenge"],
|
|
1220
|
-
["cf_chl_opt", "Cloudflare challenge opt"],
|
|
1221
|
-
["cdn-cgi/challenge-platform", "Cloudflare challenge platform"],
|
|
1222
|
-
["ddos-guard", "DDoS-Guard challenge"],
|
|
1223
|
-
["Just a moment...", "Cloudflare interstitial"],
|
|
1224
|
-
["Checking your browser before accessing", "Cloudflare interstitial"]
|
|
1225
|
-
];
|
|
1226
|
-
IMAGE_LINK_PATTERN = /\[([^\]]*)\]\((data:image\/[^)]+|[^)\s]+\.(?:svg|png|jpe?g|gif|webp|bmp|ico)(?:\?[^)]*)?)\)/gi;
|
|
1227
|
-
MARKDOWN_IMAGE_PATTERN = /!\[[^\]]*]\((?:data:image\/[^)]+|[^)\s]+)\)/gi;
|
|
1228
|
-
}
|
|
1229
|
-
});
|
|
1230
|
-
|
|
1231
1267
|
// src/utils/circuit-breaker.ts
|
|
1232
1268
|
var CircuitBreaker;
|
|
1233
1269
|
var init_circuit_breaker = __esm({
|
|
@@ -1277,7 +1313,7 @@ var init_circuit_breaker = __esm({
|
|
|
1277
1313
|
const result = await action();
|
|
1278
1314
|
const duration = Date.now() - startTime;
|
|
1279
1315
|
metrics.observe("circuit_breaker_call_duration_ms", duration, { breaker: this.options.name, status: "success" });
|
|
1280
|
-
this.onSuccess();
|
|
1316
|
+
this.onSuccess(admittedAsProbe);
|
|
1281
1317
|
return result;
|
|
1282
1318
|
} catch (error) {
|
|
1283
1319
|
const duration = Date.now() - startTime;
|
|
@@ -1295,9 +1331,10 @@ var init_circuit_breaker = __esm({
|
|
|
1295
1331
|
if (admittedAsProbe) this.halfOpenInFlight = Math.max(0, this.halfOpenInFlight - 1);
|
|
1296
1332
|
}
|
|
1297
1333
|
}
|
|
1298
|
-
onSuccess() {
|
|
1334
|
+
onSuccess(admittedAsProbe) {
|
|
1299
1335
|
metrics.increment("circuit_breaker_success_total", 1, { breaker: this.options.name, state: this.state });
|
|
1300
1336
|
if (this.state === "HALF_OPEN") {
|
|
1337
|
+
if (!admittedAsProbe) return;
|
|
1301
1338
|
this.successCount++;
|
|
1302
1339
|
if (this.successCount >= this.options.halfOpenMaxCalls) {
|
|
1303
1340
|
this.transitionTo("CLOSED");
|
|
@@ -1389,7 +1426,11 @@ function isPoolShutdownError(error) {
|
|
|
1389
1426
|
// classifier matched it, so the first attempt rethrew instead of retrying —
|
|
1390
1427
|
// and auto-recovery fires only after 3 consecutive worker failures, i.e.
|
|
1391
1428
|
// exactly when a burst of tasks is in flight and would all die together.
|
|
1392
|
-
err.message.includes("Worker pool is being reset"))
|
|
1429
|
+
err.message.includes("Worker pool is being reset") || // poolifier's wording once destroy() has COMPLETED (vs "destroying pool"
|
|
1430
|
+
// while it is in progress). A queued closure holding a pool captured before
|
|
1431
|
+
// an auto-recovery destroy dispatches into exactly this window; it is the
|
|
1432
|
+
// same transient swap the "being reset" string covers, not a hard fault.
|
|
1433
|
+
err.message.includes("Cannot execute a task on not started pool"));
|
|
1393
1434
|
}
|
|
1394
1435
|
var DEFAULT_BREAKER_CONFIG, browserCircuitBreaker;
|
|
1395
1436
|
var init_browser_error_utils = __esm({
|
|
@@ -1595,12 +1636,14 @@ function isPrivateIpv6(ip) {
|
|
|
1595
1636
|
if (normalized === "::1") return true;
|
|
1596
1637
|
if (normalized.startsWith("fc") || normalized.startsWith("fd")) return true;
|
|
1597
1638
|
if (normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb")) return true;
|
|
1639
|
+
if (normalized.startsWith("fec") || normalized.startsWith("fed") || normalized.startsWith("fee") || normalized.startsWith("fef")) return true;
|
|
1598
1640
|
const mapped = extractMappedIpv4(normalized);
|
|
1599
1641
|
if (mapped) {
|
|
1600
1642
|
return isPrivateIp(mapped);
|
|
1601
1643
|
}
|
|
1602
1644
|
if (normalized.startsWith("2002:")) return true;
|
|
1603
1645
|
if (/^2001:0{0,4}:/.test(normalized) || normalized.startsWith("2001::")) return true;
|
|
1646
|
+
if (normalized.startsWith("64:ff9b:")) return true;
|
|
1604
1647
|
if (normalized === "::" || normalized === "0:0:0:0:0:0:0:0") return true;
|
|
1605
1648
|
return false;
|
|
1606
1649
|
}
|
|
@@ -1753,12 +1796,34 @@ function setupOrphanProtection() {
|
|
|
1753
1796
|
if (!cluster.isWorker) {
|
|
1754
1797
|
return;
|
|
1755
1798
|
}
|
|
1756
|
-
|
|
1799
|
+
const originalPpid = process2.ppid;
|
|
1800
|
+
if (!originalPpid) {
|
|
1757
1801
|
return;
|
|
1758
1802
|
}
|
|
1803
|
+
const exitAsOrphan = async () => {
|
|
1804
|
+
if (cleanupBrowser) {
|
|
1805
|
+
await Promise.race([
|
|
1806
|
+
cleanupBrowser().catch((err) => {
|
|
1807
|
+
logToDebugFile("WARN", `[Worker-${workerId}] Browser cleanup failed during orphan exit:`, err);
|
|
1808
|
+
}),
|
|
1809
|
+
new Promise((resolve) => {
|
|
1810
|
+
const t = setTimeout(resolve, 1e4);
|
|
1811
|
+
if (t.unref) t.unref();
|
|
1812
|
+
})
|
|
1813
|
+
]);
|
|
1814
|
+
}
|
|
1815
|
+
cleanupOrphanProtection();
|
|
1816
|
+
process2.env["PI_PROCESS_EXITING"] = "1";
|
|
1817
|
+
process2.exit(1);
|
|
1818
|
+
};
|
|
1759
1819
|
const checkOrphan = async () => {
|
|
1760
1820
|
try {
|
|
1761
|
-
|
|
1821
|
+
if (process2.ppid !== originalPpid) {
|
|
1822
|
+
logToDebugFile("WARN", `[Worker-${workerId}] Re-parented (${originalPpid} -> ${process2.ppid}): parent died, shutting down...`);
|
|
1823
|
+
await exitAsOrphan();
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
process2.kill(originalPpid, 0);
|
|
1762
1827
|
orphanCheckTimer = setTimeout(checkOrphan, 1e4);
|
|
1763
1828
|
if (orphanCheckTimer) {
|
|
1764
1829
|
orphanCheckTimer.unref();
|
|
@@ -1770,14 +1835,7 @@ function setupOrphanProtection() {
|
|
|
1770
1835
|
return;
|
|
1771
1836
|
}
|
|
1772
1837
|
logToDebugFile("WARN", `[Worker-${workerId}] Parent process died or unreachable (orphaned), shutting down...`);
|
|
1773
|
-
|
|
1774
|
-
await cleanupBrowser().catch((err) => {
|
|
1775
|
-
logToDebugFile("WARN", `[Worker-${workerId}] Browser cleanup failed during orphan exit:`, err);
|
|
1776
|
-
});
|
|
1777
|
-
}
|
|
1778
|
-
cleanupOrphanProtection();
|
|
1779
|
-
process2.env["PI_PROCESS_EXITING"] = "1";
|
|
1780
|
-
process2.exit(1);
|
|
1838
|
+
await exitAsOrphan();
|
|
1781
1839
|
}
|
|
1782
1840
|
};
|
|
1783
1841
|
orphanCheckTimer = setTimeout(checkOrphan, 1e4);
|
|
@@ -1838,6 +1896,7 @@ import { platform as platform2 } from "node:os";
|
|
|
1838
1896
|
|
|
1839
1897
|
// src/infrastructure/browser/thread-worker-messaging.ts
|
|
1840
1898
|
init_log_utils();
|
|
1899
|
+
init_scraper_types();
|
|
1841
1900
|
import { appendFileSync as appendFileSync2 } from "node:fs";
|
|
1842
1901
|
var workerId2 = "";
|
|
1843
1902
|
var pageCreationLock = Promise.resolve();
|
|
@@ -2130,17 +2189,31 @@ async function executeScrapeTask(_context, url, signal) {
|
|
|
2130
2189
|
validateForWorkerSync(ip.includes(":") ? `http://[${ip}]/` : `http://${ip}/`);
|
|
2131
2190
|
}
|
|
2132
2191
|
const contentType = await response?.headerValue("content-type") || "";
|
|
2192
|
+
const status = response?.status?.() ?? 0;
|
|
2133
2193
|
if (contentType.includes("application/pdf")) {
|
|
2134
2194
|
if (!response) throw new Error(`[Worker] No response received for PDF URL: ${url}`);
|
|
2195
|
+
if (status >= 400) throw new Error(`HTTP ${status}`);
|
|
2196
|
+
const declaredLength = parseInt(await response.headerValue("content-length").catch(() => null) || "", 10);
|
|
2197
|
+
if (Number.isFinite(declaredLength) && declaredLength > MAX_PDF_SIZE) {
|
|
2198
|
+
const sizeMB = Math.round(declaredLength / 1024 / 1024);
|
|
2199
|
+
throw new Error(`PDF too large (${sizeMB}MB, max 100MB)`);
|
|
2200
|
+
}
|
|
2135
2201
|
const buffer = await response.body();
|
|
2202
|
+
if (buffer.byteLength > MAX_PDF_SIZE) {
|
|
2203
|
+
const sizeMB = Math.round(buffer.byteLength / 1024 / 1024);
|
|
2204
|
+
throw new Error(`PDF too large (${sizeMB}MB, max 100MB)`);
|
|
2205
|
+
}
|
|
2136
2206
|
await Promise.allSettled(pendingAddrChecks);
|
|
2137
2207
|
if (poisonedError) throw poisonedError;
|
|
2138
2208
|
await page.close();
|
|
2139
|
-
return { contentType, buffer, jitter: 0 };
|
|
2209
|
+
return { contentType, bufferB64: buffer.toString("base64"), jitter: 0 };
|
|
2140
2210
|
}
|
|
2141
2211
|
let html = await page.content();
|
|
2142
2212
|
const cfPatterns = ["_cf_chl_", "cdn-cgi/challenge-platform", "cf_chl_opt", "Just a moment...", "Checking your browser before accessing"];
|
|
2143
2213
|
const hasCloudflare = cfPatterns.some((pattern) => html.includes(pattern));
|
|
2214
|
+
if (status >= 400 && !hasCloudflare) {
|
|
2215
|
+
throw new Error(`HTTP ${status}`);
|
|
2216
|
+
}
|
|
2144
2217
|
if (hasCloudflare) {
|
|
2145
2218
|
logToDebugFile2("WARN", `[Worker-${workerId2}] Cloudflare challenge detected for: ${url}`);
|
|
2146
2219
|
try {
|
|
@@ -2165,6 +2238,10 @@ async function executeScrapeTask(_context, url, signal) {
|
|
|
2165
2238
|
await page.waitForLoadState("networkidle", { timeout: 1e4 }).catch((err) => logToDebugFile2("DEBUG", `[ThreadWorker] Swallowed page close/wait error: ${err.message || String(err)}`));
|
|
2166
2239
|
html = await page.content();
|
|
2167
2240
|
}
|
|
2241
|
+
if (html.length > MAX_HTML_SIZE) {
|
|
2242
|
+
const sizeMB = Math.round(html.length / 1024 / 1024);
|
|
2243
|
+
throw new Error(`Browser HTML too large (${sizeMB}MB, max 25MB)`);
|
|
2244
|
+
}
|
|
2168
2245
|
const jitter = process.env["PI_RESEARCH_MOCK_SCRAPE"] === "true" ? 0 : Math.floor(Math.random() * 1e3) + 500;
|
|
2169
2246
|
if (jitter > 0) await new Promise((r) => setTimeout(r, jitter));
|
|
2170
2247
|
const finalAddr = await finalMainFrameAddr;
|
package/docs/AGENT-SKILL.md
CHANGED
|
@@ -41,7 +41,7 @@ On Windows, run `pi-research` from `cmd` or use `pi-research.cmd`: PowerShell's
|
|
|
41
41
|
execution policy (`Restricted`) blocks npm's `.ps1` shims ("running scripts is
|
|
42
42
|
disabled"); or run `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned` once.
|
|
43
43
|
|
|
44
|
-

|
|
44
|
+

|
|
45
45
|
|
|
46
46
|
### How it works
|
|
47
47
|
|
|
@@ -83,8 +83,15 @@ Install in External Agents. The installer:
|
|
|
83
83
|
2. Symlinks `skills/pi-research/` into each present agent, never overwriting an
|
|
84
84
|
unrelated skill already in that slot.
|
|
85
85
|
3. Records what it created in a manifest, so Remove from External Agents
|
|
86
|
-
removes only its own links. Stale links are also garbage-collected on startup
|
|
87
|
-
|
|
86
|
+
removes only its own links. Stale links are also garbage-collected on startup.
|
|
87
|
+
|
|
88
|
+
> **Uninstalling removes nothing on its own.** The package ships a `preuninstall`
|
|
89
|
+
> script, but **npm 7 and newer do not run `preuninstall`** — verified against
|
|
90
|
+
> npm 11: `postinstall` fires, `preuninstall` does not. So `npm uninstall
|
|
91
|
+
> @lincoln504/pi-research` leaves the skill links, the state directory
|
|
92
|
+
> (`~/.pi/research/state`) and the cache directory (`~/.cache/pi-research`,
|
|
93
|
+
> including downloaded embedding models) in place. Run `pi-research skill uninstall`
|
|
94
|
+
> **before** removing the package to take the links with you.
|
|
88
95
|
|
|
89
96
|
Standalone (no pi extension). `pi-research skill install` and `pi-research skill uninstall`
|
|
90
97
|
do exactly the same from the CLI — same agent detection, same manifest, same
|
|
@@ -135,7 +142,7 @@ npm install -g @lincoln504/pi-research
|
|
|
135
142
|
node "<skill_dir>/scripts/run.mjs" status # verify the engine is detected
|
|
136
143
|
```
|
|
137
144
|
|
|
138
|
-

|
|
145
|
+

|
|
139
146
|
|
|
140
147
|
Once installed, ask the agent to research something — its skill system activates
|
|
141
148
|
pi-research automatically. The in-package readme (`skills/pi-research/README.md`) and
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -109,15 +109,21 @@ agent to invoke:
|
|
|
109
109
|
| `youtube_transcript` | ✓ | ✓ | YouTube captions via youtubei.js + BotGuard PoToken (≤3 videos, one call per researcher) |
|
|
110
110
|
| `security_search` | ✓ | ✓ | NVD, CISA KEV, GitHub Advisories, OSV |
|
|
111
111
|
| `stackexchange` | ✓ | ✓ | Stack Exchange network |
|
|
112
|
-
| `grep` | — |
|
|
112
|
+
| `grep` | — | — | Local ripgrep (from pi-coding-agent) — off by default, see below |
|
|
113
113
|
| `read` | ✓ | ✓ | Local file reads (from pi-coding-agent) |
|
|
114
114
|
|
|
115
115
|
In deep research `search` is excluded — the coordinator runs the search burst and hands
|
|
116
|
-
out URLs directly.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
out URLs directly.
|
|
117
|
+
|
|
118
|
+
`grep` is excluded by default at **every** depth and on every front-end (CLI, SDK, agent
|
|
119
|
+
skill, pi extension): this is web research, and a capable model otherwise spends turns
|
|
120
|
+
searching the local filesystem. Passing an explicit `excludeTools` list — `--exclude-tools`
|
|
121
|
+
on the CLI, the `excludeTools` tool parameter in the extension — REPLACES that default, so
|
|
122
|
+
naming other tools re-enables `grep`. `PI_RESEARCH_DISABLED_TOOLS` is separate and strictly
|
|
123
|
+
additive: it can only ever remove capabilities (see [CONFIGURATION.md](CONFIGURATION.md)).
|
|
124
|
+
|
|
125
|
+
Researchers cannot write files, run shell commands, or reach the network outside these
|
|
126
|
+
tools.
|
|
121
127
|
|
|
122
128
|
### Browser infrastructure
|
|
123
129
|
|
|
@@ -195,7 +201,7 @@ Services that hold resources implement `dispose()`; the registry disposes them i
|
|
|
195
201
|
reverse dependency order. Resolving through the registry (rather than direct imports)
|
|
196
202
|
enforces lifecycle discipline (init → use → dispose) and lets tests swap in mocks.
|
|
197
203
|
|
|
198
|
-
- Core (`src/core/`): `PlanningService`, `SchedulerService
|
|
204
|
+
- Core (`src/core/`): `PlanningService`, `SchedulerService`
|
|
199
205
|
- Infrastructure (`src/infrastructure/`): `StateManagerService`, `KnowledgeStoreService`, `MetricsService`, `WorkerPoolManager`, `FileLockService`, `GPUResourceService` (plus `WriterQueue`, defined in `src/knowledge/` and registered here)
|
|
200
206
|
- Orchestration (`src/orchestration/`): `ResearchOrchestrationService`, `ResearchSessionService`, `ResearchSynthesisService`
|
|
201
207
|
|
|
@@ -281,8 +287,12 @@ src/
|
|
|
281
287
|
|
|
282
288
|
### Key design decisions
|
|
283
289
|
|
|
284
|
-
|
|
285
|
-
write files, spawn processes, or make arbitrary network calls.
|
|
290
|
+
Read-only researchers — researcher agents are limited to the tool set above. They cannot
|
|
291
|
+
write files, spawn processes, or make arbitrary network calls. They *can* read and grep
|
|
292
|
+
files: `read` and `grep` are registered unconditionally and the researcher exclusion list
|
|
293
|
+
(`bash`, `write`, `edit`, `repl`, `git`, `terminal`) does not cover them. The `cwd` passed
|
|
294
|
+
to those tools is a resolution base, not a jail — an absolute path resolves to itself — so
|
|
295
|
+
the boundary is "no mutation", not "only this directory".
|
|
286
296
|
|
|
287
297
|
Worker pool over direct browser — browser processes are isolated in workers so a crash
|
|
288
298
|
in one cannot affect the orchestrator or other sessions.
|
|
@@ -308,7 +318,7 @@ other). This sits above `@lancedb/lancedb` 0.29's declared Arrow peer ceiling
|
|
|
308
318
|
whenever `@lancedb/lancedb` is upgraded.
|
|
309
319
|
|
|
310
320
|
Pinned validation library — `typebox` is pinned to the exact version the pi host packages
|
|
311
|
-
depend on (`@earendil-works/pi-ai` / `@earendil-works/pi-coding-agent` pin `1.
|
|
321
|
+
depend on (`@earendil-works/pi-ai` / `@earendil-works/pi-coding-agent` 0.83.0 pin `1.3.7`). Every
|
|
312
322
|
tool's parameter schema is built with TypeBox here and handed across the boundary to pi's
|
|
313
323
|
tool system, so the two must agree on `Value.Check`/`Convert` semantics. A floating `^1.1.38`
|
|
314
324
|
range let a fresh consumer install resolve pi-research to a newer TypeBox than pi's, shipping
|