@lincoln504/pi-research 1.6.5 → 1.6.7
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 +14 -9
- package/agent-skill/pi-research/README.md +5 -4
- package/agent-skill/pi-research/SKILL.md +1 -1
- package/dist/cli.mjs +616 -153
- package/dist/pdf-extract-worker.mjs +1289 -0
- package/dist/prompts/research-tool-usage.md +2 -1
- package/dist/prompts/researcher.md +1 -1
- package/dist/prompts/system-coordinator.md +2 -2
- package/dist/prompts/system-lead-router.md +1 -1
- package/docs/AGENT-SKILL.md +6 -6
- package/docs/ARCHITECTURE.md +24 -22
- package/docs/CHANGELOG.md +73 -0
- package/docs/CONFIGURATION.md +3 -2
- package/docs/SDK.md +7 -12
- package/package.json +8 -5
- package/scripts/build.cjs +35 -0
- package/scripts/setup.cjs +12 -10
- package/src/cleanup/research-cleanup.ts +11 -5
- package/src/cli.ts +7 -5
- package/src/config.ts +9 -3
- package/src/core/interfaces/knowledge-interfaces.ts +8 -0
- package/src/core/interfaces/orchestration-interfaces.ts +1 -1
- package/src/core/interfaces/research-plan-types.ts +6 -0
- package/src/core/llm/agentic-repair.ts +40 -3
- package/src/core/llm/llm-utils.ts +93 -5
- package/src/core/llm/model-registry-factory.ts +2 -15
- package/src/core/pi-version.ts +1 -1
- package/src/core/planning-service.ts +110 -15
- package/src/core/planning-utils.ts +3 -0
- package/src/core/service-registry.ts +8 -6
- package/src/index.ts +8 -5
- package/src/infrastructure/browser/config.ts +6 -7
- package/src/infrastructure/browser/ensure-browser.ts +14 -7
- package/src/infrastructure/browser/thread-worker-browser.ts +18 -19
- package/src/knowledge/chunker.ts +22 -4
- package/src/knowledge/embedder-init.ts +41 -1
- package/src/knowledge/embedder-utils.ts +7 -1
- package/src/knowledge/store.ts +68 -1
- package/src/knowledge/transformers-loader.ts +58 -1
- package/src/observers/research-observer-impl.ts +4 -1
- package/src/orchestration/deep-research-orchestrator.ts +3 -3
- package/src/orchestration/headless-observer.ts +33 -0
- package/src/orchestration/quick-research-orchestrator.ts +17 -3
- package/src/orchestration/research-orchestration-service.ts +27 -4
- package/src/orchestration/session-state.ts +36 -7
- package/src/prompts/research-tool-usage.md +2 -1
- package/src/prompts/researcher.md +1 -1
- package/src/prompts/system-coordinator.md +2 -2
- package/src/prompts/system-lead-router.md +1 -1
- package/src/sdk.ts +12 -4
- package/src/tools/research-knowledge-search.ts +23 -7
- package/src/tools/research-tool-definition.ts +1 -1
- package/src/tools/scrape.ts +12 -9
- package/src/tools/search.ts +12 -6
- package/src/tools/security.ts +6 -1
- package/src/tools/stackexchange.ts +6 -1
- package/src/utils/cancellation.ts +34 -1
- package/src/utils/llm-usage.ts +8 -0
- package/src/utils/log-utils.ts +1 -1
- package/src/web-research/browser-search.ts +6 -5
- package/src/web-research/pdf-extract-worker.mjs +1289 -0
- package/src/web-research/pdf-extract-worker.ts +42 -0
- package/src/web-research/pdf-extraction.ts +392 -0
- package/src/web-research/web-scraper.ts +3 -111
|
@@ -85,7 +85,8 @@ The coordinator will plan as many researchers as needed (up to the max). You do
|
|
|
85
85
|
|
|
86
86
|
**Multiple Topics:**
|
|
87
87
|
- If you need to research multiple unrelated topics of **truly distinct scope**, you may emit multiple `research` calls **simultaneously in a single turn**. Each call gets its own isolated research run with its own coordinator and researchers.
|
|
88
|
-
- **Maximum 4 simultaneous calls** — Do NOT make more than 4 `research` calls in a single turn, even if you have more unrelated topics. Exceeding 4 simultaneous research runs can overload the system and degrade performance. If you have more than 4 truly distinct topics, they can likely be consolidated into fewer calls (group related subtopics, use broader queries, or batch across multiple turns).
|
|
88
|
+
- **Maximum 4 simultaneous calls** — Do NOT make more than 4 `research` calls in a single turn, even if you have more unrelated topics. Exceeding 4 simultaneous research runs can overload the system and degrade performance. If you have more than 4 truly distinct topics, they can likely be consolidated into fewer calls (group related subtopics, use broader queries, or batch across multiple turns). Treat the machine-wide run cap (next bullet) as the REAL ceiling: it may be lower than 4, and it always wins.
|
|
89
|
+
- **Simultaneous calls are also bounded by the machine-wide run cap (default 3, `PI_RESEARCH_MAX_CONCURRENT_RUNS`).** A call beyond the cap WAITS for a free slot and fails with a capacity error if the wait expires — so do not count on more than 3 running at once, and never rely on the 4-call maximum being available.
|
|
89
90
|
- Example: `research("bananas")` and `research("quantum computing")` in the same turn — these have zero overlap.
|
|
90
91
|
- **Group related topics**: If topics share ANY scope or could inform each other, combine them into a single `research` call. The internal coordinator will decompose them into parallel researcher agents. Do NOT split related sub-topics into separate calls.
|
|
91
92
|
- Example: `research("Python async performance and Rust async performance comparison")` — NOT separate calls for Python and Rust, since the comparison IS the research goal.
|
|
@@ -50,7 +50,7 @@ Write your report immediately after scraping is complete, or as soon as a tool r
|
|
|
50
50
|
- `youtube_transcript`: Read the captions/transcript of YouTube videos from your source list — it is the `scrape` for video. ONE call only, batching up to a few of the most relevant YouTube links. Cite these as `Source: YouTube Transcript`.
|
|
51
51
|
- `stackexchange`: Use ONLY for Stack Overflow or Stack Exchange URLs.
|
|
52
52
|
- `security_search`: Query NVD, CVE, OSV, CISA databases.
|
|
53
|
-
- `read
|
|
53
|
+
- `read`: Read files in the local codebase. Use ONLY if local codebase context is explicitly required.
|
|
54
54
|
{{extra_tool_guidelines}}
|
|
55
55
|
|
|
56
56
|
- **Specialized Tooling Directive**: Do NOT waste time with auxiliary tools (`stackexchange`, `security_search`) unless they are **specifically necessary** for your assigned goal. They are not for exploratory steps. Your primary workflow is to **scrape** authoritative web sources and **report** findings.
|
|
@@ -43,7 +43,7 @@ Before splitting the query into researchers, pin down its scope — exactly what
|
|
|
43
43
|
|
|
44
44
|
## Output Format
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
Respond with your full team plan by CALLING the `submit_plan` tool when it is offered in the conversation — its arguments ARE the plan (primary, schema-exact channel). Only if you cannot call tools, return a JSON block instead. Use this general schema:
|
|
47
47
|
|
|
48
48
|
```json
|
|
49
49
|
{
|
|
@@ -72,4 +72,4 @@ Return ONLY a JSON block containing your full team plan. Use this general schema
|
|
|
72
72
|
- **Researcher IDs**: Use Round.Index format (e.g. **1.1**, **1.2**).
|
|
73
73
|
- **Citations**: Each researcher's queries should be designed to uncover sources that can be comprehensively cited. Prioritize queries that lead to authoritative references, data, and documented facts.
|
|
74
74
|
- **Title**: Set `"title"` to 1-2 words that best name the core topic (e.g. `"iPhone 15"`, `"React Performance"`, `"PostgreSQL Replication"`). Use proper nouns and brand names where applicable. Do NOT use generic words like "research", "overview", or "options".
|
|
75
|
-
- **Format**:
|
|
75
|
+
- **Format**: Call `submit_plan` with the plan as its arguments when the tool is offered; otherwise output valid JSON only. No markdown, no explanation outside the JSON block.
|
|
@@ -119,4 +119,4 @@ When delegating, ensure:
|
|
|
119
119
|
- **Researcher IDs**: Use Round.Index format — the current round number from the RUN CONTEXT block, then the researcher index. In round 3 that is **3.1**, **3.2**, and so on.
|
|
120
120
|
- **Query Budget**: Use the complexity-specific budget ({{query_budget}} per researcher). Fill each researcher's query budget completely.
|
|
121
121
|
- **Team Size**: Scale researcher count to match the gaps. Use up to {{max_team_size}} researchers when delegating, but a single well-targeted researcher is often sufficient for focused gaps. Don't pad the team when fewer researchers will cover the remaining gaps efficiently.
|
|
122
|
-
- **Format**:
|
|
122
|
+
- **Format**: Call `submit_routing_decision` with the updated plan as its arguments when the tool is offered; otherwise return ONLY valid JSON in a code block. Never return report prose.
|
package/docs/AGENT-SKILL.md
CHANGED
|
@@ -17,14 +17,14 @@ For standalone use without pi, install the engine globally, then link the skill
|
|
|
17
17
|
every coding agent detected on this machine:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npm install -g @lincoln504/pi-research
|
|
20
|
+
npm install -g @lincoln504/pi-research # the engine (puts `pi-research` on PATH)
|
|
21
21
|
pi-research skill install # link the skill into every detected agent
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
On npm
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
[README](../README.md#install)).
|
|
24
|
+
On npm ≥11.19 (and npm 12) dependency install scripts are skipped by default — and
|
|
25
|
+
none are needed: better-sqlite3 13 ships prebuilt bindings in its own tarball, and the
|
|
26
|
+
stealth browser self-provisions on its first use (the first scrape takes a few
|
|
27
|
+
minutes). No approval is required (see the [README](../README.md#install)).
|
|
28
28
|
|
|
29
29
|
`skill install` targets only agents already set up under `$HOME`, never overwrites a
|
|
30
30
|
different skill in the slot, and records what it created so `pi-research skill uninstall`
|
|
@@ -130,7 +130,7 @@ Manual. Symlink the directory into any agent's skills folder yourself:
|
|
|
130
130
|
(`PI_RESEARCH_MODEL`) + API key. See [Configuration](CONFIGURATION.md).
|
|
131
131
|
|
|
132
132
|
```bash
|
|
133
|
-
npm install -g @lincoln504/pi-research
|
|
133
|
+
npm install -g @lincoln504/pi-research
|
|
134
134
|
node "<skill_dir>/scripts/run.mjs" status # verify the engine is detected
|
|
135
135
|
```
|
|
136
136
|
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -309,27 +309,27 @@ Worker pool over direct browser — browser processes are isolated in workers so
|
|
|
309
309
|
in one cannot affect the orchestrator or other sessions.
|
|
310
310
|
|
|
311
311
|
Pinned browser stack — `playwright-core` and `impit` are pinned to exact versions and
|
|
312
|
-
`camoufox-js` to its `0.
|
|
313
|
-
each floating range broke fresh consumer installs that our lockfile masked.
|
|
312
|
+
`camoufox-js` is pinned to its `0.12.0` line; the three are coupled and upgraded together,
|
|
313
|
+
because each floating range broke fresh consumer installs that our lockfile masked.
|
|
314
314
|
playwright-core stays at `1.60.0` (1.61+ rejects camoufox's Juggler and fails every
|
|
315
|
-
launch — corroborated upstream
|
|
315
|
+
launch — corroborated upstream: camoufox-js `0.12.0` declares
|
|
316
316
|
`peerDependencies: { "playwright-core": "<1.61.0" }`, the same bound this pin holds by
|
|
317
|
-
hand). `impit`
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
full: `src/infrastructure/browser/thread-worker-browser.ts`.
|
|
322
|
-
|
|
323
|
-
The
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
better-sqlite3 13
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
317
|
+
hand). `impit` is exact at `0.14.4` (refreshed from `0.13.0` on 2026-08-30 together with
|
|
318
|
+
the camoufox bump) — exact because npm `overrides` do not propagate to consumers, so an
|
|
319
|
+
exact pin is the only way to force a version downstream; impit's `only-allow pnpm`
|
|
320
|
+
preinstall-guard incident (0.13.1/0.14.0, dropped in 0.14.1) is why floating ranges are
|
|
321
|
+
not trusted here. Rationale in full: `src/infrastructure/browser/thread-worker-browser.ts`.
|
|
322
|
+
|
|
323
|
+
The 0.10.x→0.12.0 camoufox bump had been held back through two refresh cycles by three
|
|
324
|
+
blockers, all since resolved: camoufox restored Windows binaries in `v152.0.4-beta.26`
|
|
325
|
+
(2026-07-16); impit's pnpm guard existed only in 0.13.1/0.14.0; and camoufox-js 0.12's
|
|
326
|
+
better-sqlite3 13 upgrade, which initially looked like it demanded a C++ toolchain on
|
|
327
|
+
every install. Measured on 13.0.3: `prebuilds/` for all eight platform/arch pairs ship
|
|
328
|
+
INSIDE its tarball and load at runtime via node-gyp-build — no install script, nothing
|
|
329
|
+
for a consumer to approve. What actually broke was tooling, not the binding: npm ≤11's
|
|
330
|
+
injected `node-gyp rebuild` needlessly recompiles a binding.gyp with no install script
|
|
331
|
+
(and its node-gyp 11.2 cannot detect the VS2026 CI runner image), which is why CI runs
|
|
332
|
+
npm 12. Any future bump checks better-sqlite3 first, not camoufox.
|
|
333
333
|
|
|
334
334
|
The browser BINARY, by contrast, is not pinned and cannot be. `camoufox-js fetch` takes
|
|
335
335
|
no version argument: it walks the `daijro/camoufox` GitHub releases newest-first and takes
|
|
@@ -339,8 +339,7 @@ which camoufox-js version is installed — the npm pins do not freeze it, and a
|
|
|
339
339
|
camoufox release could break launches for fresh installs with no change on our side.
|
|
340
340
|
Windows assets were in fact missing from `v146-hardware` through `v152.0.2-alpha` and
|
|
341
341
|
returned in `v152.0.4-beta.26` (2026-07-16). Current newest is `v152.0.4-beta.28`
|
|
342
|
-
(Firefox 152); it launches and drives cleanly under playwright-core `1.60.0
|
|
343
|
-
camoufox-js 0.10.2** (whose `MIN_VERSION` is `beta.19`, so it accepts it), verified
|
|
342
|
+
(Firefox 152); it launches and drives cleanly under playwright-core `1.60.0`, verified
|
|
344
343
|
directly, as does the older `v135.0.1-beta.24` an existing cache may still hold. The
|
|
345
344
|
practical consequence is that browser freshness is independent of the npm pin: a stale
|
|
346
345
|
`camoufox-js` does not mean a stale Firefox. Re-verify a real launch when bumping this
|
|
@@ -366,7 +365,8 @@ caret-safe. Note also that every `@lancedb/lancedb` release through 0.37 declare
|
|
|
366
365
|
changes which pairing needs re-validating.
|
|
367
366
|
|
|
368
367
|
Pinned validation library — `typebox` is pinned to the exact version the pi host packages
|
|
369
|
-
depend on (`@earendil-works/pi-ai` / `@earendil-works/pi-coding-agent`
|
|
368
|
+
depend on (`@earendil-works/pi-ai` / `@earendil-works/pi-coding-agent` pin `1.3.7` across
|
|
369
|
+
the 0.84.x line). Every
|
|
370
370
|
tool's parameter schema is built with TypeBox here and handed across the boundary to pi's
|
|
371
371
|
tool system, so the two must agree on `Value.Check`/`Convert` semantics. A floating `^1.1.38`
|
|
372
372
|
range let a fresh consumer install resolve pi-research to a newer TypeBox than pi's, shipping
|
|
@@ -427,6 +427,8 @@ npm run test:unit # unit tests, no browser required
|
|
|
427
427
|
npm run test:integration # requires camoufox (Xvfb only for the opt-in virtual-display tests)
|
|
428
428
|
npm run type-check # TypeScript strict mode (src)
|
|
429
429
|
npm run type-check:tests # TypeScript strict mode (tests)
|
|
430
|
+
npm run type-check:native # SAME checks on the TS7 native compiler (pinned; ~9x faster)
|
|
431
|
+
npm run type-check:native:tests # TS7 native check, test project
|
|
430
432
|
npm run lint # ESLint
|
|
431
433
|
npm run deps:check # architectural rule enforcement
|
|
432
434
|
npm run build:worker # bundle the browser worker (required before integration tests / publish)
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,79 @@ 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.7] - 2026-08-31
|
|
9
|
+
|
|
10
|
+
Two threads from the issue tracker collide here: #10 ("Installation via NPM fails")
|
|
11
|
+
turned out to be an install-time sharp build failure we could — and now did — design
|
|
12
|
+
away, while the skew guard shipped across 1.5.x–1.6.6 rested on a premise about pi's
|
|
13
|
+
module loading that is not how pi works.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **`pi install` no longer fails outright on machines where sharp's native image chain cannot install (#10).** The failure chain, reproduced and pinned: `@huggingface/transformers` (the local-embeddings backend) declared sharp `^0.34.5` as a regular dependency; when npm skipped sharp's platform binary (`@img/sharp-*` optionals — the npm/cli#4828 pruning family on existing trees for npm 10.3.0–11.2.x, or any interrupted install, which the re-resolve then will NOT heal because skipped optionals stay inert), sharp's install script fell back to a source build that itself is broken (node-addon-api is only a devDependency), and the whole install died with `npm error code 1` — the extension never arrived. `@huggingface/transformers` is now an **optional dependency** (the same pattern `@lancedb/lancedb` already uses for it): npm drops a failing optional subtree with a warning and the install succeeds; research, scraping, and the CLI all work without embeddings. The absence is a first-class state at runtime too: `getTransformers()` reclassifies the missing-module rejection as a typed `TRANSFORMERS_UNAVAILABLE` error carrying remediation (`src/knowledge/transformers-loader.ts`), `isNativeStackUnavailableError()` recognizes it (`src/knowledge/embedder-utils.ts`), and the knowledge store therefore memoizes DISABLED on first touch instead of re-running the full init + 5-attempt backoff storm (~15s) on every store use. A transformers-INTERNAL failure (its own onnxruntime import, a corrupt install) still routes through the pre-existing onnxruntime/lancedb arms — a broken install of a PRESENT package is not "the optional was skipped". Retry storms, probe behavior, and startup were verified: the WebGPU probe subprocess already treats a missing module as "no WebGPU" and is unreachable before the env init anyway.
|
|
18
|
+
|
|
19
|
+
### Removed
|
|
20
|
+
|
|
21
|
+
- **The pi-ai/pi-coding-agent "skew guard" is gone** (`src/core/pi-ai-skew.ts`; shipped in 1.6.5 as fail-fast, reworked in 1.6.6 to walk node_modules ancestors). The premise does not survive contact with pi's loader: pi resolves EVERY `@earendil-works/*` and `typebox` specifier in extension code to the HOST's own copies via jiti `virtualModules`/`alias` (verified in the 0.84.2 and 0.84.4 loader source — coverage includes the exact four specifiers this package imports), so the on-disk copies the guard compared are never loaded in-host. Under the host the guard measured dependencies nobody executes; its FATAL 'stale' refusal would have blocked a healthy extension after every host upgrade the moment the managed tree's inert copies lagged — and the 1.6.6 ancestor-walk made that state reachable on the primary `pi install npm:` layout (pi's own install code uses `--legacy-peer-deps` precisely because it expects extensions to declare these packages as peers, not dependencies). Host-version compatibility stays where it belongs: `checkPiCompatibility()` against the running host's VERSION — refuse below the 0.84.0 floor, warn once above the tested line — which is now the only startup check. The standalone CLI/SDK never ran the guard (it lived solely in the extension entry), so removal changes nothing there. What actually caused the original 2026-08-30 named-export crash remains unproven — in-host specifier mixing should have been impossible under 0.84.2's alias table — and is recorded here honestly rather than preserved as a false safety net.
|
|
22
|
+
|
|
23
|
+
### Verified
|
|
24
|
+
|
|
25
|
+
- 2993 unit tests over 236 files (11 new: the transformers-absent typed error and its classification — ESM and CJS not-found forms, transformers-internal failures passing through untouched, unrelated-package precision, and the DISABLED-memoization handoff to `isNativeStackUnavailableError`), plus lint, all four type-checks (TS6 and TS7 native, src and tests), dependency-cruiser, the audit gate, the full integration suite (57 passed / 8 skipped parallel, 103 serial), and the tarball manifest gate (235 files — one fewer with the deleted guard module). CLI boot verified end-to-end with the entire transformers/sharp/@img subtree physically absent from the install tree.
|
|
26
|
+
|
|
27
|
+
## [1.6.6] - 2026-08-31
|
|
28
|
+
|
|
29
|
+
Issue #9 reported deep-research runs stalling for minutes inside planning salvage
|
|
30
|
+
and then silently degrading to a single researcher. Three of its four claims were
|
|
31
|
+
real; the fourth (zero prompt-cache reads) turned out to be the provider's route
|
|
32
|
+
reporting no cached tokens — with a metric doc that wrongly implied otherwise.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- **The skew guard could not see the copies of the PRIMARY install layout, and false-warned on every healthy startup there.** `pi install npm:` installs into one flat managed npm tree (`~/.pi/agent/npm/`, measured): the extension sits at `node_modules/@lincoln504/pi-research` with NO nested `node_modules`, and its `@earendil-works/*` dependencies hoist to the managed root. The guard's nested-only lookup therefore missed both copies on every `pi install` — classifying a healthy install as 'incomplete' ("cannot resolve pi-ai") on every startup — and its remedy (`cd <package> && npm install`) would have created a rogue divergent nested install inside the managed tree. Worse, the guard's protection never actually covered that layout: when the host advances and the managed tree's hoisted copies go stale (the 2026-08-30 crash shape), the lookup still found nothing and stayed at the soft warn. Copy resolution now walks ancestors exactly as Node's bare-specifier resolution binds them — nested copies win when present (git installs clone + `npm install` inside the checkout; the dev repo has its own), otherwise the hoisting tree is found and classified — so a healthy flat install is silent 'ok', and a stale managed tree is the FATAL refusal with a remedy that `cd`s into the tree that OWNS the stale copy (`~/.pi/agent/npm`), not the package dir (`src/core/pi-ai-skew.ts`). Five fixture suites pin every layout: flat-managed, nested, half-hoisted incident shape, truly-absent, and stale-flat remedy targeting.
|
|
37
|
+
- **The release record claimed a 1.6.6 release that never happened, and the docs had drifted against it three ways.** Nothing since v1.6.5 is published (npm's latest is 1.6.5; no v1.6.6 tag or release run exists), yet this file carried both a dated '[1.6.6] - 2026-08-26' entry asserting a shipped release and a separate '[1.6.7] - Unreleased' entry — while package.json and SKILL.md sit at 1.6.6, the version the release gates enforce against the next tag. The two halves are now one '[1.6.6] - Unreleased' section, and the 'pre-1.6.7' cross-references in docs/CONFIGURATION.md and .env.example point at [1.6.6]. ARCHITECTURE.md still taught the npm 12 script-approval flow (`--allow-scripts=better-sqlite3` / `approve-scripts`+`rebuild`) as what made the camoufox 0.12 refresh shippable and said better-sqlite3 13 "dropped prebuilt binaries" — both superseded by the measured 13.0.3 truth (prebuilds/ for all eight platform/arch pairs ship in-tarball; nothing needs approving) that the same day's commits put into the README/SDK/skill docs; the paragraph now records that mechanism, and its typebox anchor covers the whole 0.84.x line (0.84.4 pins the same 1.3.7). CONFIGURATION.md's `PLAYWRIGHT_BROWSERS_PATH` row still carried the pre-0.12 camoufox caveat ("moves only where pi-research looks, not where the download lands") two lines under the corrected `CAMOUFOX_INSTALL_DIR` row — on 0.12.0+ the alias is exported AS `CAMOUFOX_INSTALL_DIR` and relocates the download too — and .env.example gains the `CAMOUFOX_INSTALL_DIR` entry its "exhaustive env-var reference" section was missing. The README states the versioning policy outright: pi-research typically follows the latest pi version and is not actively backwards compatible. Also pinned here: the cancellation classifier's "This operation was aborted" alternation, recorded as test-pinned since the re-audit but covered by no test until now (`test/unit/utils/cancellation.test.ts`).
|
|
38
|
+
- **Every `npm run test:unit` run leaked its per-worker throwaway HOME into /tmp forever** — 4,990 `pi-research-unit-home-*` directories had accumulated on one dev machine. The unit-env setup now removes its own directory when the worker exits and opportunistically reclaims leaks older than 24h, without touching dirs owned by concurrent runs.
|
|
39
|
+
- **The npm 12 install guidance was wrong for the current dependency set, and the CI Windows legs failed on the same discovery.** Measured on npm 12.0.2 (sandboxed installs of the real tarball under a supported Node): better-sqlite3 13.0.3 bundles `prebuilds/` for all eight platform/arch pairs inside its tarball and loads them at runtime via node-gyp-build — with every install script skipped, the package installs cleanly and works, and the browser self-provisions on first use. The approval dance taught by the README/SDK/AGENT-SKILL npm-12 notes (and by the status/search failure remedies) described better-sqlite3 12's download-at-install model and was replaced with the measured truth on every surface; the thread-worker pin rationale now records the real 13.x mechanism. Separately, GitHub's windows-latest image moved to Visual Studio 2026 (version 18), which npm ≤11's node-gyp 11.2 cannot detect (`unknown version "undefined"`; GYP_MSVS_VERSION does not help — the crash precedes that lookup), so `npm ci` failed at better-sqlite3's rebuild on every Windows leg; CI now runs npm 12 (node-gyp 13, VS2026-aware) on every job that installs dependencies, and provisions the integration browsers through an explicit setup step because npm ≥11.19 no longer runs the postinstall that used to do it.
|
|
40
|
+
- **A four-lane re-audit of this release's own diff found seven residual defects; all are fixed here.** The worst: the pi-ai skew guard's incomplete branch (one nested SDK copy missing) warned instead of refusing to start when the copy that IS present lags the host — exactly the half-hoisted layout that caused the 2026-08-30 crash (`src/core/pi-ai-skew.ts`). Next: `raceWithSignal`'s already-aborted fast path left the eagerly-started op unhandled, so a probe rejecting after abort fired an unhandledRejection (`src/utils/cancellation.ts`); `getResearcherFailureReasons` still created-on-read, leaking a session state for ended/never-created sessions (`src/orchestration/session-state.ts`); `isHeadlessObserverBag` covered 8 of 24 observer methods, so a full observer with `onProgress` plus one unlisted typed method was demoted to a bag and lost every typed callback (`src/orchestration/headless-observer.ts`); the store's alternation detector flagged a deliberate second model change (A→B→C) as thrash — it now tracks two migrations and fires only on a RETURN (A→B→A) (`src/knowledge/store.ts`); the scrape tool's schema advertised `maxItems: 20` with only 6 fetched per call — now stated in the schema itself (`src/tools/scrape.ts`); and the cancellation classifier missed the canonical DOM form "This operation was aborted" (`src/utils/cancellation.ts`). Ten new tests pin all of it.
|
|
41
|
+
- **The browser-fetch bounds were still sized for the ~100MB camoufox 0.10 binary.** The 0.12 refresh's browser measures ~500MB, and camoufox-js deletes the install directory BEFORE downloading with no resume — so a fetch killed by the old 15-minute bound (or by any mid-download failure on a slow link) leaves NOTHING, which is exactly how this release's dev machine lost its working browser. Postinstall and the runtime ensure path now allow 45 minutes (a working link down to ~180 KB/s), and the stale-lock/peer-wait windows scale with the fetch so a live slow fetcher is never treated as crashed or duplicated; the "~100MB" messaging is corrected everywhere (`scripts/setup.cjs`, `src/infrastructure/browser/ensure-browser.ts`).
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- **A stale extension `node_modules` crashed every LLM call with a misleading "Provider error".** When the host pi advanced past the `@earendil-works/*` copies this extension resolves (host 0.84.4 vs extension pi-ai 0.84.2), mixed-version module graphs killed researchers and knowledge triage/extraction at provider load with ESM named-export errors (`clampThinkingBudgetToAnswerRoom`, first exported in pi-ai 0.84.3). The lockfile now resolves the SDK trio at 0.84.4, and a new load-time guard (`checkPiAiSkew`) walks the host tree and the extension's resolved copies and REFUSES TO START with the exact remediation instead of five silent researcher deaths (`src/core/pi-ai-skew.ts`).
|
|
46
|
+
- **The packaged CLI crashed on the first PDF.** `resolveWorkerPath()` used bare `__dirname`, which is undefined in the esbuild `format: 'esm'` bundle (`dist/cli.mjs`) and in jiti-loaded ESM: the ReferenceError fired BEFORE any try/catch, so every PDF extraction failed in built installs (dev worked — tsx/vitest shim `__dirname`; the unit default of `PI_RESEARCH_PDF_WORKER=off` also short-circuited before the crash). Worker path resolution now derives from `fileURLToPath(import.meta.url)` exactly like the browser worker pool, pinned by a source-invariant regression test, and the built bundle was verified end-to-end with a real PDF round-trip (`src/web-research/pdf-extraction.ts`).
|
|
47
|
+
- **A wedged PDF worker could be terminated twice, siblings hung out to their own timeouts, and a stale exit event could kill a fresh worker.** The 30s timeout terminated the module-global worker and nulled it, so sibling requests never got the fail-fast `failAllPending` (each hung its full 30s), a `postMessage` sync-throw left its timer armed to terminate an unrelated future parse, and an old worker's late `exit` event (arriving after a respawn) matched the global and failed the NEW worker's requests. The timeout now terminates the worker instance captured in its own `PendingRequest`, fails all remaining pending requests unconditionally, clears timer+entry on sync postMessage throw, and crash/exit handlers act only when the dying worker is the current one (`src/web-research/pdf-extraction.ts`).
|
|
48
|
+
- **A large PDF blocked the main process's event loop for seconds.** pdf-oxide-wasm's parse is irreducibly synchronous, so it now runs in a dedicated worker_threads worker (zero-copy transfer, 30s terminate-and-retry-in-process) with byte-identical error strings, metrics, and classification; a missing bundle or `PI_RESEARCH_PDF_WORKER=off` keeps today's in-process behavior (`src/web-research/pdf-extraction.ts`).
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- **A poisoned embedding-model cache retried into the same wall instead of self-healing.** transformers.js exports embedding models as a small graph stub referencing external weights; an interrupted download leaves a cache that passes every `isModelCached` check (the weights file was only validated when present) and then fails to load with a local-file-missing error that nothing classified — so no purge, no re-download, and knowledge-store init burned its whole retry budget on every open. The cache check now requires referenced external weights to be present, and the load failure is classified as recoverable (purge + one remote re-download).
|
|
53
|
+
- **A full-surface audit (six parallel read-only lanes over core, knowledge, orchestration, web-research, surface, and release engineering) landed twelve remediation commits:** the PDF worker's ESM `__dirname` crash in built installs (every PDF failed) plus its timeout/teardown races; the structured-output empty-response error string silently bypassing the retry→degrade ladder (a run-aborting regression caught by the audit's cross-suite seam analysis); abort-unaware health checks that could hang a cancel for the full 105s probe; create-on-read leaks and hybrid-observer demotion in session-state; the SKILL.md version drift that would have failed the release gates; five stale post-refresh pin rationales; honest tool schemas (search maxItems, run-cap prompt, phantom grep tool); model-alternation detection for the shared knowledge store; chunker O(n²) fence rescans; and run-level metrics for capacity-refused/cancelled runs.
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
|
|
57
|
+
- **Planner, round-2 router, and knowledge synthesis now offer schema-constrained submit tools** (pi-ai `constrainedSampling`, `strict: 'prefer'`): schema-exact JSON where the provider supports it, plain tool calls everywhere else, and text answers still flow through the existing parse/repair pipeline unchanged (`src/core/llm/llm-utils.ts`).
|
|
58
|
+
- Runtime deps refreshed: camoufox-js 0.12, impit 0.14.4; apache-arrow stays lockstep 21.1.0 (lancedb 0.37.1 cannot read 21.2.0 IPC) and playwright-core stays 1.60.0 (camoufox 0.12 peer-caps <1.61.0).
|
|
59
|
+
- CI typechecks with the TypeScript 7 native compiler alongside TS6 (~9x faster, 3.7s vs ~35s here); TS6 remains the typescript-eslint compiler until its TS 7.1 API support lands.
|
|
60
|
+
|
|
61
|
+
### Verified
|
|
62
|
+
|
|
63
|
+
- 3000 unit tests over 236 files (16 new: the half-hoisted skew fatal, the raceWithSignal post-abort rejection swallow, peek-vs-create read discipline, the full observer-marker surface, return-only alternation thrash, capacity_refused/cancelled run metrics, the DOM-abort classifier form, and the five skew-guard install-layout walks), plus lint, all four type-checks (TS6 and TS7 native, src and tests), dependency-cruiser, the full integration suite, and the tarball manifest gate.
|
|
64
|
+
- The resumable browser install was exercised end-to-end on the machine that lost its binary: the ~500MB release asset fetched with byte-range resume across connection drops, installed through camoufox-js's own extract/version logic (`camoufox-js fetch` then reports "up to date"), and `pi-research status` reports `ready` with the browser detected.
|
|
65
|
+
|
|
66
|
+
### Fixed
|
|
67
|
+
|
|
68
|
+
- **Agentic JSON salvage could hang for two full LLM timeouts before the caller's fallback ran.** Each of `repairJsonWithLlm`'s two attempts was granted the entire `PI_RESEARCH_LLM_TIMEOUT_MS` budget (5 min default), and a timed-out attempt was still followed by a second — with a strictly larger prompt (validation errors are appended) and the same full budget, a near-certain repeat of the timeout observed in the wild as salvage hanging until manual abort. The whole salvage pass now shares ONE LLM-timeout budget: each attempt gets the remaining time, an attempt that cannot finish inside what remains (floor 30s — a repair call must re-upload the malformed payload and re-emit the full JSON) is skipped with a log line instead of launched into a guaranteed timeout, and a timed-out attempt is never retried, because unlike a validation failure the retry cannot plausibly succeed — it only delays the caller's fallback (`src/core/llm/agentic-repair.ts`).
|
|
69
|
+
- **A malformed plan JSON was diagnosed with its best evidence discarded.** Both planning call sites caught the parse error and logged a generic "JSON malformed" warning — dropping the thrown parse/validation detail — and nothing anywhere recorded the response's `stopReason`, so a plan truncated at the output-token cap (`stopReason 'length'`, the likeliest cause with a verbose model) was indistinguishable in the log from schema-incompatible output, which is exactly the ambiguity issue #9 hit ("no clear truncation/max-tokens reason captured"). The parse error, `stopReason`, and a tail preview of the response (truncation leaves the JSON dangling at the END — a head preview cannot show it) are now logged at both sites, and every planning/lead call warns when it hit its output-token cap (`src/core/planning-service.ts`).
|
|
70
|
+
- **A failed planning pass silently degraded a deep query to one researcher.** `buildFallbackCoordinatorPlan` returns a structurally ordinary plan, so when the model's plan JSON was unusable the TUI showed the fallback as a normal 'ready' coordinator and SDK consumers saw an ordinary `planning_success` — no surface said the user's deep-research request had been quietly downgraded to a single researcher with boilerplate queries. Fallback plans now carry `fallback: true` (never requested from the model; optional in the schema so persisted plans still validate), the TUI coordinator line reads 'fallback — planning failed', and the marker rides the plan object into every `onPlanningSuccess` consumer (`src/core/interfaces/research-plan-types.ts`, `src/core/planning-utils.ts`, `src/observers/research-observer-impl.ts`).
|
|
71
|
+
- **Unit tests read the developer's real `~/.pi/research/config.env`.** `getConfig()` layers that file under process env, so unit-test behavior silently varied by machine: on a dev box carrying `PI_RESEARCH_LLM_TIMEOUT_MS=900000`, `getLlmTimeoutMs()` answered 900000 in tests while CI (no config.env) saw the 300000 default — found when a budget-sensitive salvage test passed on CI and failed locally. The unit setup now redirects `HOME`/`USERPROFILE` onto a throwaway directory (os.homedir() reads HOME on POSIX, USERPROFILE on Windows, and the unit matrix runs all three OSes), isolating config.env, the state dir, and knowledge-db discovery the same way the log path was already isolated (`test/setup/unit-env.ts`).
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
|
|
75
|
+
- **The cache-metrics doc no longer implies "counter absent" distinguishes an uncaching provider.** For the openai-completions family (including OpenRouter), pi-ai's usage normalization always emits `cacheRead`/`cacheWrite`, defaulting missing upstream fields to 0 — so a route that does no prompt caching shows `llm_cache_read_tokens_total` stuck at 0, not omitted, and the 0-vs-absent distinction only holds for APIs that omit the fields outright. Issue #9's "no prompt-cache benefit during planning" is this: the model's OpenRouter route reports no cached tokens, while the run's prompts were already cache-shaped (round-invariant system prompts, run-varying content appended at the tail, session-affinity id forwarded). The doc now says how to read a stuck-at-zero counter (`src/utils/llm-usage.ts`).
|
|
76
|
+
|
|
77
|
+
### Verified
|
|
78
|
+
|
|
79
|
+
- 2943 unit tests over 233 files (5 new: salvage-timeout non-retry, below-floor skip, validation-retry regression guard, fallback marker, schema round-trip of the marker), lint, and both type-checks green — with the suite running under the isolated HOME, i.e. against CI-equivalent config for the first time.
|
|
80
|
+
|
|
8
81
|
## [1.6.5] - 2026-08-26
|
|
9
82
|
|
|
10
83
|
1.6.4 verified its commands and then recommended the wrong one first: the user-level
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -152,12 +152,13 @@ Diagnostics & platform
|
|
|
152
152
|
| `PI_RESEARCH_STATE_DIR` | `~/.pi/research/state` | Override the state directory (active sessions, browser status, project registry). |
|
|
153
153
|
| `PI_RESEARCH_TUI_REFRESH_DEBOUNCE_MS` | `100` | TUI refresh debounce (0–1000 ms). |
|
|
154
154
|
| `PI_RESEARCH_SKIP_HEALTHCHECK` | _(unset)_ | Set `1`/`true` to skip the pre-flight browser/embedding health check and rely on per-task timeouts. **Depth 0 (quick) only** — depth 1-3 runs have no such pre-flight check, so this has no effect on them. |
|
|
155
|
+
| `PI_RESEARCH_PDF_WORKER` | _(unset)_ | Set `off` to force PDF parsing onto the main thread (pre-1.6.6 behavior), bypassing the worker-threads offload. Kill-switch for worker or bundling issues; the default runs the parse in a worker when its bundle is present. |
|
|
155
156
|
| `PI_RESEARCH_USE_XVFB` | _(unset)_ | Linux only. Bare-TTY runs are true-headless and need no X server; set `true` to opt into a virtual framebuffer (`sudo apt install xvfb`). |
|
|
156
157
|
| `PI_RESEARCH_SKILL_DIR` | _(auto)_ | Override the bundled research-skill source directory used by the skill installer. |
|
|
157
158
|
| `PI_RESEARCH_PURGE_BROWSERS` | _(unset)_ | Read by the bundled `scripts/cleanup.cjs`: set `1` to also delete the shared camoufox browser cache (kept by default because other installs may use it). Note npm ≥7 does not run `preuninstall`, so that script does not fire on `npm uninstall` — see [AGENT-SKILL.md](AGENT-SKILL.md). |
|
|
158
159
|
| `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` | _(unset)_ | Set `1` during `npm install` to skip the camoufox browser download (fetched lazily on first use instead; standard Playwright convention). |
|
|
159
|
-
| `CAMOUFOX_INSTALL_DIR` | _(user cache)_ | camoufox-js's own variable, and the only one that can relocate the browser. Sets where pi-research looks the binary up, and is exported to the postinstall fetch and to browser workers.
|
|
160
|
-
| `PLAYWRIGHT_BROWSERS_PATH` | _(user cache)_ | Alias for the above, accepted for compatibility and exported onward as `CAMOUFOX_INSTALL_DIR
|
|
160
|
+
| `CAMOUFOX_INSTALL_DIR` | _(user cache)_ | camoufox-js's own variable, and the only one that can relocate the browser. Sets where pi-research looks the binary up, and is exported to the postinstall fetch and to browser workers. Effective on the pinned camoufox-js 0.12.0+, which honours it again; on older pins (<0.12, which hardcoded their cache directory) it only relocated lookup — see ARCHITECTURE.md. |
|
|
161
|
+
| `PLAYWRIGHT_BROWSERS_PATH` | _(user cache)_ | Alias for the above, accepted for compatibility and exported onward as `CAMOUFOX_INSTALL_DIR` — on camoufox-js 0.12.0+ it relocates the download itself, exactly like `CAMOUFOX_INSTALL_DIR`. |
|
|
161
162
|
| `XDG_CACHE_HOME` | `~/.cache` | Standard XDG variable. When set, every `~/.cache/pi-research/...` path below is rooted at `$XDG_CACHE_HOME/pi-research/...` instead. |
|
|
162
163
|
| `PI_RESEARCH_BIN` (alias `PI_RESEARCH_PATH`) | _(auto)_ | Agent-skill launcher only: explicit path to the pi-research engine binary when auto-resolution (PATH → local install → npx) should be bypassed. See [agent-skill/pi-research/references/configuration.md](../agent-skill/pi-research/references/configuration.md). |
|
|
163
164
|
| `PLAYWRIGHT_INSTALL_DEPS` | _(unset)_ | Linux only. Set `true` during `npm install` to also install system libraries via `npx playwright install-deps` (same as `npm run install:system-deps`). |
|
package/docs/SDK.md
CHANGED
|
@@ -13,18 +13,13 @@ run the `pi` extension, which keeps its own private copy your scripts can't impo
|
|
|
13
13
|
npm install @lincoln504/pi-research
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
On npm
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Without that script every search fails with a missing-module error, and rerunning
|
|
26
|
-
`npm install` does not run it — `npm rebuild` does. The same pair repairs an install
|
|
27
|
-
that already ran blocked.
|
|
16
|
+
On npm ≥11.19 (and npm 12), dependency install scripts are skipped by default. Nothing
|
|
17
|
+
here needs them: better-sqlite3 13 carries prebuilt bindings for every supported
|
|
18
|
+
platform and loads them at runtime, and the stealth browser self-provisions on its
|
|
19
|
+
first use. (The `npm approve-scripts better-sqlite3` + `npm rebuild` pair documented by
|
|
20
|
+
earlier releases repaired better-sqlite3 12, which downloaded its binding from an
|
|
21
|
+
install script — 13 ships the binding instead, and on npm 12.0.2 an approval does not
|
|
22
|
+
make a skipped script run anyway.)
|
|
28
23
|
|
|
29
24
|
Then pick the model: pass `model` to `initResearchSDK`, or set `PI_RESEARCH_MODEL`
|
|
30
25
|
(env or `~/.pi/research/config.env`). The SDK never follows the model selected
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lincoln504/pi-research",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.7",
|
|
4
4
|
"description": "Web research for your agents with smart and safe tooling + knowledge store",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"src/",
|
|
19
19
|
"dist/cli.mjs",
|
|
20
20
|
"dist/thread-worker.mjs",
|
|
21
|
+
"dist/pdf-extract-worker.mjs",
|
|
21
22
|
"dist/webgpu-probe.mjs",
|
|
22
23
|
"dist/prompts/",
|
|
23
24
|
"agent-skill/",
|
|
@@ -79,6 +80,8 @@
|
|
|
79
80
|
"lint:fix": "npx eslint --config config/tooling/eslint.config.js src/ --fix",
|
|
80
81
|
"type-check": "tsc --noEmit",
|
|
81
82
|
"type-check:tests": "tsc --noEmit --project config/tooling/tsconfig.test.json",
|
|
83
|
+
"type-check:native": "npx -y -p typescript@7.0.2 tsc --noEmit -p tsconfig.json",
|
|
84
|
+
"type-check:native:tests": "npx -y -p typescript@7.0.2 tsc --noEmit --project config/tooling/tsconfig.test.json",
|
|
82
85
|
"build:worker": "node scripts/build.cjs worker",
|
|
83
86
|
"test": "npm run test:unit",
|
|
84
87
|
"test:unit": "vitest run --config config/tooling/vitest.config.unit.ts",
|
|
@@ -114,14 +117,13 @@
|
|
|
114
117
|
"@earendil-works/pi-ai": ">=0.84.0 <1",
|
|
115
118
|
"@earendil-works/pi-coding-agent": ">=0.84.0 <1",
|
|
116
119
|
"@earendil-works/pi-tui": ">=0.84.0 <1",
|
|
117
|
-
"@
|
|
118
|
-
"@kreuzberg/html-to-markdown-node": "^3.5.5",
|
|
120
|
+
"@kreuzberg/html-to-markdown-node": "^3.7.2",
|
|
119
121
|
"@lancedb/lancedb": "0.37.1",
|
|
120
122
|
"apache-arrow": "21.1.0",
|
|
121
123
|
"bgutils-js": "^4.0.3",
|
|
122
|
-
"camoufox-js": "^0.
|
|
124
|
+
"camoufox-js": "^0.12.0",
|
|
123
125
|
"esbuild": "^0.28.1",
|
|
124
|
-
"impit": "0.
|
|
126
|
+
"impit": "0.14.4",
|
|
125
127
|
"jsdom": "^29.1.1",
|
|
126
128
|
"node-html-markdown": "^2.0.0",
|
|
127
129
|
"pdf-oxide-wasm": "^0.3.75",
|
|
@@ -132,6 +134,7 @@
|
|
|
132
134
|
"youtubei.js": "^18.0.0"
|
|
133
135
|
},
|
|
134
136
|
"optionalDependencies": {
|
|
137
|
+
"@huggingface/transformers": "4.2.0",
|
|
135
138
|
"webgpu": "^0.4.0"
|
|
136
139
|
},
|
|
137
140
|
"overrides": {
|
package/scripts/build.cjs
CHANGED
|
@@ -71,6 +71,18 @@ async function ensureWorkerBuilt() {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
// Build the PDF extraction worker bundle. Same lifecycle rules as the browser
|
|
75
|
+
// thread-worker: a pre-compiled .mjs next to its source, `packages: 'external'`
|
|
76
|
+
// so it resolves pdf-oxide-wasm from the installed node_modules, and never
|
|
77
|
+
// committed to git (regenerated by prepare).
|
|
78
|
+
function buildPdfWorker() {
|
|
79
|
+
return esbuild.build({
|
|
80
|
+
...COMMON,
|
|
81
|
+
entryPoints: [p('src', 'web-research', 'pdf-extract-worker.ts')],
|
|
82
|
+
outfile: p('src', 'web-research', 'pdf-extract-worker.mjs'),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
74
86
|
// Copy the built worker bundle into dist/. dist/thread-worker.mjs is resolved by
|
|
75
87
|
// worker-pool-manager.ts at runtime, so the bundled cli — which spins up the
|
|
76
88
|
// browser pool — needs it next to its bundle.
|
|
@@ -99,10 +111,32 @@ function copyPrompts() {
|
|
|
99
111
|
}
|
|
100
112
|
}
|
|
101
113
|
|
|
114
|
+
async function ensurePdfWorkerBuilt() {
|
|
115
|
+
const src = p('src', 'web-research', 'pdf-extract-worker.ts');
|
|
116
|
+
const out = p('src', 'web-research', 'pdf-extract-worker.mjs');
|
|
117
|
+
const stale =
|
|
118
|
+
!fs.existsSync(out) || fs.statSync(out).mtimeMs < fs.statSync(src).mtimeMs;
|
|
119
|
+
if (stale) {
|
|
120
|
+
await buildPdfWorker();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Copy the PDF worker bundle into dist/ next to the cli bundle; pdf-extraction.ts
|
|
125
|
+
// resolves it relative to its own compiled location at runtime.
|
|
126
|
+
function copyPdfWorker() {
|
|
127
|
+
fs.mkdirSync(p('dist'), { recursive: true });
|
|
128
|
+
fs.copyFileSync(
|
|
129
|
+
p('src', 'web-research', 'pdf-extract-worker.mjs'),
|
|
130
|
+
p('dist', 'pdf-extract-worker.mjs'),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
102
134
|
const TARGETS = {
|
|
103
135
|
worker: () => buildWorker(),
|
|
136
|
+
pdfworker: () => buildPdfWorker(),
|
|
104
137
|
cli: async () => {
|
|
105
138
|
await ensureWorkerBuilt();
|
|
139
|
+
await ensurePdfWorkerBuilt();
|
|
106
140
|
await esbuild.build({
|
|
107
141
|
...COMMON,
|
|
108
142
|
entryPoints: [p('src', 'cli.ts')],
|
|
@@ -110,6 +144,7 @@ const TARGETS = {
|
|
|
110
144
|
banner: { js: SHEBANG },
|
|
111
145
|
});
|
|
112
146
|
copyThreadWorker();
|
|
147
|
+
copyPdfWorker();
|
|
113
148
|
copyWebGpuProbe();
|
|
114
149
|
copyPrompts();
|
|
115
150
|
},
|
package/scripts/setup.cjs
CHANGED
|
@@ -53,12 +53,11 @@ function resolveCamoufoxBin() {
|
|
|
53
53
|
* agree or install and lookup land in different directories.
|
|
54
54
|
*
|
|
55
55
|
* CAMOUFOX_INSTALL_DIR first: it is camoufox-js's own variable and the only one
|
|
56
|
-
* that can relocate the install (camoufox-js <0.12.0
|
|
57
|
-
* userCacheDir("camoufox") and
|
|
58
|
-
* kept as the documented alias
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* config.ts and thread-worker-browser.ts.
|
|
56
|
+
* that can relocate the install (camoufox-js <0.12.0 hardcoded
|
|
57
|
+
* userCacheDir("camoufox") and honoured nothing — inert there; effective on the
|
|
58
|
+
* 0.12.0+ refresh). PLAYWRIGHT_BROWSERS_PATH is kept as the documented alias
|
|
59
|
+
* and is exported BELOW as CAMOUFOX_INSTALL_DIR. See
|
|
60
|
+
* src/infrastructure/browser/config.ts and thread-worker-browser.ts.
|
|
62
61
|
*/
|
|
63
62
|
function customCamoufoxDir() {
|
|
64
63
|
return process.env.CAMOUFOX_INSTALL_DIR || process.env.PLAYWRIGHT_BROWSERS_PATH || null;
|
|
@@ -126,10 +125,13 @@ if (process.env.PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD === '1') {
|
|
|
126
125
|
// shell metacharacters would execute them at install time. Windows still
|
|
127
126
|
// needs a shell to run the `.cmd` shim, and there the path is quoted; the
|
|
128
127
|
// runtime twin in ensure-browser.ts uses the same shape.
|
|
129
|
-
// Bound the ~
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
128
|
+
// Bound the ~500MB camoufox-js 0.12 browser download (was ~100MB on the
|
|
129
|
+
// 0.10 pin — the old 15-minute bound could kill a download that would
|
|
130
|
+
// have succeeded on a slow link) so a stalled/interrupted network fails
|
|
131
|
+
// eventually instead of hanging `npm install` forever (the catch below
|
|
132
|
+
// exits 0, and the browser is re-fetchable manually). 45 min covers a
|
|
133
|
+
// working link down to ~180 KB/s; a dead link aborts on its own sooner.
|
|
134
|
+
const spawnOpts = { stdio: 'inherit', env, timeout: 45 * 60 * 1000 };
|
|
133
135
|
const res = bin
|
|
134
136
|
? (isWindows
|
|
135
137
|
? spawnSync(`"${bin}"`, ['fetch'], { ...spawnOpts, shell: true })
|
|
@@ -92,11 +92,17 @@ export function createCleanupFunction(
|
|
|
92
92
|
let hasUI = false;
|
|
93
93
|
try { hasUI = ctx.hasUI; } catch { /* ctx stale (session closed mid-run) → treat as no UI */ }
|
|
94
94
|
if (hasUI) {
|
|
95
|
-
ctx
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
// Audit L3: the ctx can go stale BETWEEN the hasUI probe and these calls
|
|
96
|
+
// (session replacement races the probe) — the same throw this block exists
|
|
97
|
+
// to suppress would then reject the whole cleanup promise. Guard the calls
|
|
98
|
+
// identically to the probe.
|
|
99
|
+
try {
|
|
100
|
+
ctx.ui.setWidget(masterWidgetId, undefined);
|
|
101
|
+
const tuiUI = ctx.ui as { setWorkingVisible?: (visible: boolean) => void };
|
|
102
|
+
if (typeof tuiUI?.setWorkingVisible === 'function') {
|
|
103
|
+
tuiUI.setWorkingVisible(true);
|
|
104
|
+
}
|
|
105
|
+
} catch { /* ctx went stale after the probe → UI is gone; nothing to clean */ }
|
|
100
106
|
}
|
|
101
107
|
} else {
|
|
102
108
|
refreshAllSessions(piSessionId);
|
package/src/cli.ts
CHANGED
|
@@ -828,13 +828,15 @@ async function cmdStatus(json?: boolean): Promise<number> {
|
|
|
828
828
|
installed: browserInstalled,
|
|
829
829
|
nativeDepsOk: nativeDeps.ok,
|
|
830
830
|
nativeDepsError: nativeDeps.ok ? null : nativeDeps.error,
|
|
831
|
-
//
|
|
832
|
-
//
|
|
833
|
-
//
|
|
834
|
-
//
|
|
831
|
+
// better-sqlite3 13 ships prebuilds/ for all eight platform/arch pairs and
|
|
832
|
+
// loads them at runtime via node-gyp-build — no install script involved.
|
|
833
|
+
// On modern npm (≥11.19) the injected `node-gyp rebuild` that a
|
|
834
|
+
// binding.gyp-without-install-script triggers is skipped, so a missing
|
|
835
|
+
// binding here means an old/broken install, not an approval problem:
|
|
836
|
+
// point at the upgrade that fixes it.
|
|
835
837
|
nativeDepsFix: nativeDeps.ok
|
|
836
838
|
? null
|
|
837
|
-
: '
|
|
839
|
+
: 'Upgrade npm and reinstall: npm install -g npm@12, then reinstall @lincoln504/pi-research. better-sqlite3 13 ships prebuilt bindings (no script needed); older npm needlessly recompiles it and fails on machines without a C++ toolchain.',
|
|
838
840
|
// Same remedy the health check prints, so both surfaces name one command.
|
|
839
841
|
fix: browserInstalled ? null : 'npx camoufox-js fetch',
|
|
840
842
|
note: browserInstalled
|
package/src/config.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Configuration Module
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Resolution order (lowest → highest precedence):
|
|
5
|
+
* built-in DEFAULTS
|
|
6
|
+
* < ~/.pi/research/config.env (user file; the /research-config TUI edits this)
|
|
7
|
+
* < ~/.pi/research/{iface}.env (per-interface overlay: cli.env, pi.env, sdk.env)
|
|
8
|
+
* < legacy extension-dir .env (deprecated; migrated on load)
|
|
9
|
+
* < project registry (~/.pi/research/projects.json, per-directory)
|
|
10
|
+
* < process.env (CI / one-off overrides)
|
|
11
|
+
* See loadEnvFiles()/createConfig() below for the implementation and the
|
|
12
|
+
* scope-demotion rules for project-registered keys read from ambient files.
|
|
7
13
|
*/
|
|
8
14
|
|
|
9
15
|
import * as fs from 'node:fs';
|
|
@@ -168,6 +168,14 @@ export interface IKnowledgeStoreService extends IService {
|
|
|
168
168
|
*/
|
|
169
169
|
export interface IWriterQueue extends IService {
|
|
170
170
|
drain(): Promise<void>;
|
|
171
|
+
/**
|
|
172
|
+
* Fire-and-forget enqueue of a derived, re-derivable row (a link description
|
|
173
|
+
* or synthesis). RAM-ONLY by design: the queue is not journaled, so anything
|
|
174
|
+
* enqueued but not yet addDocuments-committed is LOST on crash or kill, and
|
|
175
|
+
* dispose() bounds its final drain (dropping the remainder on timeout). Never
|
|
176
|
+
* enqueue primary user data that cannot be re-produced by re-running the
|
|
177
|
+
* scrape/synthesis that produced it.
|
|
178
|
+
*/
|
|
171
179
|
enqueue(item: IngestionItem): void;
|
|
172
180
|
}
|
|
173
181
|
|
|
@@ -84,5 +84,5 @@ export interface IResearchOrchestration extends IService {
|
|
|
84
84
|
runSearchBurst(queries: string[], config: Config, signal?: AbortSignal, onProgress?: (links: number) => void, ctx?: any): Promise<QueryResultWithError[]>;
|
|
85
85
|
storeLinkDescriptions(sessionId: string, round: number, researchId: string, config: Config, ctx?: any): Promise<void>;
|
|
86
86
|
// Advisory only — logs infrastructure health; research never aborts on the result.
|
|
87
|
-
checkHealth(round: number, researchId?: string, ctx?: any): Promise<void>;
|
|
87
|
+
checkHealth(round: number, researchId?: string, ctx?: any, signal?: AbortSignal): Promise<void>;
|
|
88
88
|
}
|
|
@@ -44,6 +44,12 @@ export const ResearchPlanSchema = Type.Object({
|
|
|
44
44
|
allQueries: Type.Optional(Type.Array(Type.String())),
|
|
45
45
|
content: Type.Optional(Type.String()),
|
|
46
46
|
title: Type.Optional(Type.String()),
|
|
47
|
+
// Set ONLY by buildFallbackCoordinatorPlan (never requested from the model): marks a
|
|
48
|
+
// plan the engine synthesized after the model's planning output was unusable, so a
|
|
49
|
+
// single-researcher fallback is distinguishable from a plan the model actually chose.
|
|
50
|
+
// Without it a deep query silently degrades to one researcher with no user-visible
|
|
51
|
+
// notice — the TUI/SDK see an ordinary plan (issue #9).
|
|
52
|
+
fallback: Type.Optional(Type.Boolean()),
|
|
47
53
|
});
|
|
48
54
|
|
|
49
55
|
/**
|