@demigodmode/pi-web-agent 1.6.0 → 1.6.2
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/CHANGELOG.md +30 -1
- package/README.md +2 -0
- package/dist/commands/web-agent-config.d.ts +2 -0
- package/dist/commands/web-agent-config.js +61 -33
- package/package.json +9 -2
- package/scripts/patch-jiti-compat.mjs +76 -0
package/CHANGELOG.md
CHANGED
|
@@ -18,9 +18,38 @@ The format is intentionally simple and release-oriented.
|
|
|
18
18
|
### Breaking
|
|
19
19
|
- None.
|
|
20
20
|
|
|
21
|
+
## [1.6.2] - 2026-07-10
|
|
22
|
+
### Added
|
|
23
|
+
- None.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- None.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- Extension load failure on install (`Cannot find module 'punycode/'` / `Set operation called on non-Set object`) caused by pi's extension loader mishandling a couple of patterns in jsdom's dependency tree (tr46, cssstyle). Added a postinstall step that patches the affected files. Fixes #34
|
|
30
|
+
|
|
31
|
+
### Breaking
|
|
32
|
+
- None.
|
|
33
|
+
|
|
34
|
+
## [1.6.1] - 2026-07-05
|
|
35
|
+
### Added
|
|
36
|
+
- None.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
- None.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
- Setting a SearXNG URL from **Settings → Backends** now actually saves, and switches the search provider to `searxng` if it wasn't already selected. Previously the URL was silently discarded unless SearXNG was already the active provider. Reported by @Josephur in #33.
|
|
43
|
+
- Same fix applied on the fetch side: setting a Firecrawl URL now switches the fetch provider to `firecrawl` too, instead of leaving it stuck on `http` until you flip it manually.
|
|
44
|
+
- Editing the SearXNG/Firecrawl base URL no longer opens a separate nested input prompt on top of the settings list (which could leave the session unresponsive to further input). It's now an inline field in the same list, and pressing Esc there only cancels the field instead of closing the whole settings screen and discarding unrelated pending changes.
|
|
45
|
+
- The inline URL field now starts with the cursor at the end of the pre-filled value, so typing right away appends instead of inserting at the front of the string.
|
|
46
|
+
|
|
47
|
+
### Breaking
|
|
48
|
+
- None.
|
|
49
|
+
|
|
21
50
|
## [1.6.0] - 2026-07-05
|
|
22
51
|
### Added
|
|
23
|
-
- You.com is now available as a hosted search backend for `web_explore`, selectable alongside DuckDuckGo, SearXNG, and Brave. Set `YDC_API_KEY` in the environment and choose `youcom` from **Settings → Backends**. You.com handles source discovery only; `web_explore` still fetches pages, ranks evidence, handles caveats, and synthesizes the answer itself.
|
|
52
|
+
- You.com is now available as a hosted search backend for `web_explore`, selectable alongside DuckDuckGo, SearXNG, and Brave. Set `YDC_API_KEY` in the environment and choose `youcom` from **Settings → Backends**. You.com handles source discovery only; `web_explore` still fetches pages, ranks evidence, handles caveats, and synthesizes the answer itself. Contributed by @ydc-oss-bot in #32 - first outside PR this project has had.
|
|
24
53
|
- `/web-agent settings` now treats You.com as a first-class search backend (with optional DuckDuckGo fallback) while keeping API keys out of config files via `YDC_API_KEY`.
|
|
25
54
|
- `/web-agent doctor` now reports You.com setup status, warns when `YDC_API_KEY` is missing, and validates configured You.com access when a key is present.
|
|
26
55
|
|
package/README.md
CHANGED
|
@@ -111,6 +111,8 @@ Example:
|
|
|
111
111
|
|
|
112
112
|
Backend config is also supported. Defaults remain DuckDuckGo search, plain HTTP fetch, and local-browser headless fallback with managed Chromium fallback configured. If you have a Brave Search or You.com API key, either can be selected as a hosted search backend while `web_explore` still handles page reading, ranking, and caveats itself.
|
|
113
113
|
|
|
114
|
+
Search backends: DuckDuckGo (default), SearXNG (self-hosted), Brave Search (hosted), You.com (hosted).
|
|
115
|
+
|
|
114
116
|
Backend settings can be changed from:
|
|
115
117
|
|
|
116
118
|
```text
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type BackendConfig, type BackendConfigOverride } from '../backends/config.js';
|
|
2
2
|
import { type ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
3
|
+
import { type Component } from '@earendil-works/pi-tui';
|
|
3
4
|
import { loadPresentationConfigLayers, type LoadedPresentationConfig } from '../presentation/config-store.js';
|
|
4
5
|
import { type BrowserResolutionResult } from '../fetch/browser-resolution.js';
|
|
5
6
|
import type { PresentationConfig, PresentationConfigOverride, PresentationScope } from '../presentation/types.js';
|
|
@@ -32,6 +33,7 @@ export declare function validateBackendUrl(value: string): {
|
|
|
32
33
|
ok: false;
|
|
33
34
|
message: string;
|
|
34
35
|
};
|
|
36
|
+
export declare function createBackendUrlEditor(theme: any, label: string, placeholderUrl: string, onOpenChange?: (open: boolean) => void): (currentValue: string, done: (selectedValue?: string) => void) => Component;
|
|
35
37
|
export declare function getInheritedConfigForScope(loaded: Awaited<LoadedPresentationConfig>, scope: PresentationScope): PresentationConfig;
|
|
36
38
|
export declare function getScopeDisplayConfig(loaded: Awaited<LoadedPresentationConfig>, scope: PresentationScope): PresentationConfig;
|
|
37
39
|
export declare function getInheritedBackendsForScope(loaded: Awaited<LoadedPresentationConfig>, scope: PresentationScope): BackendConfig;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DEFAULT_BACKEND_CONFIG, mergeBackendConfigLayers, validateBackendConfig } from '../backends/config.js';
|
|
2
2
|
import { checkBackendHealth } from '../backends/doctor.js';
|
|
3
3
|
import { DynamicBorder, getSettingsListTheme } from '@earendil-works/pi-coding-agent';
|
|
4
|
-
import { Container, SelectList, SettingsList, Text } from '@earendil-works/pi-tui';
|
|
4
|
+
import { Container, Input, SelectList, SettingsList, Text } from '@earendil-works/pi-tui';
|
|
5
5
|
import { DEFAULT_PRESENTATION_CONFIG, mergePresentationConfigLayers, resolvePresentationMode } from '../presentation/config.js';
|
|
6
6
|
import { loadPresentationConfigLayers, resetPresentationConfigScope, saveBackendConfigScope, savePresentationConfigScope } from '../presentation/config-store.js';
|
|
7
7
|
import { resolveBrowserExecutable } from '../fetch/browser-resolution.js';
|
|
@@ -112,7 +112,49 @@ function buildPresentationSettingsItems(scope, config) {
|
|
|
112
112
|
}))
|
|
113
113
|
];
|
|
114
114
|
}
|
|
115
|
-
function
|
|
115
|
+
export function createBackendUrlEditor(theme, label, placeholderUrl, onOpenChange) {
|
|
116
|
+
return (currentValue, done) => {
|
|
117
|
+
onOpenChange?.(true);
|
|
118
|
+
const initialValue = currentValue && currentValue !== 'not set' ? currentValue : placeholderUrl;
|
|
119
|
+
const container = new Container();
|
|
120
|
+
const hint = new Text(theme.fg('muted', `${label} · enter to save · empty clears · esc cancels`), 1, 0);
|
|
121
|
+
const input = new Input();
|
|
122
|
+
input.setValue(initialValue);
|
|
123
|
+
input.handleInput('\x1b[F'); // move cursor to end; setValue leaves it at 0
|
|
124
|
+
input.focused = true;
|
|
125
|
+
const errorText = new Text('', 1, 0);
|
|
126
|
+
const finish = (selectedValue) => {
|
|
127
|
+
onOpenChange?.(false);
|
|
128
|
+
done(selectedValue);
|
|
129
|
+
};
|
|
130
|
+
const showError = (message) => {
|
|
131
|
+
errorText.setText(theme.fg('warning', message));
|
|
132
|
+
container.invalidate();
|
|
133
|
+
};
|
|
134
|
+
input.onSubmit = (value) => {
|
|
135
|
+
if (!value.trim()) {
|
|
136
|
+
finish('');
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const validated = validateBackendUrl(value);
|
|
140
|
+
if (!validated.ok) {
|
|
141
|
+
showError(validated.message);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
finish(validated.value);
|
|
145
|
+
};
|
|
146
|
+
input.onEscape = () => finish(undefined);
|
|
147
|
+
container.addChild(hint);
|
|
148
|
+
container.addChild(input);
|
|
149
|
+
container.addChild(errorText);
|
|
150
|
+
return {
|
|
151
|
+
render: (width) => container.render(width),
|
|
152
|
+
invalidate: () => container.invalidate(),
|
|
153
|
+
handleInput: (data) => input.handleInput(data)
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function buildBackendSettingsItems(scope, backends, theme, onUrlEditorOpenChange) {
|
|
116
158
|
return [
|
|
117
159
|
{
|
|
118
160
|
id: 'scope',
|
|
@@ -130,7 +172,7 @@ function buildBackendSettingsItems(scope, backends) {
|
|
|
130
172
|
id: 'backend:search:baseUrl',
|
|
131
173
|
label: 'SearXNG URL',
|
|
132
174
|
currentValue: backends.search.baseUrl ?? 'not set',
|
|
133
|
-
|
|
175
|
+
submenu: createBackendUrlEditor(theme, 'SearXNG base URL', 'http://localhost:8080', onUrlEditorOpenChange)
|
|
134
176
|
},
|
|
135
177
|
{
|
|
136
178
|
id: 'backend:search:fallback',
|
|
@@ -160,7 +202,7 @@ function buildBackendSettingsItems(scope, backends) {
|
|
|
160
202
|
id: 'backend:fetch:baseUrl',
|
|
161
203
|
label: 'Firecrawl URL',
|
|
162
204
|
currentValue: backends.fetch.baseUrl ?? 'not set',
|
|
163
|
-
|
|
205
|
+
submenu: createBackendUrlEditor(theme, 'Firecrawl base URL', 'http://localhost:3002', onUrlEditorOpenChange)
|
|
164
206
|
},
|
|
165
207
|
{
|
|
166
208
|
id: 'backend:fetch:fallback',
|
|
@@ -280,7 +322,8 @@ export function applySettingsValue(state, id, newValue) {
|
|
|
280
322
|
}
|
|
281
323
|
}
|
|
282
324
|
if (id === 'backend:search:baseUrl') {
|
|
283
|
-
if (
|
|
325
|
+
if (newValue.trim()) {
|
|
326
|
+
currentBackends.search.provider = 'searxng';
|
|
284
327
|
currentBackends.search.baseUrl = newValue.trim();
|
|
285
328
|
}
|
|
286
329
|
else {
|
|
@@ -303,6 +346,7 @@ export function applySettingsValue(state, id, newValue) {
|
|
|
303
346
|
}
|
|
304
347
|
if (id === 'backend:fetch:baseUrl') {
|
|
305
348
|
if (newValue.trim()) {
|
|
349
|
+
currentBackends.fetch.provider = 'firecrawl';
|
|
306
350
|
currentBackends.fetch.baseUrl = newValue.trim();
|
|
307
351
|
}
|
|
308
352
|
else {
|
|
@@ -494,43 +538,20 @@ async function openPresentationSettingsUi(ctx, loaded, initialScope) {
|
|
|
494
538
|
});
|
|
495
539
|
}
|
|
496
540
|
async function openBackendSettingsUi(ctx, loaded, initialScope) {
|
|
497
|
-
return ctx.ui.custom((
|
|
541
|
+
return ctx.ui.custom((_tui, theme, _kb, done) => {
|
|
498
542
|
let state = createSettingsDraftState(loaded, initialScope);
|
|
499
543
|
let settingsList;
|
|
544
|
+
let urlEditorOpen = false;
|
|
500
545
|
const container = new Container();
|
|
501
546
|
container.addChild(new Text(theme.fg('accent', theme.bold('pi-web-agent · backends')), 1, 1));
|
|
502
547
|
container.addChild(new Text(theme.fg('muted', 'Ctrl+S save · Ctrl+R reset scope · Esc cancel · API keys stay in env vars'), 1, 2));
|
|
503
|
-
const editUrl = async (id) => {
|
|
504
|
-
const isSearchUrl = id === 'backend:search:baseUrl';
|
|
505
|
-
const label = isSearchUrl ? 'SearXNG base URL' : 'Firecrawl base URL';
|
|
506
|
-
const currentValue = isSearchUrl ? state.backends.search.baseUrl : state.backends.fetch.baseUrl;
|
|
507
|
-
const entered = await ctx.ui.input(label, currentValue ?? (isSearchUrl ? 'http://localhost:8080' : 'http://localhost:3002'));
|
|
508
|
-
if (entered === undefined)
|
|
509
|
-
return;
|
|
510
|
-
if (!entered.trim()) {
|
|
511
|
-
state = applySettingsValue(state, id, '');
|
|
512
|
-
rebuildSettingsList();
|
|
513
|
-
tui.requestRender?.();
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
const validated = validateBackendUrl(entered);
|
|
517
|
-
if (!validated.ok) {
|
|
518
|
-
ctx.ui.notify(validated.message, 'warning');
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
state = applySettingsValue(state, id, validated.value);
|
|
522
|
-
rebuildSettingsList();
|
|
523
|
-
tui.requestRender?.();
|
|
524
|
-
};
|
|
525
548
|
const rebuildSettingsList = () => {
|
|
526
549
|
if (settingsList) {
|
|
527
550
|
container.removeChild(settingsList);
|
|
528
551
|
}
|
|
529
|
-
settingsList = new SettingsList(buildBackendSettingsItems(state.scope, state.backends
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
552
|
+
settingsList = new SettingsList(buildBackendSettingsItems(state.scope, state.backends, theme, (open) => {
|
|
553
|
+
urlEditorOpen = open;
|
|
554
|
+
}), 12, getSettingsListTheme(), (id, newValue) => {
|
|
534
555
|
state = applySettingsValue(state, id, newValue);
|
|
535
556
|
rebuildSettingsList();
|
|
536
557
|
container.invalidate();
|
|
@@ -542,6 +563,13 @@ async function openBackendSettingsUi(ctx, loaded, initialScope) {
|
|
|
542
563
|
render: (width) => container.render(width),
|
|
543
564
|
invalidate: () => container.invalidate(),
|
|
544
565
|
handleInput: (data) => {
|
|
566
|
+
// While the inline URL editor is open, let it (and its own Escape/Enter
|
|
567
|
+
// handling) consume input first — otherwise these shortcuts close the
|
|
568
|
+
// whole settings UI out from under an in-progress edit.
|
|
569
|
+
if (urlEditorOpen) {
|
|
570
|
+
settingsList.handleInput?.(data);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
545
573
|
const shortcut = handleSettingsShortcut(JSON.stringify(data).slice(1, -1));
|
|
546
574
|
if (shortcut?.action === 'cancel') {
|
|
547
575
|
done({ action: 'cancel' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@demigodmode/pi-web-agent",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"description": "Pi package for reliable web access with explicit search, fetch, and headless boundaries.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/extension.js",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"dist",
|
|
16
|
+
"scripts/patch-jiti-compat.mjs",
|
|
16
17
|
"README.md",
|
|
17
18
|
"CHANGELOG.md"
|
|
18
19
|
],
|
|
@@ -21,7 +22,12 @@
|
|
|
21
22
|
"pi",
|
|
22
23
|
"extension",
|
|
23
24
|
"web-search",
|
|
24
|
-
"web-fetch"
|
|
25
|
+
"web-fetch",
|
|
26
|
+
"duckduckgo",
|
|
27
|
+
"searxng",
|
|
28
|
+
"brave-search",
|
|
29
|
+
"you-com",
|
|
30
|
+
"firecrawl"
|
|
25
31
|
],
|
|
26
32
|
"repository": {
|
|
27
33
|
"type": "git",
|
|
@@ -36,6 +42,7 @@
|
|
|
36
42
|
"access": "public"
|
|
37
43
|
},
|
|
38
44
|
"scripts": {
|
|
45
|
+
"postinstall": "node scripts/patch-jiti-compat.mjs",
|
|
39
46
|
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json",
|
|
40
47
|
"build:dev": "tsc -p tsconfig.json",
|
|
41
48
|
"test": "vitest run --coverage",
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Works around two incompatibilities between pi's extension loader (a patched
|
|
3
|
+
// jiti) and jsdom's dependency tree:
|
|
4
|
+
// 1. jiti can't resolve the trailing-slash bare specifier require("punycode/")
|
|
5
|
+
// used by tr46.
|
|
6
|
+
// 2. jiti wraps `module.exports = new Set(...)` (cssstyle) in a Proxy, which
|
|
7
|
+
// breaks native Set methods on the exported value.
|
|
8
|
+
//
|
|
9
|
+
// Runs as a postinstall step so it self-heals after every install, including
|
|
10
|
+
// when this package is installed as a pi extension via `pi install npm:...`.
|
|
11
|
+
// Safe to run multiple times and safe to no-op if the target files or
|
|
12
|
+
// patterns are missing (e.g. a future dependency bump changes the shape).
|
|
13
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { dirname, join } from "node:path";
|
|
16
|
+
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const nodeModules = join(__dirname, "..", "node_modules");
|
|
19
|
+
|
|
20
|
+
function patchTr46() {
|
|
21
|
+
const file = join(nodeModules, "tr46", "index.js");
|
|
22
|
+
if (!existsSync(file)) {
|
|
23
|
+
console.debug("patch-jiti-compat: tr46/index.js not found, skipping");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const contents = readFileSync(file, "utf8");
|
|
27
|
+
if (!contents.includes('require("punycode/")')) {
|
|
28
|
+
console.debug("patch-jiti-compat: tr46/index.js does not match expected pattern, skipping");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
writeFileSync(
|
|
32
|
+
file,
|
|
33
|
+
contents.replaceAll('require("punycode/")', 'require("punycode/punycode.js")'),
|
|
34
|
+
);
|
|
35
|
+
console.log("patch-jiti-compat: patched tr46/index.js");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const SET_SHIM = `
|
|
39
|
+
// pi/jiti workaround: expose bound native Set methods as own properties so a
|
|
40
|
+
// Proxy wrapper around this export does not break Set brand checks.
|
|
41
|
+
for (const k of ["has", "add", "delete", "forEach", "keys", "values", "entries"]) {
|
|
42
|
+
module.exports[k] = Set.prototype[k].bind(module.exports);
|
|
43
|
+
}
|
|
44
|
+
module.exports[Symbol.iterator] = Set.prototype[Symbol.iterator].bind(module.exports);
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
function patchCssstyleSetExports() {
|
|
48
|
+
const files = [
|
|
49
|
+
join(nodeModules, "cssstyle", "lib", "allExtraProperties.js"),
|
|
50
|
+
join(nodeModules, "cssstyle", "lib", "generated", "allProperties.js"),
|
|
51
|
+
join(nodeModules, "cssstyle", "lib", "generated", "implementedProperties.js"),
|
|
52
|
+
];
|
|
53
|
+
for (const file of files) {
|
|
54
|
+
const label = `cssstyle/${file.slice(nodeModules.length + 1)}`;
|
|
55
|
+
if (!existsSync(file)) {
|
|
56
|
+
console.debug(`patch-jiti-compat: ${label} not found, skipping`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const contents = readFileSync(file, "utf8");
|
|
60
|
+
if (contents.includes("pi/jiti workaround")) continue;
|
|
61
|
+
if (!contents.includes("module.exports = new Set(")) {
|
|
62
|
+
console.debug(`patch-jiti-compat: ${label} does not match expected pattern, skipping`);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
writeFileSync(file, contents + SET_SHIM);
|
|
66
|
+
console.log(`patch-jiti-compat: patched ${label}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
patchTr46();
|
|
72
|
+
patchCssstyleSetExports();
|
|
73
|
+
} catch (err) {
|
|
74
|
+
// Never fail the install over a best-effort compat patch.
|
|
75
|
+
console.warn("patch-jiti-compat: skipped, non-fatal error:", err.message);
|
|
76
|
+
}
|