@demigodmode/pi-web-agent 0.2.2 → 0.3.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/README.md +119 -63
- package/dist/commands/web-agent-config.d.ts +23 -0
- package/dist/commands/web-agent-config.js +254 -0
- package/dist/extension.js +113 -4
- package/dist/presentation/config-store.d.ts +23 -0
- package/dist/presentation/config-store.js +64 -0
- package/dist/presentation/config.d.ts +7 -0
- package/dist/presentation/config.js +44 -0
- package/dist/presentation/explore-presentation.d.ts +3 -0
- package/dist/presentation/explore-presentation.js +34 -0
- package/dist/presentation/fetch-presentation.d.ts +5 -0
- package/dist/presentation/fetch-presentation.js +40 -0
- package/dist/presentation/search-presentation.d.ts +3 -0
- package/dist/presentation/search-presentation.js +30 -0
- package/dist/presentation/select-view.d.ts +2 -0
- package/dist/presentation/select-view.js +12 -0
- package/dist/presentation/types.d.ts +50 -0
- package/dist/presentation/types.js +1 -0
- package/dist/search/duckduckgo.d.ts +6 -1
- package/dist/search/duckduckgo.js +11 -1
- package/dist/tools/web-explore.d.ts +6 -16
- package/dist/tools/web-explore.js +12 -10
- package/dist/tools/web-fetch-headless.js +11 -2
- package/dist/tools/web-fetch.js +11 -2
- package/dist/tools/web-search.js +99 -12
- package/dist/types.d.ts +15 -0
- package/package.json +1 -1
- package/dist/scripts/live-web-eval.d.ts +0 -1
- package/dist/scripts/live-web-eval.js +0 -411
- package/dist/src/cache/ttl-cache.d.ts +0 -8
- package/dist/src/cache/ttl-cache.js +0 -21
- package/dist/src/extension.d.ts +0 -2
- package/dist/src/extension.js +0 -155
- package/dist/src/extract/readability.d.ts +0 -8
- package/dist/src/extract/readability.js +0 -93
- package/dist/src/fetch/browser-resolution.d.ts +0 -15
- package/dist/src/fetch/browser-resolution.js +0 -55
- package/dist/src/fetch/headless-fetch.d.ts +0 -18
- package/dist/src/fetch/headless-fetch.js +0 -87
- package/dist/src/fetch/http-fetch.d.ts +0 -4
- package/dist/src/fetch/http-fetch.js +0 -50
- package/dist/src/orchestration/index.d.ts +0 -41
- package/dist/src/orchestration/index.js +0 -9
- package/dist/src/orchestration/research-orchestrator.d.ts +0 -43
- package/dist/src/orchestration/research-orchestrator.js +0 -87
- package/dist/src/orchestration/research-types.d.ts +0 -41
- package/dist/src/orchestration/research-types.js +0 -1
- package/dist/src/orchestration/research-worker.d.ts +0 -16
- package/dist/src/orchestration/research-worker.js +0 -131
- package/dist/src/search/duckduckgo.d.ts +0 -9
- package/dist/src/search/duckduckgo.js +0 -52
- package/dist/src/tools/web-explore.d.ts +0 -44
- package/dist/src/tools/web-explore.js +0 -50
- package/dist/src/tools/web-fetch-headless.d.ts +0 -6
- package/dist/src/tools/web-fetch-headless.js +0 -14
- package/dist/src/tools/web-fetch.d.ts +0 -6
- package/dist/src/tools/web-fetch.js +0 -14
- package/dist/src/tools/web-search.d.ts +0 -10
- package/dist/src/tools/web-search.js +0 -103
- package/dist/src/types.d.ts +0 -48
- package/dist/src/types.js +0 -7
- package/dist/tests/cache/ttl-cache.test.d.ts +0 -1
- package/dist/tests/cache/ttl-cache.test.js +0 -19
- package/dist/tests/contracts.test.d.ts +0 -1
- package/dist/tests/contracts.test.js +0 -65
- package/dist/tests/extension.test.d.ts +0 -1
- package/dist/tests/extension.test.js +0 -123
- package/dist/tests/extract/readability.test.d.ts +0 -1
- package/dist/tests/extract/readability.test.js +0 -79
- package/dist/tests/fetch/browser-resolution.test.d.ts +0 -1
- package/dist/tests/fetch/browser-resolution.test.js +0 -37
- package/dist/tests/fetch/headless-fetch.smoke.test.d.ts +0 -1
- package/dist/tests/fetch/headless-fetch.smoke.test.js +0 -17
- package/dist/tests/fetch/headless-fetch.test.d.ts +0 -1
- package/dist/tests/fetch/headless-fetch.test.js +0 -150
- package/dist/tests/fetch/http-fetch.test.d.ts +0 -1
- package/dist/tests/fetch/http-fetch.test.js +0 -129
- package/dist/tests/orchestration/research-orchestrator.test.d.ts +0 -1
- package/dist/tests/orchestration/research-orchestrator.test.js +0 -298
- package/dist/tests/orchestration/research-worker.test.d.ts +0 -1
- package/dist/tests/orchestration/research-worker.test.js +0 -171
- package/dist/tests/orchestration/research-workflow.test.d.ts +0 -1
- package/dist/tests/orchestration/research-workflow.test.js +0 -119
- package/dist/tests/package-manifest.test.d.ts +0 -1
- package/dist/tests/package-manifest.test.js +0 -29
- package/dist/tests/release-foundation.test.d.ts +0 -1
- package/dist/tests/release-foundation.test.js +0 -16
- package/dist/tests/release-script.test.d.ts +0 -1
- package/dist/tests/release-script.test.js +0 -72
- package/dist/tests/search/duckduckgo.test.d.ts +0 -1
- package/dist/tests/search/duckduckgo.test.js +0 -103
- package/dist/tests/tools/web-explore.test.d.ts +0 -1
- package/dist/tests/tools/web-explore.test.js +0 -163
- package/dist/tests/tools/web-fetch-headless.test.d.ts +0 -1
- package/dist/tests/tools/web-fetch-headless.test.js +0 -31
- package/dist/tests/tools/web-fetch.test.d.ts +0 -1
- package/dist/tests/tools/web-fetch.test.js +0 -27
- package/dist/tests/tools/web-search.test.d.ts +0 -1
- package/dist/tests/tools/web-search.test.js +0 -125
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -13
package/README.md
CHANGED
|
@@ -1,63 +1,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
|
-
The whole point is keeping the boundaries straight:
|
|
10
|
-
|
|
11
|
-
- `web_search` is for discovery
|
|
12
|
-
- `web_fetch` is for plain HTTP reads
|
|
13
|
-
- `web_fetch_headless` is the explicit browser path
|
|
14
|
-
- `web_explore` is the bounded research path
|
|
15
|
-
|
|
16
|
-
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.
|
|
17
|
-
|
|
18
|
-
## Install
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
pi install npm:@demigodmode/pi-web-agent
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Later on, update installed packages with:
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
pi update
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Docs
|
|
31
|
-
|
|
32
|
-
Docs site:
|
|
33
|
-
|
|
34
|
-
- https://demigodmode.github.io/pi-web-agent/
|
|
35
|
-
|
|
36
|
-
Work on the docs locally:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npm run docs:dev
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Build the docs:
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
npm run docs:build
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
##
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
+
The whole point is keeping the boundaries straight:
|
|
10
|
+
|
|
11
|
+
- `web_search` is for discovery
|
|
12
|
+
- `web_fetch` is for plain HTTP reads
|
|
13
|
+
- `web_fetch_headless` is the explicit browser path
|
|
14
|
+
- `web_explore` is the bounded research path
|
|
15
|
+
|
|
16
|
+
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.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pi install npm:@demigodmode/pi-web-agent
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Later on, update installed packages with:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pi update
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Docs
|
|
31
|
+
|
|
32
|
+
Docs site:
|
|
33
|
+
|
|
34
|
+
- https://demigodmode.github.io/pi-web-agent/
|
|
35
|
+
|
|
36
|
+
Work on the docs locally:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm run docs:dev
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Build the docs:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm run docs:build
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Presentation modes
|
|
49
|
+
|
|
50
|
+
`pi-web-agent` renders web tool output in one visible mode at a time:
|
|
51
|
+
|
|
52
|
+
- `compact` — short summary, default everywhere
|
|
53
|
+
- `preview` — slightly richer bounded view
|
|
54
|
+
- `verbose` — fuller bounded view
|
|
55
|
+
|
|
56
|
+
## Settings
|
|
57
|
+
|
|
58
|
+
Primary UI:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
/web-agent settings
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Helper commands:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
/web-agent show
|
|
68
|
+
/web-agent reset project
|
|
69
|
+
/web-agent reset global
|
|
70
|
+
/web-agent mode preview
|
|
71
|
+
/web-agent mode web_search verbose
|
|
72
|
+
/web-agent mode web_search inherit
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Config files:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
Global: ~/.pi/agent/extensions/pi-web-agent/config.json
|
|
79
|
+
Project: .pi/extensions/pi-web-agent/config.json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Precedence:
|
|
83
|
+
|
|
84
|
+
- built-in defaults
|
|
85
|
+
- global config
|
|
86
|
+
- project config
|
|
87
|
+
|
|
88
|
+
Project config overrides global config.
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"presentation": {
|
|
95
|
+
"defaultMode": "compact",
|
|
96
|
+
"tools": {
|
|
97
|
+
"web_search": { "mode": "preview" },
|
|
98
|
+
"web_explore": { "mode": "verbose" }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Local development
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npm install
|
|
108
|
+
npm test
|
|
109
|
+
npm run lint
|
|
110
|
+
npm run build
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
For local Pi work, this repo includes `.pi/extensions/pi-web-agent.ts`.
|
|
114
|
+
|
|
115
|
+
If Pi is already running, use `/reload` after changes.
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
AGPL-3.0-only. See `LICENSE`.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ExtensionAPI } from '@mariozechner/pi-coding-agent';
|
|
2
|
+
import { loadPresentationConfigLayers, type LoadedPresentationConfig } from '../presentation/config-store.js';
|
|
3
|
+
import type { PresentationConfig, PresentationConfigOverride, PresentationScope } from '../presentation/types.js';
|
|
4
|
+
type CommandDeps = {
|
|
5
|
+
load?: () => ReturnType<typeof loadPresentationConfigLayers>;
|
|
6
|
+
save?: (scope: PresentationScope, config: PresentationConfigOverride) => Promise<void>;
|
|
7
|
+
reset?: (scope: PresentationScope) => Promise<void>;
|
|
8
|
+
};
|
|
9
|
+
export type SettingsDraftState = {
|
|
10
|
+
scope: PresentationScope;
|
|
11
|
+
drafts: Record<PresentationScope, PresentationConfig>;
|
|
12
|
+
config: PresentationConfig;
|
|
13
|
+
};
|
|
14
|
+
export declare function getInheritedConfigForScope(loaded: Awaited<LoadedPresentationConfig>, scope: PresentationScope): PresentationConfig;
|
|
15
|
+
export declare function getScopeDisplayConfig(loaded: Awaited<LoadedPresentationConfig>, scope: PresentationScope): PresentationConfig;
|
|
16
|
+
export declare function createSettingsDraftState(loaded: Awaited<LoadedPresentationConfig>, initialScope: PresentationScope): SettingsDraftState;
|
|
17
|
+
export declare function applySettingsValue(state: SettingsDraftState, id: string, newValue: string): SettingsDraftState;
|
|
18
|
+
export declare function collapsePresentationConfigToOverride(config: PresentationConfig, inheritedConfig: PresentationConfig): PresentationConfigOverride;
|
|
19
|
+
export declare function handleSettingsShortcut(data: string): {
|
|
20
|
+
action: 'cancel' | 'reset' | 'save';
|
|
21
|
+
} | undefined;
|
|
22
|
+
export declare function registerWebAgentConfigCommands(pi: ExtensionAPI, deps?: CommandDeps): void;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { getSettingsListTheme } from '@mariozechner/pi-coding-agent';
|
|
2
|
+
import { Container, SettingsList, Text } from '@mariozechner/pi-tui';
|
|
3
|
+
import { DEFAULT_PRESENTATION_CONFIG, mergePresentationConfigLayers, resolvePresentationMode } from '../presentation/config.js';
|
|
4
|
+
import { loadPresentationConfigLayers, resetPresentationConfigScope, savePresentationConfigScope } from '../presentation/config-store.js';
|
|
5
|
+
const PRESENTATION_TOOL_NAMES = [
|
|
6
|
+
'web_search',
|
|
7
|
+
'web_fetch',
|
|
8
|
+
'web_fetch_headless',
|
|
9
|
+
'web_explore'
|
|
10
|
+
];
|
|
11
|
+
function parseScopeToken(token) {
|
|
12
|
+
return token === 'global' || token === 'project' ? token : undefined;
|
|
13
|
+
}
|
|
14
|
+
function clonePresentationConfig(config) {
|
|
15
|
+
return {
|
|
16
|
+
defaultMode: config.defaultMode,
|
|
17
|
+
tools: { ...config.tools }
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function formatConfigSummary(config) {
|
|
21
|
+
const lines = [`defaultMode: ${config.defaultMode}`];
|
|
22
|
+
for (const toolName of PRESENTATION_TOOL_NAMES) {
|
|
23
|
+
lines.push(`${toolName}: ${config.tools[toolName]?.mode ?? 'inherit'}`);
|
|
24
|
+
}
|
|
25
|
+
return lines.join('\n');
|
|
26
|
+
}
|
|
27
|
+
function buildSettingsItems(scope, config) {
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
id: 'scope',
|
|
31
|
+
label: 'Write scope',
|
|
32
|
+
currentValue: scope,
|
|
33
|
+
values: ['project', 'global']
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'defaultMode',
|
|
37
|
+
label: 'Default mode',
|
|
38
|
+
currentValue: config.defaultMode,
|
|
39
|
+
values: ['compact', 'preview', 'verbose']
|
|
40
|
+
},
|
|
41
|
+
...PRESENTATION_TOOL_NAMES.map((toolName) => ({
|
|
42
|
+
id: `tool:${toolName}`,
|
|
43
|
+
label: toolName,
|
|
44
|
+
currentValue: config.tools[toolName]?.mode ?? 'inherit',
|
|
45
|
+
values: ['inherit', 'compact', 'preview', 'verbose']
|
|
46
|
+
}))
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
function isToolName(value) {
|
|
50
|
+
return PRESENTATION_TOOL_NAMES.includes(value);
|
|
51
|
+
}
|
|
52
|
+
function isModeOrInherit(value) {
|
|
53
|
+
return ['inherit', 'compact', 'preview', 'verbose'].includes(value);
|
|
54
|
+
}
|
|
55
|
+
export function getInheritedConfigForScope(loaded, scope) {
|
|
56
|
+
if (scope === 'global') {
|
|
57
|
+
return DEFAULT_PRESENTATION_CONFIG;
|
|
58
|
+
}
|
|
59
|
+
return mergePresentationConfigLayers(DEFAULT_PRESENTATION_CONFIG, loaded.global.rawConfig);
|
|
60
|
+
}
|
|
61
|
+
export function getScopeDisplayConfig(loaded, scope) {
|
|
62
|
+
if (scope === 'global') {
|
|
63
|
+
return mergePresentationConfigLayers(DEFAULT_PRESENTATION_CONFIG, loaded.global.rawConfig);
|
|
64
|
+
}
|
|
65
|
+
return mergePresentationConfigLayers(DEFAULT_PRESENTATION_CONFIG, loaded.global.rawConfig, loaded.project.rawConfig);
|
|
66
|
+
}
|
|
67
|
+
export function createSettingsDraftState(loaded, initialScope) {
|
|
68
|
+
const drafts = {
|
|
69
|
+
global: getScopeDisplayConfig(loaded, 'global'),
|
|
70
|
+
project: getScopeDisplayConfig(loaded, 'project')
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
scope: initialScope,
|
|
74
|
+
drafts,
|
|
75
|
+
config: clonePresentationConfig(drafts[initialScope])
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export function applySettingsValue(state, id, newValue) {
|
|
79
|
+
const nextDrafts = {
|
|
80
|
+
global: clonePresentationConfig(state.drafts.global),
|
|
81
|
+
project: clonePresentationConfig(state.drafts.project)
|
|
82
|
+
};
|
|
83
|
+
let nextScope = state.scope;
|
|
84
|
+
if (id === 'scope' && (newValue === 'project' || newValue === 'global')) {
|
|
85
|
+
nextScope = newValue;
|
|
86
|
+
return {
|
|
87
|
+
scope: nextScope,
|
|
88
|
+
drafts: nextDrafts,
|
|
89
|
+
config: clonePresentationConfig(nextDrafts[nextScope])
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const currentDraft = clonePresentationConfig(nextDrafts[nextScope]);
|
|
93
|
+
if (id === 'defaultMode' && (newValue === 'compact' || newValue === 'preview' || newValue === 'verbose')) {
|
|
94
|
+
currentDraft.defaultMode = newValue;
|
|
95
|
+
}
|
|
96
|
+
if (id.startsWith('tool:')) {
|
|
97
|
+
const toolName = id.slice('tool:'.length);
|
|
98
|
+
const nextTools = { ...currentDraft.tools };
|
|
99
|
+
if (newValue === 'inherit') {
|
|
100
|
+
delete nextTools[toolName];
|
|
101
|
+
}
|
|
102
|
+
else if (newValue === 'compact' ||
|
|
103
|
+
newValue === 'preview' ||
|
|
104
|
+
newValue === 'verbose') {
|
|
105
|
+
nextTools[toolName] = { mode: newValue };
|
|
106
|
+
}
|
|
107
|
+
currentDraft.tools = nextTools;
|
|
108
|
+
}
|
|
109
|
+
nextDrafts[nextScope] = currentDraft;
|
|
110
|
+
return {
|
|
111
|
+
scope: nextScope,
|
|
112
|
+
drafts: nextDrafts,
|
|
113
|
+
config: clonePresentationConfig(nextDrafts[nextScope])
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export function collapsePresentationConfigToOverride(config, inheritedConfig) {
|
|
117
|
+
const tools = Object.fromEntries(PRESENTATION_TOOL_NAMES.flatMap((toolName) => {
|
|
118
|
+
const configuredMode = config.tools[toolName]?.mode;
|
|
119
|
+
if (!configuredMode) {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
const inheritedMode = resolvePresentationMode(toolName, inheritedConfig);
|
|
123
|
+
if (configuredMode === inheritedMode) {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
return [[toolName, { mode: configuredMode }]];
|
|
127
|
+
}));
|
|
128
|
+
return {
|
|
129
|
+
defaultMode: config.defaultMode === inheritedConfig.defaultMode ? undefined : config.defaultMode,
|
|
130
|
+
tools
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export function handleSettingsShortcut(data) {
|
|
134
|
+
if (data === '\u001b') {
|
|
135
|
+
return { action: 'cancel' };
|
|
136
|
+
}
|
|
137
|
+
if (data === '\u0012') {
|
|
138
|
+
return { action: 'reset' };
|
|
139
|
+
}
|
|
140
|
+
if (data === '\u0013') {
|
|
141
|
+
return { action: 'save' };
|
|
142
|
+
}
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
async function openSettingsUi(ctx, loaded, initialScope) {
|
|
146
|
+
return ctx.ui.custom((_tui, theme, _kb, done) => {
|
|
147
|
+
let state = createSettingsDraftState(loaded, initialScope);
|
|
148
|
+
let settingsList;
|
|
149
|
+
const container = new Container();
|
|
150
|
+
container.addChild(new Text(theme.fg('accent', theme.bold('pi-web-agent settings')), 1, 1));
|
|
151
|
+
container.addChild(new Text(theme.fg('muted', 'Ctrl+S save · Ctrl+R reset scope · Esc cancel'), 1, 2));
|
|
152
|
+
const rebuildSettingsList = () => {
|
|
153
|
+
if (settingsList) {
|
|
154
|
+
container.removeChild(settingsList);
|
|
155
|
+
}
|
|
156
|
+
settingsList = new SettingsList(buildSettingsItems(state.scope, state.config), Math.min(PRESENTATION_TOOL_NAMES.length + 8, 18), getSettingsListTheme(), (id, newValue) => {
|
|
157
|
+
state = applySettingsValue(state, id, newValue);
|
|
158
|
+
rebuildSettingsList();
|
|
159
|
+
container.invalidate();
|
|
160
|
+
}, () => done({ action: 'save', scope: state.scope, config: state.config }), { enableSearch: true });
|
|
161
|
+
container.addChild(settingsList);
|
|
162
|
+
};
|
|
163
|
+
rebuildSettingsList();
|
|
164
|
+
return {
|
|
165
|
+
render: (width) => container.render(width),
|
|
166
|
+
invalidate: () => container.invalidate(),
|
|
167
|
+
handleInput: (data) => {
|
|
168
|
+
const shortcut = handleSettingsShortcut(JSON.stringify(data).slice(1, -1));
|
|
169
|
+
if (shortcut?.action === 'cancel') {
|
|
170
|
+
done({ action: 'cancel' });
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (shortcut?.action === 'reset') {
|
|
174
|
+
done({ action: 'reset', scope: state.scope });
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (shortcut?.action === 'save') {
|
|
178
|
+
done({ action: 'save', scope: state.scope, config: state.config });
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
settingsList.handleInput?.(data);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
export function registerWebAgentConfigCommands(pi, deps = {}) {
|
|
187
|
+
const load = deps.load ?? (() => loadPresentationConfigLayers());
|
|
188
|
+
const save = deps.save ?? ((scope, config) => savePresentationConfigScope({}, scope, config));
|
|
189
|
+
const reset = deps.reset ?? ((scope) => resetPresentationConfigScope({}, scope));
|
|
190
|
+
pi.registerCommand('web-agent', {
|
|
191
|
+
description: 'Open settings or manage pi-web-agent presentation config',
|
|
192
|
+
handler: async (args, ctx) => {
|
|
193
|
+
const [action, maybeScope] = (args ?? '').trim().split(/\s+/).filter(Boolean);
|
|
194
|
+
if (action === 'show') {
|
|
195
|
+
const loaded = await load();
|
|
196
|
+
ctx.ui.notify([
|
|
197
|
+
formatConfigSummary(loaded.effectiveConfig),
|
|
198
|
+
`global: ${loaded.global.path}${loaded.global.exists ? '' : ' (missing)'}`,
|
|
199
|
+
`project: ${loaded.project.path}${loaded.project.exists ? '' : ' (missing)'}`
|
|
200
|
+
].join('\n'), 'info');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (action === 'reset') {
|
|
204
|
+
const scope = parseScopeToken(maybeScope) ?? 'project';
|
|
205
|
+
await reset(scope);
|
|
206
|
+
ctx.ui.notify(`Reset ${scope} config`, 'info');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (action === 'mode') {
|
|
210
|
+
const [, first, second] = (args ?? '').trim().split(/\s+/).filter(Boolean);
|
|
211
|
+
const loaded = await load();
|
|
212
|
+
const scope = 'project';
|
|
213
|
+
const baseConfig = getScopeDisplayConfig(loaded, scope);
|
|
214
|
+
const inheritedConfig = getInheritedConfigForScope(loaded, scope);
|
|
215
|
+
if (first && isModeOrInherit(first) && first !== 'inherit') {
|
|
216
|
+
await save(scope, collapsePresentationConfigToOverride({ ...baseConfig, defaultMode: first }, inheritedConfig));
|
|
217
|
+
ctx.ui.notify(`Saved project default mode = ${first}`, 'info');
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (first && second && isToolName(first) && isModeOrInherit(second)) {
|
|
221
|
+
const nextTools = { ...baseConfig.tools };
|
|
222
|
+
if (second === 'inherit') {
|
|
223
|
+
delete nextTools[first];
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
nextTools[first] = { mode: second };
|
|
227
|
+
}
|
|
228
|
+
await save(scope, collapsePresentationConfigToOverride({ ...baseConfig, tools: nextTools }, inheritedConfig));
|
|
229
|
+
ctx.ui.notify(`Saved project ${first} = ${second}`, 'info');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
ctx.ui.notify('Usage: /web-agent mode <compact|preview|verbose> or /web-agent mode <tool> <inherit|compact|preview|verbose>', 'info');
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (!action || action === 'settings') {
|
|
236
|
+
const loaded = await load();
|
|
237
|
+
const initialScope = 'project';
|
|
238
|
+
const result = await openSettingsUi(ctx, loaded, initialScope);
|
|
239
|
+
if (!result || result.action === 'cancel') {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (result.action === 'reset') {
|
|
243
|
+
await reset(result.scope);
|
|
244
|
+
ctx.ui.notify(`Reset ${result.scope} config`, 'info');
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
await save(result.scope, collapsePresentationConfigToOverride(result.config, getInheritedConfigForScope(loaded, result.scope)));
|
|
248
|
+
ctx.ui.notify(`Saved ${result.scope} config`, 'info');
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
ctx.ui.notify('Use /web-agent, /web-agent show, /web-agent reset project, or /web-agent settings', 'info');
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
package/dist/extension.js
CHANGED
|
@@ -1,14 +1,111 @@
|
|
|
1
1
|
import { Type } from '@sinclair/typebox';
|
|
2
|
+
import { registerWebAgentConfigCommands } from './commands/web-agent-config.js';
|
|
3
|
+
import { DEFAULT_PRESENTATION_CONFIG, resolvePresentationMode } from './presentation/config.js';
|
|
4
|
+
import { loadPresentationConfigLayers } from './presentation/config-store.js';
|
|
5
|
+
import { selectPresentationView } from './presentation/select-view.js';
|
|
2
6
|
import { createWebExploreTool } from './tools/web-explore.js';
|
|
3
7
|
import { createWebFetchTool } from './tools/web-fetch.js';
|
|
4
8
|
import { createWebFetchHeadlessTool } from './tools/web-fetch-headless.js';
|
|
5
9
|
import { createWebSearchTool } from './tools/web-search.js';
|
|
10
|
+
async function getEffectivePresentationConfig(pi) {
|
|
11
|
+
const store = pi.__presentationConfigStore;
|
|
12
|
+
try {
|
|
13
|
+
const loaded = await (store?.load?.() ?? loadPresentationConfigLayers());
|
|
14
|
+
return loaded.effectiveConfig;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return DEFAULT_PRESENTATION_CONFIG;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function renderToolText(pi, toolName, details) {
|
|
21
|
+
const config = await getEffectivePresentationConfig(pi);
|
|
22
|
+
const mode = resolvePresentationMode(toolName, config);
|
|
23
|
+
return selectPresentationView(details.presentation, mode) ?? JSON.stringify(details, null, 2);
|
|
24
|
+
}
|
|
6
25
|
export default function extension(pi) {
|
|
26
|
+
registerWebAgentConfigCommands(pi);
|
|
7
27
|
const webSearch = createWebSearchTool();
|
|
8
28
|
const webFetch = createWebFetchTool();
|
|
9
29
|
const webFetchHeadless = createWebFetchHeadlessTool();
|
|
10
30
|
const webExplore = createWebExploreTool();
|
|
31
|
+
let webExploreUsedInCurrentFlow = false;
|
|
32
|
+
const postWebExploreGuardError = {
|
|
33
|
+
code: 'POST_WEB_EXPLORE_GUARD',
|
|
34
|
+
message: 'web_explore already ran for this research task. Only use low-level web tools if there is a specific unresolved gap.'
|
|
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
|
+
}
|
|
11
107
|
pi.on('before_agent_start', async (event) => {
|
|
108
|
+
webExploreUsedInCurrentFlow = false;
|
|
12
109
|
return {
|
|
13
110
|
systemPrompt: `${event.systemPrompt}\n\n` +
|
|
14
111
|
'For web research questions that require finding and comparing multiple sources, prefer web_explore. ' +
|
|
@@ -25,9 +122,12 @@ export default function extension(pi) {
|
|
|
25
122
|
query: Type.String({ description: 'Search query.' })
|
|
26
123
|
}),
|
|
27
124
|
async execute(_toolCallId, params) {
|
|
125
|
+
if (webExploreUsedInCurrentFlow) {
|
|
126
|
+
return guardSearchResponse();
|
|
127
|
+
}
|
|
28
128
|
const result = await webSearch({ query: params.query });
|
|
29
129
|
return {
|
|
30
|
-
content: [{ type: 'text', text:
|
|
130
|
+
content: [{ type: 'text', text: await renderToolText(pi, 'web_search', result) }],
|
|
31
131
|
details: result,
|
|
32
132
|
isError: result.status === 'error'
|
|
33
133
|
};
|
|
@@ -41,9 +141,12 @@ export default function extension(pi) {
|
|
|
41
141
|
url: Type.String({ description: 'HTTP or HTTPS URL to fetch.' })
|
|
42
142
|
}),
|
|
43
143
|
async execute(_toolCallId, params) {
|
|
144
|
+
if (webExploreUsedInCurrentFlow) {
|
|
145
|
+
return guardFetchResponse(params.url);
|
|
146
|
+
}
|
|
44
147
|
const result = await webFetch({ url: params.url });
|
|
45
148
|
return {
|
|
46
|
-
content: [{ type: 'text', text:
|
|
149
|
+
content: [{ type: 'text', text: await renderToolText(pi, 'web_fetch', result) }],
|
|
47
150
|
details: result,
|
|
48
151
|
isError: result.status === 'error'
|
|
49
152
|
};
|
|
@@ -57,9 +160,12 @@ export default function extension(pi) {
|
|
|
57
160
|
url: Type.String({ description: 'HTTP or HTTPS URL to fetch in headless mode.' })
|
|
58
161
|
}),
|
|
59
162
|
async execute(_toolCallId, params) {
|
|
163
|
+
if (webExploreUsedInCurrentFlow) {
|
|
164
|
+
return guardHeadlessResponse(params.url);
|
|
165
|
+
}
|
|
60
166
|
const result = await webFetchHeadless({ url: params.url });
|
|
61
167
|
return {
|
|
62
|
-
content: [{ type: 'text', text:
|
|
168
|
+
content: [{ type: 'text', text: await renderToolText(pi, 'web_fetch_headless', result) }],
|
|
63
169
|
details: result,
|
|
64
170
|
isError: result.status === 'error'
|
|
65
171
|
};
|
|
@@ -74,11 +180,14 @@ export default function extension(pi) {
|
|
|
74
180
|
}),
|
|
75
181
|
async execute(_toolCallId, params) {
|
|
76
182
|
const result = await webExplore({ query: params.query });
|
|
183
|
+
if (result.status === 'ok') {
|
|
184
|
+
webExploreUsedInCurrentFlow = true;
|
|
185
|
+
}
|
|
77
186
|
return {
|
|
78
187
|
content: [
|
|
79
188
|
{
|
|
80
189
|
type: 'text',
|
|
81
|
-
text:
|
|
190
|
+
text: await renderToolText(pi, 'web_explore', result)
|
|
82
191
|
}
|
|
83
192
|
],
|
|
84
193
|
details: result,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PresentationConfig, PresentationConfigOverride, PresentationScope } from './types.js';
|
|
2
|
+
export type PresentationConfigStoreOptions = {
|
|
3
|
+
homeDir?: string;
|
|
4
|
+
projectDir?: string;
|
|
5
|
+
};
|
|
6
|
+
export type PresentationConfigLayer = {
|
|
7
|
+
path: string;
|
|
8
|
+
exists: boolean;
|
|
9
|
+
rawConfig?: PresentationConfigOverride;
|
|
10
|
+
error?: string;
|
|
11
|
+
};
|
|
12
|
+
export type LoadedPresentationConfig = {
|
|
13
|
+
global: PresentationConfigLayer;
|
|
14
|
+
project: PresentationConfigLayer;
|
|
15
|
+
effectiveConfig: PresentationConfig;
|
|
16
|
+
};
|
|
17
|
+
export declare function getPresentationConfigPaths(options?: PresentationConfigStoreOptions): {
|
|
18
|
+
globalPath: string;
|
|
19
|
+
projectPath: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function loadPresentationConfigLayers(options?: PresentationConfigStoreOptions): Promise<LoadedPresentationConfig>;
|
|
22
|
+
export declare function savePresentationConfigScope(options: PresentationConfigStoreOptions, scope: PresentationScope, config: PresentationConfigOverride): Promise<void>;
|
|
23
|
+
export declare function resetPresentationConfigScope(options: PresentationConfigStoreOptions, scope: PresentationScope): Promise<void>;
|