@lincoln504/pi-research 1.3.0 → 1.3.1

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/docs/CHANGELOG.md CHANGED
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.3.1] - 2026-08-06
9
+
10
+ A dedicated post-release investigation pass over 1.3.0 — three independent
11
+ agents (fresh-eyes code review, packaging/release-artifact audit, isolated
12
+ runtime CLI smoke test) plus a full from-scratch local gate run — to confirm
13
+ the shipped artifact and codebase were correct before considering the
14
+ release closed. The packaging audit and runtime smoke test found nothing;
15
+ the code review found one real, currently-live defect, fixed below.
16
+
17
+ ### Fixed
18
+
19
+ - **The pi-extension research tool could report a cancelled-but-partial run as an ordinary success.** `DeepResearchOrchestrator.run()` does not throw on cancellation once at least one researcher has produced a report — it resolves normally with a fallback synthesis (the same documented contract 1.3.0's `cmdResearch` fix was built around). `research-tool-definition.ts`'s abort check only lived in its `catch` block, so this resolves-normally path fell straight through to unconditional success handling: metrics recorded `status: 'success'`, and the text handed back to the calling agent carried no indication it was a partial report. This matters more here than at the CLI: `AgentToolResult`'s `details` field is logs/UI-only by its own type contract, so `content[].text` is the *only* channel the calling agent has to learn a report is partial rather than complete — a silent miscategorization here could lead an agent to treat cut-short research as a finished answer. Same bug class as the 1.3.0 CLI fix, at a call site that round's review missed. Fixed by checking the abort signal immediately after `runResearch()` resolves: the metrics status now reflects the real outcome, and a cancellation note is prepended to the report text itself.
20
+
21
+ Not a minor bump: unlike 1.3.0's `cmdResearch` fix, this does not touch the CLI's exit-code contract — it is scoped entirely to the pi-extension tool call path.
22
+
8
23
  ## [1.3.0] - 2026-08-06
9
24
 
10
25
  Bundles three unreleased fix rounds that accumulated on top of 1.2.0 (each
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lincoln504/pi-research",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
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.3.0", "package": "@lincoln504/pi-research", "engine": "pi-research-sdk", "openclaw": { "requires": { "bins": ["node"] } } }
5
+ metadata: { "author": "Lincoln Deen", "version": "1.3.1", "package": "@lincoln504/pi-research", "engine": "pi-research-sdk", "openclaw": { "requires": { "bins": ["node"] } } }
6
6
  allowed-tools: Bash(node:*)
7
7
  ---
8
8
 
@@ -259,7 +259,7 @@ export function createResearchTool(iface?: ConfigInterface): ToolDefinition {
259
259
  metrics.increment('session_runs_started_total');
260
260
 
261
261
  try {
262
- const researchRunResult = await runWithRunRegistry<{ result: string; tokens: number; researchId: string }>(runRegistry, () =>
262
+ const researchRunResult = await runWithRunRegistry<{ result: string; tokens: number; researchId: string; cancelled: boolean }>(runRegistry, () =>
263
263
  logger.runCapturingStderr(async () => {
264
264
  const config = getConfig(ctx.cwd, iface);
265
265
  validateConfig(config);
@@ -389,6 +389,17 @@ export function createResearchTool(iface?: ConfigInterface): ToolDefinition {
389
389
  config,
390
390
  }, internalAbort.signal);
391
391
 
392
+ // DeepResearchOrchestrator.run() does not throw on cancellation once at
393
+ // least one researcher has produced a report — it returns a fallback
394
+ // synthesis through its normal return path (see
395
+ // deep-research-orchestrator.ts's synthesisService.hasReports branch).
396
+ // Left unchecked, a cancel mid-run that still yields a partial report
397
+ // would fall straight through as if the run had completed normally,
398
+ // with no indication in the returned text — the only channel the
399
+ // calling agent sees — that it is reading a partial result. Mirrors the
400
+ // CLI's cancellation-latch check on its own success path (cmdResearch).
401
+ const wasCancelled = aborted?.aborted === true || internalAbort.signal.aborted;
402
+
392
403
  // Stop wave animation
393
404
  if (observerState && panelState) stopObserverWaveAnimation(observerState, panelState);
394
405
 
@@ -418,10 +429,17 @@ export function createResearchTool(iface?: ConfigInterface): ToolDefinition {
418
429
  const synthesisService = await getService<IResearchSynthesisService>(ServiceNames.RESEARCH_SYNTHESIS_SERVICE, ctx, container);
419
430
  finalResult = synthesisService.appendMetadata(finalResult, selectedModel!.id);
420
431
 
421
- return { result: finalResult, tokens: runTokens, researchId };
432
+ // The calling agent only ever sees `content[].text` — `details` is
433
+ // logs/UI-only (AgentToolResult's own doc comment) — so a cancellation
434
+ // note has to live IN the report text itself, not just in metadata.
435
+ if (wasCancelled) {
436
+ finalResult = `*Research cancelled — the report below is a partial synthesis from what had been collected before the run was stopped.*\n\n${finalResult}`;
437
+ }
438
+
439
+ return { result: finalResult, tokens: runTokens, researchId, cancelled: wasCancelled };
422
440
  } catch (error) {
423
441
  if (aborted?.aborted || internalAbort.signal.aborted) {
424
- return { result: 'Research cancelled.', tokens: 0, researchId };
442
+ return { result: 'Research cancelled.', tokens: 0, researchId, cancelled: true };
425
443
  }
426
444
  throw error;
427
445
  } finally {
@@ -431,17 +449,27 @@ export function createResearchTool(iface?: ConfigInterface): ToolDefinition {
431
449
  return researchRunResult;
432
450
  })); // end runCapturingStderr / runWithRunRegistry
433
451
 
434
- // Snapshot the run registry
452
+ // Snapshot the run registry. Both branches that produce researchRunResult
453
+ // (the success path above and its inner catch) can represent a cancelled
454
+ // run — status must reflect that, not assume every non-throwing outcome
455
+ // was an ordinary success.
435
456
  metrics.recordRunSummary({
436
457
  runId: researchId,
437
458
  startedAt: runStartedAt,
438
459
  completedAt: Date.now(),
439
460
  durationMs: Date.now() - runStartedAt,
440
- status: 'success',
461
+ status: researchRunResult.cancelled ? 'cancelled' : 'success',
441
462
  snapshot: runRegistry.getSnapshot(),
442
463
  });
443
464
 
444
- return { content: [{ type: 'text', text: researchRunResult.result }], details: { totalTokens: researchRunResult.tokens, researchId: researchRunResult.researchId } };
465
+ return {
466
+ content: [{ type: 'text', text: researchRunResult.result }],
467
+ details: {
468
+ totalTokens: researchRunResult.tokens,
469
+ researchId: researchRunResult.researchId,
470
+ ...(researchRunResult.cancelled ? { cancelled: true } : {}),
471
+ },
472
+ };
445
473
  } catch (error) {
446
474
  if (aborted?.aborted || internalAbort.signal.aborted) {
447
475
  metrics.recordRunSummary({