@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
package/dist/cli.mjs
CHANGED
|
@@ -269,7 +269,10 @@ var init_log_utils = __esm({
|
|
|
269
269
|
`${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}`,
|
|
270
270
|
"g"
|
|
271
271
|
);
|
|
272
|
-
JWT_PATTERN = new RegExp(
|
|
272
|
+
JWT_PATTERN = new RegExp(
|
|
273
|
+
`${B_LEFT}eyJ[A-Za-z0-9_-]{1,1024}\\.[A-Za-z0-9_-]{1,8192}\\.[A-Za-z0-9_-]{1,4096}`,
|
|
274
|
+
"g"
|
|
275
|
+
);
|
|
273
276
|
BASIC_AUTH_PATTERN = new RegExp(`${B_LEFT}Basic\\s+[A-Za-z0-9+/]{16,}={0,2}`, "gi");
|
|
274
277
|
BEARER_AUTH_PATTERN = new RegExp(`${B_LEFT}Bearer\\s+[A-Za-z0-9._~+/-]+=*`, "gi");
|
|
275
278
|
PROVIDER_TOKEN_PATTERN = new RegExp(
|
|
@@ -1158,8 +1161,16 @@ var init_service_registry = __esm({
|
|
|
1158
1161
|
*/
|
|
1159
1162
|
async registerAndReplace(name, factory, options = {}) {
|
|
1160
1163
|
const mergedOptions = { ...this.defaultOptions, ...options, allowOverwrite: true };
|
|
1161
|
-
|
|
1162
|
-
|
|
1164
|
+
const registration = this.services.get(name);
|
|
1165
|
+
if (registration) {
|
|
1166
|
+
while (registration.disposalPromise) {
|
|
1167
|
+
await registration.disposalPromise.catch(() => {
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
if (registration.initializationPromise) {
|
|
1171
|
+
await registration.initializationPromise.catch(() => {
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1163
1174
|
if (registration.instance && registration.instance.dispose) {
|
|
1164
1175
|
try {
|
|
1165
1176
|
await registration.instance.dispose();
|
|
@@ -2148,13 +2159,18 @@ function isPlausibleCitationUrl(url) {
|
|
|
2148
2159
|
}
|
|
2149
2160
|
}
|
|
2150
2161
|
function lastCitedLinksHeaderIndex(text) {
|
|
2162
|
+
return lastCitedLinksHeader(text).textStart;
|
|
2163
|
+
}
|
|
2164
|
+
function lastCitedLinksHeader(text) {
|
|
2151
2165
|
const re = /(^|\n)([ \t]*(?:#{1,6}[ \t]*|>[ \t]*|\*\*)?)CITED LINKS\b/gi;
|
|
2152
|
-
let
|
|
2166
|
+
let lineStart = -1;
|
|
2167
|
+
let textStart = -1;
|
|
2153
2168
|
let m;
|
|
2154
2169
|
while ((m = re.exec(text)) !== null) {
|
|
2155
|
-
|
|
2170
|
+
lineStart = m.index + m[1].length;
|
|
2171
|
+
textStart = m.index + m[1].length + m[2].length;
|
|
2156
2172
|
}
|
|
2157
|
-
return
|
|
2173
|
+
return { lineStart, textStart };
|
|
2158
2174
|
}
|
|
2159
2175
|
function parseCitations(report) {
|
|
2160
2176
|
const headerIdx = lastCitedLinksHeaderIndex(report);
|
|
@@ -2662,7 +2678,6 @@ function saveConfig(config, scope = "local", cwd = process.cwd(), changedKeys) {
|
|
|
2662
2678
|
PI_RESEARCH_LLM_THINKING_LEVEL: config.LLM_THINKING_LEVEL,
|
|
2663
2679
|
PI_RESEARCH_PLANNING_MAX_TOKENS: String(config.PLANNING_MAX_TOKENS),
|
|
2664
2680
|
PI_RESEARCH_SYNTHESIS_MAX_TOKENS: String(config.SYNTHESIS_MAX_TOKENS),
|
|
2665
|
-
PI_RESEARCH_RESEARCHER_MAX_TOKENS: String(config.RESEARCHER_MAX_TOKENS),
|
|
2666
2681
|
PI_RESEARCH_MIGRATION_STRATEGY: config.MIGRATION_STRATEGY,
|
|
2667
2682
|
PI_RESEARCH_CONSOLE_LOG: String(config.CONSOLE_LOG),
|
|
2668
2683
|
...config.RESEARCH_MODEL ? { PI_RESEARCH_MODEL: config.RESEARCH_MODEL } : {},
|
|
@@ -2827,7 +2842,6 @@ function createConfig(env, processEnv) {
|
|
|
2827
2842
|
LLM_THINKING_LEVEL: parseEnvEnum(e, "PI_RESEARCH_LLM_THINKING_LEVEL", ["off", "minimal", "low", "medium", "high"], DEFAULTS.LLM_THINKING_LEVEL),
|
|
2828
2843
|
PLANNING_MAX_TOKENS: parseEnvNumber(e, "PI_RESEARCH_PLANNING_MAX_TOKENS", DEFAULTS.PLANNING_MAX_TOKENS, 1024, 131072),
|
|
2829
2844
|
SYNTHESIS_MAX_TOKENS: parseEnvNumber(e, "PI_RESEARCH_SYNTHESIS_MAX_TOKENS", DEFAULTS.SYNTHESIS_MAX_TOKENS, 1024, 131072),
|
|
2830
|
-
RESEARCHER_MAX_TOKENS: parseEnvNumber(e, "PI_RESEARCH_RESEARCHER_MAX_TOKENS", DEFAULTS.RESEARCHER_MAX_TOKENS, 1024, 131072),
|
|
2831
2845
|
MIGRATION_STRATEGY: parseEnvEnum(e, "PI_RESEARCH_MIGRATION_STRATEGY", ["drop", "re-embed", "backup"], DEFAULTS.MIGRATION_STRATEGY),
|
|
2832
2846
|
CONSOLE_LOG: parseEnvBool(e, "PI_RESEARCH_CONSOLE_LOG", DEFAULTS.CONSOLE_LOG),
|
|
2833
2847
|
RESEARCH_MODEL: parseEnvString(e, "PI_RESEARCH_MODEL", DEFAULTS.RESEARCH_MODEL),
|
|
@@ -2891,7 +2905,7 @@ function parseEnvNumber(env, key, def, min, max, integer = false) {
|
|
|
2891
2905
|
return n;
|
|
2892
2906
|
}
|
|
2893
2907
|
function parseEnvBool(env, key, def) {
|
|
2894
|
-
const v = env[key];
|
|
2908
|
+
const v = env[key]?.trim();
|
|
2895
2909
|
if (v === void 0 || v === "") return def;
|
|
2896
2910
|
return v.toLowerCase() === "true";
|
|
2897
2911
|
}
|
|
@@ -2899,7 +2913,7 @@ function parseEnvString(env, key, def) {
|
|
|
2899
2913
|
return env[key] || def;
|
|
2900
2914
|
}
|
|
2901
2915
|
function parseEnvEnum(env, key, allowed, def) {
|
|
2902
|
-
const raw = env[key];
|
|
2916
|
+
const raw = env[key]?.trim();
|
|
2903
2917
|
if (raw === void 0 || raw === "") return def;
|
|
2904
2918
|
if (allowed.includes(raw)) return raw;
|
|
2905
2919
|
logger.warn(`[config] Invalid ${key}="${raw}" (expected one of: ${allowed.join(", ")}). Using default "${def}".`);
|
|
@@ -3015,15 +3029,6 @@ var init_config = __esm({
|
|
|
3015
3029
|
* the model's real maxTokens at call time.
|
|
3016
3030
|
* Not exposed in TUI — controlled via PI_RESEARCH_SYNTHESIS_MAX_TOKENS env var. */
|
|
3017
3031
|
SYNTHESIS_MAX_TOKENS: Type.Number({ minimum: 1024, maximum: 131072, default: 32768 }),
|
|
3018
|
-
/** Max OUTPUT tokens per researcher LLM call (deep and quick sub-agent sessions).
|
|
3019
|
-
* Without a cap the session inherits the model's full catalog ceiling as the
|
|
3020
|
-
* per-request max_tokens — ~230k for large-context models — which providers that
|
|
3021
|
-
* budget-check max_tokens up front (OpenRouter 402s against remaining credits)
|
|
3022
|
-
* reject outright on low balances, even though the actual response would cost
|
|
3023
|
-
* cents. Default 16384: researcher turns are tool calls plus a cited report, far
|
|
3024
|
-
* below this. Clamped to the model's real ceiling at session build time.
|
|
3025
|
-
* Not exposed in TUI — controlled via PI_RESEARCH_RESEARCHER_MAX_TOKENS env var. */
|
|
3026
|
-
RESEARCHER_MAX_TOKENS: Type.Number({ minimum: 1024, maximum: 131072, default: 16384 }),
|
|
3027
3032
|
/** LLM Model override for researcher sub-agents and knowledge synthesis.
|
|
3028
3033
|
* Format: provider/model-id (e.g. google/gemini-2.0-flash-001) or just model-id.
|
|
3029
3034
|
* When set, this overrides ctx.model for researcher sub-agents (both deep and quick)
|
|
@@ -3060,7 +3065,8 @@ var init_config = __esm({
|
|
|
3060
3065
|
/**
|
|
3061
3066
|
* Comma-separated list of research tools to DISABLE for every researcher in a run
|
|
3062
3067
|
* (e.g. "scrape", or "youtube_transcript,stackexchange"). Valid tool names:
|
|
3063
|
-
* search, scrape, security_search, stackexchange, youtube_transcript, grep
|
|
3068
|
+
* search, scrape, security_search, stackexchange, youtube_transcript, grep, read
|
|
3069
|
+
* (RESEARCH_TOOL_NAMES in constants.ts is the canonical list).
|
|
3064
3070
|
* Disable-only: the list is merged into the per-run excludeTools stream, so disabled
|
|
3065
3071
|
* tools are both removed from each researcher's toolset AND named in the
|
|
3066
3072
|
* coordinator/evaluator "DISABLED TOOLS" prompt section. Env-only
|
|
@@ -3101,7 +3107,6 @@ var init_config = __esm({
|
|
|
3101
3107
|
"PI_RESEARCH_LLM_THINKING_LEVEL",
|
|
3102
3108
|
"PI_RESEARCH_PLANNING_MAX_TOKENS",
|
|
3103
3109
|
"PI_RESEARCH_SYNTHESIS_MAX_TOKENS",
|
|
3104
|
-
"PI_RESEARCH_RESEARCHER_MAX_TOKENS",
|
|
3105
3110
|
"PI_RESEARCH_MIGRATION_STRATEGY",
|
|
3106
3111
|
"PI_RESEARCH_CONSOLE_LOG",
|
|
3107
3112
|
"PI_RESEARCH_MODEL",
|
|
@@ -3146,7 +3151,7 @@ function getMaxScrapeBatches(config) {
|
|
|
3146
3151
|
function resolveExcludedTools(callerExclusions, configDisabled = []) {
|
|
3147
3152
|
return [.../* @__PURE__ */ new Set([...DEFAULT_EXCLUDED_TOOLS, ...callerExclusions ?? [], ...configDisabled])];
|
|
3148
3153
|
}
|
|
3149
|
-
var MAX_GATHERING_CALLS, MAX_GREP_CALLS, MAX_SCRAPE_URLS, MAX_TEAM_SIZE_LEVEL_1, MAX_TEAM_SIZE_LEVEL_2, MAX_TEAM_SIZE_LEVEL_3, MAX_ROUNDS_LEVEL_1, MAX_ROUNDS_LEVEL_2, MAX_ROUNDS_LEVEL_3, MAX_EXTRA_ROUNDS_WITH_STEERING, OSV_TIMEOUT_MS, WEBGPU_PROBE_TIMEOUT_MS, MAX_QUERY_LENGTH, MAX_FILENAME_QUERY_LENGTH, MIN_QUERY_LENGTH, MAX_EXPORT_RETRIES, DEFAULT_MAX_RETRIES, DEFAULT_INITIAL_DELAY_MS, DEFAULT_MAX_DELAY_MS, DEFAULT_MODEL_CONTEXT_WINDOW, BATCH_2_DEFAULT_CONCURRENCY, MAX_SCRAPE_CONTENT_CHARS_PER_DOC, RESEARCHER_LAUNCH_DELAY_MS, MAX_QUERIES_PER_RESEARCHER_LEVEL_1, MAX_QUERIES_PER_RESEARCHER_LEVEL_2, MAX_QUERIES_PER_RESEARCHER_LEVEL_3, DEFAULT_EXCLUDED_TOOLS;
|
|
3154
|
+
var MAX_GATHERING_CALLS, MAX_GREP_CALLS, MAX_SCRAPE_URLS, MAX_TEAM_SIZE_LEVEL_1, MAX_TEAM_SIZE_LEVEL_2, MAX_TEAM_SIZE_LEVEL_3, MAX_ROUNDS_LEVEL_1, MAX_ROUNDS_LEVEL_2, MAX_ROUNDS_LEVEL_3, MAX_EXTRA_ROUNDS_WITH_STEERING, OSV_TIMEOUT_MS, WEBGPU_PROBE_TIMEOUT_MS, MAX_QUERY_LENGTH, MAX_FILENAME_QUERY_LENGTH, MIN_QUERY_LENGTH, MAX_EXPORT_RETRIES, DEFAULT_MAX_RETRIES, DEFAULT_INITIAL_DELAY_MS, DEFAULT_MAX_DELAY_MS, DEFAULT_MODEL_CONTEXT_WINDOW, BATCH_2_DEFAULT_CONCURRENCY, MAX_SCRAPE_CONTENT_CHARS_PER_DOC, RESEARCHER_LAUNCH_DELAY_MS, MAX_QUERIES_PER_RESEARCHER_LEVEL_1, MAX_QUERIES_PER_RESEARCHER_LEVEL_2, MAX_QUERIES_PER_RESEARCHER_LEVEL_3, DEFAULT_EXCLUDED_TOOLS, RESEARCH_TOOL_NAMES;
|
|
3150
3155
|
var init_constants = __esm({
|
|
3151
3156
|
"src/constants.ts"() {
|
|
3152
3157
|
"use strict";
|
|
@@ -3178,6 +3183,15 @@ var init_constants = __esm({
|
|
|
3178
3183
|
MAX_QUERIES_PER_RESEARCHER_LEVEL_2 = 15;
|
|
3179
3184
|
MAX_QUERIES_PER_RESEARCHER_LEVEL_3 = 20;
|
|
3180
3185
|
DEFAULT_EXCLUDED_TOOLS = ["grep"];
|
|
3186
|
+
RESEARCH_TOOL_NAMES = [
|
|
3187
|
+
"search",
|
|
3188
|
+
"scrape",
|
|
3189
|
+
"security_search",
|
|
3190
|
+
"stackexchange",
|
|
3191
|
+
"youtube_transcript",
|
|
3192
|
+
"grep",
|
|
3193
|
+
"read"
|
|
3194
|
+
];
|
|
3181
3195
|
}
|
|
3182
3196
|
});
|
|
3183
3197
|
|
|
@@ -5395,6 +5409,9 @@ var init_store = __esm({
|
|
|
5395
5409
|
logger.warn("[store] Failed to read table schema:", schemaErr);
|
|
5396
5410
|
}
|
|
5397
5411
|
} else {
|
|
5412
|
+
if (this.options.embedder.getDimension() === null) {
|
|
5413
|
+
await this.withEmbedderReconnect((embedder) => embedder.embedMany([" "]));
|
|
5414
|
+
}
|
|
5398
5415
|
this.table = await this.createTable();
|
|
5399
5416
|
}
|
|
5400
5417
|
await this.evictOldRecords();
|
|
@@ -5776,6 +5793,45 @@ var init_store = __esm({
|
|
|
5776
5793
|
}
|
|
5777
5794
|
}
|
|
5778
5795
|
}
|
|
5796
|
+
/**
|
|
5797
|
+
* Refresh the timestamp of one CURRENT generation for url+type — the
|
|
5798
|
+
* revalidation "touch" for the writer-queue's content-unchanged skip. Without
|
|
5799
|
+
* it a stable page kept its original timestamp forever: with
|
|
5800
|
+
* KNOWLEDGE_STORE_MAX_SERVE_AGE_DAYS set, the serve-age gate then treated the
|
|
5801
|
+
* entry as permanently stale (a live re-scrape on every run, for exactly the
|
|
5802
|
+
* content that never changes), and TTL eviction eventually deleted an entry
|
|
5803
|
+
* that had been re-verified byte-identical possibly hours earlier.
|
|
5804
|
+
*/
|
|
5805
|
+
async touchByUrlAndType(url, ingestionType, generationTs, newTs) {
|
|
5806
|
+
return this.trackOperation("touchByUrlAndType", void 0, async () => {
|
|
5807
|
+
let retryCount = 0;
|
|
5808
|
+
const MAX_RETRIES = 5;
|
|
5809
|
+
const BASE_DELAY = 100;
|
|
5810
|
+
const scopeFilter = this.getScopeFilter();
|
|
5811
|
+
while (retryCount <= MAX_RETRIES) {
|
|
5812
|
+
const table = await this.getFreshTable();
|
|
5813
|
+
try {
|
|
5814
|
+
const escapedUrl = url.replace(/'/g, "''");
|
|
5815
|
+
const escapedType = ingestionType.replace(/'/g, "''");
|
|
5816
|
+
await table.update({
|
|
5817
|
+
where: `url = '${escapedUrl}' AND ingestion_type = '${escapedType}' AND timestamp = ${Math.floor(generationTs)} AND (${scopeFilter})`,
|
|
5818
|
+
valuesSql: { timestamp: String(Math.floor(newTs)) }
|
|
5819
|
+
});
|
|
5820
|
+
logger.log(`[store] Refreshed timestamp for unchanged ${ingestionType} chunks of ${url}`);
|
|
5821
|
+
return;
|
|
5822
|
+
} catch (err) {
|
|
5823
|
+
if (isLanceCommitConflict(err)) {
|
|
5824
|
+
retryCount++;
|
|
5825
|
+
if (retryCount > MAX_RETRIES) throw err;
|
|
5826
|
+
const delay2 = Math.floor(Math.random() * (BASE_DELAY * Math.pow(2, retryCount - 1)));
|
|
5827
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
5828
|
+
continue;
|
|
5829
|
+
}
|
|
5830
|
+
throw err;
|
|
5831
|
+
}
|
|
5832
|
+
}
|
|
5833
|
+
});
|
|
5834
|
+
}
|
|
5779
5835
|
async findDocumentsByUrl(url) {
|
|
5780
5836
|
return this.trackOperation("findDocumentsByUrl", [], async () => {
|
|
5781
5837
|
const table = await this.getFreshTable();
|
|
@@ -6013,7 +6069,10 @@ var init_store = __esm({
|
|
|
6013
6069
|
backupDirs.map(async (name) => ({ name, ts: await this.backupDirTimestamp(name) }))
|
|
6014
6070
|
);
|
|
6015
6071
|
ranked.sort((a, b) => a.ts - b.ts);
|
|
6016
|
-
|
|
6072
|
+
const ttlDays = this.options.ttlDays ?? 30;
|
|
6073
|
+
const backupCutoff = Math.trunc(Date.now() - ttlDays * 24 * 60 * 60 * 1e3);
|
|
6074
|
+
for (const { name, ts } of ranked.slice(0, -1)) {
|
|
6075
|
+
if (ts >= backupCutoff) continue;
|
|
6017
6076
|
const fullPath = path13.join(this.options.dbDir, name);
|
|
6018
6077
|
logger.info(`[store] Removing superseded backup directory: ${name}`);
|
|
6019
6078
|
await fsPromises.rm(fullPath, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
|
|
@@ -6309,6 +6368,17 @@ var init_writer_queue = __esm({
|
|
|
6309
6368
|
metrics.increment("knowledge_ingest_stale_prune_failed_total", 1);
|
|
6310
6369
|
});
|
|
6311
6370
|
}
|
|
6371
|
+
if (this.options.store.touchByUrlAndType) {
|
|
6372
|
+
try {
|
|
6373
|
+
const recheck = (await this.options.store.findByUrl(normalizedUrl)).filter((c) => c.metadata["ingestionType"] === incomingType);
|
|
6374
|
+
const stillNewest = recheck.length > 0 && Math.max(...recheck.map((d) => Number(d.timestamp) || 0)) === newestTs;
|
|
6375
|
+
if (stillNewest) {
|
|
6376
|
+
await this.options.store.touchByUrlAndType(normalizedUrl, incomingType, newestTs, Date.now());
|
|
6377
|
+
}
|
|
6378
|
+
} catch (err) {
|
|
6379
|
+
logger.warn(`[writer-queue] Timestamp refresh for unchanged ${normalizedUrl} (${incomingType}) failed:`, err);
|
|
6380
|
+
}
|
|
6381
|
+
}
|
|
6312
6382
|
logger.log(`[writer-queue] Skipping ${normalizedUrl} (${incomingType}) \u2014 content unchanged.`);
|
|
6313
6383
|
return;
|
|
6314
6384
|
}
|
|
@@ -6787,7 +6857,17 @@ async function loadPipelineWithTimeout(model, device, timeoutMs, useCache) {
|
|
|
6787
6857
|
interOpNumThreads: 1
|
|
6788
6858
|
}
|
|
6789
6859
|
});
|
|
6790
|
-
|
|
6860
|
+
let timedOut = true;
|
|
6861
|
+
try {
|
|
6862
|
+
const p = await withTimeout3(pipelinePromise, timeoutMs, errorMessage);
|
|
6863
|
+
timedOut = false;
|
|
6864
|
+
return p;
|
|
6865
|
+
} finally {
|
|
6866
|
+
if (timedOut) {
|
|
6867
|
+
pipelinePromise.then((late) => late.dispose?.()).catch(() => {
|
|
6868
|
+
});
|
|
6869
|
+
}
|
|
6870
|
+
}
|
|
6791
6871
|
});
|
|
6792
6872
|
return { pipeline: loadedPipeline, errorMessage };
|
|
6793
6873
|
}
|
|
@@ -6916,7 +6996,17 @@ async function loadModelOnCPU(model, initializationTimeoutMs, useCache) {
|
|
|
6916
6996
|
interOpNumThreads: 1
|
|
6917
6997
|
}
|
|
6918
6998
|
});
|
|
6919
|
-
|
|
6999
|
+
let timedOut = true;
|
|
7000
|
+
try {
|
|
7001
|
+
const p = await withTimeout3(pipelinePromise, initializationTimeoutMs, "CPU fallback model load timed out");
|
|
7002
|
+
timedOut = false;
|
|
7003
|
+
return p;
|
|
7004
|
+
} finally {
|
|
7005
|
+
if (timedOut) {
|
|
7006
|
+
pipelinePromise.then((late) => late.dispose?.()).catch(() => {
|
|
7007
|
+
});
|
|
7008
|
+
}
|
|
7009
|
+
}
|
|
6920
7010
|
});
|
|
6921
7011
|
return loadedPipeline;
|
|
6922
7012
|
}
|
|
@@ -9113,6 +9203,7 @@ function formatQueriesBlock(queries) {
|
|
|
9113
9203
|
}
|
|
9114
9204
|
function showKnowledgeSearchWidget(ctx) {
|
|
9115
9205
|
if (ctx.mode !== "tui" || !ctx.hasUI) return;
|
|
9206
|
+
knowledgeWidgetRefCount++;
|
|
9116
9207
|
try {
|
|
9117
9208
|
const panelFactory = createKnowledgeSearchPanel();
|
|
9118
9209
|
ctx.ui.setWidget(KNOWLEDGE_WIDGET_ID, panelFactory, { placement: "aboveEditor" });
|
|
@@ -9122,6 +9213,8 @@ function showKnowledgeSearchWidget(ctx) {
|
|
|
9122
9213
|
}
|
|
9123
9214
|
function hideKnowledgeSearchWidget(ctx) {
|
|
9124
9215
|
if (ctx.mode !== "tui" || !ctx.hasUI) return;
|
|
9216
|
+
knowledgeWidgetRefCount = Math.max(0, knowledgeWidgetRefCount - 1);
|
|
9217
|
+
if (knowledgeWidgetRefCount > 0) return;
|
|
9125
9218
|
try {
|
|
9126
9219
|
ctx.ui.setWidget(KNOWLEDGE_WIDGET_ID, void 0, { placement: "aboveEditor" });
|
|
9127
9220
|
} catch (err) {
|
|
@@ -9601,7 +9694,7 @@ function createResearchKnowledgeSearchTool(iface) {
|
|
|
9601
9694
|
}
|
|
9602
9695
|
};
|
|
9603
9696
|
}
|
|
9604
|
-
var ResearchKnowledgeSearchParams, MAX_REFERENCE_CHARS, MAX_DOCUMENTS, MAX_CANDIDATES, TRIAGE_DESCRIPTION_MAX_CHARS, TRIAGE_MAX_TOKENS, KNOWLEDGE_WIDGET_ID, RESEARCH_KNOWLEDGE_MISS_STRING, RESEARCH_KNOWLEDGE_MAYBE_STRING, KNOWLEDGE_SYNTHESIS_MAX_ATTEMPTS, KNOWLEDGE_SYNTHESIS_RETRY_BASE_MS;
|
|
9697
|
+
var ResearchKnowledgeSearchParams, MAX_REFERENCE_CHARS, MAX_DOCUMENTS, MAX_CANDIDATES, TRIAGE_DESCRIPTION_MAX_CHARS, TRIAGE_MAX_TOKENS, KNOWLEDGE_WIDGET_ID, knowledgeWidgetRefCount, RESEARCH_KNOWLEDGE_MISS_STRING, RESEARCH_KNOWLEDGE_MAYBE_STRING, KNOWLEDGE_SYNTHESIS_MAX_ATTEMPTS, KNOWLEDGE_SYNTHESIS_RETRY_BASE_MS;
|
|
9605
9698
|
var init_research_knowledge_search = __esm({
|
|
9606
9699
|
"src/tools/research-knowledge-search.ts"() {
|
|
9607
9700
|
"use strict";
|
|
@@ -9636,6 +9729,7 @@ var init_research_knowledge_search = __esm({
|
|
|
9636
9729
|
TRIAGE_DESCRIPTION_MAX_CHARS = 600;
|
|
9637
9730
|
TRIAGE_MAX_TOKENS = 2048;
|
|
9638
9731
|
KNOWLEDGE_WIDGET_ID = "pi-research-knowledge-search";
|
|
9732
|
+
knowledgeWidgetRefCount = 0;
|
|
9639
9733
|
RESEARCH_KNOWLEDGE_MISS_STRING = "No results found. Live research can get the info.";
|
|
9640
9734
|
RESEARCH_KNOWLEDGE_MAYBE_STRING = "Partial results found in knowledge store. Live research can fill gaps.";
|
|
9641
9735
|
KNOWLEDGE_SYNTHESIS_MAX_ATTEMPTS = 3;
|
|
@@ -10432,7 +10526,7 @@ function normalizeCitations(reports) {
|
|
|
10432
10526
|
localToGlobal.set(localKey, globalId);
|
|
10433
10527
|
}
|
|
10434
10528
|
});
|
|
10435
|
-
const headerIdx =
|
|
10529
|
+
const headerIdx = lastCitedLinksHeader(report).lineStart;
|
|
10436
10530
|
let content = headerIdx >= 0 ? report.slice(0, headerIdx) : report;
|
|
10437
10531
|
content = rewriteCitationMarkers(
|
|
10438
10532
|
content,
|
|
@@ -11391,9 +11485,13 @@ import { platform, homedir as homedir2 } from "node:os";
|
|
|
11391
11485
|
function getWindowsCamoufoxDir() {
|
|
11392
11486
|
return join7(homedir2(), "AppData", "Local", "camoufox", "camoufox", "Cache");
|
|
11393
11487
|
}
|
|
11488
|
+
function getCustomCamoufoxDir() {
|
|
11489
|
+
return process.env["CAMOUFOX_INSTALL_DIR"] || process.env["PLAYWRIGHT_BROWSERS_PATH"] || void 0;
|
|
11490
|
+
}
|
|
11394
11491
|
function getBrowserCacheDir() {
|
|
11395
|
-
|
|
11396
|
-
|
|
11492
|
+
const custom = getCustomCamoufoxDir();
|
|
11493
|
+
if (custom) {
|
|
11494
|
+
return custom;
|
|
11397
11495
|
}
|
|
11398
11496
|
const osPlatform = platform();
|
|
11399
11497
|
if (osPlatform === "win32") {
|
|
@@ -11424,11 +11522,13 @@ function getBrowserProfileDir(config) {
|
|
|
11424
11522
|
}
|
|
11425
11523
|
function getBrowserEnv(config) {
|
|
11426
11524
|
const env = { ...process.env };
|
|
11427
|
-
const customPath =
|
|
11525
|
+
const customPath = getCustomCamoufoxDir();
|
|
11428
11526
|
if (customPath) {
|
|
11429
11527
|
env["PLAYWRIGHT_BROWSERS_PATH"] = customPath;
|
|
11528
|
+
env["CAMOUFOX_INSTALL_DIR"] = customPath;
|
|
11430
11529
|
} else {
|
|
11431
11530
|
delete env["PLAYWRIGHT_BROWSERS_PATH"];
|
|
11531
|
+
delete env["CAMOUFOX_INSTALL_DIR"];
|
|
11432
11532
|
}
|
|
11433
11533
|
if (!env["PI_RESEARCH_LOG_FILE"]) {
|
|
11434
11534
|
const logFilePath = getLogger().getLogFilePath();
|
|
@@ -11457,7 +11557,7 @@ function ensureBrowserCacheDir() {
|
|
|
11457
11557
|
return cacheDir;
|
|
11458
11558
|
}
|
|
11459
11559
|
function getCamoufoxBinaryPath() {
|
|
11460
|
-
const customPath =
|
|
11560
|
+
const customPath = getCustomCamoufoxDir();
|
|
11461
11561
|
if (customPath) {
|
|
11462
11562
|
return customPath;
|
|
11463
11563
|
}
|
|
@@ -14235,8 +14335,22 @@ var GPUResourceService = class {
|
|
|
14235
14335
|
name = ServiceNames.GPU_RESOURCE_SERVICE;
|
|
14236
14336
|
lifecycle = "uninitialized" /* UNINITIALIZED */;
|
|
14237
14337
|
processLifecycle = null;
|
|
14238
|
-
|
|
14239
|
-
|
|
14338
|
+
/**
|
|
14339
|
+
* Backstop for a holder that is alive but wedged. Step 3 already reclaims from
|
|
14340
|
+
* *dead* owners via PID+startTime liveness, so this only ever fires against a
|
|
14341
|
+
* process we just proved is running — which makes it a lock STEAL, and the
|
|
14342
|
+
* threshold must therefore exceed the longest legitimate hold.
|
|
14343
|
+
*
|
|
14344
|
+
* It previously did not: at 5 minutes it exactly equalled the default
|
|
14345
|
+
* EMBEDDING_MODEL_INIT_TIMEOUT_MS (300000, schema max 600000), and the
|
|
14346
|
+
* embedder holds this lock across the whole model download + pipeline load.
|
|
14347
|
+
* A first-time HuggingFace fetch running near its own permitted budget would
|
|
14348
|
+
* have the GPU lock taken out from under it, admitting exactly the concurrent
|
|
14349
|
+
* WebGPU/Dawn init this lock exists to prevent. Keep this strictly above that
|
|
14350
|
+
* schema maximum.
|
|
14351
|
+
*/
|
|
14352
|
+
gpuLockStaleThresholdMs = 1e3 * 60 * 15;
|
|
14353
|
+
// 15 min > EMBEDDING_MODEL_INIT_TIMEOUT_MS max (10 min)
|
|
14240
14354
|
constructor(options) {
|
|
14241
14355
|
if (options) {
|
|
14242
14356
|
this.processLifecycle = options.processLifecycle;
|
|
@@ -17046,19 +17160,33 @@ function isSsrfBlockError(error) {
|
|
|
17046
17160
|
return false;
|
|
17047
17161
|
}
|
|
17048
17162
|
var cachedSsrfFetcher = void 0;
|
|
17049
|
-
|
|
17050
|
-
|
|
17163
|
+
function getSsrfSafeFetcher() {
|
|
17164
|
+
cachedSsrfFetcher ??= (async () => {
|
|
17165
|
+
try {
|
|
17166
|
+
const { Agent: Agent2, fetch: undiciFetch } = await import("undici");
|
|
17167
|
+
return {
|
|
17168
|
+
fetch: undiciFetch,
|
|
17169
|
+
dispatcher: new Agent2({ connect: { lookup: ssrfSafeLookup } })
|
|
17170
|
+
};
|
|
17171
|
+
} catch (e) {
|
|
17172
|
+
logger.warn(`[Scrapers] undici unavailable; connect-time SSRF pinning disabled (request-time validation still active): ${e instanceof Error ? e.message : String(e)}`);
|
|
17173
|
+
return null;
|
|
17174
|
+
}
|
|
17175
|
+
})();
|
|
17176
|
+
return cachedSsrfFetcher;
|
|
17177
|
+
}
|
|
17178
|
+
async function disposeSsrfSafeFetcher() {
|
|
17179
|
+
const pending = cachedSsrfFetcher;
|
|
17180
|
+
cachedSsrfFetcher = void 0;
|
|
17181
|
+
if (pending === void 0) return;
|
|
17182
|
+
const cached = await pending.catch(() => null);
|
|
17183
|
+
const closable = cached?.dispatcher;
|
|
17184
|
+
if (!closable?.close) return;
|
|
17051
17185
|
try {
|
|
17052
|
-
|
|
17053
|
-
cachedSsrfFetcher = {
|
|
17054
|
-
fetch: undiciFetch,
|
|
17055
|
-
dispatcher: new Agent2({ connect: { lookup: ssrfSafeLookup } })
|
|
17056
|
-
};
|
|
17186
|
+
await closable.close();
|
|
17057
17187
|
} catch (e) {
|
|
17058
|
-
logger.
|
|
17059
|
-
cachedSsrfFetcher = null;
|
|
17188
|
+
logger.debug(`[Scrapers] Error closing SSRF dispatcher: ${e instanceof Error ? e.message : String(e)}`);
|
|
17060
17189
|
}
|
|
17061
|
-
return cachedSsrfFetcher;
|
|
17062
17190
|
}
|
|
17063
17191
|
function formatErrorWithCause(err) {
|
|
17064
17192
|
try {
|
|
@@ -17917,7 +18045,6 @@ import { Value as Value7 } from "typebox/value";
|
|
|
17917
18045
|
// src/security/nvd-client.ts
|
|
17918
18046
|
init_retry_utils();
|
|
17919
18047
|
init_circuit_breaker();
|
|
17920
|
-
init_safe_unref();
|
|
17921
18048
|
init_metrics();
|
|
17922
18049
|
init_http_body();
|
|
17923
18050
|
|
|
@@ -17980,7 +18107,6 @@ var NVDRateLimiter = class {
|
|
|
17980
18107
|
signal?.removeEventListener("abort", onAbort);
|
|
17981
18108
|
resolve9();
|
|
17982
18109
|
}, waitTime);
|
|
17983
|
-
safeUnref(timeoutId);
|
|
17984
18110
|
const onAbort = () => {
|
|
17985
18111
|
clearTimeout(timeoutId);
|
|
17986
18112
|
reject(Object.assign(new Error("NVD search aborted"), { name: "AbortError" }));
|
|
@@ -18202,6 +18328,7 @@ async function fetchPaginated(term, options, maxResults, severityVersion, admitE
|
|
|
18202
18328
|
const signal = options?.signal;
|
|
18203
18329
|
let startIndex = 0;
|
|
18204
18330
|
let totalPagesFetched = 0;
|
|
18331
|
+
let seekedToNewest = false;
|
|
18205
18332
|
while (totalPagesFetched < maxPages && allVulnerabilities.length < maxResults) {
|
|
18206
18333
|
if (signal?.aborted) break;
|
|
18207
18334
|
const url = buildURL(term, options, pageSize, startIndex, severityVersion);
|
|
@@ -18222,9 +18349,17 @@ async function fetchPaginated(term, options, maxResults, severityVersion, admitE
|
|
|
18222
18349
|
break;
|
|
18223
18350
|
}
|
|
18224
18351
|
metrics.increment("nvd_cache_hits_total", 1);
|
|
18225
|
-
allVulnerabilities.push(...admitted);
|
|
18226
18352
|
const responseData = data;
|
|
18227
|
-
|
|
18353
|
+
const reportedTotal = typeof responseData?.["totalResults"] === "number" ? responseData["totalResults"] : void 0;
|
|
18354
|
+
if (!seekedToNewest && startIndex === 0 && reportedTotal !== void 0 && reportedTotal > maxResults) {
|
|
18355
|
+
seekedToNewest = true;
|
|
18356
|
+
startIndex = reportedTotal - maxResults;
|
|
18357
|
+
allVulnerabilities.length = 0;
|
|
18358
|
+
totalPagesFetched++;
|
|
18359
|
+
continue;
|
|
18360
|
+
}
|
|
18361
|
+
allVulnerabilities.push(...admitted);
|
|
18362
|
+
if (reportedTotal !== void 0 && startIndex + pageSize >= reportedTotal) {
|
|
18228
18363
|
break;
|
|
18229
18364
|
}
|
|
18230
18365
|
startIndex += pageSize;
|
|
@@ -18232,6 +18367,14 @@ async function fetchPaginated(term, options, maxResults, severityVersion, admitE
|
|
|
18232
18367
|
}
|
|
18233
18368
|
metrics.increment("nvd_pagination_requests_total", totalPagesFetched);
|
|
18234
18369
|
metrics.observe("nvd_pagination_pages_fetched", totalPagesFetched);
|
|
18370
|
+
allVulnerabilities.sort((a, b) => {
|
|
18371
|
+
const ta = a.published ? Date.parse(a.published) : NaN;
|
|
18372
|
+
const tb = b.published ? Date.parse(b.published) : NaN;
|
|
18373
|
+
if (Number.isNaN(ta) && Number.isNaN(tb)) return 0;
|
|
18374
|
+
if (Number.isNaN(ta)) return 1;
|
|
18375
|
+
if (Number.isNaN(tb)) return -1;
|
|
18376
|
+
return tb - ta;
|
|
18377
|
+
});
|
|
18235
18378
|
return allVulnerabilities.slice(0, maxResults);
|
|
18236
18379
|
}
|
|
18237
18380
|
function shouldSupplementWithV2(options) {
|
|
@@ -19242,7 +19385,14 @@ async function searchOSV(terms, options) {
|
|
|
19242
19385
|
uniqueVulns.set(vuln.id, vuln);
|
|
19243
19386
|
}
|
|
19244
19387
|
}
|
|
19245
|
-
vulnerabilities.push(...Array.from(uniqueVulns.values()))
|
|
19388
|
+
vulnerabilities.push(...Array.from(uniqueVulns.values()).sort((a, b) => {
|
|
19389
|
+
const ta = a.published ? Date.parse(a.published) : NaN;
|
|
19390
|
+
const tb = b.published ? Date.parse(b.published) : NaN;
|
|
19391
|
+
if (Number.isNaN(ta) && Number.isNaN(tb)) return 0;
|
|
19392
|
+
if (Number.isNaN(ta)) return 1;
|
|
19393
|
+
if (Number.isNaN(tb)) return -1;
|
|
19394
|
+
return tb - ta;
|
|
19395
|
+
}));
|
|
19246
19396
|
if (failures.length > 0 && failures.length === terms.length) {
|
|
19247
19397
|
throw new Error(`All OSV lookups failed: ${failures.join("; ")}`);
|
|
19248
19398
|
}
|
|
@@ -21549,8 +21699,6 @@ async function createResearcherSession(options) {
|
|
|
21549
21699
|
cwd
|
|
21550
21700
|
});
|
|
21551
21701
|
logger.info(`[Researcher] Using model for researcher ${researcherId}: ${modelToUse.provider}/${modelToUse.id}`);
|
|
21552
|
-
const outputCap = config?.RESEARCHER_MAX_TOKENS ?? 16384;
|
|
21553
|
-
const sessionModel = modelToUse.maxTokens > outputCap ? { ...modelToUse, maxTokens: outputCap } : modelToUse;
|
|
21554
21702
|
try {
|
|
21555
21703
|
const tracker = new ToolUsageTracker(createDefaultToolLimits(config));
|
|
21556
21704
|
const globalLinks = updateGlobalLinks || (() => {
|
|
@@ -21591,7 +21739,7 @@ async function createResearcherSession(options) {
|
|
|
21591
21739
|
sessionManager: SessionManager.inMemory(),
|
|
21592
21740
|
// Each researcher gets its own isolated session
|
|
21593
21741
|
settingsManager: researcherSettings,
|
|
21594
|
-
model:
|
|
21742
|
+
model: modelToUse,
|
|
21595
21743
|
// pi 0.80.8 replaced the SDK's `modelRegistry`/`authStorage` session options
|
|
21596
21744
|
// with the async `modelRuntime`. Pass the runtime backing OUR registry when
|
|
21597
21745
|
// this run built one (standalone CLI/SDK): it carries any explicit API key
|
|
@@ -21622,7 +21770,7 @@ async function createResearcherSession(options) {
|
|
|
21622
21770
|
throw new Error("Session creation returned invalid result");
|
|
21623
21771
|
}
|
|
21624
21772
|
sessionRef.session = result.session;
|
|
21625
|
-
return { session: result.session, resolvedModel:
|
|
21773
|
+
return { session: result.session, resolvedModel: modelToUse };
|
|
21626
21774
|
} catch (tailError) {
|
|
21627
21775
|
const session = result?.session;
|
|
21628
21776
|
if (session && typeof session.abort === "function") {
|
|
@@ -23091,6 +23239,17 @@ var ResearchOrchestrationService = class {
|
|
|
23091
23239
|
const { ctx, query, depth = 0, observer, onUpdate, sessionId, researchId, config, excludeTools } = options;
|
|
23092
23240
|
const researchConfig = config || getConfig(ctx.cwd);
|
|
23093
23241
|
const configDisabledTools = researchConfig.DISABLED_TOOLS ? researchConfig.DISABLED_TOOLS.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
23242
|
+
for (const [surface, names] of [
|
|
23243
|
+
["excludeTools", excludeTools ?? []],
|
|
23244
|
+
["PI_RESEARCH_DISABLED_TOOLS", configDisabledTools]
|
|
23245
|
+
]) {
|
|
23246
|
+
const unknown = names.filter((t) => !RESEARCH_TOOL_NAMES.includes(t));
|
|
23247
|
+
if (unknown.length > 0) {
|
|
23248
|
+
logger.warn(
|
|
23249
|
+
`[ResearchOrchestrationService] ${surface}: ignoring unknown tool name${unknown.length === 1 ? "" : "s"} ${unknown.map((t) => `"${t}"`).join(", ")} \u2014 these exclude nothing. Valid names: ${RESEARCH_TOOL_NAMES.join(", ")}.`
|
|
23250
|
+
);
|
|
23251
|
+
}
|
|
23252
|
+
}
|
|
23094
23253
|
const mergedExcludeTools = resolveExcludedTools(excludeTools, configDisabledTools);
|
|
23095
23254
|
const selectedModel = await this.resolveResearchModel(options);
|
|
23096
23255
|
logger.info(`[ResearchOrchestrationService] Using model: ${selectedModel.provider}/${selectedModel.id}`);
|
|
@@ -23104,6 +23263,13 @@ var ResearchOrchestrationService = class {
|
|
|
23104
23263
|
});
|
|
23105
23264
|
logger.info(`[ResearchOrchestrationService] Acquired research run slot ${runSlot.slotIndex} (cap ${semaphore.getMaxSlots()}).`);
|
|
23106
23265
|
} catch (err) {
|
|
23266
|
+
const isTerminal = err instanceof ResearchRunCapacityError || err instanceof Error && err.name === "AbortError";
|
|
23267
|
+
if (isTerminal) {
|
|
23268
|
+
try {
|
|
23269
|
+
observer?.onError?.(err);
|
|
23270
|
+
} catch {
|
|
23271
|
+
}
|
|
23272
|
+
}
|
|
23107
23273
|
if (err instanceof ResearchRunCapacityError) throw err;
|
|
23108
23274
|
if (err instanceof Error && err.name === "AbortError") throw err;
|
|
23109
23275
|
logger.warn(`[ResearchOrchestrationService] Run-cap unavailable, proceeding without it: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -23855,7 +24021,7 @@ Research performed using ${modelId}`;
|
|
|
23855
24021
|
if (globalId !== void 0) localToGlobal.set(localKey, globalId);
|
|
23856
24022
|
else droppedNumbers.add(localKey);
|
|
23857
24023
|
});
|
|
23858
|
-
let body = synthesis.slice(0,
|
|
24024
|
+
let body = synthesis.slice(0, lastCitedLinksHeader(synthesis).lineStart).trimEnd();
|
|
23859
24025
|
if (localToGlobal.size > 0 || droppedNumbers.size > 0) {
|
|
23860
24026
|
body = rewriteCitationMarkers(body, localToGlobal, (n) => droppedNumbers.has(n));
|
|
23861
24027
|
}
|
|
@@ -24502,6 +24668,12 @@ async function _doShutdown() {
|
|
|
24502
24668
|
logger.error("[SDK] Error clearing session state:", err);
|
|
24503
24669
|
errors.push(err instanceof Error ? err : new Error(String(err)));
|
|
24504
24670
|
}
|
|
24671
|
+
try {
|
|
24672
|
+
await disposeSsrfSafeFetcher();
|
|
24673
|
+
} catch (err) {
|
|
24674
|
+
logger.error("[SDK] Error disposing SSRF fetcher:", err);
|
|
24675
|
+
errors.push(err instanceof Error ? err : new Error(String(err)));
|
|
24676
|
+
}
|
|
24505
24677
|
if (globalContainer) {
|
|
24506
24678
|
try {
|
|
24507
24679
|
await disposeCoreServices(globalContainer);
|
|
@@ -24778,6 +24950,7 @@ Total cost: ${costStr}`;
|
|
|
24778
24950
|
// src/cli.ts
|
|
24779
24951
|
init_config();
|
|
24780
24952
|
init_model_registry_factory();
|
|
24953
|
+
init_constants();
|
|
24781
24954
|
import { getAgentDir as getAgentDir2 } from "@earendil-works/pi-coding-agent";
|
|
24782
24955
|
function readPkgVersion() {
|
|
24783
24956
|
try {
|
|
@@ -24898,14 +25071,15 @@ function makeProgressObserver() {
|
|
|
24898
25071
|
var ConfigFileError = class extends Error {
|
|
24899
25072
|
};
|
|
24900
25073
|
function bridgeConfigEnv(explicitConfigPath) {
|
|
24901
|
-
const
|
|
25074
|
+
const globalBasePath = getGlobalEnvFilePath();
|
|
25075
|
+
const basePath = explicitConfigPath ?? globalBasePath;
|
|
24902
25076
|
const cliOverlayPath = getInterfaceEnvFilePath("cli");
|
|
24903
25077
|
let loaded = false;
|
|
24904
25078
|
const merged = {};
|
|
24905
25079
|
const fromExplicitBase = /* @__PURE__ */ new Set();
|
|
24906
|
-
const layers = explicitConfigPath !== void 0 ? [cliOverlayPath,
|
|
25080
|
+
const layers = explicitConfigPath !== void 0 ? [globalBasePath, cliOverlayPath, explicitConfigPath] : [globalBasePath, cliOverlayPath];
|
|
24907
25081
|
for (const filePath of layers) {
|
|
24908
|
-
const isExplicit = explicitConfigPath !== void 0 && filePath ===
|
|
25082
|
+
const isExplicit = explicitConfigPath !== void 0 && filePath === explicitConfigPath;
|
|
24909
25083
|
try {
|
|
24910
25084
|
if (!fs17.existsSync(filePath)) {
|
|
24911
25085
|
if (isExplicit) throw new ConfigFileError(`--config file not found: ${filePath}`);
|
|
@@ -25042,7 +25216,11 @@ async function detectCredentials(explicitModel) {
|
|
|
25042
25216
|
modelFrom,
|
|
25043
25217
|
piAuthPresent,
|
|
25044
25218
|
piModelsPresent,
|
|
25045
|
-
|
|
25219
|
+
// A model configured with no usable key is a different (and misleading to
|
|
25220
|
+
// conflate) gap than nothing configured at all: SKILL.md tells agents to relay
|
|
25221
|
+
// this text verbatim, so "No model … is configured" pointed users who HAD set
|
|
25222
|
+
// PI_RESEARCH_MODEL at the wrong knob.
|
|
25223
|
+
problem: model ? `A model is configured (${model}) but no API key is available for ${provider ? `provider "${provider}"` : "its provider"}. Provide one via the PI_RESEARCH_API_KEY environment variable (with PI_RESEARCH_PROVIDER), or, if you use pi, via pi's configuration (a provider entry in ~/.pi/agent/auth.json, or a per-provider apiKey in ~/.pi/agent/models.json).` : "No model or API key is configured for pi-research. Provide credentials via environment variables (PI_RESEARCH_API_KEY + PI_RESEARCH_PROVIDER + PI_RESEARCH_MODEL) or, if you use pi, via pi's configuration (a provider entry in ~/.pi/agent/auth.json, or a per-provider apiKey in ~/.pi/agent/models.json)."
|
|
25046
25224
|
};
|
|
25047
25225
|
}
|
|
25048
25226
|
function configBlock(det, extraNote) {
|
|
@@ -25112,6 +25290,8 @@ Error: ${vmsg}
|
|
|
25112
25290
|
...args.excludeTools ? { excludeTools: args.excludeTools } : {},
|
|
25113
25291
|
...args.initialLinks ? { initialLinks: args.initialLinks } : {}
|
|
25114
25292
|
}, abortController.signal);
|
|
25293
|
+
const outcome = getLastResearcherOutcome();
|
|
25294
|
+
const errorReport = getLastErrorReport();
|
|
25115
25295
|
let reportPath = null;
|
|
25116
25296
|
const exportCfg = getConfig(process.cwd(), "cli");
|
|
25117
25297
|
if (exportCfg.RESEARCH_REPORT_EXPORT_ENABLED) {
|
|
@@ -25126,8 +25306,6 @@ Error: ${vmsg}
|
|
|
25126
25306
|
exit = cancellationSignal ? exitCodeForSignal(cancellationSignal) : EXIT.CANCELLED;
|
|
25127
25307
|
}
|
|
25128
25308
|
if (args.json) {
|
|
25129
|
-
const outcome = getLastResearcherOutcome();
|
|
25130
|
-
const errorReport = getLastErrorReport();
|
|
25131
25309
|
toStdout(pretty({
|
|
25132
25310
|
ok: true,
|
|
25133
25311
|
depth,
|
|
@@ -25204,8 +25382,11 @@ Configure in:
|
|
|
25204
25382
|
if (activeResearchAbortController === abortController) activeResearchAbortController = null;
|
|
25205
25383
|
await safeShutdown();
|
|
25206
25384
|
}
|
|
25385
|
+
if (cancellationRequested) {
|
|
25386
|
+
exit = cancellationSignal ? exitCodeForSignal(cancellationSignal) : EXIT.CANCELLED;
|
|
25387
|
+
}
|
|
25207
25388
|
if (json) {
|
|
25208
|
-
toStdout(pretty({ ok: true, ...result }));
|
|
25389
|
+
toStdout(pretty({ ok: true, ...cancellationRequested ? { cancelled: true } : {}, ...result }));
|
|
25209
25390
|
} else {
|
|
25210
25391
|
toStdout(result.text.endsWith("\n") ? result.text : result.text + "\n");
|
|
25211
25392
|
}
|
|
@@ -25215,6 +25396,7 @@ async function cmdStatus(json) {
|
|
|
25215
25396
|
const det = await detectCredentials();
|
|
25216
25397
|
const paths = resolvedConfigPaths();
|
|
25217
25398
|
const cfg = getConfig(process.cwd(), "cli");
|
|
25399
|
+
const browserInstalled = isBrowserAvailable();
|
|
25218
25400
|
const summary = {
|
|
25219
25401
|
package: "@lincoln504/pi-research",
|
|
25220
25402
|
version: PKG_VERSION,
|
|
@@ -25228,6 +25410,12 @@ async function cmdStatus(json) {
|
|
|
25228
25410
|
modelFrom: det.modelFrom,
|
|
25229
25411
|
problem: det.problem ?? null
|
|
25230
25412
|
},
|
|
25413
|
+
browser: {
|
|
25414
|
+
installed: browserInstalled,
|
|
25415
|
+
// Same remedy the health check prints, so both surfaces name one command.
|
|
25416
|
+
fix: browserInstalled ? null : "npx camoufox-js fetch",
|
|
25417
|
+
note: browserInstalled ? null : "Stealth browser not installed \u2014 scraping falls back to plain fetch, which some sites block. npm 11.19+ blocks install scripts by default, which skips this package's postinstall."
|
|
25418
|
+
},
|
|
25231
25419
|
knowledgeStoreMode: cfg.KNOWLEDGE_STORE_MODE,
|
|
25232
25420
|
defaultDepth: cfg.DEFAULT_RESEARCH_DEPTH,
|
|
25233
25421
|
paths
|
|
@@ -25242,6 +25430,13 @@ async function cmdStatus(json) {
|
|
|
25242
25430
|
|
|
25243
25431
|
`);
|
|
25244
25432
|
toStdout(configBlock(det, summary.ready ? void 0 : "\u2014 fix the problem above, then re-run.") + "\n");
|
|
25433
|
+
if (!summary.browser.installed) {
|
|
25434
|
+
toStdout(`
|
|
25435
|
+
stealth browser: NOT INSTALLED \u2014 run: ${summary.browser.fix}
|
|
25436
|
+
`);
|
|
25437
|
+
toStdout(` ${summary.browser.note}
|
|
25438
|
+
`);
|
|
25439
|
+
}
|
|
25245
25440
|
toStdout(`
|
|
25246
25441
|
knowledge store mode: ${cfg.KNOWLEDGE_STORE_MODE} (default depth: ${cfg.DEFAULT_RESEARCH_DEPTH})
|
|
25247
25442
|
`);
|
|
@@ -25440,7 +25635,7 @@ async function reportError(err, what, json) {
|
|
|
25440
25635
|
const isRateLimit = !isCancellation && (lower.includes("429") || lower.includes("rate limit") || lower.includes("too many requests"));
|
|
25441
25636
|
const exitCode = isCancellation ? cancellationSignal ? exitCodeForSignal(cancellationSignal) : EXIT.CANCELLED : isCapacity ? EXIT.TEMPFAIL : isConfigError && !isRateLimit ? EXIT.CONFIG : EXIT.SOFTWARE;
|
|
25442
25637
|
if (json) {
|
|
25443
|
-
const payload = { ok: false, error: msg2, exitCode };
|
|
25638
|
+
const payload = isCancellation ? { ok: false, error: `pi-research ${what} cancelled.`, cause: msg2, exitCode } : { ok: false, error: msg2, exitCode };
|
|
25444
25639
|
if ((isCapacity || isRateLimit) && !isCancellation) payload["retryable"] = true;
|
|
25445
25640
|
if (isCancellation) payload["cancelled"] = true;
|
|
25446
25641
|
if (process.env["PI_RESEARCH_DEBUG"] === "true" && err instanceof Error && err.stack) {
|
|
@@ -25540,6 +25735,7 @@ COMMANDS
|
|
|
25540
25735
|
--dry-run | --json Plan only; JSON output.
|
|
25541
25736
|
|
|
25542
25737
|
help, --help, -h Show this help.
|
|
25738
|
+
--version, -v Print the pi-research version.
|
|
25543
25739
|
|
|
25544
25740
|
CONFIGURE
|
|
25545
25741
|
Credentials are resolved in this order (first match wins):
|
|
@@ -25729,6 +25925,12 @@ function parseArgs(argv) {
|
|
|
25729
25925
|
} else if (a === "--exclude-tools") {
|
|
25730
25926
|
const v = takeOptionValue(rest, ++i, "--exclude-tools");
|
|
25731
25927
|
excludeTools = v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
25928
|
+
const unknown = excludeTools.filter((t) => !RESEARCH_TOOL_NAMES.includes(t));
|
|
25929
|
+
if (unknown.length > 0) {
|
|
25930
|
+
throw new UsageError(
|
|
25931
|
+
`--exclude-tools: unknown tool name${unknown.length === 1 ? "" : "s"} ${unknown.map((t) => `"${t}"`).join(", ")}. Valid names: ${RESEARCH_TOOL_NAMES.join(", ")}.`
|
|
25932
|
+
);
|
|
25933
|
+
}
|
|
25732
25934
|
if (excludeTools.length === 0) excludeTools = void 0;
|
|
25733
25935
|
} else if (a === "--initial-links") {
|
|
25734
25936
|
let j = i + 1;
|
|
@@ -25742,6 +25944,7 @@ function parseArgs(argv) {
|
|
|
25742
25944
|
}
|
|
25743
25945
|
j++;
|
|
25744
25946
|
}
|
|
25947
|
+
if (j === i + 1) throw new UsageError("--initial-links requires at least one URL.");
|
|
25745
25948
|
i = j - 1;
|
|
25746
25949
|
} else if (a === "--config") {
|
|
25747
25950
|
configPath = takeOptionValue(rest, ++i, "--config");
|