@demigodmode/pi-web-agent 0.3.1 → 0.5.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.
@@ -25,6 +25,12 @@ export type ResearchWorkerResult = {
25
25
  suggestedHeadlessUrl?: string;
26
26
  exhaustedBudget: boolean;
27
27
  };
28
+ export type ResearchRunMetadata = {
29
+ searchPasses: number;
30
+ fetchedPages: number;
31
+ headlessAttempts: number;
32
+ exhaustedBudget: boolean;
33
+ };
28
34
  export type ResearchOrchestratorDecision = {
29
35
  action: 'answer';
30
36
  rationale: string;
@@ -1,8 +1,11 @@
1
+ import { selectCandidates } from './candidate-selector.js';
1
2
  function classifySource(url) {
2
3
  if (url.includes('/docs/api/') || url.includes('/config/'))
3
4
  return 'official-api';
4
5
  if (url.includes('playwright.dev/docs') || url.includes('vitest.dev/guide/'))
5
6
  return 'official-docs';
7
+ if (url.includes('github.com/vitest-dev/vitest') && url.includes('/docs/'))
8
+ return 'official-docs';
6
9
  if (url.includes('learn.microsoft.com'))
7
10
  return 'official-docs';
8
11
  if (url.includes('github.com/') && url.includes('/issues/'))
@@ -91,7 +94,11 @@ export function createResearchWorker({ search, fetchPage }) {
91
94
  exhaustedBudget: false
92
95
  };
93
96
  }
94
- const candidates = searchResult.results.slice(0, maxFetches);
97
+ const candidates = selectCandidates({
98
+ results: searchResult.results,
99
+ seenUrls: new Set(evidence.map((item) => item.url)),
100
+ maxCandidates: maxFetches
101
+ });
95
102
  for (const candidate of candidates) {
96
103
  const fetched = await fetchPage({ url: candidate.url });
97
104
  if (fetched.status === 'ok') {
@@ -0,0 +1,19 @@
1
+ import type { ResearchEvidence } from './research-types.js';
2
+ export type ResearchStepDecision = {
3
+ action: 'answer';
4
+ } | {
5
+ action: 'answer-with-caveat';
6
+ } | {
7
+ action: 'search-again';
8
+ } | {
9
+ action: 'headless';
10
+ url: string;
11
+ };
12
+ export declare function decideNextResearchStep({ evidence, suggestedHeadlessUrls, passIndex, maxPasses, headlessAttempts, maxHeadlessAttempts }: {
13
+ evidence: ResearchEvidence[];
14
+ suggestedHeadlessUrls: string[];
15
+ passIndex: number;
16
+ maxPasses: number;
17
+ headlessAttempts: number;
18
+ maxHeadlessAttempts: number;
19
+ }): ResearchStepDecision;
@@ -0,0 +1,14 @@
1
+ import { hasOfficialEvidence, strongEvidenceCount } from './evidence-ranker.js';
2
+ export function decideNextResearchStep({ evidence, suggestedHeadlessUrls, passIndex, maxPasses, headlessAttempts, maxHeadlessAttempts }) {
3
+ if (strongEvidenceCount(evidence) >= 2 && hasOfficialEvidence(evidence)) {
4
+ return { action: 'answer' };
5
+ }
6
+ const headlessUrl = suggestedHeadlessUrls.find((url) => !url.includes('npmjs.com/package/'));
7
+ if (headlessUrl && headlessAttempts < maxHeadlessAttempts) {
8
+ return { action: 'headless', url: headlessUrl };
9
+ }
10
+ if (passIndex + 1 < maxPasses) {
11
+ return { action: 'search-again' };
12
+ }
13
+ return { action: 'answer-with-caveat' };
14
+ }
@@ -1,3 +1,10 @@
1
+ function internalReaderLabel(method) {
2
+ if (method === 'headless')
3
+ return 'web_fetch_headless';
4
+ if (method === 'http')
5
+ return 'web_fetch';
6
+ return 'web_explore';
7
+ }
1
8
  export function buildExplorePresentation(result) {
2
9
  if (result.status === 'error') {
3
10
  return {
@@ -7,13 +14,26 @@ export function buildExplorePresentation(result) {
7
14
  }
8
15
  };
9
16
  }
10
- const preview = result.findings.map((finding) => `- ${finding}`).join('\n');
17
+ const internalSummary = result.metadata
18
+ ? `Internal research: web_search ×${result.metadata.searchPasses}, web_fetch ×${result.metadata.fetchedPages}, web_fetch_headless ×${result.metadata.headlessAttempts}`
19
+ : undefined;
20
+ const hasEvidence = result.findings.length > 0 || result.sources.length > 0;
21
+ const evidenceLines = hasEvidence
22
+ ? result.findings.map((finding, index) => `- [${internalReaderLabel(result.sources[index]?.method)}] ${finding}`)
23
+ : ['No usable evidence found.'];
24
+ const preview = [
25
+ ...evidenceLines,
26
+ internalSummary ? `\n${internalSummary}` : undefined
27
+ ]
28
+ .filter((line) => line !== undefined)
29
+ .join('\n');
11
30
  const verbose = [
12
31
  'Findings',
13
- ...result.findings.map((finding) => `- ${finding}`),
32
+ ...evidenceLines,
14
33
  '',
15
34
  'Sources',
16
- ...result.sources.map((source) => `- ${source.title}: ${source.url}`),
35
+ ...result.sources.map((source) => `- [${internalReaderLabel(source.method)}] ${source.title}: ${source.url}`),
36
+ internalSummary ? `\nInternal tools\n${internalSummary}` : undefined,
17
37
  result.caveat ? `\nCaveat\n${result.caveat}` : undefined
18
38
  ]
19
39
  .filter((line) => line !== undefined)
@@ -21,7 +41,9 @@ export function buildExplorePresentation(result) {
21
41
  return {
22
42
  mode: 'compact',
23
43
  views: {
24
- compact: `Reviewed ${result.sources.length} sources · synthesized answer with ${result.findings.length} findings`,
44
+ compact: hasEvidence
45
+ ? `Reviewed ${result.sources.length} sources · synthesized answer with ${result.findings.length} findings`
46
+ : 'No usable evidence found',
25
47
  preview,
26
48
  verbose
27
49
  },
@@ -1,4 +1,5 @@
1
1
  import type { ResearchEvidence } from '../orchestration/research-types.js';
2
+ import type { WebExploreResponse } from '../types.js';
2
3
  export declare function createWebExploreTool({ explore }?: {
3
4
  explore?: {
4
5
  run: (input: {
@@ -9,6 +10,7 @@ export declare function createWebExploreTool({ explore }?: {
9
10
  };
10
11
  evidence: ResearchEvidence[];
11
12
  workerPass: unknown;
13
+ metadata?: WebExploreResponse['metadata'];
12
14
  }>;
13
15
  } | ((input: {
14
16
  query: string;
@@ -18,6 +20,7 @@ export declare function createWebExploreTool({ explore }?: {
18
20
  };
19
21
  evidence: ResearchEvidence[];
20
22
  workerPass: unknown;
23
+ metadata?: WebExploreResponse['metadata'];
21
24
  }>);
22
25
  }): ({ query }: {
23
26
  query: string;
@@ -28,7 +31,14 @@ export declare function createWebExploreTool({ explore }?: {
28
31
  sources: Array<{
29
32
  title: string;
30
33
  url: string;
34
+ method?: "http" | "headless";
31
35
  }>;
32
36
  caveat?: string;
37
+ metadata?: {
38
+ searchPasses: number;
39
+ fetchedPages: number;
40
+ headlessAttempts: number;
41
+ exhaustedBudget: boolean;
42
+ };
33
43
  error?: import("../types.js").ToolError;
34
44
  }>;
@@ -1,18 +1,6 @@
1
1
  import { createResearchWorkflow } from '../orchestration/index.js';
2
+ import { synthesizeAnswer } from '../orchestration/answer-synthesizer.js';
2
3
  import { buildExplorePresentation } from '../presentation/explore-presentation.js';
3
- function findingFromEvidence(evidence, index) {
4
- if (evidence.summary.includes('Use channel')) {
5
- return 'Use channel for branded Chrome or Edge when possible.';
6
- }
7
- if (evidence.summary.includes('use at your own risk') || evidence.summary.includes('risky')) {
8
- return 'Treat executablePath as a fallback because Playwright documents it as use-at-your-own-risk.';
9
- }
10
- if (evidence.summary.includes('coverage.provider to v8') ||
11
- evidence.summary.includes('@vitest/coverage-v8')) {
12
- return 'Vitest coverage docs say to set coverage.provider to v8 and install @vitest/coverage-v8.';
13
- }
14
- return evidence.summary || `Finding ${index + 1}`;
15
- }
16
4
  export function createWebExploreTool({ explore = createResearchWorkflow() } = {}) {
17
5
  const runExplore = typeof explore === 'function' ? explore : explore.run.bind(explore);
18
6
  return async function webExplore({ query }) {
@@ -30,19 +18,21 @@ export function createWebExploreTool({ explore = createResearchWorkflow() } = {}
30
18
  };
31
19
  }
32
20
  const result = await runExplore({ query: normalizedQuery });
33
- const findings = result.evidence.slice(0, 5).map(findingFromEvidence);
34
21
  const sources = result.evidence.slice(0, 4).map((item) => ({
35
22
  title: item.title,
36
- url: item.url
23
+ url: item.url,
24
+ method: item.method
37
25
  }));
38
- const caveat = result.decision.action === 'answer'
39
- ? undefined
40
- : 'Evidence is partial, so this answer is based on the strongest source found so far.';
26
+ const synthesized = synthesizeAnswer({
27
+ evidence: result.evidence,
28
+ partial: result.decision.action !== 'answer'
29
+ });
41
30
  const shaped = {
42
31
  status: 'ok',
43
- findings,
32
+ findings: synthesized.findings,
44
33
  sources,
45
- caveat
34
+ caveat: synthesized.caveat,
35
+ metadata: result.metadata
46
36
  };
47
37
  return {
48
38
  ...shaped,
package/dist/types.d.ts CHANGED
@@ -19,7 +19,7 @@ export type FetchMetadata = {
19
19
  cacheHit: boolean;
20
20
  contentType?: string;
21
21
  truncated?: boolean;
22
- browser?: 'configured' | 'chrome' | 'edge';
22
+ browser?: 'configured' | 'chrome' | 'edge' | 'brave' | 'chromium';
23
23
  navigationMs?: number;
24
24
  };
25
25
  export type ExtractedContent = {
@@ -56,8 +56,15 @@ export type WebExploreResponse = {
56
56
  sources: Array<{
57
57
  title: string;
58
58
  url: string;
59
+ method?: 'http' | 'headless';
59
60
  }>;
60
61
  caveat?: string;
62
+ metadata?: {
63
+ searchPasses: number;
64
+ fetchedPages: number;
65
+ headlessAttempts: number;
66
+ exhaustedBudget: boolean;
67
+ };
61
68
  presentation?: PresentationEnvelope;
62
69
  error?: ToolError;
63
70
  };
package/package.json CHANGED
@@ -1,75 +1,74 @@
1
- {
2
- "name": "@demigodmode/pi-web-agent",
3
- "version": "0.3.1",
4
- "description": "Pi package for reliable web access with explicit search, fetch, and headless boundaries.",
5
- "type": "module",
6
- "main": "./dist/extension.js",
7
- "types": "./dist/extension.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/extension.d.ts",
11
- "import": "./dist/extension.js"
12
- }
13
- },
14
- "files": [
15
- "dist",
16
- "README.md"
17
- ],
18
- "keywords": [
19
- "pi-package",
20
- "pi",
21
- "extension",
22
- "web-search",
23
- "web-fetch"
24
- ],
25
- "repository": {
26
- "type": "git",
27
- "url": "git+https://github.com/demigodmode/pi-web-agent.git"
28
- },
29
- "homepage": "https://github.com/demigodmode/pi-web-agent#readme",
30
- "bugs": {
31
- "url": "https://github.com/demigodmode/pi-web-agent/issues"
32
- },
33
- "license": "AGPL-3.0-only",
34
- "publishConfig": {
35
- "access": "public"
36
- },
37
- "scripts": {
38
- "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json",
39
- "build:dev": "tsc -p tsconfig.json",
40
- "test": "vitest run --coverage",
41
- "test:watch": "vitest",
42
- "lint": "tsc -p tsconfig.json --noEmit",
43
- "docs:dev": "vitepress dev docs",
44
- "docs:build": "vitepress build docs",
45
- "docs:preview": "vitepress preview docs",
46
- "eval:live": "npm run build:dev && node dist/scripts/live-web-eval.js",
47
- "release:dry-run": "node scripts/release.mjs --dry-run",
48
- "release": "node scripts/release.mjs"
49
- },
50
- "pi": {
51
- "extensions": [
52
- "./dist/extension.js"
53
- ]
54
- },
55
- "dependencies": {
56
- "@mozilla/readability": "^0.6.0",
57
- "cheerio": "^1.1.0",
58
- "jsdom": "^26.0.0",
59
- "playwright-core": "^1.54.0"
60
- },
61
- "devDependencies": {
62
- "@mariozechner/pi-coding-agent": "^0.67.2",
63
- "@sinclair/typebox": "^0.34.41",
64
- "@types/jsdom": "^21.1.7",
65
- "@types/node": "^24.0.0",
66
- "@vitest/coverage-v8": "^3.2.4",
67
- "typescript": "^5.8.0",
68
- "vitepress": "^1.6.4",
69
- "vitest": "^3.2.0"
70
- },
71
- "peerDependencies": {
72
- "@mariozechner/pi-coding-agent": "*",
73
- "@sinclair/typebox": "*"
74
- }
75
- }
1
+ {
2
+ "name": "@demigodmode/pi-web-agent",
3
+ "version": "0.5.1",
4
+ "description": "Pi package for reliable web access with explicit search, fetch, and headless boundaries.",
5
+ "type": "module",
6
+ "main": "./dist/extension.js",
7
+ "types": "./dist/extension.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/extension.d.ts",
11
+ "import": "./dist/extension.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "keywords": [
19
+ "pi-package",
20
+ "pi",
21
+ "extension",
22
+ "web-search",
23
+ "web-fetch"
24
+ ],
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/demigodmode/pi-web-agent.git"
28
+ },
29
+ "homepage": "https://github.com/demigodmode/pi-web-agent#readme",
30
+ "bugs": {
31
+ "url": "https://github.com/demigodmode/pi-web-agent/issues"
32
+ },
33
+ "license": "AGPL-3.0-only",
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "scripts": {
38
+ "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json",
39
+ "build:dev": "tsc -p tsconfig.json",
40
+ "test": "vitest run --coverage",
41
+ "test:watch": "vitest",
42
+ "lint": "tsc -p tsconfig.json --noEmit",
43
+ "docs:dev": "vitepress dev docs",
44
+ "docs:build": "vitepress build docs",
45
+ "docs:preview": "vitepress preview docs",
46
+ "eval:live": "npm run build:dev && node dist/scripts/live-web-eval.js",
47
+ "release:dry-run": "node scripts/release.mjs --dry-run",
48
+ "release": "node scripts/release.mjs"
49
+ },
50
+ "pi": {
51
+ "extensions": [
52
+ "./dist/extension.js"
53
+ ]
54
+ },
55
+ "dependencies": {
56
+ "@mozilla/readability": "^0.6.0",
57
+ "cheerio": "^1.1.0",
58
+ "jsdom": "^26.0.0",
59
+ "playwright-core": "^1.54.0",
60
+ "typebox": "^1.1.37"
61
+ },
62
+ "devDependencies": {
63
+ "@mariozechner/pi-coding-agent": "^0.69.0",
64
+ "@types/jsdom": "^21.1.7",
65
+ "@types/node": "^24.0.0",
66
+ "@vitest/coverage-v8": "^3.2.4",
67
+ "typescript": "^5.8.0",
68
+ "vitepress": "^1.6.4",
69
+ "vitest": "^3.2.0"
70
+ },
71
+ "peerDependencies": {
72
+ "@mariozechner/pi-coding-agent": "*"
73
+ }
74
+ }