@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.
- package/dist/cli.mjs +1334 -750
- package/dist/thread-worker.mjs +194 -136
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/CHANGELOG.md +129 -16
- package/package.json +1 -1
- package/scripts/cleanup.cjs +64 -2
- package/skills/pi-research/SKILL.md +1 -1
- package/src/cleanup/research-cleanup.ts +12 -4
- package/src/cli.ts +228 -70
- package/src/config.ts +78 -2
- package/src/core/interfaces/health-check-interfaces.ts +3 -39
- package/src/core/interfaces/planning-interfaces.ts +8 -0
- package/src/core/interfaces/service-names.ts +0 -1
- package/src/core/interfaces/state-manager-interfaces.ts +10 -2
- package/src/core/llm/agentic-repair.ts +14 -0
- package/src/core/planning-service.ts +43 -1
- package/src/core/service-initialization.ts +6 -14
- package/src/core/service-interfaces.ts +1 -1
- package/src/healthcheck/index.ts +7 -2
- package/src/infrastructure/browser/browser-client.ts +64 -9
- package/src/infrastructure/browser/browser-server.ts +24 -7
- package/src/infrastructure/browser/browser-task-scheduler.ts +58 -12
- package/src/infrastructure/browser/cleanup-utils.ts +50 -2
- package/src/infrastructure/browser/client-agent.ts +6 -6
- package/src/infrastructure/browser/config.ts +14 -3
- package/src/infrastructure/browser/scheduler-factory.ts +18 -3
- package/src/infrastructure/browser/thread-worker-messaging.ts +54 -2
- package/src/infrastructure/browser/thread-worker-types.ts +3 -1
- package/src/infrastructure/browser/thread-worker.mjs +194 -136
- package/src/infrastructure/browser/worker-pool-manager.ts +7 -2
- package/src/infrastructure/embedding/embedding-factory.ts +35 -6
- package/src/infrastructure/embedding/embedding-server.ts +40 -7
- package/src/infrastructure/file-lock-service.ts +72 -2
- package/src/infrastructure/knowledge-store-service.ts +48 -3
- package/src/infrastructure/process-lifecycle-service.ts +28 -5
- package/src/infrastructure/research-run-semaphore.ts +37 -3
- package/src/infrastructure/state/state-browser-api.ts +5 -2
- package/src/infrastructure/state/state-browser-manager.ts +15 -1
- package/src/infrastructure/state/state-manager-service.ts +4 -4
- package/src/infrastructure/state/state-manager.ts +15 -3
- package/src/knowledge/chunker.ts +88 -1
- package/src/knowledge/embedder.ts +136 -43
- package/src/knowledge/store.ts +66 -12
- package/src/logger.ts +5 -1
- package/src/observers/research-observer-impl.ts +31 -0
- package/src/orchestration/abort-utils.ts +60 -0
- package/src/orchestration/deep-research-orchestrator.ts +12 -2
- package/src/orchestration/headless-observer.ts +8 -0
- package/src/orchestration/research-orchestration-service.ts +63 -11
- package/src/orchestration/research-session-service.ts +37 -11
- package/src/orchestration/researcher-executor.ts +100 -20
- package/src/orchestration/researcher.ts +39 -20
- package/src/orchestration/session-state.ts +11 -0
- package/src/sdk.ts +125 -19
- package/src/security/index.ts +51 -30
- package/src/security/nvd-client.ts +7 -2
- package/src/skill-install/skill-installer.ts +18 -2
- package/src/stackexchange/index.ts +20 -5
- package/src/stackexchange/output/table.ts +7 -2
- package/src/stackexchange/rest-client.ts +35 -1
- package/src/tools/research-tool-definition.ts +42 -8
- package/src/tools/scrape.ts +48 -25
- package/src/tools/search.ts +10 -1
- package/src/tools/security.ts +68 -7
- package/src/tui/research-health.ts +18 -2
- package/src/utils/circuit-breaker.ts +9 -2
- package/src/utils/disk-space-checker.ts +11 -6
- package/src/utils/json-utils.ts +116 -76
- package/src/utils/llm-usage.ts +17 -1
- package/src/utils/log-rotation.ts +42 -10
- package/src/utils/log-utils.ts +40 -0
- package/src/utils/metrics-summary.ts +27 -9
- package/src/utils/stdio-capture.ts +63 -22
- package/src/utils/text-utils.ts +8 -1
- package/src/utils/tool-usage-tracker.ts +9 -2
- package/src/web-research/browser-search.ts +26 -7
- package/src/web-research/scraper-utils.ts +13 -0
- package/src/web-research/search.ts +6 -2
- package/src/web-research/types.ts +4 -2
- package/src/web-research/web-scraper.ts +65 -4
- 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
|
|
260
|
-
|
|
261
|
-
The
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
package/scripts/cleanup.cjs
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
88
|
-
clearSessionCircuitBreaker(piSessionId);
|
|
89
|
-
}
|
|
97
|
+
clearSessionCircuitBreaker(researchId);
|
|
90
98
|
|
|
91
99
|
const activePanels = getPiActivePanels(piSessionId);
|
|
92
100
|
if (activePanels.length === 0) {
|