@lincoln504/pi-research 1.2.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.
Files changed (81) hide show
  1. package/dist/cli.mjs +1334 -750
  2. package/dist/thread-worker.mjs +194 -136
  3. package/docs/ARCHITECTURE.md +1 -1
  4. package/docs/CHANGELOG.md +129 -16
  5. package/package.json +1 -1
  6. package/scripts/cleanup.cjs +64 -2
  7. package/skills/pi-research/SKILL.md +1 -1
  8. package/src/cleanup/research-cleanup.ts +12 -4
  9. package/src/cli.ts +228 -70
  10. package/src/config.ts +78 -2
  11. package/src/core/interfaces/health-check-interfaces.ts +3 -39
  12. package/src/core/interfaces/planning-interfaces.ts +8 -0
  13. package/src/core/interfaces/service-names.ts +0 -1
  14. package/src/core/interfaces/state-manager-interfaces.ts +10 -2
  15. package/src/core/llm/agentic-repair.ts +14 -0
  16. package/src/core/planning-service.ts +43 -1
  17. package/src/core/service-initialization.ts +6 -14
  18. package/src/core/service-interfaces.ts +1 -1
  19. package/src/healthcheck/index.ts +7 -2
  20. package/src/infrastructure/browser/browser-client.ts +64 -9
  21. package/src/infrastructure/browser/browser-server.ts +24 -7
  22. package/src/infrastructure/browser/browser-task-scheduler.ts +58 -12
  23. package/src/infrastructure/browser/cleanup-utils.ts +50 -2
  24. package/src/infrastructure/browser/client-agent.ts +6 -6
  25. package/src/infrastructure/browser/config.ts +14 -3
  26. package/src/infrastructure/browser/scheduler-factory.ts +18 -3
  27. package/src/infrastructure/browser/thread-worker-messaging.ts +54 -2
  28. package/src/infrastructure/browser/thread-worker-types.ts +3 -1
  29. package/src/infrastructure/browser/thread-worker.mjs +194 -136
  30. package/src/infrastructure/browser/worker-pool-manager.ts +7 -2
  31. package/src/infrastructure/embedding/embedding-factory.ts +35 -6
  32. package/src/infrastructure/embedding/embedding-server.ts +40 -7
  33. package/src/infrastructure/file-lock-service.ts +72 -2
  34. package/src/infrastructure/knowledge-store-service.ts +48 -3
  35. package/src/infrastructure/process-lifecycle-service.ts +28 -5
  36. package/src/infrastructure/research-run-semaphore.ts +37 -3
  37. package/src/infrastructure/state/state-browser-api.ts +5 -2
  38. package/src/infrastructure/state/state-browser-manager.ts +15 -1
  39. package/src/infrastructure/state/state-manager-service.ts +4 -4
  40. package/src/infrastructure/state/state-manager.ts +15 -3
  41. package/src/knowledge/chunker.ts +88 -1
  42. package/src/knowledge/embedder.ts +136 -43
  43. package/src/knowledge/store.ts +66 -12
  44. package/src/logger.ts +5 -1
  45. package/src/observers/research-observer-impl.ts +31 -0
  46. package/src/orchestration/abort-utils.ts +60 -0
  47. package/src/orchestration/deep-research-orchestrator.ts +12 -2
  48. package/src/orchestration/headless-observer.ts +8 -0
  49. package/src/orchestration/research-orchestration-service.ts +63 -11
  50. package/src/orchestration/research-session-service.ts +37 -11
  51. package/src/orchestration/researcher-executor.ts +100 -20
  52. package/src/orchestration/researcher.ts +39 -20
  53. package/src/orchestration/session-state.ts +11 -0
  54. package/src/sdk.ts +125 -19
  55. package/src/security/index.ts +51 -30
  56. package/src/security/nvd-client.ts +7 -2
  57. package/src/skill-install/skill-installer.ts +18 -2
  58. package/src/stackexchange/index.ts +20 -5
  59. package/src/stackexchange/output/table.ts +7 -2
  60. package/src/stackexchange/rest-client.ts +35 -1
  61. package/src/tools/research-tool-definition.ts +42 -8
  62. package/src/tools/scrape.ts +48 -25
  63. package/src/tools/search.ts +10 -1
  64. package/src/tools/security.ts +68 -7
  65. package/src/tui/research-health.ts +18 -2
  66. package/src/utils/circuit-breaker.ts +9 -2
  67. package/src/utils/disk-space-checker.ts +11 -6
  68. package/src/utils/json-utils.ts +116 -76
  69. package/src/utils/llm-usage.ts +17 -1
  70. package/src/utils/log-rotation.ts +42 -10
  71. package/src/utils/log-utils.ts +40 -0
  72. package/src/utils/metrics-summary.ts +27 -9
  73. package/src/utils/stdio-capture.ts +63 -22
  74. package/src/utils/text-utils.ts +8 -1
  75. package/src/utils/tool-usage-tracker.ts +9 -2
  76. package/src/web-research/browser-search.ts +26 -7
  77. package/src/web-research/scraper-utils.ts +13 -0
  78. package/src/web-research/search.ts +6 -2
  79. package/src/web-research/types.ts +4 -2
  80. package/src/web-research/web-scraper.ts +65 -4
  81. package/src/core/health-check-service.ts +0 -265
package/docs/CHANGELOG.md CHANGED
@@ -5,6 +5,66 @@ 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
+
23
+ ## [1.3.0] - 2026-08-06
24
+
25
+ Bundles three unreleased fix rounds that accumulated on top of 1.2.0 (each
26
+ already pushed to main but never cut), plus a final fresh-eyes review pass
27
+ done specifically to gate this release: an independent reviewer read every
28
+ line of the combined diff against 1.2.0 with no knowledge of why any of it
29
+ was written, and found several real defects the rounds that produced the
30
+ code had missed — two of them the most severe in this release. Every item
31
+ below was re-verified against the current code (not the original bug
32
+ report) before being counted as fixed; the two highest-severity findings
33
+ were personally reproduced (one as an actual OOM crash) before dispatching
34
+ a fix.
35
+
36
+ A minor (not patch) version for the same reason 1.2.0 was: the range
37
+ changes the cancellation exit-code contract again, in the same category
38
+ the last release documented as minor-worthy. A cancelled research run that
39
+ had already collected at least one report used to report plain success
40
+ (`ok: true`, exit `0`) because the orchestrator's fallback-synthesis path
41
+ resolves normally on cancellation rather than throwing — `cmdResearch`'s
42
+ success branch never consulted the cancellation latch, only its error
43
+ branch did. It now exits `128 + signal` (or `130` for a programmatic
44
+ abort) and sets `cancelled: true` in `--json`, matching every other
45
+ cancellation-reporting path fixed in 1.2.0.
46
+
47
+ ### Fixed
48
+
49
+ **Found in this release's own fresh-eyes pass (new, on top of the three prior rounds):**
50
+ - **The chunker's own safety invariant was false — `overlap < targetSize` does not, in fact, prevent infinite loops.** Reproduced directly: `new Chunker({targetSize: 5, overlap: 4}).chunk(...)` on text containing a boundary-straddling emoji crashed the worker process with an out-of-memory error rather than merely running slowly. The UTF-16 surrogate-pair backoff (added earlier in this range) can land on the exact fixed point the forward-progress guard already enforces for `end`, so neither `start` nor `end` advances between iterations and the same chunk is pushed forever. A matching, milder gap let a lone high surrogate ship uncorrected into stored/embedded text whenever backing off would have violated that same guard. Both closed: the surrogate fix now grows past the low surrogate instead of silently doing nothing when shrinking would re-collide, and a new forward-progress invariant on `start` — mirroring the existing one on `end` — guarantees real per-iteration progress independent of any heuristic interaction, not just the ones already traced by hand. A brute-force sweep across small targetSize/overlap/emoji-position combinations backs the fix, not just the one reproducing triple.
51
+ - **Uninstall's "is this directory exclusively ours" check was a bare name match, not an ownership check.** `isPiResearchNamespaced()` treated any path segment literally equal to `pi-research` as sufficient proof a `PI_RESEARCH_STATE_DIR` override was safe to delete wholesale — but naming a directory after the tool pointed at it is a completely ordinary thing to do (e.g. organizing several tools' state under `~/agents-state/<tool-name>/`), and nothing stops a user from keeping unrelated content in that same folder. The one regression test guarding this path only ever planted pi-research-owned entries, never foreign content, so it could never have caught this. Now only the exact default `<dirName>/research` layout — something the runtime actually constructs itself and can therefore prove is exclusively its own — authorizes wholesale removal; every other override, however named, only ever loses the specific entries the runtime is known to create there.
52
+ - **A researcher could still run a full billed session after its run was told to stop.** The three fast-stop checkpoints added earlier in this range (top-of-loop, initial-links loop, post-backoff sleep) left one window uncovered: between `createResearcherSession()` resolving and the session being registered for `abortAllSessions()`'s visibility, a few lines later behind an awaited call. A stop tripping in exactly that gap was invisible to the abort scan, and the caller's bounded ~10s wait for self-stopping researchers elapsed before the session's own LLM call and scrape burst finished — the run returned and released its slot while the researcher kept going in the background. A fourth checkpoint immediately after registration closes it.
53
+ - **A "FIX" comment described a circuit-breaker key format that has never existed in production.** `research-cleanup.ts` cleared session breakers assuming they're keyed `${piSessionId}-<8hex>`, but every real caller keys them by the plain `researchId` (generated independently of the pi session) — the function that would produce the assumed format has no production caller at all. The clear was consequently a no-op, masked on the normal run-completion path by a second, correctly-keyed clear elsewhere; the gap only mattered when this was the sole cleanup path (a throw before the orchestrator ever runs). Fixed to clear by the real key; the now-unused prefix-clearing utility this bug motivated, along with its own comment repeating the same wrong premise, is removed rather than left as bait for the same mistake again.
54
+ - **A follower's healthcheck request couldn't be cancelled the way search/scrape already can.** The abort-signal plumbing added earlier in this range for search/scrape was never extended to healthcheck: the interface type carries no `signal` parameter and the one production caller always calls without one — currently harmless, but silently ready to reintroduce "follower cancellation burns a leader pool slot to timeout" the moment any caller starts passing a real signal, with no compiler error to flag it. Wired through, mirroring search/scrape exactly.
55
+ - **Routine follower-cancellation was logged and counted as a server error.** The same abort-signal work introduced rejections shaped like `"Task search aborted while running"`, which the pool-shutdown error classifier doesn't recognize — so every cancelled in-flight browser task (a normal, frequent event whenever a research run is cancelled) logged at ERROR and inflated `browser_search_errors_total`/`browser_scrape_errors_total` before the layer that correctly demotes it ever saw the error. Each of the three call sites now checks the actual signal the rejection is a consequence of, rather than pattern-matching text — deliberately not widened inside the shared classifier, which a different caller also uses to detect a genuine leader handover.
56
+ - **Severity filtering for a security-database search normalized only one of three backends.** `getSeverityParam()` was applied on the NVD leg only; GitHub and OSV read the raw, unvalidated value directly — reproducing, for two of three backends, the exact "unrecognized value silently filters everything to zero" bug an earlier round in this range fixed for NVD. Masked today because the sole production caller does its own separate, stricter validation first. Now normalized once, ahead of all three legs.
57
+ - A flaky CI failure surfaced during this release's own gate run traced to a test relying on a fixed 50ms sleep to assume a lock had been fully acquired before tearing it down — on a loaded runner, teardown could occasionally win the race and land in a different, already-correctly-handled code path than the one the test meant to exercise. Replaced with a real "critical section has started" signal; no production code was at fault.
58
+
59
+ **From the first post-1.2.0 audit round** (orchestration, browser, embedding/state, knowledge, CLI/config, utils, tools, SDK/planning — ~40 defects): aborted researchers restarting as untracked zombies (an `=== 'Aborted'` string match missed the actual sentinel shape); PDF content zeroed over cluster IPC and the resulting extraction error cached as a successful scrape; the stale-profile sweep touching arbitrary shared-tmp entries outside its own namespace; cluster workers leaking the research query into `ps` output; `clearEmbeddingServer` gaining compare-and-delete semantics (a mere follower's clean shutdown could deregister a live leader); `getEmbedder` refusing to join an in-flight init for a different model/device; a code-fence-straddling chunk boundary emitting one overlap-sliver per character; an explicit `--config` file's keys previously applying nowhere when shadowed by the ambient overlay; CLI flags silently swallowing their own values (`--model --json` running against the literal model `"--json"`); nested embedder-init stdio capture losing LIFO ordering on a mid-capture cancel; the scrape context gate billing pre-slice batch counts; StackExchange rate-limit/quota state not surviving across tool calls; and NVD severity filtering not normalizing case or the MODERATE→MEDIUM synonym.
60
+
61
+ **From the second post-1.2.0 audit round** (browser/web-research, orchestration/SDK, knowledge/embedding, CLI/config/install — ~38 defects, including 2 regressions in the first round's own diff caught by that round's fresh-eyes pass): browser scrapes never checking HTTP status, so 4xx/5xx error pages were cached as citable content; the 100MB PDF / 25MB HTML size caps enforced only after the worker had already buffered and IPC'd the full body; search failures always hitting the global circuit breaker instead of the run's own; a follower's cancelled run burning its leader's pool slot to full timeout instead of aborting on socket close; fast-stop not reaching a researcher still acquiring links, sleeping between attempts, or rebuilding its session; the inter-attempt backoff sleep being non-abortable; a poisoned embedding leader staying eligible to re-elect onto GPU in the same process; NaN embedding vectors laundering to 0 across the HTTP hop; the full-text-search skip-guard's version bookkeeping missing rows committed mid-rebuild; a missing/unreadable explicit `--config` file being silently ignored instead of erroring; duplicate SIGINT delivery (process-group plus launcher-forwarded) hitting a consumed one-shot handler and force-exiting mid-cleanup; and the uninstall script's original, pre-this-release ownership gap for `PI_RESEARCH_STATE_DIR` (the one further hardened above).
62
+
63
+ **From the backlog-clearing round** (re-auditing every item deferred since v1.0.7 plus both rounds above): the cancellation-reporting gap described above; `knowledge --json` success payloads not carrying `ok: true` despite claiming parity with the error shape, and `knowledge-config` having no `--json` error handling at all; the SSRF IPv6 gate missing `fec0::/10` and the NAT64 well-known prefix `64:ff9b::/96` (a real bypass on hosts whose egress traverses a NAT64 gateway, reachable via `64:ff9b::169.254.169.254`); steering messages already consumed by a finished run leaking into the next unrelated run's system-prompt injection; `clearBrowserServer` gaining the same compare-and-delete semantics its embedding-leader sibling already had; a second, independent chunker surrogate-pair gap (raw UTF-16 boundaries with no pair awareness); and steering-extended round budgets being invisible to the round-phase prompt logic (`PlanningService` recomputed `maxRounds` from the base complexity table only). `HealthCheckService` — zero runtime consumers, fully superseded by the live `src/healthcheck/registry.ts` system — was deleted.
64
+
65
+ ### Changed
66
+ - **A cancelled-but-partially-successful research run now reports its true status.** See above — this is the same exit-code contract 1.2.0 established, extended to a success-path case that fix didn't reach.
67
+
8
68
  ## [1.2.0] - 2026-08-05
9
69
 
10
70
  Clears the medium/low findings deferred out of the 1.1.2 audit, then a second
@@ -256,20 +316,73 @@ and the host-version self-check gained the tested-ceiling warning.
256
316
 
257
317
  ---
258
318
 
259
- ## Deferred Findings (from v1.0.7 audit)
260
-
261
- The following issues were identified during the v1.0.7 audit but intentionally deferred to future releases:
262
-
263
- 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.
264
-
265
- 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.
266
-
267
- 3. **Lower-severity hardening items** - Various low-confidence or low-impact findings noted for future investigation:
268
- - SIGINT-adjacent cancellation race
269
- - Knowledge-store migration vs. concurrent access
270
- - Global activity gate reused for per-session decisions
271
- - Zombie queued browser tasks after timeout
272
- - Non-atomic `clearBrowserServer`
273
- - Inconsistent `knowledge --json` payload shapes
274
- - Backward-clock-step tolerance across multiple rate limiters
319
+ ## Deferred Findings — cleared 2026-08-06
320
+
321
+ The v1.0.7-era list below was re-audited against the current tree, three weeks
322
+ and many fix rounds after it was written. Every item resolved one of two ways:
323
+ already fixed as a side effect of unrelated work since, or fixed directly in
324
+ this pass with a regression test. None remain open.
325
+
326
+ - **KnowledgeStore.close() timeout behavior** — already safe: writes register
327
+ into the active-write set before the close-in-progress check can be seen,
328
+ and LanceDB table handles keep working after the owning connection closes,
329
+ so a timed-out close no longer strands or corrupts an in-flight write.
330
+ - **Cross-session teardown via shared singletons** — not reachable: pi fully
331
+ serializes a session's teardown before the next one is created, so shared
332
+ singletons are never disposed out from under a still-live session.
333
+ - **SIGINT-adjacent cancellation race** — real gap, fixed: a cancelled run
334
+ that had already produced a report returned through the orchestrator's
335
+ normal (non-throwing) partial-synthesis path, so the CLI's success branch
336
+ never saw the cancellation latch and reported `ok:true`/exit 0 for a run
337
+ the user had stopped. The success path now checks the latch and reports
338
+ the same `cancelled: true` / `128+signal` contract the error path already
339
+ used.
340
+ - **Knowledge-store migration vs. concurrent access** — already safe: a
341
+ cross-process file lock serializes migrations, and every read/write
342
+ re-resolves its table handle, so a migration racing another process's
343
+ handle fails that operation cleanly instead of serving stale data.
344
+ - **Global activity gate reused for per-session decisions** — real gap,
345
+ fixed: steering messages already consumed by a finished research run
346
+ survived in session state and leaked into the next, unrelated run's
347
+ system-prompt injection and final-report attribution. Only genuinely
348
+ still-queued messages now survive a run boundary.
349
+ - **Zombie queued browser tasks after timeout** — already fixed (in the
350
+ first post-1.2.0 audit round): a queued task's client-side timeout now
351
+ actually removes it from the scheduler's queue instead of leaving it to
352
+ be dispatched later.
353
+ - **Non-atomic `clearBrowserServer`** — real gap, fixed: the file write
354
+ itself was already atomic, but the read-decide-clear sequence around it
355
+ was not — a dead-leader restart path could wipe a legitimately new
356
+ leader's registration elected in the same window. `clearBrowserServer`
357
+ now takes an optional expected-owner argument and compares-and-deletes
358
+ inside the lock, mirroring the embedding leader's existing CAS.
359
+ - **Inconsistent `knowledge --json` payload shapes** — real gap, fixed: the
360
+ success payload never carried `ok:true` despite a comment claiming parity
361
+ with the error shape, and `knowledge-config`'s failure path had no
362
+ try/catch at all and fell through to a plain-text-only handler even under
363
+ `--json`. Both now emit a consistent, documented shape.
364
+ - **Backward-clock-step tolerance across multiple rate limiters** — already
365
+ safe: every limiter's timing arithmetic leans toward waiting longer on a
366
+ negative clock delta, never toward a burst or a permanent wedge.
367
+
368
+ Two further items surfaced and closed in the same pass, found via later
369
+ audit rounds' own deferred lists rather than the v1.0.7 one:
370
+ - **SSRF: `isPrivateIpv6` missed `fec0::/10`** (deprecated site-local) and
371
+ **`64:ff9b::/96`** (the NAT64 well-known prefix — actively deployed today,
372
+ and a real bypass vector on a host whose egress traverses a NAT64
373
+ gateway). Both ranges are now blocked.
374
+ - **Chunk boundaries could split a UTF-16 surrogate pair**, corrupting one
375
+ character (renders as U+FFFD) in stored/embedded text at an unlucky
376
+ boundary — the same class of bug already handled in three other places
377
+ in the codebase, now handled here too, on both the truncation and the
378
+ overlap-derived start boundary.
379
+ - **Steering-extended round budgets weren't visible to the round-phase
380
+ prompt.** When mid-run steering extended the round cap beyond the base
381
+ per-complexity value, the planning service still computed guidance
382
+ against the base value, producing an impossible "Round 4 of 3" and,
383
+ more consequentially, prematurely selecting LATE-phase framing during
384
+ rounds steering had explicitly unlocked.
385
+
386
+ One dead-code cleanup: `HealthCheckService` (a superseded, zero-consumer
387
+ predecessor of the live `src/healthcheck/registry.ts` system) was removed.
275
388
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lincoln504/pi-research",
3
- "version": "1.2.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,
@@ -16,7 +16,7 @@
16
16
  */
17
17
 
18
18
  const { spawnSync } = require('child_process');
19
- const { rmSync, existsSync, lstatSync, readlinkSync, readFileSync, writeFileSync, unlinkSync } = require('fs');
19
+ const { rmSync, existsSync, lstatSync, readdirSync, readlinkSync, readFileSync, writeFileSync, unlinkSync } = require('fs');
20
20
  const os = require('os');
21
21
  const path = require('path');
22
22
 
@@ -181,9 +181,71 @@ const rawDirName = process.env.PI_RESEARCH_CONFIG_DIR_NAME;
181
181
  const dirName = rawDirName && !rawDirName.includes('/') && !rawDirName.includes('\\') && rawDirName !== '.' && rawDirName !== '..'
182
182
  ? rawDirName
183
183
  : '.pi';
184
+
185
+ /**
186
+ * Is this path PROVABLY equivalent to the layout the runtime itself would have
187
+ * constructed by default (`<dirName>/research`)? Wholesale rm of a state-dir
188
+ * override is only safe when the answer is yes: PI_RESEARCH_STATE_DIR may point
189
+ * at a SHARED directory, and rm'ing the override itself (unlike the cache sweep,
190
+ * which is namespaced via join(cacheHome, 'pi-research')) deleted everything the
191
+ * user kept beside our state.
192
+ *
193
+ * Deliberately NOT a bare "does any segment literally say pi-research" check —
194
+ * that matches naming convention, not ownership. A user is entirely free to
195
+ * point the override at, say, `~/agents-state/pi-research/` while keeping other
196
+ * tools' notes alongside it in the same folder (a natural way to organize
197
+ * several tools' state by name); a substring match would treat that shared
198
+ * folder as exclusively ours and wholesale-delete it. Only the exact default
199
+ * layout is something WE constructed and can therefore prove is ours.
200
+ */
201
+ function isPiResearchNamespaced(p) {
202
+ const segs = p.split(/[\\/]+/).filter(Boolean);
203
+ for (let i = 0; i < segs.length - 1; i++) {
204
+ if (segs[i] === dirName && segs[i + 1] === 'research') return true;
205
+ }
206
+ return false;
207
+ }
208
+
209
+ /**
210
+ * Targeted state sweep for a non-namespaced override dir: delete ONLY the
211
+ * entries the runtime actually creates there and leave everything else — and
212
+ * the directory itself — untouched. The entry list mirrors the writers:
213
+ * research-state.json + research-state-<hex>.tmp (state-manager)
214
+ * .locks/, backups/ (state-path-configuration)
215
+ * project-settings.json + its .lock / .tmp. / .lock.stale- companions (config.ts)
216
+ * research-run-slot-<i>.lock (research-run-semaphore)
217
+ * Best-effort; never throws.
218
+ */
219
+ function removeOwnedStateEntries(dir) {
220
+ if (!dir || !existsSync(dir)) return;
221
+ let entries;
222
+ try { entries = readdirSync(dir); } catch { return; }
223
+ const owned = (name) =>
224
+ name === 'research-state.json' ||
225
+ (name.startsWith('research-state-') && name.endsWith('.tmp')) ||
226
+ name === '.locks' ||
227
+ name === 'backups' ||
228
+ name === 'project-settings.json' ||
229
+ name.startsWith('project-settings.json.') ||
230
+ /^research-run-slot-\d+\.lock$/.test(name);
231
+ for (const name of entries) {
232
+ if (!owned(name)) continue;
233
+ try {
234
+ rmSync(path.join(dir, name), { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
235
+ } catch (error) {
236
+ console.warn(`pi-research: could not remove state entry ${path.join(dir, name)}: ${error instanceof Error ? error.message : String(error)}`);
237
+ }
238
+ }
239
+ console.log(`pi-research: removed pi-research state entries from ${dir} (directory preserved — not a pi-research-namespaced path).`);
240
+ }
241
+
184
242
  const stateDir = stateOverride && path.isAbsolute(stateOverride)
185
243
  ? stateOverride
186
244
  : path.join(os.homedir(), dirName, 'research', 'state');
187
- removeOwnedScratchDir('state dir', stateDir);
245
+ if (stateOverride && path.isAbsolute(stateOverride) && !isPiResearchNamespaced(stateDir)) {
246
+ removeOwnedStateEntries(stateDir);
247
+ } else {
248
+ removeOwnedScratchDir('state dir', stateDir);
249
+ }
188
250
 
189
251
  process.exit(0);
@@ -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.2.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
 
@@ -82,11 +82,19 @@ export function createCleanupFunction(
82
82
  const { resetLogger } = await import('../logger.ts');
83
83
  resetLogger(researchId);
84
84
 
85
- // FIX (#10): Clear session circuit breaker to prevent unbounded map growth.
85
+ // FIX (#10): Clear the session's browser circuit breaker to prevent unbounded
86
+ // map growth. Breakers are keyed by the plain researchId — createResearchRunId()
87
+ // generates it (`run-<8 hex>`) independently of piSessionId in
88
+ // research-tool-definition.ts, and tools/scrape.ts / tools/search.ts thread that
89
+ // same bare researchId into getBrowserCircuitBreaker() as BrowserTask.sessionId.
90
+ // (The `${piSessionId}-<8 hex>` shape this comment used to assume is produced
91
+ // only by generateSessionId(), which has no production caller.) This duplicates
92
+ // the clearSessionCircuitBreaker(targetId) call research-orchestration-service.ts
93
+ // already makes in its own finally block on the normal run-completion path; it
94
+ // matters here on paths where this cleanup runs alone, e.g. a throw before
95
+ // orchestrator.run() is ever reached.
86
96
  const { clearSessionCircuitBreaker } = await import('../infrastructure/browser/browser-error-utils.ts');
87
- if (piSessionId) {
88
- clearSessionCircuitBreaker(piSessionId);
89
- }
97
+ clearSessionCircuitBreaker(researchId);
90
98
 
91
99
  const activePanels = getPiActivePanels(piSessionId);
92
100
  if (activePanels.length === 0) {