@demigodmode/pi-web-agent 0.3.1 → 0.4.0
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/LICENSE +661 -661
- package/README.md +119 -119
- package/dist/commands/web-agent-config.js +1 -6
- package/dist/extension.js +9 -154
- package/dist/orchestration/answer-synthesizer.d.ts +8 -0
- package/dist/orchestration/answer-synthesizer.js +17 -0
- package/dist/orchestration/candidate-selector.d.ts +6 -0
- package/dist/orchestration/candidate-selector.js +24 -0
- package/dist/orchestration/evidence-ranker.d.ts +4 -0
- package/dist/orchestration/evidence-ranker.js +36 -0
- package/dist/orchestration/index.d.ts +6 -21
- package/dist/orchestration/query-planner.d.ts +7 -0
- package/dist/orchestration/query-planner.js +37 -0
- package/dist/orchestration/research-orchestrator.d.ts +7 -22
- package/dist/orchestration/research-orchestrator.js +185 -73
- package/dist/orchestration/research-types.d.ts +6 -0
- package/dist/orchestration/research-worker.js +8 -1
- package/dist/orchestration/stop-decider.d.ts +19 -0
- package/dist/orchestration/stop-decider.js +14 -0
- package/dist/presentation/explore-presentation.js +26 -4
- package/dist/tools/web-explore.d.ts +10 -0
- package/dist/tools/web-explore.js +10 -20
- package/dist/types.d.ts +7 -0
- package/package.json +75 -75
|
@@ -1,87 +1,199 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
import { rankEvidence } from './evidence-ranker.js';
|
|
2
|
+
import { planSearchQueries } from './query-planner.js';
|
|
3
|
+
import { decideNextResearchStep } from './stop-decider.js';
|
|
4
|
+
const DEFAULT_MAX_PASSES = 3;
|
|
5
|
+
const DEFAULT_MAX_FETCHES_PER_PASS = 4;
|
|
6
|
+
const DEFAULT_MAX_HEADLESS_ATTEMPTS = 2;
|
|
7
|
+
function classifyEvidenceUrl(url) {
|
|
8
|
+
if (url.includes('/docs/api/') || url.includes('/config/'))
|
|
9
|
+
return 'official-api';
|
|
10
|
+
if (url.includes('playwright.dev/docs') || url.includes('vitest.dev/guide/'))
|
|
11
|
+
return 'official-docs';
|
|
12
|
+
if (url.includes('github.com/vitest-dev/vitest') && url.includes('/docs/'))
|
|
13
|
+
return 'official-docs';
|
|
14
|
+
if (url.includes('learn.microsoft.com'))
|
|
15
|
+
return 'official-docs';
|
|
16
|
+
if (url.includes('github.com/') && url.includes('/issues/'))
|
|
17
|
+
return 'issue-thread';
|
|
18
|
+
if (url.includes('npmjs.com/package/'))
|
|
19
|
+
return 'package-page';
|
|
20
|
+
return 'community';
|
|
18
21
|
}
|
|
19
|
-
function
|
|
20
|
-
return
|
|
22
|
+
function summarizeText(text, maxLength = 180) {
|
|
23
|
+
return text.replace(/\s+/g, ' ').trim().slice(0, maxLength);
|
|
21
24
|
}
|
|
22
|
-
function
|
|
23
|
-
return
|
|
24
|
-
item.sourceKind === 'official-api' ||
|
|
25
|
-
item.sourceKind === 'official-discussion');
|
|
25
|
+
function isBotCheckContent({ title = '', text }) {
|
|
26
|
+
return /performing security verification|security service|verify you are not a bot|just a moment|checking your browser/i.test(`${title}\n${text}`);
|
|
26
27
|
}
|
|
27
|
-
function
|
|
28
|
-
|
|
28
|
+
function evidenceFromHeadless(result) {
|
|
29
|
+
if (result.status !== 'ok' || !result.content?.text.trim())
|
|
30
|
+
return null;
|
|
31
|
+
if (isBotCheckContent({ title: result.content.title, text: result.content.text }))
|
|
32
|
+
return null;
|
|
33
|
+
return {
|
|
34
|
+
title: result.content.title ?? result.url,
|
|
35
|
+
url: result.url,
|
|
36
|
+
sourceKind: classifyEvidenceUrl(result.url),
|
|
37
|
+
method: 'headless',
|
|
38
|
+
summary: summarizeText(result.content.text),
|
|
39
|
+
supports: [summarizeText(result.content.text, 120)]
|
|
40
|
+
};
|
|
29
41
|
}
|
|
30
|
-
function
|
|
31
|
-
return
|
|
42
|
+
function fallbackWorkerPass({ previousQueries, allGaps, allLowValueOutcomes, exhaustedBudget }) {
|
|
43
|
+
return {
|
|
44
|
+
searchQueries: previousQueries,
|
|
45
|
+
evidence: [],
|
|
46
|
+
gaps: allGaps,
|
|
47
|
+
lowValueOutcomes: allLowValueOutcomes,
|
|
48
|
+
exhaustedBudget
|
|
49
|
+
};
|
|
32
50
|
}
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
function buildMetadata({ previousQueries, allEvidence, allGaps, allLowValueOutcomes, headlessAttempts, exhaustedBudget }) {
|
|
52
|
+
return {
|
|
53
|
+
searchPasses: previousQueries.length,
|
|
54
|
+
fetchedPages: allEvidence.length + allGaps.length + allLowValueOutcomes.length,
|
|
55
|
+
headlessAttempts,
|
|
56
|
+
exhaustedBudget
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function decisionForAnswer(action, query, ranked) {
|
|
60
|
+
if (action === 'answer') {
|
|
61
|
+
return {
|
|
62
|
+
action: 'answer',
|
|
63
|
+
rationale: 'Adaptive research gathered enough strong evidence.',
|
|
64
|
+
approvedEvidence: ranked
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
action: 'research-again',
|
|
69
|
+
rationale: 'Research budget exhausted; answer with caveat.',
|
|
70
|
+
followupQuery: query
|
|
71
|
+
};
|
|
42
72
|
}
|
|
43
73
|
export function createResearchOrchestrator({ worker, headlessFetch }) {
|
|
44
74
|
return {
|
|
45
75
|
async run({ query }) {
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
const allEvidence = [];
|
|
77
|
+
const allGaps = [];
|
|
78
|
+
const allLowValueOutcomes = [];
|
|
79
|
+
const previousQueries = [];
|
|
80
|
+
const suggestedHeadlessUrls = [];
|
|
81
|
+
let headlessAttempts = 0;
|
|
82
|
+
let lastPass;
|
|
83
|
+
for (let passIndex = 0; passIndex < DEFAULT_MAX_PASSES; passIndex++) {
|
|
84
|
+
const queries = planSearchQueries({
|
|
85
|
+
originalQuery: query,
|
|
86
|
+
passIndex,
|
|
87
|
+
previousQueries,
|
|
88
|
+
gaps: allGaps.map((gap) => gap.message)
|
|
89
|
+
});
|
|
90
|
+
for (const plannedQuery of queries) {
|
|
91
|
+
previousQueries.push(plannedQuery);
|
|
92
|
+
const pass = await worker.run({
|
|
93
|
+
query: plannedQuery,
|
|
94
|
+
maxSearchRounds: 1,
|
|
95
|
+
maxFetches: DEFAULT_MAX_FETCHES_PER_PASS
|
|
96
|
+
});
|
|
97
|
+
lastPass = pass;
|
|
98
|
+
allEvidence.push(...pass.evidence);
|
|
99
|
+
allGaps.push(...pass.gaps);
|
|
100
|
+
allLowValueOutcomes.push(...pass.lowValueOutcomes);
|
|
101
|
+
if (pass.suggestedHeadlessUrl)
|
|
102
|
+
suggestedHeadlessUrls.push(pass.suggestedHeadlessUrl);
|
|
103
|
+
const ranked = rankEvidence(allEvidence.filter((item) => item.sourceKind !== 'package-page'));
|
|
104
|
+
const decision = decideNextResearchStep({
|
|
105
|
+
evidence: ranked,
|
|
106
|
+
suggestedHeadlessUrls,
|
|
107
|
+
passIndex,
|
|
108
|
+
maxPasses: DEFAULT_MAX_PASSES,
|
|
109
|
+
headlessAttempts,
|
|
110
|
+
maxHeadlessAttempts: DEFAULT_MAX_HEADLESS_ATTEMPTS
|
|
111
|
+
});
|
|
112
|
+
if (decision.action === 'headless') {
|
|
113
|
+
headlessAttempts++;
|
|
114
|
+
const headlessResult = await headlessFetch({ url: decision.url });
|
|
115
|
+
const headlessEvidence = evidenceFromHeadless(headlessResult);
|
|
116
|
+
if (headlessEvidence) {
|
|
117
|
+
allEvidence.push(headlessEvidence);
|
|
118
|
+
const updatedRanked = rankEvidence(allEvidence.filter((item) => item.sourceKind !== 'package-page'));
|
|
119
|
+
const updatedDecision = decideNextResearchStep({
|
|
120
|
+
evidence: updatedRanked,
|
|
121
|
+
suggestedHeadlessUrls: [],
|
|
122
|
+
passIndex,
|
|
123
|
+
maxPasses: DEFAULT_MAX_PASSES,
|
|
124
|
+
headlessAttempts,
|
|
125
|
+
maxHeadlessAttempts: DEFAULT_MAX_HEADLESS_ATTEMPTS
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
decision: decisionForAnswer(updatedDecision.action === 'answer' ? 'answer' : 'answer-with-caveat', query, updatedRanked),
|
|
129
|
+
evidence: updatedRanked,
|
|
130
|
+
workerPass: lastPass,
|
|
131
|
+
metadata: buildMetadata({
|
|
132
|
+
previousQueries,
|
|
133
|
+
allEvidence,
|
|
134
|
+
allGaps,
|
|
135
|
+
allLowValueOutcomes,
|
|
136
|
+
headlessAttempts,
|
|
137
|
+
exhaustedBudget: updatedDecision.action !== 'answer'
|
|
138
|
+
})
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
decision: {
|
|
143
|
+
action: 'escalate-headless',
|
|
144
|
+
rationale: 'One high-value page is worth a single orchestrator-approved headless retry.',
|
|
145
|
+
url: decision.url,
|
|
146
|
+
approvedEvidence: ranked
|
|
147
|
+
},
|
|
148
|
+
evidence: ranked,
|
|
149
|
+
workerPass: lastPass,
|
|
150
|
+
metadata: buildMetadata({
|
|
151
|
+
previousQueries,
|
|
152
|
+
allEvidence,
|
|
153
|
+
allGaps,
|
|
154
|
+
allLowValueOutcomes,
|
|
155
|
+
headlessAttempts,
|
|
156
|
+
exhaustedBudget: false
|
|
157
|
+
})
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (decision.action === 'answer' || decision.action === 'answer-with-caveat') {
|
|
161
|
+
return {
|
|
162
|
+
decision: decisionForAnswer(decision.action, query, ranked),
|
|
163
|
+
evidence: ranked,
|
|
164
|
+
workerPass: lastPass,
|
|
165
|
+
metadata: buildMetadata({
|
|
166
|
+
previousQueries,
|
|
167
|
+
allEvidence,
|
|
168
|
+
allGaps,
|
|
169
|
+
allLowValueOutcomes,
|
|
170
|
+
headlessAttempts,
|
|
171
|
+
exhaustedBudget: decision.action === 'answer-with-caveat'
|
|
172
|
+
})
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
78
176
|
}
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
177
|
+
const ranked = rankEvidence(allEvidence.filter((item) => item.sourceKind !== 'package-page'));
|
|
178
|
+
return {
|
|
179
|
+
decision: decisionForAnswer('answer-with-caveat', query, ranked),
|
|
180
|
+
evidence: ranked,
|
|
181
|
+
workerPass: lastPass ??
|
|
182
|
+
fallbackWorkerPass({
|
|
183
|
+
previousQueries,
|
|
184
|
+
allGaps,
|
|
185
|
+
allLowValueOutcomes,
|
|
186
|
+
exhaustedBudget: true
|
|
187
|
+
}),
|
|
188
|
+
metadata: buildMetadata({
|
|
189
|
+
previousQueries,
|
|
190
|
+
allEvidence,
|
|
191
|
+
allGaps,
|
|
192
|
+
allLowValueOutcomes,
|
|
193
|
+
headlessAttempts,
|
|
194
|
+
exhaustedBudget: true
|
|
195
|
+
})
|
|
83
196
|
};
|
|
84
|
-
return { decision, evidence: approvedEvidence, workerPass: pass };
|
|
85
197
|
}
|
|
86
198
|
};
|
|
87
199
|
}
|
|
@@ -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 =
|
|
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
|
|
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
|
-
...
|
|
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:
|
|
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
|
|
39
|
-
|
|
40
|
-
:
|
|
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
|
@@ -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,75 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@demigodmode/pi-web-agent",
|
|
3
|
-
"version": "0.
|
|
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.4.0",
|
|
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
|
+
}
|