@lincoln504/pi-research 1.0.1 → 1.0.2

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 CHANGED
@@ -20166,7 +20166,8 @@ ${prompt}`);
20166
20166
  const msg2 = `Researcher ${id} (${researcherConfig.name}) timed out after ${config.RESEARCHER_TIMEOUT_MS}ms`;
20167
20167
  session.abort().catch((err) => {
20168
20168
  logger.warn("[ResearcherExecutor] Failed to abort timed-out researcher session:", err);
20169
- }).finally(() => reject(new Error(msg2)));
20169
+ });
20170
+ reject(new Error(msg2));
20170
20171
  }, config.RESEARCHER_TIMEOUT_MS);
20171
20172
  });
20172
20173
  let abortCleanup;
@@ -20248,9 +20249,18 @@ ${responseText}`);
20248
20249
  }
20249
20250
  } finally {
20250
20251
  subscription();
20251
- await session.abort().catch((err) => {
20252
- logger.warn(`[ResearcherExecutor] Failed to abort researcher session ${id}:`, err);
20253
- });
20252
+ await Promise.race([
20253
+ session.abort().catch((err) => {
20254
+ logger.warn(`[ResearcherExecutor] Failed to abort researcher session ${id}:`, err);
20255
+ }),
20256
+ new Promise((resolve8) => {
20257
+ const t = setTimeout(() => {
20258
+ logger.warn(`[ResearcherExecutor] Researcher ${id} session abort did not settle within 10s; continuing cleanup`);
20259
+ resolve8();
20260
+ }, 1e4);
20261
+ t.unref?.();
20262
+ })
20263
+ ]);
20254
20264
  sessionService?.unregisterSession(researchId, id);
20255
20265
  if (ctx.mode !== "tui" && ctx.hasUI && typeof ctx.ui.setHiddenThinkingLabel === "function") {
20256
20266
  ctx.ui.setHiddenThinkingLabel();
@@ -20526,7 +20536,8 @@ ${prompt}`);
20526
20536
  const msg2 = `Quick research timed out after ${this.config.RESEARCHER_TIMEOUT_MS}ms`;
20527
20537
  session.abort().catch((err) => {
20528
20538
  logger.warn("[QuickOrchestrator] Failed to abort timed-out session:", err);
20529
- }).finally(() => reject(new Error(msg2)));
20539
+ });
20540
+ reject(new Error(msg2));
20530
20541
  }, this.config.RESEARCHER_TIMEOUT_MS);
20531
20542
  });
20532
20543
  let abortCleanup;
@@ -301,7 +301,7 @@ Browser & scraping
301
301
 
302
302
  - [Camoufox](https://camoufox.com) — stealth Firefox (driven via [Playwright](https://playwright.dev)) for undetected search and scraping
303
303
  - [poolifier](https://github.com/poolifier/poolifier) — the worker-process pool behind the browser workers
304
- - [html-to-markdown](https://github.com/kreuzberg-dev/html-to-markdown) & [node-html-markdown](https://github.com/crosstype/node-html-markdown) — convert scraped HTML to Markdown
304
+ - [html-to-markdown](https://github.com/kreuzberg-dev/html-to-markdown) — converts scraped HTML to Markdown (node-html-markdown serves as the pure-JS fallback)
305
305
  - `pdf-oxide-wasm` — PDF text extraction (Rust/WASM)
306
306
 
307
307
  Knowledge store & embeddings
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lincoln504/pi-research",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
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,
@@ -2,7 +2,7 @@
2
2
  name: pi-research
3
3
  description: "Multi-agent web research for any coding agent. Use for anything requiring current information from the internet — news, trends, product/service/company facts, statistics, people, places, events, APIs, library behavior, or 'what is X / how does X work' questions. Also use to search a local knowledge store of previously researched findings before doing live research. Powered by the pi-research engine (search, stealth scraping, security databases, Stack Exchange). Not for reading local files, running project commands, or analyzing this repository's own source."
4
4
  license: MIT
5
- metadata: { "author": "Lincoln Deen", "version": "1.0.1", "package": "@lincoln504/pi-research", "engine": "pi-research-sdk", "openclaw": { "requires": { "bins": ["node"] } } }
5
+ metadata: { "author": "Lincoln Deen", "version": "1.0.2", "package": "@lincoln504/pi-research", "engine": "pi-research-sdk", "openclaw": { "requires": { "bins": ["node"] } } }
6
6
  allowed-tools: Bash(node:*)
7
7
  ---
8
8
 
@@ -269,9 +269,13 @@ export class QuickResearchOrchestrator {
269
269
  const timeoutPromise = new Promise<void>((_, reject) => {
270
270
  timeoutId = setTimeout(() => {
271
271
  const msg = `Quick research timed out after ${this.config.RESEARCHER_TIMEOUT_MS}ms`;
272
+ // Reject FIRST, abort as fire-and-forget cleanup — see researcher-executor:
273
+ // abort() awaits the stuck in-flight request, so gating the rejection on it
274
+ // settling disables the timeout exactly when it is needed.
272
275
  session.abort().catch((err) => {
273
276
  logger.warn('[QuickOrchestrator] Failed to abort timed-out session:', err);
274
- }).finally(() => reject(new Error(msg)));
277
+ });
278
+ reject(new Error(msg));
275
279
  }, this.config.RESEARCHER_TIMEOUT_MS);
276
280
  });
277
281
 
@@ -264,9 +264,14 @@ export async function runResearcher(options: RunResearcherOptions): Promise<void
264
264
  const timeoutPromise = new Promise<void>((_, reject) => {
265
265
  timeoutId = setTimeout(() => {
266
266
  const msg = `Researcher ${id} (${researcherConfig.name}) timed out after ${config.RESEARCHER_TIMEOUT_MS}ms`;
267
+ // Reject FIRST, abort as fire-and-forget cleanup: session.abort() awaits the
268
+ // in-flight request, which is the very thing that is stuck (e.g. a provider
269
+ // retry loop against an exhausted quota) — gating the rejection on it settling
270
+ // makes the timeout ineffective exactly when it is needed.
267
271
  session.abort().catch((err) => {
268
272
  logger.warn('[ResearcherExecutor] Failed to abort timed-out researcher session:', err);
269
- }).finally(() => reject(new Error(msg)));
273
+ });
274
+ reject(new Error(msg));
270
275
  }, config.RESEARCHER_TIMEOUT_MS);
271
276
  });
272
277
 
@@ -375,9 +380,23 @@ export async function runResearcher(options: RunResearcherOptions): Promise<void
375
380
  }
376
381
  } finally {
377
382
  subscription();
378
- await session.abort().catch((err) => {
379
- logger.warn(`[ResearcherExecutor] Failed to abort researcher session ${id}:`, err);
380
- });
383
+ // Bounded: after a timeout, session.abort() awaits the very in-flight request
384
+ // that is stuck (e.g. a provider retry loop against an exhausted quota) and may
385
+ // never settle — never let per-attempt cleanup hang the retry loop on it. The
386
+ // retry builds a fresh session; the abandoned abort keeps draining in the
387
+ // background and its session dies with the run's teardown.
388
+ await Promise.race([
389
+ session.abort().catch((err) => {
390
+ logger.warn(`[ResearcherExecutor] Failed to abort researcher session ${id}:`, err);
391
+ }),
392
+ new Promise<void>((resolve) => {
393
+ const t = setTimeout(() => {
394
+ logger.warn(`[ResearcherExecutor] Researcher ${id} session abort did not settle within 10s; continuing cleanup`);
395
+ resolve();
396
+ }, 10_000);
397
+ t.unref?.();
398
+ }),
399
+ ]);
381
400
  sessionService?.unregisterSession(researchId, id);
382
401
 
383
402
  // Restore the default thinking label now that the researcher is done.