@lincoln504/pi-research 1.0.6 → 1.0.8
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/dist/cli.mjs +195 -36
- package/dist/prompts/research-tool-usage.md +1 -0
- package/dist/thread-worker.mjs +2 -0
- package/docs/ARCHITECTURE.md +19 -4
- package/docs/CHANGELOG.md +108 -0
- package/docs/CONFIGURATION.md +1 -0
- package/docs/SDK.md +4 -1
- package/package.json +6 -5
- package/skills/pi-research/SKILL.md +1 -1
- package/src/cli.ts +53 -4
- package/src/config.ts +4 -0
- package/src/index.ts +2 -0
- package/src/infrastructure/browser/task-execution-service.ts +15 -3
- package/src/infrastructure/browser/thread-worker.mjs +2 -0
- package/src/orchestration/research-orchestration-service.ts +6 -6
- package/src/orchestration/research-synthesis-service.ts +31 -20
- package/src/orchestration/researcher-executor.ts +51 -13
- package/src/orchestration/session-state.ts +27 -4
- package/src/prompts/research-tool-usage.md +1 -0
- package/src/sdk.ts +57 -4
- package/src/youtube/index.ts +5 -4
package/dist/cli.mjs
CHANGED
|
@@ -2295,6 +2295,7 @@ function saveConfig(config, scope = "local", cwd = process.cwd(), changedKeys) {
|
|
|
2295
2295
|
PI_RESEARCH_MAX_RESEARCHERS: String(config.MAX_CONCURRENT_RESEARCHERS),
|
|
2296
2296
|
PI_RESEARCH_MAX_RETRIES: String(config.RESEARCHER_MAX_RETRIES),
|
|
2297
2297
|
PI_RESEARCH_RETRY_DELAY_MS: String(config.RESEARCHER_MAX_RETRY_DELAY_MS),
|
|
2298
|
+
PI_RESEARCH_MAX_FAILED_RESEARCHERS: String(config.MAX_FAILED_RESEARCHERS ?? DEFAULTS.MAX_FAILED_RESEARCHERS),
|
|
2298
2299
|
PI_RESEARCH_HEALTH_CHECK_TIMEOUT_MS: String(config.HEALTH_CHECK_TIMEOUT_MS ?? DEFAULTS.HEALTH_CHECK_TIMEOUT_MS),
|
|
2299
2300
|
PI_RESEARCH_SEARCH_TIMEOUT_MS: String(config.SEARCH_TIMEOUT_MS),
|
|
2300
2301
|
PI_RESEARCH_TUI_REFRESH_DEBOUNCE_MS: String(config.TUI_REFRESH_DEBOUNCE_MS),
|
|
@@ -2457,6 +2458,7 @@ function createConfig(env, processEnv) {
|
|
|
2457
2458
|
MAX_CONCURRENT_RESEARCHERS: parseEnvNumber(e, "PI_RESEARCH_MAX_RESEARCHERS", DEFAULTS.MAX_CONCURRENT_RESEARCHERS, 1, 5, true),
|
|
2458
2459
|
RESEARCHER_MAX_RETRIES: parseEnvNumber(e, "PI_RESEARCH_MAX_RETRIES", DEFAULTS.RESEARCHER_MAX_RETRIES, 0, 5, true),
|
|
2459
2460
|
RESEARCHER_MAX_RETRY_DELAY_MS: parseEnvNumber(e, "PI_RESEARCH_RETRY_DELAY_MS", DEFAULTS.RESEARCHER_MAX_RETRY_DELAY_MS, 100, 1e4),
|
|
2461
|
+
MAX_FAILED_RESEARCHERS: parseEnvNumber(e, "PI_RESEARCH_MAX_FAILED_RESEARCHERS", DEFAULTS.MAX_FAILED_RESEARCHERS, 1, 10, true),
|
|
2460
2462
|
DEFAULT_RESEARCH_DEPTH: parseEnvNumber(e, "PI_RESEARCH_DEFAULT_RESEARCH_DEPTH", DEFAULTS.DEFAULT_RESEARCH_DEPTH, 1, 3, true),
|
|
2461
2463
|
MAX_SCRAPE_BATCHES: parseEnvNumber(e, "PI_RESEARCH_MAX_SCRAPE_BATCHES", DEFAULTS.MAX_SCRAPE_BATCHES, 0, 99, true),
|
|
2462
2464
|
MAX_GATHERING_CALLS: parseEnvNumber(e, "PI_RESEARCH_MAX_GATHERING_CALLS", DEFAULTS.MAX_GATHERING_CALLS, 1, 100, true),
|
|
@@ -2580,6 +2582,8 @@ var init_config = __esm({
|
|
|
2580
2582
|
RESEARCHER_MAX_RETRIES: Type.Number({ minimum: 0, maximum: 5, default: 2 }),
|
|
2581
2583
|
/** Base delay between retries in milliseconds (default: 2000, range: 100-10000) */
|
|
2582
2584
|
RESEARCHER_MAX_RETRY_DELAY_MS: Type.Number({ minimum: 100, maximum: 1e4, default: 2e3 }),
|
|
2585
|
+
/** Unique researcher failures that abort the entire run (default: 2, range: 1-10) */
|
|
2586
|
+
MAX_FAILED_RESEARCHERS: Type.Number({ minimum: 1, maximum: 10, default: 2 }),
|
|
2583
2587
|
/** Target depth for recursive research (default: 1, range: 1-3) */
|
|
2584
2588
|
DEFAULT_RESEARCH_DEPTH: Type.Number({ minimum: 1, maximum: 3, default: 1 }),
|
|
2585
2589
|
/** Number of batches to allow for a single scrape tool call (default: 2, 0=unlimited) */
|
|
@@ -14650,7 +14654,7 @@ async function runBrowserTask(taskOrUrl, type = "scrape", config, signal, retrie
|
|
|
14650
14654
|
if (now - lastRestartTime > RESTART_COOLDOWN_MS) {
|
|
14651
14655
|
lastRestartTime = now;
|
|
14652
14656
|
logger.warn(`[BrowserManager] Forcing scheduler restart and retrying...`);
|
|
14653
|
-
await forceSchedulerRestart2(
|
|
14657
|
+
await forceSchedulerRestart2(false, container);
|
|
14654
14658
|
await waitForBrowserPoolIdle(15e3).catch((err) => logger.debug("Wait for browser idle timed out or failed:", err));
|
|
14655
14659
|
} else {
|
|
14656
14660
|
logger.warn(`[BrowserManager] Scheduler restart recently triggered, waiting for pool idle...`);
|
|
@@ -14685,7 +14689,7 @@ async function runBrowserHealthCheck(config, retries = 1, signal, container = ge
|
|
|
14685
14689
|
if (now - lastRestartTime > RESTART_COOLDOWN_MS) {
|
|
14686
14690
|
lastRestartTime = now;
|
|
14687
14691
|
logger.warn(`[BrowserManager] Forcing scheduler restart and retrying...`);
|
|
14688
|
-
await forceSchedulerRestart2(
|
|
14692
|
+
await forceSchedulerRestart2(false, container);
|
|
14689
14693
|
await waitForBrowserPoolIdle(15e3).catch((err) => logger.debug("Wait for browser idle timed out or failed:", err));
|
|
14690
14694
|
} else {
|
|
14691
14695
|
await waitForBrowserPoolIdle(15e3).catch((err) => logger.debug("Wait for browser idle timed out or failed:", err));
|
|
@@ -14724,7 +14728,7 @@ async function runWorkerSearch(query, config, signal, retries = 1, sessionId, co
|
|
|
14724
14728
|
if (now - lastRestartTime > RESTART_COOLDOWN_MS) {
|
|
14725
14729
|
lastRestartTime = now;
|
|
14726
14730
|
logger.warn(`[BrowserManager] Forcing scheduler restart and retrying...`);
|
|
14727
|
-
await forceSchedulerRestart2(
|
|
14731
|
+
await forceSchedulerRestart2(false, container);
|
|
14728
14732
|
await waitForBrowserPoolIdle(15e3).catch((err) => logger.debug("Wait for browser idle timed out or failed:", err));
|
|
14729
14733
|
} else {
|
|
14730
14734
|
await waitForBrowserPoolIdle(15e3).catch((err) => logger.debug("Wait for browser idle timed out or failed:", err));
|
|
@@ -19445,10 +19449,8 @@ function formatResults(results, rejected) {
|
|
|
19445
19449
|
if (typeof r.charCount === "number") meta.push(`${r.charCount.toLocaleString()} chars`);
|
|
19446
19450
|
if (meta.length) md += `**${meta.join(" \xB7 ")}**
|
|
19447
19451
|
`;
|
|
19448
|
-
|
|
19449
|
-
md += `**Cite as:** ${r.url} \u2014 '${r.title}'${r.author ? ` by ${r.author}` : ""}
|
|
19452
|
+
md += `**Cite as:** ${r.url}${r.title ? ` \u2014 '${r.title}'${r.author ? ` by ${r.author}` : ""}` : ""}
|
|
19450
19453
|
`;
|
|
19451
|
-
}
|
|
19452
19454
|
md += `
|
|
19453
19455
|
${r.text}
|
|
19454
19456
|
|
|
@@ -20108,10 +20110,10 @@ function getFailedResearchers(piSessionId, researchId) {
|
|
|
20108
20110
|
const failures = state.failures.get(researchId) || [];
|
|
20109
20111
|
return [...new Set(failures)];
|
|
20110
20112
|
}
|
|
20111
|
-
var
|
|
20112
|
-
function shouldStopResearch(piSessionId, researchId) {
|
|
20113
|
+
var DEFAULT_MAX_FAILED_RESEARCHERS = 2;
|
|
20114
|
+
function shouldStopResearch(piSessionId, researchId, maxFailedResearchers = DEFAULT_MAX_FAILED_RESEARCHERS) {
|
|
20113
20115
|
const sid = normalizeSessionId(piSessionId);
|
|
20114
|
-
return getFailedResearchers(sid, researchId).length >=
|
|
20116
|
+
return getFailedResearchers(sid, researchId).length >= maxFailedResearchers;
|
|
20115
20117
|
}
|
|
20116
20118
|
function getResearchStopMessage(piSessionId, researchId) {
|
|
20117
20119
|
const sid = normalizeSessionId(piSessionId);
|
|
@@ -20137,6 +20139,12 @@ function getResearchStopMessage(piSessionId, researchId) {
|
|
|
20137
20139
|
"Partial results may be available below."
|
|
20138
20140
|
].join("\n");
|
|
20139
20141
|
}
|
|
20142
|
+
var RESEARCH_STOPPED_ERROR_CODE = "RESEARCH_STOPPED";
|
|
20143
|
+
function createResearchStopError(piSessionId, researchId) {
|
|
20144
|
+
const err = new Error(getResearchStopMessage(piSessionId, researchId));
|
|
20145
|
+
err.code = RESEARCH_STOPPED_ERROR_CODE;
|
|
20146
|
+
return err;
|
|
20147
|
+
}
|
|
20140
20148
|
|
|
20141
20149
|
// src/orchestration/researcher-executor.ts
|
|
20142
20150
|
async function runResearcher(options) {
|
|
@@ -20171,18 +20179,42 @@ ${currentPlan.allQueries.map((q) => `- ${q}`).join("\n")}
|
|
|
20171
20179
|
Previous summary: ${e.description}`).join("\n");
|
|
20172
20180
|
}
|
|
20173
20181
|
const researcherPromptTemplate = loadPrompt("researcher");
|
|
20174
|
-
|
|
20175
|
-
|
|
20176
|
-
|
|
20177
|
-
|
|
20178
|
-
|
|
20179
|
-
|
|
20182
|
+
let effectiveInitialLinks = initialLinks;
|
|
20183
|
+
if (effectiveInitialLinks.length === 0 && historicalUrls.length === 0) {
|
|
20184
|
+
const retryQueries = researcherConfig.queries?.length ? researcherConfig.queries : [researcherConfig.goal];
|
|
20185
|
+
const maxLinkAttempts = config.RESEARCHER_MAX_RETRIES + 1;
|
|
20186
|
+
for (let linkAttempt = 1; linkAttempt <= maxLinkAttempts && effectiveInitialLinks.length === 0; linkAttempt++) {
|
|
20187
|
+
if (linkAttempt > 1) {
|
|
20188
|
+
if (signal?.aborted || container?.isDisposing) break;
|
|
20189
|
+
const delay2 = Math.min(1e3 * Math.pow(2, linkAttempt - 2), config.RESEARCHER_MAX_RETRY_DELAY_MS);
|
|
20190
|
+
logger.warn(`[ResearcherExecutor] Researcher ${id} has no initial search results; retry ${linkAttempt - 1}/${config.RESEARCHER_MAX_RETRIES} after ${delay2}ms`);
|
|
20191
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
20192
|
+
}
|
|
20193
|
+
if (signal?.aborted || container?.isDisposing) break;
|
|
20194
|
+
try {
|
|
20195
|
+
const queryResults = await search(retryQueries, config, signal, void 0, container);
|
|
20196
|
+
const urls = /* @__PURE__ */ new Set();
|
|
20197
|
+
for (const qr of queryResults) {
|
|
20198
|
+
for (const r of qr.results || []) urls.add(r.url);
|
|
20199
|
+
}
|
|
20200
|
+
effectiveInitialLinks = [...urls];
|
|
20201
|
+
} catch (err) {
|
|
20202
|
+
logger.debug(`[ResearcherExecutor] Researcher ${id} link-retry search burst failed:`, err);
|
|
20203
|
+
}
|
|
20204
|
+
}
|
|
20205
|
+
if (effectiveInitialLinks.length === 0) {
|
|
20206
|
+
logger.warn(`[ResearcherExecutor] Researcher ${id} has no initial search results or historical links after ${maxLinkAttempts} attempt(s); skipping.`);
|
|
20207
|
+
recordResearcherFailure(sessionId, researchId, id, "no initial search results or historical links (search produced nothing usable)");
|
|
20208
|
+
metrics.increment("researcher_skipped_total", 1, { mode: "deep", complexity: String(complexity), reason: "no_initial_links" });
|
|
20209
|
+
observer?.onResearcherFailure?.(id, "No initial search results or historical links available");
|
|
20210
|
+
return;
|
|
20211
|
+
}
|
|
20180
20212
|
}
|
|
20181
20213
|
let evidenceSection = "";
|
|
20182
|
-
if (
|
|
20214
|
+
if (effectiveInitialLinks.length > 0) {
|
|
20183
20215
|
evidenceSection = `## Evidence Provided
|
|
20184
20216
|
Initial search results provided the following URLs to investigate:
|
|
20185
|
-
${
|
|
20217
|
+
${effectiveInitialLinks.map((l) => `- ${l}`).join("\n")}`;
|
|
20186
20218
|
}
|
|
20187
20219
|
const maxAttempts = config.RESEARCHER_MAX_RETRIES + 1;
|
|
20188
20220
|
let lastError;
|
|
@@ -21414,12 +21446,13 @@ var ResearchOrchestrationService = class {
|
|
|
21414
21446
|
const researchers = plan.researchers || [];
|
|
21415
21447
|
const active = /* @__PURE__ */ new Set();
|
|
21416
21448
|
const maxConcurrent = orchestratorOptions.config?.MAX_CONCURRENT_RESEARCHERS ?? DEFAULTS.MAX_CONCURRENT_RESEARCHERS;
|
|
21449
|
+
const maxFailedResearchers = orchestratorOptions.config?.MAX_FAILED_RESEARCHERS ?? DEFAULTS.MAX_FAILED_RESEARCHERS;
|
|
21417
21450
|
for (const configItem of researchers) {
|
|
21418
21451
|
if (signal?.aborted) break;
|
|
21419
|
-
if (shouldStopResearch(sessionId, researchId)) {
|
|
21452
|
+
if (shouldStopResearch(sessionId, researchId, maxFailedResearchers)) {
|
|
21420
21453
|
const sessionService = await getService(ServiceNames.RESEARCH_SESSION_SERVICE, ctx, container);
|
|
21421
21454
|
await sessionService.abortAllSessions(researchId);
|
|
21422
|
-
throw
|
|
21455
|
+
throw createResearchStopError(sessionId, researchId);
|
|
21423
21456
|
}
|
|
21424
21457
|
if (active.size >= maxConcurrent) {
|
|
21425
21458
|
if (signal?.aborted) break;
|
|
@@ -21488,11 +21521,10 @@ var ResearchOrchestrationService = class {
|
|
|
21488
21521
|
});
|
|
21489
21522
|
if (signal?.aborted) break;
|
|
21490
21523
|
}
|
|
21491
|
-
if (shouldStopResearch(sessionId, researchId)) {
|
|
21524
|
+
if (shouldStopResearch(sessionId, researchId, maxFailedResearchers)) {
|
|
21492
21525
|
const sessionService = await getService(ServiceNames.RESEARCH_SESSION_SERVICE, ctx, container);
|
|
21493
21526
|
await sessionService.abortAllSessions(researchId);
|
|
21494
|
-
|
|
21495
|
-
throw new Error(stopMessage);
|
|
21527
|
+
throw createResearchStopError(sessionId, researchId);
|
|
21496
21528
|
}
|
|
21497
21529
|
}
|
|
21498
21530
|
await Promise.all(active);
|
|
@@ -21889,18 +21921,26 @@ Research performed using ${modelId}`;
|
|
|
21889
21921
|
ensureCitedLinks(sessionId, synthesis) {
|
|
21890
21922
|
const reports = this.getSessionReports(sessionId);
|
|
21891
21923
|
let globalCitations = reports.size > 0 ? normalizeCitations(reports).globalCitations : [];
|
|
21892
|
-
if (
|
|
21924
|
+
if (reports.size > 0) {
|
|
21893
21925
|
const scraped = getScrapedLinks(sessionId);
|
|
21894
21926
|
if (scraped.length > 0) {
|
|
21895
|
-
|
|
21896
|
-
|
|
21897
|
-
|
|
21898
|
-
|
|
21899
|
-
|
|
21900
|
-
|
|
21901
|
-
|
|
21902
|
-
|
|
21903
|
-
|
|
21927
|
+
const known = new Set(globalCitations.map((c) => normalizeUrl(c.url)));
|
|
21928
|
+
const missing = scraped.filter((url) => !known.has(normalizeUrl(url)));
|
|
21929
|
+
if (missing.length > 0) {
|
|
21930
|
+
const startId = globalCitations.reduce((max, c) => Math.max(max, c.id), 0) + 1;
|
|
21931
|
+
globalCitations = [
|
|
21932
|
+
...globalCitations,
|
|
21933
|
+
...missing.map((url, i) => ({
|
|
21934
|
+
id: startId + i,
|
|
21935
|
+
url,
|
|
21936
|
+
description: "",
|
|
21937
|
+
source: isTranscribedLink(sessionId, url) ? "YouTube Transcript" : "Scrape"
|
|
21938
|
+
}))
|
|
21939
|
+
];
|
|
21940
|
+
logger.warn(
|
|
21941
|
+
`[ResearchSynthesisService] Folded in ${missing.length} source(s) from scrape/transcript provenance not covered by parsed citations.`
|
|
21942
|
+
);
|
|
21943
|
+
}
|
|
21904
21944
|
}
|
|
21905
21945
|
}
|
|
21906
21946
|
if (globalCitations.length > 0) {
|
|
@@ -22114,6 +22154,79 @@ init_metrics();
|
|
|
22114
22154
|
|
|
22115
22155
|
// src/utils/metrics-summary.ts
|
|
22116
22156
|
init_text_utils();
|
|
22157
|
+
function sumCounter(counters, baseName) {
|
|
22158
|
+
let total = 0;
|
|
22159
|
+
for (const [key, value] of Object.entries(counters)) {
|
|
22160
|
+
if (key === baseName || key.startsWith(baseName + "{")) {
|
|
22161
|
+
total += value;
|
|
22162
|
+
}
|
|
22163
|
+
}
|
|
22164
|
+
return total;
|
|
22165
|
+
}
|
|
22166
|
+
function getLabeledCounter(counters, baseName, labels) {
|
|
22167
|
+
const labelParts = Object.entries(labels).map(([k, v]) => `${k}="${v}"`);
|
|
22168
|
+
const suffix = `{${labelParts.join(",")}}`;
|
|
22169
|
+
for (const [key, value] of Object.entries(counters)) {
|
|
22170
|
+
if (key === `${baseName}${suffix}`) {
|
|
22171
|
+
return value;
|
|
22172
|
+
}
|
|
22173
|
+
}
|
|
22174
|
+
return 0;
|
|
22175
|
+
}
|
|
22176
|
+
function extractRunStats(snapshot) {
|
|
22177
|
+
const counters = snapshot.counters || {};
|
|
22178
|
+
const histograms = snapshot.histograms || {};
|
|
22179
|
+
const researchersLaunched = sumCounter(counters, "researchers_launched_total");
|
|
22180
|
+
const browserSearchQueries = sumCounter(counters, "browser_search_queries_total");
|
|
22181
|
+
const searchQueries = sumCounter(counters, "search_queries_total") + browserSearchQueries;
|
|
22182
|
+
const urlsDiscovered = sumCounter(counters, "browser_search_results_total") + sumCounter(counters, "browser_search_unique_urls_total");
|
|
22183
|
+
const fetchSuccess = getLabeledCounter(counters, "scrape_results_total", { outcome: "fetch_success" });
|
|
22184
|
+
const browserSuccess = getLabeledCounter(counters, "scrape_results_total", { outcome: "browser_success" });
|
|
22185
|
+
const totalFailure = getLabeledCounter(counters, "scrape_results_total", { outcome: "total_failure" });
|
|
22186
|
+
const browserFallbacks = sumCounter(counters, "scrape_layer_fallbacks_total");
|
|
22187
|
+
const urlsAnalyzed = fetchSuccess + browserSuccess;
|
|
22188
|
+
const urlsFailed = totalFailure;
|
|
22189
|
+
const toolUsage = {
|
|
22190
|
+
searches: sumCounter(counters, "tool_search_calls_total"),
|
|
22191
|
+
scrapes: sumCounter(counters, "tool_scrape_calls_total"),
|
|
22192
|
+
securitySearches: sumCounter(counters, "tool_security_search_calls_total"),
|
|
22193
|
+
stackexchangeQueries: sumCounter(counters, "stackexchange_requests_total"),
|
|
22194
|
+
knowledgeLookups: sumCounter(counters, "research_knowledge_search_total")
|
|
22195
|
+
};
|
|
22196
|
+
const errors = sumCounter(counters, "researcher_errors_total") + sumCounter(counters, "llm_api_errors_total");
|
|
22197
|
+
const tokens = sumCounter(counters, "llm_tokens_total");
|
|
22198
|
+
const cost = sumCounter(counters, "llm_cost_total");
|
|
22199
|
+
const durationMs = histograms['research_session_duration_ms{mode="deep",complexity="1",status="success"}']?.max || histograms['research_session_duration_ms{mode="deep",complexity="2",status="success"}']?.max || histograms['research_session_duration_ms{mode="deep",complexity="3",status="success"}']?.max || histograms['research_session_duration_ms{mode="quick",complexity="0",status="success"}']?.max || 0;
|
|
22200
|
+
const roundsCompleted = sumCounter(counters, "evaluator_runs_total");
|
|
22201
|
+
let complexity = 1;
|
|
22202
|
+
for (const key of Object.keys(counters)) {
|
|
22203
|
+
if (key.startsWith("research_sessions_total{")) {
|
|
22204
|
+
const match = key.match(/complexity="(\d+)"/);
|
|
22205
|
+
if (match?.[1]) complexity = parseInt(match[1], 10);
|
|
22206
|
+
break;
|
|
22207
|
+
}
|
|
22208
|
+
}
|
|
22209
|
+
if (researchersLaunched === 0 && searchQueries === 0 && urlsAnalyzed === 0 && tokens === 0) {
|
|
22210
|
+
return null;
|
|
22211
|
+
}
|
|
22212
|
+
return {
|
|
22213
|
+
durationMs,
|
|
22214
|
+
complexity,
|
|
22215
|
+
researchersLaunched,
|
|
22216
|
+
roundsCompleted,
|
|
22217
|
+
searchQueries,
|
|
22218
|
+
urlsDiscovered,
|
|
22219
|
+
fetchSuccess,
|
|
22220
|
+
browserSuccess,
|
|
22221
|
+
browserFallbacks,
|
|
22222
|
+
urlsAnalyzed,
|
|
22223
|
+
urlsFailed,
|
|
22224
|
+
errors,
|
|
22225
|
+
tokens,
|
|
22226
|
+
cost,
|
|
22227
|
+
toolUsage
|
|
22228
|
+
};
|
|
22229
|
+
}
|
|
22117
22230
|
|
|
22118
22231
|
// src/sdk.ts
|
|
22119
22232
|
init_error_tracker();
|
|
@@ -22135,6 +22248,7 @@ var _isRunning = false;
|
|
|
22135
22248
|
var _lastResearchId = null;
|
|
22136
22249
|
var _lastRunSummary = null;
|
|
22137
22250
|
var _lastErrorReport = null;
|
|
22251
|
+
var _lastResearcherOutcome = null;
|
|
22138
22252
|
var _onSignal = null;
|
|
22139
22253
|
var _sigintHandler = null;
|
|
22140
22254
|
var _sigtermHandler = null;
|
|
@@ -22321,6 +22435,7 @@ async function runDeepResearch(query, options = {}, signal) {
|
|
|
22321
22435
|
metrics.recordRunSummary(_lastRunSummary);
|
|
22322
22436
|
_lastErrorReport = runTracker.getReport();
|
|
22323
22437
|
logRunErrorSummary(_lastErrorReport, depthLabel, "success");
|
|
22438
|
+
_lastResearcherOutcome = await captureResearcherOutcome(sessionId, researchId, _lastRunSummary.snapshot);
|
|
22324
22439
|
return result;
|
|
22325
22440
|
} catch (err) {
|
|
22326
22441
|
const completedAt = Date.now();
|
|
@@ -22336,6 +22451,7 @@ async function runDeepResearch(query, options = {}, signal) {
|
|
|
22336
22451
|
metrics.recordRunSummary(_lastRunSummary);
|
|
22337
22452
|
_lastErrorReport = runTracker.getReport();
|
|
22338
22453
|
logRunErrorSummary(_lastErrorReport, depthLabel, "error");
|
|
22454
|
+
_lastResearcherOutcome = await captureResearcherOutcome(sessionId, researchId, _lastRunSummary.snapshot);
|
|
22339
22455
|
throw err;
|
|
22340
22456
|
} finally {
|
|
22341
22457
|
try {
|
|
@@ -22345,11 +22461,35 @@ async function runDeepResearch(query, options = {}, signal) {
|
|
|
22345
22461
|
_isRunning = false;
|
|
22346
22462
|
}
|
|
22347
22463
|
}
|
|
22464
|
+
function getLastErrorReport() {
|
|
22465
|
+
return _lastErrorReport;
|
|
22466
|
+
}
|
|
22467
|
+
function getLastResearcherOutcome() {
|
|
22468
|
+
return _lastResearcherOutcome;
|
|
22469
|
+
}
|
|
22348
22470
|
function logRunErrorSummary(report, depthLabel, status) {
|
|
22349
22471
|
if (!report || report.totalErrors === 0) return;
|
|
22350
22472
|
const top = report.patterns.slice(0, 3).map((p) => `"${String(p.message ?? p.signature ?? "error").slice(0, 48)}" \xD7${p.count}`).join(", ");
|
|
22351
22473
|
logger.warn(`[SDK] run ${status} with ${report.totalErrors} tracked error(s) across ${report.uniquePatterns} pattern(s) [${depthLabel}]${top ? ` \u2014 top: ${top}` : ""}`);
|
|
22352
22474
|
}
|
|
22475
|
+
async function captureResearcherOutcome(sessionId, researchId, snapshot) {
|
|
22476
|
+
try {
|
|
22477
|
+
const planningService = await getService(ServiceNames.PLANNING, void 0, globalContainer);
|
|
22478
|
+
const planned = planningService.getTotalResearchersPlanned(researchId);
|
|
22479
|
+
const launched = extractRunStats(snapshot)?.researchersLaunched ?? 0;
|
|
22480
|
+
const failed = getFailedResearchers(sessionId, researchId);
|
|
22481
|
+
return {
|
|
22482
|
+
planned,
|
|
22483
|
+
launched,
|
|
22484
|
+
succeeded: Math.max(0, launched - failed.length),
|
|
22485
|
+
failed: failed.length,
|
|
22486
|
+
failureReasons: getResearcherFailureReasons(sessionId, researchId)
|
|
22487
|
+
};
|
|
22488
|
+
} catch (err) {
|
|
22489
|
+
logger.debug("[SDK] Failed to capture researcher outcome (non-fatal):", err);
|
|
22490
|
+
return null;
|
|
22491
|
+
}
|
|
22492
|
+
}
|
|
22353
22493
|
async function searchKnowledge(queries, signal) {
|
|
22354
22494
|
if (!isInitialized || !globalContainer) {
|
|
22355
22495
|
throw new Error("SDK not initialized. Call initResearchSDK() first.");
|
|
@@ -22433,6 +22573,7 @@ async function _doShutdown() {
|
|
|
22433
22573
|
_lastResearchId = null;
|
|
22434
22574
|
_lastRunSummary = null;
|
|
22435
22575
|
_lastErrorReport = null;
|
|
22576
|
+
_lastResearcherOutcome = null;
|
|
22436
22577
|
resetConfig();
|
|
22437
22578
|
_removeSignalHandlers();
|
|
22438
22579
|
if (errors.length > 0) {
|
|
@@ -22938,6 +23079,8 @@ Error: ${vmsg}
|
|
|
22938
23079
|
`);
|
|
22939
23080
|
const observer = makeProgressObserver();
|
|
22940
23081
|
let exit = EXIT.OK;
|
|
23082
|
+
const abortController = new AbortController();
|
|
23083
|
+
activeResearchAbortController = abortController;
|
|
22941
23084
|
try {
|
|
22942
23085
|
await initResearchSDK({
|
|
22943
23086
|
model: runModel,
|
|
@@ -22954,7 +23097,7 @@ Error: ${vmsg}
|
|
|
22954
23097
|
observer,
|
|
22955
23098
|
...args.excludeTools ? { excludeTools: args.excludeTools } : {},
|
|
22956
23099
|
...args.initialLinks ? { initialLinks: args.initialLinks } : {}
|
|
22957
|
-
});
|
|
23100
|
+
}, abortController.signal);
|
|
22958
23101
|
let reportPath = null;
|
|
22959
23102
|
const exportCfg = getConfig(process.cwd(), "cli");
|
|
22960
23103
|
if (exportCfg.RESEARCH_REPORT_EXPORT_ENABLED) {
|
|
@@ -22966,13 +23109,25 @@ Error: ${vmsg}
|
|
|
22966
23109
|
}
|
|
22967
23110
|
}
|
|
22968
23111
|
if (args.json) {
|
|
22969
|
-
|
|
23112
|
+
const outcome = getLastResearcherOutcome();
|
|
23113
|
+
const errorReport = getLastErrorReport();
|
|
23114
|
+
toStdout(pretty({
|
|
23115
|
+
ok: true,
|
|
23116
|
+
depth,
|
|
23117
|
+
report,
|
|
23118
|
+
...reportPath ? { reportPath } : {},
|
|
23119
|
+
...outcome ? { researchers: { planned: outcome.planned, launched: outcome.launched, succeeded: outcome.succeeded, failed: outcome.failed } } : {},
|
|
23120
|
+
...errorReport && errorReport.totalErrors > 0 ? {
|
|
23121
|
+
trackedErrors: { total: errorReport.totalErrors, uniquePatterns: errorReport.uniquePatterns }
|
|
23122
|
+
} : {}
|
|
23123
|
+
}));
|
|
22970
23124
|
} else {
|
|
22971
23125
|
toStdout(report.endsWith("\n") ? report : report + "\n");
|
|
22972
23126
|
}
|
|
22973
23127
|
} catch (err) {
|
|
22974
23128
|
exit = reportError(err, "research", args.json);
|
|
22975
23129
|
} finally {
|
|
23130
|
+
if (activeResearchAbortController === abortController) activeResearchAbortController = null;
|
|
22976
23131
|
await safeShutdown();
|
|
22977
23132
|
}
|
|
22978
23133
|
return exit;
|
|
@@ -23249,7 +23404,8 @@ function describeUninstall(r) {
|
|
|
23249
23404
|
function reportError(err, what, json) {
|
|
23250
23405
|
const msg2 = err instanceof Error ? err.message : String(err);
|
|
23251
23406
|
const lower = msg2.toLowerCase();
|
|
23252
|
-
const
|
|
23407
|
+
const isTaggedResearchStop = err?.code === RESEARCH_STOPPED_ERROR_CODE;
|
|
23408
|
+
const isConfigError = !isTaggedResearchStop && (lower.includes("no llm model available") || lower.includes("not found in pi") || lower.includes("no api key") || lower.includes("provider must be specified") || lower.includes("invalid model string") || lower.includes("authentication") || lower.includes("unauthorized") || lower.includes("api key"));
|
|
23253
23409
|
const isRateLimit = lower.includes("429") || lower.includes("rate limit") || lower.includes("too many requests");
|
|
23254
23410
|
const exitCode = isConfigError && !isRateLimit ? EXIT.CONFIG : EXIT.SOFTWARE;
|
|
23255
23411
|
if (json) {
|
|
@@ -23557,6 +23713,7 @@ var flushAndExit = (code) => {
|
|
|
23557
23713
|
});
|
|
23558
23714
|
});
|
|
23559
23715
|
};
|
|
23716
|
+
var activeResearchAbortController = null;
|
|
23560
23717
|
async function main(argv) {
|
|
23561
23718
|
let _signalCleanupDone = false;
|
|
23562
23719
|
const onSignal = (sig) => {
|
|
@@ -23565,6 +23722,7 @@ async function main(argv) {
|
|
|
23565
23722
|
toStderr(`
|
|
23566
23723
|
[pi-research] ${sig} \u2014 cleaning up\u2026
|
|
23567
23724
|
`);
|
|
23725
|
+
activeResearchAbortController?.abort();
|
|
23568
23726
|
void safeShutdown().finally(() => flushAndExit(EXIT.SOFTWARE));
|
|
23569
23727
|
};
|
|
23570
23728
|
process.once("SIGINT", () => onSignal("SIGINT"));
|
|
@@ -23634,5 +23792,6 @@ export {
|
|
|
23634
23792
|
EXIT,
|
|
23635
23793
|
UsageError,
|
|
23636
23794
|
bridgeConfigEnv,
|
|
23637
|
-
parseArgs
|
|
23795
|
+
parseArgs,
|
|
23796
|
+
reportError
|
|
23638
23797
|
};
|
|
@@ -85,6 +85,7 @@ The coordinator will plan as many researchers as needed (up to the max). You do
|
|
|
85
85
|
|
|
86
86
|
**Multiple Topics:**
|
|
87
87
|
- If you need to research multiple unrelated topics of **truly distinct scope**, you may emit multiple `research` calls **simultaneously in a single turn**. Each call gets its own isolated research run with its own coordinator and researchers.
|
|
88
|
+
- **Maximum 4 simultaneous calls** — Do NOT make more than 4 `research` calls in a single turn, even if you have more unrelated topics. Exceeding 4 simultaneous research runs can overload the system and degrade performance. If you have more than 4 truly distinct topics, they can likely be consolidated into fewer calls (group related subtopics, use broader queries, or batch across multiple turns).
|
|
88
89
|
- Example: `research("bananas")` and `research("quantum computing")` in the same turn — these have zero overlap.
|
|
89
90
|
- **Group related topics**: If topics share ANY scope or could inform each other, combine them into a single `research` call. The internal coordinator will decompose them into parallel researcher agents. Do NOT split related sub-topics into separate calls.
|
|
90
91
|
- Example: `research("Python async performance and Rust async performance comparison")` — NOT separate calls for Python and Rust, since the comparison IS the research goal.
|
package/dist/thread-worker.mjs
CHANGED
|
@@ -2219,6 +2219,8 @@ var ConfigSchema = Type.Object({
|
|
|
2219
2219
|
RESEARCHER_MAX_RETRIES: Type.Number({ minimum: 0, maximum: 5, default: 2 }),
|
|
2220
2220
|
/** Base delay between retries in milliseconds (default: 2000, range: 100-10000) */
|
|
2221
2221
|
RESEARCHER_MAX_RETRY_DELAY_MS: Type.Number({ minimum: 100, maximum: 1e4, default: 2e3 }),
|
|
2222
|
+
/** Unique researcher failures that abort the entire run (default: 2, range: 1-10) */
|
|
2223
|
+
MAX_FAILED_RESEARCHERS: Type.Number({ minimum: 1, maximum: 10, default: 2 }),
|
|
2222
2224
|
/** Target depth for recursive research (default: 1, range: 1-3) */
|
|
2223
2225
|
DEFAULT_RESEARCH_DEPTH: Type.Number({ minimum: 1, maximum: 3, default: 1 }),
|
|
2224
2226
|
/** Number of batches to allow for a single scrape tool call (default: 2, 0=unlimited) */
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -84,10 +84,23 @@ Two conventions apply:
|
|
|
84
84
|
report. A mid-round evaluation that cannot be parsed continues the existing agenda
|
|
85
85
|
rather than finalizing early, so a parse failure never truncates a run.
|
|
86
86
|
|
|
87
|
-
###
|
|
87
|
+
### Tool inventory
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
This is the canonical list of every tool the system exposes, on both surfaces.
|
|
90
|
+
|
|
91
|
+
**Host-facing tools** — registered with the pi session (`src/index.ts`) for the calling
|
|
92
|
+
agent to invoke:
|
|
93
|
+
|
|
94
|
+
| Tool | Purpose |
|
|
95
|
+
|------|---------|
|
|
96
|
+
| `research` | Run a full multi-source research session and return the cited Markdown report |
|
|
97
|
+
| `research_knowledge_search` | Instant local search of the knowledge store — checked before live research; always registered, reports why when the store is disabled |
|
|
98
|
+
| `health` | Verify system status (browser pool, knowledge store, GPU lock); optional liveness probe |
|
|
99
|
+
|
|
100
|
+
**Researcher-agent tools** — the fixed set each researcher sub-agent works with
|
|
101
|
+
(`src/tools/index.ts`). `search`, `security_search`, `stackexchange`, and
|
|
102
|
+
`youtube_transcript` share a budget of 12 gathering calls per phase
|
|
103
|
+
(`MAX_GATHERING_CALLS`); `scrape` and local `grep` have their own budgets:
|
|
91
104
|
|
|
92
105
|
| Tool | Quick | Deep | Backend |
|
|
93
106
|
|------|-------|------|---------|
|
|
@@ -102,7 +115,9 @@ Each researcher has a fixed tool set and a shared budget of 12 gathering calls p
|
|
|
102
115
|
In deep research `search` is excluded — the coordinator runs the search burst and hands
|
|
103
116
|
out URLs directly. In quick research `grep` is excluded — the single session is not
|
|
104
117
|
expected to traverse a local codebase. Researchers cannot write files, run shell
|
|
105
|
-
commands, or reach the network outside these tools.
|
|
118
|
+
commands, or reach the network outside these tools. Individual researcher tools can be
|
|
119
|
+
disabled per run with `PI_RESEARCH_DISABLED_TOOLS`
|
|
120
|
+
(see [CONFIGURATION.md](CONFIGURATION.md)).
|
|
106
121
|
|
|
107
122
|
### Browser infrastructure
|
|
108
123
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.8] - 2024-07-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **CHANGELOG** — Added comprehensive CHANGELOG.md documenting all versions from 0.1.13 through 1.0.7
|
|
12
|
+
- **System prompt constraint** — Added "Maximum 4 simultaneous calls" constraint to research-tool-usage prompt to prevent system overload
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- **Topic consolidation guidance** — Emphasized that topics can likely be consolidated to fit the 4-call maximum, with strategies for grouping related subtopics
|
|
16
|
+
- **Package structure** — CHANGELOG.md now included in published package (docs/CHANGELOG.md)
|
|
17
|
+
|
|
18
|
+
## [1.0.7] - 2024-07-14
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **Browser-pool leader-election race** - Fixed a critical bug where a single client's transient socket error could force-clear shared leader state, causing a live, healthy leader to self-destruct via the leadership-miss watchdog. The fix reserves the "unreachable, force clear" path for cases where liveness probes actually confirm the registered leader PID or port is gone.
|
|
22
|
+
- **Researcher retry-exemption** - Fixed the "no initial search results or historical links" failure path that bypassed the retry loop entirely. This now routes through the same `RESEARCHER_MAX_RETRIES`/`RESEARCHER_MAX_RETRY_DELAY_MS` retry loop as other failures.
|
|
23
|
+
- **Exit-code misclassification** - Fixed false-positive classification of "Research stopped" errors as config errors (exit 78) via substring matching on the boilerplate "API key" advice text. Error classification now properly distinguishes infrastructure failures from credential problems.
|
|
24
|
+
- **Unsurfaced quality signals** - Added per-run error tracking and per-researcher grounding signals to the log output for better debugging of report quality variance.
|
|
25
|
+
- **SIGINT/SIGTERM cancellation wiring** - Improved cancellation handling for graceful shutdown on interrupt signals.
|
|
26
|
+
- **Multi-researcher citation-provenance folding** - Fixed citation provenance tracking when multiple researchers contribute to a report.
|
|
27
|
+
- **Untitled YouTube video citation line** - Fixed citation formatting for YouTube videos without titles.
|
|
28
|
+
|
|
29
|
+
### Security
|
|
30
|
+
- Several hardening improvements from the adversarial-review audit:
|
|
31
|
+
- Migration abort safety gates
|
|
32
|
+
- Hollow-run detection gates
|
|
33
|
+
- Streaming caps for large responses
|
|
34
|
+
- Socket drain on Content-Length pre-screen throws
|
|
35
|
+
- Observer isolation improvements
|
|
36
|
+
|
|
37
|
+
## [1.0.6] - 2024-07-01
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
- **Observer isolation** - Fixed `makeSafeObserver` proxying the observer directly, which could cause state leakage.
|
|
41
|
+
|
|
42
|
+
### Documentation
|
|
43
|
+
- Corrected depth-0 availability documentation
|
|
44
|
+
- Fixed migration.ts attribution in architecture docs
|
|
45
|
+
|
|
46
|
+
## [1.0.5] - 2024-06-28
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
- **Content-Length pre-screen throws** - Added proper body draining to prevent resource leaks
|
|
50
|
+
- **TTL refresh for transcribed links** - Fixed TTL handling for YouTube transcripts
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
- **YouTube transcript provenance** - Fixed transcript provenance tracking in final report citations
|
|
54
|
+
|
|
55
|
+
## [1.0.4] - 2024-06-25
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
- **Credential detection** - Detect pi keys by pi's own semantics, not file presence; registry no longer gated on auth.json
|
|
59
|
+
- **Configuration clarity** - Updated docs to reflect that pi has configuration files, not a named 'auth' feature
|
|
60
|
+
|
|
61
|
+
## [1.0.3] - 2024-06-20
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
- **Model resolution** - Require a configured model on the standalone CLI/skill; align SDK session model with RESEARCH_MODEL
|
|
65
|
+
- **Configuration requirement** - Added proper error messages when model is not configured
|
|
66
|
+
|
|
67
|
+
### Documentation
|
|
68
|
+
- Updated installation and configuration documentation
|
|
69
|
+
|
|
70
|
+
## [1.0.2] - 2024-06-15
|
|
71
|
+
|
|
72
|
+
### Fixed
|
|
73
|
+
- **Timeout robustness** - Improved timeout handling for long-running operations
|
|
74
|
+
- De-emphasized the markdown-conversion fallback in favor of native HTML-to-markdown conversion
|
|
75
|
+
|
|
76
|
+
## [1.0.1] - 2024-06-10
|
|
77
|
+
|
|
78
|
+
### Fixed
|
|
79
|
+
- Initial stable release after rebuild against current pi APIs
|
|
80
|
+
- Stealth-browser stack integration
|
|
81
|
+
|
|
82
|
+
## [0.1.13] - 2024-04-15
|
|
83
|
+
|
|
84
|
+
### Added
|
|
85
|
+
- Initial research functionality
|
|
86
|
+
- Knowledge store integration
|
|
87
|
+
- Multi-agent orchestration
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Deferred Findings (from v1.0.7 audit)
|
|
92
|
+
|
|
93
|
+
The following issues were identified during the v1.0.7 audit but intentionally deferred to future releases:
|
|
94
|
+
|
|
95
|
+
1. **KnowledgeStore.close() timeout behavior** - The close() method proceeds even when the pending-write wait times out. A correct fix requires threading cancellation tokens through write paths or documenting the 10s timeout as intended behavior.
|
|
96
|
+
|
|
97
|
+
2. **Cross-session teardown via shared singletons** - Session shutdown handlers run process-wide cleanup for reload/new/fork events. The correct behavior depends on pi's session model semantics and needs further investigation.
|
|
98
|
+
|
|
99
|
+
3. **Lower-severity hardening items** - Various low-confidence or low-impact findings noted for future investigation:
|
|
100
|
+
- SIGINT-adjacent cancellation race
|
|
101
|
+
- Knowledge-store migration vs. concurrent access
|
|
102
|
+
- Global activity gate reused for per-session decisions
|
|
103
|
+
- Zombie queued browser tasks after timeout
|
|
104
|
+
- Non-atomic `clearBrowserServer`
|
|
105
|
+
- Inconsistent `knowledge --json` payload shapes
|
|
106
|
+
- Backward-clock-step tolerance across multiple rate limiters
|
|
107
|
+
|
|
108
|
+
See `DEFERRED-FINDINGS-2026-07-14.md` for full details.
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -73,6 +73,7 @@ Research
|
|
|
73
73
|
| `PI_RESEARCH_MAX_CONCURRENT_SCRAPES` | `3` | 1–20 | Concurrent URLs fetched per scrape batch. |
|
|
74
74
|
| `PI_RESEARCH_MAX_RETRIES` | `2` | 0–5 | Retries per researcher request. |
|
|
75
75
|
| `PI_RESEARCH_RETRY_DELAY_MS` | `2000` | 100–10000 | Base delay between retries. |
|
|
76
|
+
| `PI_RESEARCH_MAX_FAILED_RESEARCHERS` | `2` | 1–10 | Unique researcher failures that abort the whole run. Raise to let slower, still-in-flight researchers finish before giving up. |
|
|
76
77
|
| `PI_RESEARCH_WORKER_THREADS` | `4` | 1–10 | Browser worker processes. Higher = more throughput, more CPU/RAM. |
|
|
77
78
|
| `PI_RESEARCH_WORKER_CONCURRENCY` | `2` | 1–10 | Tasks per worker process. |
|
|
78
79
|
| `PI_RESEARCH_MODEL` | _(pi: session model; CLI/skill: required)_ | — | The model research runs on. **Required for the standalone CLI / agent skill** — they use only this configured model (never the model selected inside the pi extension) and refuse to start without one (the CLI's per-run `--model` flag also satisfies this). On the SDK it selects the session model when no `model` option is given. In the pi extension it overrides researcher sub-agents and knowledge synthesis, while the coordinator and evaluator keep using the session model. Accepts `provider/id` or a bare model id. |
|
package/docs/SDK.md
CHANGED
|
@@ -76,7 +76,10 @@ await shutdownResearchSDK();
|
|
|
76
76
|
`runResearchDetailed`, `searchKnowledge`, `scrapeUrl`, `getResearchHealth`,
|
|
77
77
|
`getLastRunStats`, and `getSessionMetrics`, plus `exportKnowledge` (write the
|
|
78
78
|
knowledge store to a web-consumable JSON file) and the post-run telemetry accessors
|
|
79
|
-
`getLastRunMetrics`, `getLastRunSummary`,
|
|
79
|
+
`getLastRunMetrics`, `getLastRunSummary`, `getLastErrorReport`, and
|
|
80
|
+
`getLastResearcherOutcome` (planned/launched/succeeded/failed researcher counts plus
|
|
81
|
+
per-researcher failure reasons for the most recent run — lets a caller tell a thin
|
|
82
|
+
report caused by a sparse topic apart from one where most researchers failed). Both
|
|
80
83
|
`@lincoln504/pi-research` and `@lincoln504/pi-research/sdk` export these symbols.
|
|
81
84
|
|
|
82
85
|
> Concurrency: a single initialized SDK instance runs one research call at a time.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lincoln504/pi-research",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "A set of web research tools (search, scrape, and others) and research agent system that uses them safely and efficiently.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"scripts/prepare.cjs",
|
|
38
38
|
"LICENSE",
|
|
39
39
|
"docs/THIRD-PARTY-NOTICES.md",
|
|
40
|
-
"README.md"
|
|
40
|
+
"README.md",
|
|
41
|
+
"docs/CHANGELOG.md"
|
|
41
42
|
],
|
|
42
43
|
"repository": {
|
|
43
44
|
"type": "git",
|
|
@@ -109,9 +110,9 @@
|
|
|
109
110
|
"image": "https://raw.githubusercontent.com/Lincoln504/pi-research/main/docs/media/hero.png"
|
|
110
111
|
},
|
|
111
112
|
"dependencies": {
|
|
112
|
-
"@earendil-works/pi-ai": ">=0.80.
|
|
113
|
-
"@earendil-works/pi-coding-agent": ">=0.80.
|
|
114
|
-
"@earendil-works/pi-tui": ">=0.80.
|
|
113
|
+
"@earendil-works/pi-ai": ">=0.80.7 <1",
|
|
114
|
+
"@earendil-works/pi-coding-agent": ">=0.80.7 <1",
|
|
115
|
+
"@earendil-works/pi-tui": ">=0.80.7 <1",
|
|
115
116
|
"@huggingface/transformers": "4.2.0",
|
|
116
117
|
"@kreuzberg/html-to-markdown-node": "^3.5.5",
|
|
117
118
|
"@lancedb/lancedb": "0.29.0",
|