@lincoln504/pi-research 1.6.9 → 1.6.10

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 CHANGED
@@ -53,6 +53,10 @@ tries to compile from source; upgrading npm fixes it.
53
53
 
54
54
  In pi it works out of the box on the session's model and pi's configuration. Standalone use ([agent skill](docs/AGENT-SKILL.md) or [SDK](docs/SDK.md)) needs a model configured. See [Configuration](docs/CONFIGURATION.md).
55
55
 
56
+ ### Uninstall
57
+
58
+ `pi remove npm:@lincoln504/pi-research` removes the extension, and `npm uninstall -g @lincoln504/pi-research` the standalone engine. npm 7+ no longer runs `preuninstall`, so **nothing else is removed on its own**: skill links into other agents (Claude Code, Codex, …), the state directory (`~/.pi/research/state`), and the cache (`~/.cache/pi-research`, including any downloaded embedding models) stay in place. Remove the skill links first with `pi-research skill uninstall` (or `/research-config` → Remove from External Agents); the shared stealth-browser cache (`~/.cache/camoufox`) is preserved unless `PI_RESEARCH_PURGE_BROWSERS=1`. See [AGENT-SKILL.md](docs/AGENT-SKILL.md#installation-flow) for the full picture.
59
+
56
60
  ### How it works
57
61
 
58
62
  A research run loops through agent teams: a coordinator plans and starts with a search, researcher agents scrape and read in parallel, and a research lead decides whether to go another round, then writes the report from every report collected. The result is one cited Markdown report with findings optionally saved to the knowledge store.
@@ -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.6.9", "package": "@lincoln504/pi-research", "engine": "pi-research-sdk", "openclaw": { "requires": { "bins": ["node"] } } }
5
+ metadata: { "author": "Lincoln Deen", "version": "1.6.10", "package": "@lincoln504/pi-research", "engine": "pi-research-sdk", "openclaw": { "requires": { "bins": ["node"] } } }
6
6
  allowed-tools: Bash(node:*)
7
7
  ---
8
8
 
package/docs/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ 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.6.10] - 2026-09-02
9
+
10
+ ### Changed
11
+
12
+ - **`/research-config` now behaves properly on every pi host — TUI, RPC/web-hub GUIs, print/json, and the SDK (`guis` is a family of frontends, not one client).** The interactive settings menu is now gated on `ctx.mode === 'tui'` (not just `ctx.hasUI`, which is true in RPC too): on non-TUI hosts the command replies with a clear non-interactive guidance message instead of silently entering a menu that cannot render. Those hosts get working headless diagnostics instead — `/research-config health` and `/research-config knowledge-status` now run on any host (previously they were reachable only through the menu, even though the tool failure text points users at health). Every destructive `ctx.ui.confirm` now carries a 60s timeout, so no host can wedge on a dialog whose client never responds.
13
+
14
+ - **The knowledge store's Arrow/LanceDB boundary is now verified in the actual consumer layout on every CI run.** The dev tree resolves a single apache-arrow 21.1.0 (forced by `overrides`), but a fresh npm consumer install resolves two copies — 21.1.0 nested under the package for the direct pin and 18.1.0 hoisted for `@lancedb/lancedb`'s declared peer ceiling. docs/ARCHITECTURE.md pins 21.1.0 for a measured reason and warns mismatched Arrow copies may not interoperate, and this divergence is invisible to the dev-tree tests. The fresh-install CI job now runs `scripts/consumer-interop-probe.cjs`, which replicates `createStoreTable` (same fields, FTS/BTree index set, reopen-from-disk) with the consumer's exact resolution. README gains an Uninstall section documenting that npm 7+ runs no `preuninstall`, so skill links / `~/.pi/research/state` / caches persist unless removed first.
15
+
16
+ ### Fixed
17
+
18
+ - **Ctrl+C during a research run no longer cancels the run while the editor still holds typed text — it clears the editor first, and only a subsequent Ctrl+C (editor now empty) cancels the research.** The global TUI controller (`src/tui/tui-controller.ts`) treated Ctrl+C and Esc identically as cancel keys, aborting the session's active research runs on every press of either. That fought pi's own keybinding contract: Ctrl+C is `app.clear` ("Clear editor" — pi's `handleCtrlC()` clears the editor text and never interrupts the agent, doubling as the two-press-within-500ms exit), while Esc is `app.interrupt` ("Cancel or abort"), so a user discarding a drafted follow-up mid-run got the whole run killed instead of an empty editor. The controller now reads the editor via `ctx.ui.getEditorText()`: with text present, Ctrl+C falls through untouched (pi clears the text; the run keeps going); with an empty editor it is an explicit cancel gesture and aborts, still scoped to the current pi session as before; Esc keeps its unconditional panic-cancel role; and a throwing `getEditorText()` (ctx gone stale mid-run) is treated as empty — cancel — the safe default. The two gestures coexist with pi's native double-press exit in practice: the clear-then-decide rhythm is naturally slower than the 500ms window, so the human cancel path never trips pi's shutdown. Seven new tests pin the policy (`test/unit/tui/tui-controller.test.ts`).
19
+
20
+ - **`/research-config` could silently do nothing on RPC hosts.** `ctx.hasUI` is true in RPC, so the old guard passed and `ctx.ui.custom(...)` was entered even though no non-TUI host can render or answer the menu. The mode gate in the change above makes the menu reachable only on a real TUI and hands every other host the guidance message plus the headless `health` / `knowledge-status` diagnostics.
21
+
22
+ ### Verified
23
+
24
+ - 3,027 unit tests over 240 files (14 new across this Unreleased cycle: 7 Ctrl+C-policy + 7 host-mode), ESLint clean on the touched source, all four type-checks (TS 6 src + tests, TS 7 native src + tests), and dependency-cruiser clean (238 modules / 1,218 dependencies).
25
+
26
+
8
27
  ## [1.6.9] - 2026-09-01
9
28
 
10
29
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lincoln504/pi-research",
3
- "version": "1.6.9",
3
+ "version": "1.6.10",
4
4
  "description": "Web research for your agents with smart and safe tooling + knowledge store",
5
5
  "type": "module",
6
6
  "private": false,
@@ -89,12 +89,34 @@ const ENV_KEY_BY_SETTING_ID: Record<string, string> = {
89
89
  * Main command handler for /research-config
90
90
  */
91
91
  export async function handleResearchConfigCommand(
92
- _args: string,
92
+ args: string,
93
93
  ctx: ExtensionContext,
94
94
  pi: ExtensionAPI
95
95
  ): Promise<void> {
96
- if (!ctx.hasUI) {
97
- ctx.ui.notify('Interactive menu requires UI mode', 'error');
96
+ const verb = args.trim().split(/\s+/)[0]?.toLowerCase() ?? '';
97
+ if (verb === 'health') {
98
+ await runHealthCheckAction(ctx, pi);
99
+ return;
100
+ }
101
+ if (verb === 'knowledge-status' || verb === 'knowledge_status') {
102
+ await showKnowledgeStatusAction(ctx, pi);
103
+ return;
104
+ }
105
+ if (ctx.mode !== 'tui' || !ctx.hasUI) {
106
+ // The interactive menu needs pi's real TUI. In RPC/web-hub/print/json/SDK hosts
107
+ // ctx.hasUI is true (RPC) and the menu cannot render — awaiting it silently
108
+ // nudges the host toward a hang and at best does nothing visible. Give a clear
109
+ // non-interactive answer with the headless diagnostics that DO work here.
110
+ pi.sendMessage({
111
+ customType: 'research-config',
112
+ content: 'The interactive `/research-config` menu requires pi\'s terminal UI (TUI).\n\n' +
113
+ 'In this host, use the headless diagnostics instead:\n' +
114
+ '- `/research-config health` — system health status\n' +
115
+ '- `/research-config knowledge-status` — knowledge store status\n\n' +
116
+ 'Configuration is read from the environment and config files; set `PI_RESEARCH_*` ' +
117
+ 'environment variables to change it.',
118
+ display: true,
119
+ });
98
120
  return;
99
121
  }
100
122
  await showInteractiveMenu(ctx, pi);
@@ -498,6 +520,7 @@ async function showInteractiveMenu(ctx: ExtensionContext, pi: ExtensionAPI): Pro
498
520
  modelChangeConfirmed = !cancelled && await ctx.ui.confirm(
499
521
  'Change Embedding Model',
500
522
  `Switch the embedding model to ${config.EMBEDDING_MODEL.split('/').pop()}? This permanently clears the current knowledge store.`,
523
+ { timeout: 60_000 },
501
524
  );
502
525
  if (modelChangeConfirmed) {
503
526
  logger.info(`[research-config] Embedding model changed from ${initialConfig.EMBEDDING_MODEL} to ${config.EMBEDDING_MODEL}. Clearing knowledge store.`);
@@ -550,7 +573,7 @@ async function showInteractiveMenu(ctx: ExtensionContext, pi: ExtensionAPI): Pro
550
573
  await showKnowledgeStatusAction(ctx, pi);
551
574
  break;
552
575
  case 'knowledge_clear_global': {
553
- const confirmed = await ctx.ui.confirm('Clear User Store', 'Permanently delete all user-scoped store data?');
576
+ const confirmed = await ctx.ui.confirm('Clear User Store', 'Permanently delete all user-scoped store data?', { timeout: 60_000 });
554
577
  if (confirmed) {
555
578
  try {
556
579
  const service = await getService<IKnowledgeStoreService>(ServiceNames.KNOWLEDGE_STORE, ctx, container);
@@ -563,7 +586,7 @@ async function showInteractiveMenu(ctx: ExtensionContext, pi: ExtensionAPI): Pro
563
586
  break;
564
587
  }
565
588
  case 'knowledge_clear_local': {
566
- const confirmed = await ctx.ui.confirm('Clear Project Store', 'Permanently delete all project-scoped store data?');
589
+ const confirmed = await ctx.ui.confirm('Clear Project Store', 'Permanently delete all project-scoped store data?', { timeout: 60_000 });
567
590
  if (confirmed) {
568
591
  try {
569
592
  const service = await getService<IKnowledgeStoreService>(ServiceNames.KNOWLEDGE_STORE, ctx, container);
@@ -577,13 +600,13 @@ async function showInteractiveMenu(ctx: ExtensionContext, pi: ExtensionAPI): Pro
577
600
  }
578
601
  case 'metrics':
579
602
  await showMetricsAction(ctx, pi);
580
- if (await ctx.ui.confirm('Session Metrics', 'Reset all session counters?')) {
603
+ if (await ctx.ui.confirm('Session Metrics', 'Reset all session counters?', { timeout: 60_000 })) {
581
604
  metrics.clearSession();
582
605
  ctx.ui.notify('Session metrics reset.', 'info');
583
606
  }
584
607
  break;
585
608
  case 'logs_clear': {
586
- const confirmed = await ctx.ui.confirm('Clear Logs', 'Delete the main diagnostic log file and all archived rotation files?');
609
+ const confirmed = await ctx.ui.confirm('Clear Logs', 'Delete the main diagnostic log file and all archived rotation files?', { timeout: 60_000 });
587
610
  if (confirmed) {
588
611
  logger.clear();
589
612
  ctx.ui.notify('Diagnostic logs cleared.', 'info');
@@ -704,6 +727,7 @@ async function uninstallSkillAction(ctx: ExtensionContext, pi: ExtensionAPI): Pr
704
727
  const confirmed = await ctx.ui.confirm(
705
728
  'Uninstall Skill',
706
729
  `Remove the pi-research skill from ${names}? (Only symlinks this extension created are removed.)`,
730
+ { timeout: 60_000 },
707
731
  );
708
732
  if (!confirmed) return;
709
733
  try {
@@ -66,6 +66,23 @@ export function initGlobalTuiController(ui: ExtensionUIContext, piSessionId?: st
66
66
  // Check for specific global cancel keys (Escape and Ctrl+C)
67
67
  // We only trigger abort if research is actually running.
68
68
  if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c')) {
69
+ // Ctrl+C is pi's app.clear ("Clear editor"), NOT its cancel key — pi's own
70
+ // handleCtrlC() clears the editor text and never interrupts the agent (Esc
71
+ // is app.interrupt). Aborting the run on every Ctrl+C stole the key from
72
+ // users who merely wanted to discard typed text mid-run. Follow pi's
73
+ // convention: with text in the editor, Ctrl+C only clears (fall through and
74
+ // let pi do the clearing); an EMPTY editor is an explicit cancel gesture and
75
+ // aborts the active research.
76
+ if (matchesKey(data, 'ctrl+c')) {
77
+ let editorText = '';
78
+ try {
79
+ editorText = typeof ui.getEditorText === 'function' ? (ui.getEditorText() ?? '') : '';
80
+ } catch { /* editor gone mid-run — treat as empty (cancel) */ }
81
+ if (editorText.length > 0) {
82
+ logger.debug('[TUI] Ctrl+C with editor text — clearing only, research keeps running.');
83
+ return undefined;
84
+ }
85
+ }
69
86
  const activeCount = getActiveSessionCount();
70
87
  if (activeCount > 0) {
71
88
  // FIX (New Issues B & E): Use abortAllSessions scoped to the current Pi session