@lincoln504/pi-research 1.7.6 → 1.7.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 CHANGED
@@ -92,7 +92,7 @@ Three depth levels (normal, deep, ultra) set the team size and number of rounds.
92
92
  - Node.js >= 22.22.2
93
93
  - An LLM with a 100k+ context window (an API key or a local model)
94
94
  - Internet access on a residential IP, since search, scraping, and YouTube transcripts get bot-blocked from datacenter/VPS/cloud IPs
95
- - pi. The pi extension uses the host's copy while the standalone CLI and agent skill install it as a dependency.
95
+ - pi (`@earendil-works/pi-coding-agent`) 0.99.0 or newer, including the 1.x line. The pi extension uses the host's copy while the standalone CLI and agent skill install it as a dependency. A host newer than the last version this release was tested against still runs, with a one-line notice; the tested ceiling is in `src/core/pi-version.ts`.
96
96
  - Local knowledge-store embeddings need `@huggingface/transformers`, an optional dependency. If its native image chain (sharp) cannot install, npm skips it with a warning and the install still succeeds, so everything works except local embeddings. The store tells you how to restore them. Prefer no embedding model at all? Set `PI_RESEARCH_KNOWLEDGE_STORE_RETRIEVAL=bm25` for a pure lexical (BM25) knowledge store — see [KNOWLEDGE-STORE.md](docs/KNOWLEDGE-STORE.md).
97
97
  - Cloudflare Turnstile and similar systems block scraping on some sites. A run compensates with a wide pool of search results to scrape.
98
98
 
@@ -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.7.6", "package": "@lincoln504/pi-research", "engine": "pi-research-sdk", "openclaw": { "requires": { "bins": ["node"] } } }
5
+ metadata: { "author": "Lincoln Deen", "version": "1.7.7", "package": "@lincoln504/pi-research", "engine": "pi-research-sdk", "openclaw": { "requires": { "bins": ["node"] } } }
6
6
  allowed-tools: Bash(node:*)
7
7
  ---
8
8
 
package/dist/cli.mjs CHANGED
@@ -9928,6 +9928,25 @@ var init_cancellation = __esm({
9928
9928
  }
9929
9929
  });
9930
9930
 
9931
+ // src/tools/tool-metadata.ts
9932
+ var PI_RESEARCH_TOOL_NAMESPACE, KNOWLEDGE_SEARCH_TOOL_ANNOTATIONS;
9933
+ var init_tool_metadata = __esm({
9934
+ "src/tools/tool-metadata.ts"() {
9935
+ "use strict";
9936
+ PI_RESEARCH_TOOL_NAMESPACE = {
9937
+ name: "pi-research",
9938
+ description: "Web research, knowledge-store search, and system health checks.",
9939
+ instructions: "research_knowledge_search is a local lookup and should be tried before research: a complete hit answers the question without any live work. research runs a multi-agent web investigation and takes minutes. health reports local service status and is the cheapest of the three."
9940
+ };
9941
+ KNOWLEDGE_SEARCH_TOOL_ANNOTATIONS = {
9942
+ readOnlyHint: false,
9943
+ destructiveHint: false,
9944
+ idempotentHint: false,
9945
+ openWorldHint: true
9946
+ };
9947
+ }
9948
+ });
9949
+
9931
9950
  // src/tools/research-knowledge-types.ts
9932
9951
  import { Type as Type9 } from "typebox";
9933
9952
  var AnswerStatusEnum, ResearchKnowledgeSynthesisResponseSchema, ResearchKnowledgeSynthesisResponseSchemaAsTSchema, KnowledgeRelevanceTriageSchema;
@@ -10473,6 +10492,8 @@ function createResearchKnowledgeSearchTool(iface) {
10473
10492
  "Do not call knowledge search and research for the same query in the same turn \u2014 wait for the result."
10474
10493
  ],
10475
10494
  parameters: ResearchKnowledgeSearchParams,
10495
+ annotations: KNOWLEDGE_SEARCH_TOOL_ANNOTATIONS,
10496
+ namespace: PI_RESEARCH_TOOL_NAMESPACE,
10476
10497
  executionMode: "parallel",
10477
10498
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
10478
10499
  const startTime = Date.now();
@@ -10599,6 +10620,7 @@ var init_research_knowledge_search = __esm({
10599
10620
  init_llm_utils();
10600
10621
  init_service_registry();
10601
10622
  init_llm_timeout();
10623
+ init_tool_metadata();
10602
10624
  init_retry_utils();
10603
10625
  init_service_interfaces();
10604
10626
  init_research_knowledge_types();
@@ -372,7 +372,8 @@ changes which pairing needs re-validating.
372
372
 
373
373
  Pinned validation library — `typebox` (TypeBox 1.x, unscoped package name) is pinned to the
374
374
  exact version the pi host packages depend on (`@earendil-works/pi-ai` /
375
- `@earendil-works/pi-coding-agent` 0.99.2 both pin `1.3.27` — verified 2026-10-01, after an
375
+ `@earendil-works/pi-coding-agent` 1.0.0 both pin `1.3.27` — verified 2026-10-01 against 1.0.0
376
+ (and against 0.99.2 before it), after an
376
377
  undocumented drift was caught in the 0.84/0.85 era and realigned; that drift was to `1.3.26`,
377
378
  and the version now pinned is `1.3.27`, both re-checked directly in the installed host
378
379
  packages' `package.json`). Every
package/docs/CHANGELOG.md CHANGED
@@ -5,13 +5,24 @@ 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.7.7] - 2026-10-01
9
+
10
+ ### Changed
11
+
12
+ - **pi 1.0.0 support.** The peer ranges said `>=0.99.0 <1`, which excluded the current pi release: a standalone install resolved 0.99.x while the user's pi was 1.0.0, and the extension printed an untested-host notice on every session. The ranges are now `>=0.99.0 <2` and the tested ceiling is 1.0.0. The whole public surface was diffed 0.99.2 -> 1.0.0: `@earendil-works/pi-ai` is identical in its declarations, the extension API (`core/extensions/index.d.ts`, the file every tool, event and context type comes from) is unchanged, and `docs/extensions.md` is unchanged. `pi-coding-agent` adds `ModelRegistry.generateImages()` and widens `quietStartup` to `boolean | "header"`; `pi-tui` adds `isAppleTerminalSession`, `flattenLines()` and `TUI.getScreenLines()`. Nothing was removed or re-signed, so the 0.99.0 floor still describes the oldest host those APIs exist on, and the version number is a milestone rather than a break for this package. Verified by installing 1.0.0 into this repo, type-checking both projects against it, running the unit and integration suites (254 files / 3207 tests, 57 + 103 integration), and calling the `health` tool through a real pi 1.0.0 session. Two 1.0.0 behaviour changes are worth knowing and change nothing here: the TUI now defaults to fullscreen (this extension renders through `ctx.ui.*` widgets, which work in both modes) and built-in extensions and tools are named `builtin:<name>` in errors and diagnostics.
13
+ - **The three tools declare MCP-style `annotations` and one shared `namespace`.** Both fields arrived in pi 0.99 and are surfaced by 1.0.0: `pi.getAllTools()` reports them, the documented permission pattern reads the annotations to decide which calls to confirm, and codemode lists the tools of one namespace under a single heading. `research` and `research_knowledge_search` are declared `openWorldHint: true` because they reach the network and spend provider tokens, `health` is `idempotentHint: true` and closed-world, and all three are `readOnlyHint: false` with `destructiveHint: false`: they append to the knowledge store, and `health` with `probe: true` starts the browser pool. The hints are author-declared and unverified, so they under-claim deliberately, because `readOnlyHint` is the one hint that can suppress a confirmation a user should have been asked for.
14
+
15
+ ### Fixed
16
+
17
+ - Host-version references in `docs/ARCHITECTURE.md` and the ES/JA/ZH docs said the typebox pin was verified against 0.99.2. It is still `1.3.27` in both host packages, re-verified against 1.0.0, and the docs now say so. The README requirements state the supported host range and that a host newer than the tested ceiling still runs, with a one-line notice.
18
+
8
19
  ## [1.7.6] - 2026-10-01
9
20
 
10
21
  ### Fixed
11
22
 
12
23
  - **The missing-peer remedy names the package that is actually missing.** The 1.7.5 shim's bullet list correctly enumerated every absent `@earendil-works/pi-*` package, but the command underneath it was hardcoded to `npm install -g @earendil-works/pi-coding-agent` with the comment "add just the missing host package". When `pi-ai` (and not `pi-coding-agent`) was the absent one, the instruction named a package the user already had and following it changed nothing. The remedy is now built from the detected set, so both peers are named when both are missing and only the absent one is named otherwise. Caught in review; pinned by a test that asserts the present package is never the one the remedy suggests installing.
13
24
  - **The bin preflight detects an install whose files are incomplete, not just an absent package.** It resolved each peer and treated a successful resolve as healthy. Resolving is not loading: an interrupted install, a pruned `node_modules` or a bad package-cache restore leaves `package.json` and its `exports` map intact with the file they point at gone, so the preflight passed it through and the bundle died on that import with the raw `ERR_MODULE_NOT_FOUND` the shim exists to replace. The preflight now also checks that the resolved file exists and reports `installed, but its files are incomplete` with the same exit 78. Checked with `existsSync` rather than by importing the module, which would run the host packages' side effects before the CLI has decided to start. Verified by reverting each half in isolation: without the `argv[1]` rewrite the stub engine exits 97, with a resolve-only preflight a deliberately truncated peer produces `ERR_MODULE_NOT_FOUND`, and the fixed shim produces exit 78.
14
- - **`dist/cli-bin.mjs` ships executable.** It was published at mode 644 against 755 for `dist/cli.mjs`, because the build copies it with `copyFileSync`, which preserves the committed source mode. Every package manager chmods the declared `bin` target itself, so no install was broken, but the shipped artifact should not depend on that. The build now chmods 755 and the source is committed executable.
25
+ - **`dist/cli-bin.mjs` ships executable.** It went out at mode 644 because the build copies it with `copyFileSync`, which preserves the committed source mode. No install was broken by that: npm, pnpm and bun each chmod the declared `bin` target when they link it, which is how 1.7.5 worked. The point is not to depend on it, since the bin is the one file a shell may execute directly. The build now chmods 755 after the copy and the source is committed executable. (Measured correction, 2026-10-01: this entry first said `dist/cli.mjs` shipped at 755 for contrast. It does not. npm publishes every file in this package at 644, `dist/cli.mjs` included, in both the 1.7.5 and 1.7.6 tarballs. That file is never executed directly, only via `node` or a manager-generated shim, so its mode does not matter.)
15
26
 
16
27
  ## [1.7.5] - 2026-10-01
17
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lincoln504/pi-research",
3
- "version": "1.7.6",
3
+ "version": "1.7.7",
4
4
  "description": "Web research for your agents with smart and safe tooling + knowledge store",
5
5
  "type": "module",
6
6
  "private": false,
@@ -133,9 +133,9 @@
133
133
  "youtubei.js": "^18.0.0"
134
134
  },
135
135
  "peerDependencies": {
136
- "@earendil-works/pi-ai": ">=0.99.0 <1",
137
- "@earendil-works/pi-coding-agent": ">=0.99.0 <1",
138
- "@earendil-works/pi-tui": ">=0.99.0 <1"
136
+ "@earendil-works/pi-ai": ">=0.99.0 <2",
137
+ "@earendil-works/pi-coding-agent": ">=0.99.0 <2",
138
+ "@earendil-works/pi-tui": ">=0.99.0 <2"
139
139
  },
140
140
  "optionalDependencies": {
141
141
  "@huggingface/transformers": "4.3.0",
@@ -159,9 +159,9 @@
159
159
  "sharp": "^0.35.0"
160
160
  },
161
161
  "devDependencies": {
162
- "@earendil-works/pi-ai": ">=0.99.0 <1",
163
- "@earendil-works/pi-coding-agent": ">=0.99.0 <1",
164
- "@earendil-works/pi-tui": ">=0.99.0 <1",
162
+ "@earendil-works/pi-ai": ">=0.99.0 <2",
163
+ "@earendil-works/pi-coding-agent": ">=0.99.0 <2",
164
+ "@earendil-works/pi-tui": ">=0.99.0 <2",
165
165
  "@eslint/js": "^10.0.1",
166
166
  "@types/jsdom": "^30.0.0",
167
167
  "@typescript-eslint/eslint-plugin": "8.71.0",
@@ -7,10 +7,13 @@
7
7
  *
8
8
  * - As a pi EXTENSION, the host supplies `@earendil-works/*`. Our package.json
9
9
  * range never constrains it, so an in-process check is the only enforcement.
10
- * - As a standalone CLI/SDK, the range is `>=0.99.0 <1` and published tarballs
11
- * carry no lockfile, so every fresh install resolves the newest 0.x at that
12
- * instant. pi is pre-1.0: a minor bump may break anything under semver, and
13
- * already has (0.83.0 extended the ResourceLoader contract).
10
+ * - As a standalone CLI/SDK, the range is `>=0.99.0 <2` and published tarballs
11
+ * carry no lockfile, so every fresh install resolves the newest release in
12
+ * that window at that instant. The host is young: a minor bump may break
13
+ * anything under semver, and already has (0.83.0 extended the ResourceLoader
14
+ * contract). The upper bound is `<2` rather than `<1` because 1.0.0 changed
15
+ * no API this package uses — see the 1.0.0 note on the tested ceiling — and
16
+ * `<1` would exclude the current release from a fresh install.
14
17
  *
15
18
  * Hence two thresholds, not one. Below the FLOOR we throw — those APIs are gone
16
19
  * and nothing will work. Above the TESTED ceiling we warn once and continue: a
@@ -102,8 +105,25 @@ export const PI_MIN_VERSION: SemverParts = { major: 0, minor: 99, patch: 0 };
102
105
  * Bump this — deliberately — when a new pi is verified, not automatically.
103
106
  * Compared on MAJOR.MINOR only: a patch bump within a tested line is not a new
104
107
  * surface, and warning on it would be noise.
108
+ *
109
+ * 1.0.0 is a major version number, not a breaking change for this package. The
110
+ * full public surface was diffed against 0.99.2 (2026-10-01): `@earendil-works/pi-ai`
111
+ * is byte-identical in its declarations, and the extension API
112
+ * (`core/extensions/index.d.ts`, the file every tool, event and context type
113
+ * comes from) is unchanged, as is `docs/extensions.md`. `pi-coding-agent` adds
114
+ * one method (`ModelRegistry.generateImages()`), widens `quietStartup` to
115
+ * `boolean | "header"`, and adds internal fields; `pi-tui` adds
116
+ * `isAppleTerminalSession`, `flattenLines()` and `TUI.getScreenLines()`.
117
+ * Nothing was removed or re-signed, so the 0.99.0 floor still describes the
118
+ * oldest host these APIs exist on.
119
+ *
120
+ * The behavioural changes in 1.0.0 that a user can notice are the fullscreen TUI
121
+ * default (this extension renders through `ctx.ui.*` widgets, which work in both
122
+ * modes), the `builtin:<name>` naming for built-in extensions and tools, and
123
+ * `--no-extensions` now also disabling the built-in extensions. None of them
124
+ * change what this package does.
105
125
  */
106
- export const PI_TESTED_MAX_VERSION: SemverParts = { major: 0, minor: 99, patch: 2 };
126
+ export const PI_TESTED_MAX_VERSION: SemverParts = { major: 1, minor: 0, patch: 0 };
107
127
 
108
128
  export type PiCompatibilityLevel = 'ok' | 'unparseable' | 'too-old' | 'untested';
109
129
 
@@ -13,6 +13,7 @@ import type {
13
13
  import { Type, type Static } from 'typebox';
14
14
  import { Value } from 'typebox/value';
15
15
  import { healthRegistry } from '../healthcheck/index.ts';
16
+ import { HEALTH_TOOL_ANNOTATIONS, PI_RESEARCH_TOOL_NAMESPACE } from './tool-metadata.ts';
16
17
 
17
18
  /**
18
19
  * Create the health check tool definition
@@ -37,6 +38,8 @@ export function createHealthTool(): ToolDefinition {
37
38
  description: 'Verify system status (browser pool, knowledge store, GPU lock).',
38
39
  promptSnippet: 'Run system health checks',
39
40
  parameters,
41
+ annotations: HEALTH_TOOL_ANNOTATIONS,
42
+ namespace: PI_RESEARCH_TOOL_NAMESPACE,
40
43
  renderShell: 'self',
41
44
  executionMode: 'parallel',
42
45
  async execute(
@@ -37,6 +37,7 @@ import { recordLlmUsage } from '../utils/llm-usage.ts';
37
37
  import { buildSafeOptions, validateAndExtractText, buildConstrainedSubmitTool, completeSimpleStructured } from '../core/llm/llm-utils.ts';
38
38
  import { getService, tryGetServiceContainerFromCtx } from '../core/service-registry.ts';
39
39
  import { withTimeout } from '../core/llm/llm-timeout.ts';
40
+ import { KNOWLEDGE_SEARCH_TOOL_ANNOTATIONS, PI_RESEARCH_TOOL_NAMESPACE } from './tool-metadata.ts';
40
41
  import { abortableDelay } from '../web-research/retry-utils.ts';
41
42
  import { ServiceNames } from '../core/service-interfaces.ts';
42
43
  import type { IKnowledgeStoreService } from '../core/service-interfaces.ts';
@@ -894,6 +895,8 @@ export function createResearchKnowledgeSearchTool(iface?: ConfigInterface): Tool
894
895
  'Do not call knowledge search and research for the same query in the same turn — wait for the result.',
895
896
  ],
896
897
  parameters: ResearchKnowledgeSearchParams,
898
+ annotations: KNOWLEDGE_SEARCH_TOOL_ANNOTATIONS,
899
+ namespace: PI_RESEARCH_TOOL_NAMESPACE,
897
900
  executionMode: 'parallel',
898
901
  async execute(
899
902
  _toolCallId: string,
@@ -19,6 +19,7 @@ import type { ResearchDepth, CleanupContext } from '../types/index.ts';
19
19
  import { Type, type Static } from 'typebox';
20
20
  import { Value } from 'typebox/value';
21
21
  import { validateConfig, getConfig, type ConfigInterface } from '../config.ts';
22
+ import { RESEARCH_TOOL_ANNOTATIONS, PI_RESEARCH_TOOL_NAMESPACE } from './tool-metadata.ts';
22
23
  import { tryGetServiceContainerFromCtx, getService } from '../core/service-registry.ts';
23
24
 
24
25
  import { ServiceNames } from '../core/service-interfaces.ts';
@@ -159,6 +160,8 @@ export function createResearchTool(iface?: ConfigInterface): ToolDefinition {
159
160
  'Perform multi-source web research using search, scraping, and specialized databases.',
160
161
  promptSnippet: 'Execute comprehensive web research',
161
162
  parameters,
163
+ annotations: RESEARCH_TOOL_ANNOTATIONS,
164
+ namespace: PI_RESEARCH_TOOL_NAMESPACE,
162
165
  renderShell: 'self',
163
166
  executionMode: 'parallel',
164
167
  prepareArguments: (args: unknown) => {
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Shared metadata for the tools this extension registers.
3
+ *
4
+ * pi 0.99 added two MCP-shaped fields to `ToolDefinition`: `annotations` and
5
+ * `namespace`. Neither changes how a tool runs. They are what pi reports and what
6
+ * other extensions read:
7
+ *
8
+ * - `pi.getAllTools()` reports each tool's `exposure`, `namespace` and
9
+ * `annotations`, and the documented permission pattern reads the annotations
10
+ * to decide which calls to confirm.
11
+ * - codemode lists the tools of one namespace under a single heading with the
12
+ * namespace `description`; `instructions` is returned on request by
13
+ * `describeNamespace(name)` and is not part of any tool listing.
14
+ *
15
+ * The hints come from the tool's author and are NOT verified by pi, so the rule
16
+ * here is to under-claim. `readOnlyHint` is true only for a tool that cannot
17
+ * change anything the user would have to undo, and `openWorldHint` is true for
18
+ * anything that can reach the network. A permission extension that trusts these
19
+ * hints to skip a confirmation must not be able to skip one for a call that
20
+ * starts a browser, spends money on a provider, or writes to the knowledge store.
21
+ *
22
+ * MCP defaults for absent hints are "not read-only, may be destructive, may reach
23
+ * an open world", so declaring a hint can only ever narrow what a permission
24
+ * extension confirms — with one exception: `readOnlyHint: true` on a tool that
25
+ * does write would silence a confirmation it should have asked for. That is the
26
+ * claim to be most careful about.
27
+ */
28
+
29
+ import type { ToolAnnotations, ToolNamespace } from '@earendil-works/pi-coding-agent';
30
+
31
+ /**
32
+ * One namespace for every tool this extension registers, so codemode lists them
33
+ * together instead of scattering three unrelated entries through its tool list.
34
+ */
35
+ export const PI_RESEARCH_TOOL_NAMESPACE: ToolNamespace = {
36
+ name: 'pi-research',
37
+ description: 'Web research, knowledge-store search, and system health checks.',
38
+ instructions:
39
+ 'research_knowledge_search is a local lookup and should be tried before research: ' +
40
+ 'a complete hit answers the question without any live work. research runs a ' +
41
+ 'multi-agent web investigation and takes minutes. health reports local service ' +
42
+ 'status and is the cheapest of the three.',
43
+ };
44
+
45
+ /**
46
+ * `research`: reaches the open web, spawns sub-agents, spends provider tokens, and
47
+ * appends to the knowledge store and the session report. Additive, so not
48
+ * destructive, and every run is a new investigation, so not idempotent.
49
+ */
50
+ export const RESEARCH_TOOL_ANNOTATIONS: ToolAnnotations = {
51
+ readOnlyHint: false,
52
+ destructiveHint: false,
53
+ idempotentHint: false,
54
+ openWorldHint: true,
55
+ };
56
+
57
+ /**
58
+ * `research_knowledge_search`: reads the local store, and on a miss can go live
59
+ * (network, provider tokens) and write what it learned back. Not read-only for the
60
+ * same reason as `research`.
61
+ */
62
+ export const KNOWLEDGE_SEARCH_TOOL_ANNOTATIONS: ToolAnnotations = {
63
+ readOnlyHint: false,
64
+ destructiveHint: false,
65
+ idempotentHint: false,
66
+ openWorldHint: true,
67
+ };
68
+
69
+ /**
70
+ * `health`: reports local status. Not read-only because `probe: true` forces
71
+ * liveness checks that can start the browser pool and load models, and repeating a
72
+ * probe has no further effect, so it is idempotent. It reaches the local machine,
73
+ * not the open web.
74
+ */
75
+ export const HEALTH_TOOL_ANNOTATIONS: ToolAnnotations = {
76
+ readOnlyHint: false,
77
+ destructiveHint: false,
78
+ idempotentHint: true,
79
+ openWorldHint: false,
80
+ };