@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.
- package/LICENSE +661 -661
- package/README.md +124 -119
- package/dist/commands/web-agent-config.d.ts +8 -0
- package/dist/commands/web-agent-config.js +90 -10
- package/dist/extension.js +10 -155
- package/dist/fetch/browser-resolution.d.ts +7 -2
- package/dist/fetch/browser-resolution.js +111 -17
- 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 +8 -1
- package/package.json +74 -75
package/README.md
CHANGED
|
@@ -1,119 +1,124 @@
|
|
|
1
|
-
# pi-web-agent
|
|
2
|
-
|
|
3
|
-
[](https://github.com/demigodmode/pi-web-agent/actions/workflows/ci.yml)
|
|
4
|
-
[](https://www.npmjs.com/package/@demigodmode/pi-web-agent)
|
|
5
|
-
[](https://demigodmode.github.io/pi-web-agent/)
|
|
6
|
-
|
|
7
|
-
`@demigodmode/pi-web-agent` is a Pi package for web access.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- `
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
- `
|
|
54
|
-
- `
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
/web-agent
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
1
|
+
# pi-web-agent
|
|
2
|
+
|
|
3
|
+
[](https://github.com/demigodmode/pi-web-agent/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@demigodmode/pi-web-agent)
|
|
5
|
+
[](https://demigodmode.github.io/pi-web-agent/)
|
|
6
|
+
|
|
7
|
+
`@demigodmode/pi-web-agent` is a Pi package for web access.
|
|
8
|
+
|
|
9
|
+
Most agent web tools blur search, fetch, browser rendering, and research into one vague thing. `pi-web-agent` exposes one public research tool, `web_explore`, and keeps search/fetch/headless work inside that bounded workflow.
|
|
10
|
+
|
|
11
|
+
The point is keeping the model-facing boundary simple: ask `web_explore` to research a question, and it handles discovery, HTTP reads, targeted browser rendering, source ranking, and caveats internally.
|
|
12
|
+
|
|
13
|
+
That sounds obvious, but a lot of agent tooling gets fuzzy right there. This package is meant to be stricter about what it actually did and more willing to say when a read was not good enough to trust.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pi install npm:@demigodmode/pi-web-agent
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
After installing, reload or restart Pi. Run `/web-agent` for the action menu, or `/web-agent doctor` to check whether the package loaded cleanly and whether headless rendering can find a browser.
|
|
22
|
+
|
|
23
|
+
Headless rendering currently requires a detectable Chromium-family browser: Chrome, Chromium, Edge, or Brave. Firefox/Safari-only systems can still use search and plain HTTP reads, but browser-rendered fallback pages need a supported Chromium-family browser for now.
|
|
24
|
+
|
|
25
|
+
Later on, update installed packages with:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pi update
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Docs
|
|
32
|
+
|
|
33
|
+
Docs site:
|
|
34
|
+
|
|
35
|
+
- https://demigodmode.github.io/pi-web-agent/
|
|
36
|
+
|
|
37
|
+
Work on the docs locally:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm run docs:dev
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Build the docs:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm run docs:build
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Presentation modes
|
|
50
|
+
|
|
51
|
+
`pi-web-agent` renders web tool output in one visible mode at a time:
|
|
52
|
+
|
|
53
|
+
- `compact` — short summary, default everywhere
|
|
54
|
+
- `preview` — slightly richer bounded view
|
|
55
|
+
- `verbose` — fuller bounded view
|
|
56
|
+
|
|
57
|
+
See the `v0.3.0` release notes for a before/after of the transcript cleanup:
|
|
58
|
+
|
|
59
|
+
- https://github.com/demigodmode/pi-web-agent/releases/tag/v0.3.0
|
|
60
|
+
|
|
61
|
+
## Settings
|
|
62
|
+
|
|
63
|
+
Primary UI:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
/web-agent settings
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Helper commands:
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
/web-agent doctor
|
|
73
|
+
/web-agent show
|
|
74
|
+
/web-agent reset project
|
|
75
|
+
/web-agent reset global
|
|
76
|
+
/web-agent mode preview
|
|
77
|
+
/web-agent mode web_explore verbose
|
|
78
|
+
/web-agent mode web_explore inherit
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Config files:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
Global: ~/.pi/agent/extensions/pi-web-agent/config.json
|
|
85
|
+
Project: .pi/extensions/pi-web-agent/config.json
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Precedence:
|
|
89
|
+
|
|
90
|
+
- built-in defaults
|
|
91
|
+
- global config
|
|
92
|
+
- project config
|
|
93
|
+
|
|
94
|
+
Project config overrides global config.
|
|
95
|
+
|
|
96
|
+
Example:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"presentation": {
|
|
101
|
+
"defaultMode": "compact",
|
|
102
|
+
"tools": {
|
|
103
|
+
"web_explore": { "mode": "verbose" }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Local development
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npm install
|
|
113
|
+
npm test
|
|
114
|
+
npm run lint
|
|
115
|
+
npm run build
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
For local Pi work, this repo includes `.pi/extensions/pi-web-agent.ts`.
|
|
119
|
+
|
|
120
|
+
If Pi is already running, use `/reload` after changes.
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
AGPL-3.0-only. See `LICENSE`.
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { type ExtensionAPI } from '@mariozechner/pi-coding-agent';
|
|
2
2
|
import { loadPresentationConfigLayers, type LoadedPresentationConfig } from '../presentation/config-store.js';
|
|
3
|
+
import { type BrowserResolutionResult } from '../fetch/browser-resolution.js';
|
|
3
4
|
import type { PresentationConfig, PresentationConfigOverride, PresentationScope } from '../presentation/types.js';
|
|
4
5
|
type CommandDeps = {
|
|
5
6
|
load?: () => ReturnType<typeof loadPresentationConfigLayers>;
|
|
6
7
|
save?: (scope: PresentationScope, config: PresentationConfigOverride) => Promise<void>;
|
|
7
8
|
reset?: (scope: PresentationScope) => Promise<void>;
|
|
9
|
+
resolveBrowser?: () => Promise<BrowserResolutionResult>;
|
|
10
|
+
runtime?: {
|
|
11
|
+
nodeVersion: string;
|
|
12
|
+
platform: string;
|
|
13
|
+
arch: string;
|
|
14
|
+
};
|
|
15
|
+
checkTypebox?: () => Promise<boolean>;
|
|
8
16
|
};
|
|
9
17
|
export type SettingsDraftState = {
|
|
10
18
|
scope: PresentationScope;
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import { getSettingsListTheme } from '@mariozechner/pi-coding-agent';
|
|
2
|
-
import { Container, SettingsList, Text } from '@mariozechner/pi-tui';
|
|
1
|
+
import { DynamicBorder, getSettingsListTheme } from '@mariozechner/pi-coding-agent';
|
|
2
|
+
import { Container, SelectList, SettingsList, Text } from '@mariozechner/pi-tui';
|
|
3
3
|
import { DEFAULT_PRESENTATION_CONFIG, mergePresentationConfigLayers, resolvePresentationMode } from '../presentation/config.js';
|
|
4
4
|
import { loadPresentationConfigLayers, resetPresentationConfigScope, savePresentationConfigScope } from '../presentation/config-store.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
'web_fetch',
|
|
8
|
-
'web_fetch_headless',
|
|
9
|
-
'web_explore'
|
|
10
|
-
];
|
|
5
|
+
import { resolveBrowserExecutable } from '../fetch/browser-resolution.js';
|
|
6
|
+
const PRESENTATION_TOOL_NAMES = ['web_explore'];
|
|
11
7
|
function parseScopeToken(token) {
|
|
12
8
|
return token === 'global' || token === 'project' ? token : undefined;
|
|
13
9
|
}
|
|
@@ -17,6 +13,15 @@ function clonePresentationConfig(config) {
|
|
|
17
13
|
tools: { ...config.tools }
|
|
18
14
|
};
|
|
19
15
|
}
|
|
16
|
+
async function defaultCheckTypebox() {
|
|
17
|
+
try {
|
|
18
|
+
await import('typebox');
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
20
25
|
function formatConfigSummary(config) {
|
|
21
26
|
const lines = [`defaultMode: ${config.defaultMode}`];
|
|
22
27
|
for (const toolName of PRESENTATION_TOOL_NAMES) {
|
|
@@ -142,6 +147,40 @@ export function handleSettingsShortcut(data) {
|
|
|
142
147
|
}
|
|
143
148
|
return undefined;
|
|
144
149
|
}
|
|
150
|
+
async function openActionMenu(ctx) {
|
|
151
|
+
return ctx.ui.custom((tui, theme, _kb, done) => {
|
|
152
|
+
const container = new Container();
|
|
153
|
+
const items = [
|
|
154
|
+
{ value: 'settings', label: 'Settings', description: 'Edit presentation modes' },
|
|
155
|
+
{ value: 'show', label: 'Show config', description: 'Print effective config paths and modes' },
|
|
156
|
+
{ value: 'doctor', label: 'Doctor', description: 'Check runtime dependencies and browser detection' },
|
|
157
|
+
{ value: 'reset-project', label: 'Reset project config', description: 'Delete project-level overrides' },
|
|
158
|
+
{ value: 'reset-global', label: 'Reset global config', description: 'Delete global overrides' }
|
|
159
|
+
];
|
|
160
|
+
container.addChild(new DynamicBorder((text) => theme.fg('accent', text)));
|
|
161
|
+
container.addChild(new Text(theme.fg('accent', theme.bold('pi-web-agent')), 1, 0));
|
|
162
|
+
const list = new SelectList(items, Math.min(items.length, 8), {
|
|
163
|
+
selectedPrefix: (text) => theme.fg('accent', text),
|
|
164
|
+
selectedText: (text) => theme.fg('accent', text),
|
|
165
|
+
description: (text) => theme.fg('muted', text),
|
|
166
|
+
scrollInfo: (text) => theme.fg('dim', text),
|
|
167
|
+
noMatch: (text) => theme.fg('warning', text)
|
|
168
|
+
});
|
|
169
|
+
list.onSelect = (item) => done(item.value);
|
|
170
|
+
list.onCancel = () => done(undefined);
|
|
171
|
+
container.addChild(list);
|
|
172
|
+
container.addChild(new Text(theme.fg('dim', '↑↓ navigate • enter select • esc cancel'), 1, 0));
|
|
173
|
+
container.addChild(new DynamicBorder((text) => theme.fg('accent', text)));
|
|
174
|
+
return {
|
|
175
|
+
render: (width) => container.render(width),
|
|
176
|
+
invalidate: () => container.invalidate(),
|
|
177
|
+
handleInput: (data) => {
|
|
178
|
+
list.handleInput?.(data);
|
|
179
|
+
tui.requestRender?.();
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
}
|
|
145
184
|
async function openSettingsUi(ctx, loaded, initialScope) {
|
|
146
185
|
return ctx.ui.custom((_tui, theme, _kb, done) => {
|
|
147
186
|
let state = createSettingsDraftState(loaded, initialScope);
|
|
@@ -187,10 +226,51 @@ export function registerWebAgentConfigCommands(pi, deps = {}) {
|
|
|
187
226
|
const load = deps.load ?? (() => loadPresentationConfigLayers());
|
|
188
227
|
const save = deps.save ?? ((scope, config) => savePresentationConfigScope({}, scope, config));
|
|
189
228
|
const reset = deps.reset ?? ((scope) => resetPresentationConfigScope({}, scope));
|
|
229
|
+
const resolveBrowser = deps.resolveBrowser ?? (() => resolveBrowserExecutable({}));
|
|
230
|
+
const runtime = deps.runtime ?? {
|
|
231
|
+
nodeVersion: process.version,
|
|
232
|
+
platform: process.platform,
|
|
233
|
+
arch: process.arch
|
|
234
|
+
};
|
|
235
|
+
const checkTypebox = deps.checkTypebox ?? defaultCheckTypebox;
|
|
190
236
|
pi.registerCommand('web-agent', {
|
|
191
237
|
description: 'Open settings or manage pi-web-agent presentation config',
|
|
192
238
|
handler: async (args, ctx) => {
|
|
193
|
-
|
|
239
|
+
let [action, maybeScope] = (args ?? '').trim().split(/\s+/).filter(Boolean);
|
|
240
|
+
if (!action) {
|
|
241
|
+
const selectedAction = await openActionMenu(ctx);
|
|
242
|
+
if (!selectedAction)
|
|
243
|
+
return;
|
|
244
|
+
if (selectedAction === 'reset-project') {
|
|
245
|
+
action = 'reset';
|
|
246
|
+
maybeScope = 'project';
|
|
247
|
+
}
|
|
248
|
+
else if (selectedAction === 'reset-global') {
|
|
249
|
+
action = 'reset';
|
|
250
|
+
maybeScope = 'global';
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
action = selectedAction;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (action === 'doctor') {
|
|
257
|
+
const [typeboxOk, browser] = await Promise.all([checkTypebox(), resolveBrowser()]);
|
|
258
|
+
const lines = [
|
|
259
|
+
'pi-web-agent: loaded',
|
|
260
|
+
`runtime: node ${runtime.nodeVersion} ${runtime.platform} ${runtime.arch}`,
|
|
261
|
+
`typebox: ${typeboxOk ? 'ok' : 'missing'}`
|
|
262
|
+
];
|
|
263
|
+
if (browser.ok) {
|
|
264
|
+
lines.push(`browser: ${browser.browser} ${browser.executablePath}`);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
lines.push(`browser: missing (${browser.error.code})`);
|
|
268
|
+
lines.push(browser.error.message);
|
|
269
|
+
lines.push('Install Chrome, Chromium, Edge, or Brave and run /web-agent doctor again.');
|
|
270
|
+
}
|
|
271
|
+
ctx.ui.notify(lines.join('\n'), 'info');
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
194
274
|
if (action === 'show') {
|
|
195
275
|
const loaded = await load();
|
|
196
276
|
ctx.ui.notify([
|
|
@@ -248,7 +328,7 @@ export function registerWebAgentConfigCommands(pi, deps = {}) {
|
|
|
248
328
|
ctx.ui.notify(`Saved ${result.scope} config`, 'info');
|
|
249
329
|
return;
|
|
250
330
|
}
|
|
251
|
-
ctx.ui.notify('Use /web-agent, /web-agent show, /web-agent reset project, or /web-agent settings', 'info');
|
|
331
|
+
ctx.ui.notify('Use /web-agent, /web-agent show, /web-agent doctor, /web-agent reset project, or /web-agent settings', 'info');
|
|
252
332
|
}
|
|
253
333
|
});
|
|
254
334
|
}
|
package/dist/extension.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { Type } from '
|
|
1
|
+
import { Type } from 'typebox';
|
|
2
2
|
import { registerWebAgentConfigCommands } from './commands/web-agent-config.js';
|
|
3
3
|
import { DEFAULT_PRESENTATION_CONFIG, resolvePresentationMode } from './presentation/config.js';
|
|
4
4
|
import { loadPresentationConfigLayers } from './presentation/config-store.js';
|
|
5
5
|
import { selectPresentationView } from './presentation/select-view.js';
|
|
6
6
|
import { createWebExploreTool } from './tools/web-explore.js';
|
|
7
|
-
import { createWebFetchTool } from './tools/web-fetch.js';
|
|
8
|
-
import { createWebFetchHeadlessTool } from './tools/web-fetch-headless.js';
|
|
9
|
-
import { createWebSearchTool } from './tools/web-search.js';
|
|
10
7
|
async function getEffectivePresentationConfig(pi) {
|
|
11
8
|
const store = pi.__presentationConfigStore;
|
|
12
9
|
try {
|
|
@@ -24,165 +21,23 @@ async function renderToolText(pi, toolName, details) {
|
|
|
24
21
|
}
|
|
25
22
|
export default function extension(pi) {
|
|
26
23
|
registerWebAgentConfigCommands(pi);
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
};
|
|
36
|
-
async function guardSearchResponse() {
|
|
37
|
-
const result = {
|
|
38
|
-
status: 'error',
|
|
39
|
-
results: [],
|
|
40
|
-
metadata: {
|
|
41
|
-
backend: 'duckduckgo',
|
|
42
|
-
cacheHit: false
|
|
43
|
-
},
|
|
44
|
-
error: postWebExploreGuardError,
|
|
45
|
-
presentation: {
|
|
46
|
-
mode: 'compact',
|
|
47
|
-
views: {
|
|
48
|
-
compact: `Search failed: ${postWebExploreGuardError.message}`
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
return {
|
|
53
|
-
content: [{ type: 'text', text: await renderToolText(pi, 'web_search', result) }],
|
|
54
|
-
details: result,
|
|
55
|
-
isError: true
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
async function guardFetchResponse(url) {
|
|
59
|
-
const result = {
|
|
60
|
-
status: 'error',
|
|
61
|
-
url,
|
|
62
|
-
metadata: {
|
|
63
|
-
method: 'http',
|
|
64
|
-
cacheHit: false
|
|
65
|
-
},
|
|
66
|
-
error: postWebExploreGuardError,
|
|
67
|
-
presentation: {
|
|
68
|
-
mode: 'compact',
|
|
69
|
-
views: {
|
|
70
|
-
compact: `Fetch failed: ${postWebExploreGuardError.message}`
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
return {
|
|
75
|
-
content: [{ type: 'text', text: await renderToolText(pi, 'web_fetch', result) }],
|
|
76
|
-
details: result,
|
|
77
|
-
isError: true
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
async function guardHeadlessResponse(url) {
|
|
81
|
-
const result = {
|
|
82
|
-
status: 'error',
|
|
83
|
-
url,
|
|
84
|
-
metadata: {
|
|
85
|
-
method: 'headless',
|
|
86
|
-
cacheHit: false
|
|
87
|
-
},
|
|
88
|
-
error: postWebExploreGuardError,
|
|
89
|
-
presentation: {
|
|
90
|
-
mode: 'compact',
|
|
91
|
-
views: {
|
|
92
|
-
compact: `Fetch failed: ${postWebExploreGuardError.message}`
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
return {
|
|
97
|
-
content: [
|
|
98
|
-
{
|
|
99
|
-
type: 'text',
|
|
100
|
-
text: await renderToolText(pi, 'web_fetch_headless', result)
|
|
101
|
-
}
|
|
102
|
-
],
|
|
103
|
-
details: result,
|
|
104
|
-
isError: true
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
pi.on('before_agent_start', async (event) => {
|
|
108
|
-
webExploreUsedInCurrentFlow = false;
|
|
109
|
-
return {
|
|
110
|
-
systemPrompt: `${event.systemPrompt}\n\n` +
|
|
111
|
-
'For web research questions that require finding and comparing multiple sources, prefer web_explore. ' +
|
|
112
|
-
'Use web_search, web_fetch, and web_fetch_headless for direct/manual operations like explicit search calls, specific URL reads, or debugging. ' +
|
|
113
|
-
'After using web_explore, only call low-level web tools if there is a specific unresolved gap. ' +
|
|
114
|
-
'Do not keep searching or fetching just for extra confirmation.'
|
|
115
|
-
};
|
|
116
|
-
});
|
|
117
|
-
pi.registerTool({
|
|
118
|
-
name: 'web_search',
|
|
119
|
-
label: 'Web Search',
|
|
120
|
-
description: 'Direct search tool for manual discovery of links and snippets. Use for explicit search requests or when the user wants raw search results. Prefer web_explore for broader research questions.',
|
|
121
|
-
parameters: Type.Object({
|
|
122
|
-
query: Type.String({ description: 'Search query.' })
|
|
123
|
-
}),
|
|
124
|
-
async execute(_toolCallId, params) {
|
|
125
|
-
if (webExploreUsedInCurrentFlow) {
|
|
126
|
-
return guardSearchResponse();
|
|
127
|
-
}
|
|
128
|
-
const result = await webSearch({ query: params.query });
|
|
129
|
-
return {
|
|
130
|
-
content: [{ type: 'text', text: await renderToolText(pi, 'web_search', result) }],
|
|
131
|
-
details: result,
|
|
132
|
-
isError: result.status === 'error'
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
pi.registerTool({
|
|
137
|
-
name: 'web_fetch',
|
|
138
|
-
label: 'Web Fetch',
|
|
139
|
-
description: 'Direct HTTP page fetch for a specific URL. Use when the user wants one page read directly. Prefer web_explore for broader research across multiple sources.',
|
|
140
|
-
parameters: Type.Object({
|
|
141
|
-
url: Type.String({ description: 'HTTP or HTTPS URL to fetch.' })
|
|
142
|
-
}),
|
|
143
|
-
async execute(_toolCallId, params) {
|
|
144
|
-
if (webExploreUsedInCurrentFlow) {
|
|
145
|
-
return guardFetchResponse(params.url);
|
|
146
|
-
}
|
|
147
|
-
const result = await webFetch({ url: params.url });
|
|
148
|
-
return {
|
|
149
|
-
content: [{ type: 'text', text: await renderToolText(pi, 'web_fetch', result) }],
|
|
150
|
-
details: result,
|
|
151
|
-
isError: result.status === 'error'
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
pi.registerTool({
|
|
156
|
-
name: 'web_fetch_headless',
|
|
157
|
-
label: 'Web Fetch Headless',
|
|
158
|
-
description: 'Direct headless page fetch for a specific URL when browser rendering is explicitly needed. Prefer web_explore for research tasks; it decides headless escalation internally.',
|
|
159
|
-
parameters: Type.Object({
|
|
160
|
-
url: Type.String({ description: 'HTTP or HTTPS URL to fetch in headless mode.' })
|
|
161
|
-
}),
|
|
162
|
-
async execute(_toolCallId, params) {
|
|
163
|
-
if (webExploreUsedInCurrentFlow) {
|
|
164
|
-
return guardHeadlessResponse(params.url);
|
|
165
|
-
}
|
|
166
|
-
const result = await webFetchHeadless({ url: params.url });
|
|
167
|
-
return {
|
|
168
|
-
content: [{ type: 'text', text: await renderToolText(pi, 'web_fetch_headless', result) }],
|
|
169
|
-
details: result,
|
|
170
|
-
isError: result.status === 'error'
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
});
|
|
24
|
+
const webExplore = pi.__webExploreTool ??
|
|
25
|
+
createWebExploreTool();
|
|
26
|
+
pi.on('before_agent_start', async (event) => ({
|
|
27
|
+
systemPrompt: `${event.systemPrompt}\n\n` +
|
|
28
|
+
'For web research questions that require finding and comparing sources, use web_explore. ' +
|
|
29
|
+
'web_explore handles search, fetch, source ranking, and headless escalation internally. ' +
|
|
30
|
+
'If more web evidence is needed after web_explore, call web_explore again with a narrower query; do not use shell/network commands such as curl, Invoke-WebRequest, npm view/search/pack, or direct HTTP URLs for web research.'
|
|
31
|
+
}));
|
|
174
32
|
pi.registerTool({
|
|
175
33
|
name: 'web_explore',
|
|
176
34
|
label: 'Web Explore',
|
|
177
|
-
description: 'Research a web question using bounded search/fetch passes, source ranking, and targeted headless escalation.
|
|
35
|
+
description: 'Research a web question using bounded search/fetch passes, source ranking, and targeted headless escalation. Use this for web research, current docs/discussion lookups, and recommendation summaries.',
|
|
178
36
|
parameters: Type.Object({
|
|
179
37
|
query: Type.String({ description: 'Web research question to explore.' })
|
|
180
38
|
}),
|
|
181
39
|
async execute(_toolCallId, params) {
|
|
182
40
|
const result = await webExplore({ query: params.query });
|
|
183
|
-
if (result.status === 'ok') {
|
|
184
|
-
webExploreUsedInCurrentFlow = true;
|
|
185
|
-
}
|
|
186
41
|
return {
|
|
187
42
|
content: [
|
|
188
43
|
{
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
type SupportedPlatform = NodeJS.Platform | string;
|
|
2
|
+
type BrowserName = 'configured' | 'chrome' | 'edge' | 'brave' | 'chromium';
|
|
1
3
|
export type BrowserResolutionResult = {
|
|
2
4
|
ok: true;
|
|
3
5
|
executablePath: string;
|
|
4
|
-
browser:
|
|
6
|
+
browser: BrowserName;
|
|
5
7
|
} | {
|
|
6
8
|
ok: false;
|
|
7
9
|
error: {
|
|
@@ -9,7 +11,10 @@ export type BrowserResolutionResult = {
|
|
|
9
11
|
message: string;
|
|
10
12
|
};
|
|
11
13
|
};
|
|
12
|
-
export declare function resolveBrowserExecutable({ configuredPath, fileExists }: {
|
|
14
|
+
export declare function resolveBrowserExecutable({ configuredPath, platform, env, fileExists }: {
|
|
13
15
|
configuredPath?: string;
|
|
16
|
+
platform?: SupportedPlatform;
|
|
17
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
14
18
|
fileExists?: (path: string) => Promise<boolean>;
|
|
15
19
|
}): Promise<BrowserResolutionResult>;
|
|
20
|
+
export {};
|