@oh-my-pi/pi-coding-agent 14.4.3 → 14.5.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/package.json +9 -8
- package/src/config/settings-schema.ts +42 -0
- package/src/edit/modes/atom.ts +15 -4
- package/src/modes/components/session-observer-overlay.ts +635 -295
- package/src/modes/components/settings-defs.ts +1 -0
- package/src/modes/controllers/command-controller.ts +16 -5
- package/src/modes/controllers/selector-controller.ts +32 -19
- package/src/modes/interactive-mode.ts +10 -1
- package/src/modes/types.ts +1 -0
- package/src/session/agent-session.ts +9 -1
- package/src/session/session-manager.ts +13 -0
- package/src/session/session-storage.ts +4 -0
- package/src/slash-commands/builtin-registry.ts +8 -0
- package/src/tools/browser.ts +157 -35
- package/src/web/search/index.ts +2 -2
- package/src/web/search/provider.ts +3 -0
- package/src/web/search/providers/searxng.ts +238 -0
- package/src/web/search/types.ts +3 -1
|
@@ -348,6 +348,7 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
348
348
|
{ value: "kagi", label: "Kagi", description: "Requires KAGI_API_KEY and Kagi Search API beta access" },
|
|
349
349
|
{ value: "synthetic", label: "Synthetic", description: "Requires SYNTHETIC_API_KEY" },
|
|
350
350
|
{ value: "parallel", label: "Parallel", description: "Requires PARALLEL_API_KEY" },
|
|
351
|
+
{ value: "searxng", label: "SearXNG", description: "Self-hosted metasearch; set searxng.endpoint" },
|
|
351
352
|
],
|
|
352
353
|
"providers.image": [
|
|
353
354
|
{
|
|
@@ -28,6 +28,7 @@ import { buildHotkeysMarkdown } from "../../modes/utils/hotkeys-markdown";
|
|
|
28
28
|
import { buildToolsMarkdown } from "../../modes/utils/tools-markdown";
|
|
29
29
|
import type { AsyncJobSnapshotItem } from "../../session/agent-session";
|
|
30
30
|
import type { AuthStorage } from "../../session/auth-storage";
|
|
31
|
+
import type { NewSessionOptions } from "../../session/session-manager";
|
|
31
32
|
import { outputMeta } from "../../tools/output-meta";
|
|
32
33
|
import { resolveToCwd, stripOuterDoubleQuotes } from "../../tools/path-utils";
|
|
33
34
|
import { replaceTabs } from "../../tools/render-utils";
|
|
@@ -573,7 +574,7 @@ export class CommandController {
|
|
|
573
574
|
this.ctx.showError("Usage: /memory <view|clear|reset|enqueue|rebuild>");
|
|
574
575
|
}
|
|
575
576
|
|
|
576
|
-
async
|
|
577
|
+
async #runNewSessionFlow(options?: NewSessionOptions, label: string = "New session started"): Promise<void> {
|
|
577
578
|
if (this.ctx.loadingAnimation) {
|
|
578
579
|
this.ctx.loadingAnimation.stop();
|
|
579
580
|
this.ctx.loadingAnimation = undefined;
|
|
@@ -586,7 +587,7 @@ export class CommandController {
|
|
|
586
587
|
await Bun.sleep(10);
|
|
587
588
|
}
|
|
588
589
|
}
|
|
589
|
-
await this.ctx.session.newSession();
|
|
590
|
+
if (!(await this.ctx.session.newSession(options))) return;
|
|
590
591
|
this.ctx.resetObserverRegistry();
|
|
591
592
|
setSessionTerminalTitle(
|
|
592
593
|
this.ctx.sessionManager.getSessionName(),
|
|
@@ -608,13 +609,23 @@ export class CommandController {
|
|
|
608
609
|
this.ctx.pendingTools.clear();
|
|
609
610
|
|
|
610
611
|
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
611
|
-
this.ctx.chatContainer.addChild(
|
|
612
|
-
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 1),
|
|
613
|
-
);
|
|
612
|
+
this.ctx.chatContainer.addChild(new Text(`${theme.fg("accent", `${theme.status.success} ${label}`)}`, 1, 1));
|
|
614
613
|
await this.ctx.reloadTodos();
|
|
615
614
|
this.ctx.ui.requestRender();
|
|
616
615
|
}
|
|
617
616
|
|
|
617
|
+
async handleClearCommand(): Promise<void> {
|
|
618
|
+
await this.#runNewSessionFlow();
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
async handleDropCommand(): Promise<void> {
|
|
622
|
+
if (!this.ctx.sessionManager.getSessionFile()) {
|
|
623
|
+
this.ctx.showError("Nothing to drop (in-memory session)");
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
await this.#runNewSessionFlow({ drop: true }, "Session dropped");
|
|
627
|
+
}
|
|
628
|
+
|
|
618
629
|
async handleForkCommand(): Promise<void> {
|
|
619
630
|
if (this.ctx.session.isStreaming) {
|
|
620
631
|
this.ctx.showWarning("Wait for the current response to finish or abort it before forking.");
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import * as os from "node:os";
|
|
2
|
+
import * as path from "node:path";
|
|
1
3
|
import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
4
|
import { getOAuthProviders, type OAuthProvider } from "@oh-my-pi/pi-ai";
|
|
3
|
-
import type { Component } from "@oh-my-pi/pi-tui";
|
|
5
|
+
import type { Component, OverlayHandle } from "@oh-my-pi/pi-tui";
|
|
4
6
|
import { Input, Loader, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
5
|
-
import { getAgentDbPath, getProjectDir } from "@oh-my-pi/pi-utils";
|
|
7
|
+
import { getAgentDbPath, getConfigDirName, getProjectDir } from "@oh-my-pi/pi-utils";
|
|
8
|
+
import { invalidate as invalidateFsCache } from "../../capability/fs";
|
|
6
9
|
import { getRoleInfo } from "../../config/model-registry";
|
|
7
10
|
import { formatModelSelectorValue } from "../../config/model-resolver";
|
|
8
11
|
import { settings } from "../../config/settings";
|
|
9
12
|
import { DebugSelectorComponent } from "../../debug";
|
|
10
13
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
11
|
-
import {
|
|
14
|
+
import { clearClaudePluginRootsCache, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
|
|
12
15
|
import {
|
|
13
16
|
getInstalledPluginsRegistryPath,
|
|
14
17
|
getMarketplacesCacheDir,
|
|
@@ -440,7 +443,13 @@ export class SelectorController {
|
|
|
440
443
|
projectInstalledRegistryPath: (await resolveActiveProjectRegistryPath(getProjectDir())) ?? undefined,
|
|
441
444
|
marketplacesCacheDir: getMarketplacesCacheDir(),
|
|
442
445
|
pluginsCacheDir: getPluginsCacheDir(),
|
|
443
|
-
clearPluginRootsCache:
|
|
446
|
+
clearPluginRootsCache: (extraPaths?: readonly string[]) => {
|
|
447
|
+
const home = os.homedir();
|
|
448
|
+
invalidateFsCache(path.join(home, ".claude", "plugins", "installed_plugins.json"));
|
|
449
|
+
invalidateFsCache(path.join(home, getConfigDirName(), "plugins", "installed_plugins.json"));
|
|
450
|
+
for (const p of extraPaths ?? []) invalidateFsCache(p);
|
|
451
|
+
clearClaudePluginRootsCache();
|
|
452
|
+
},
|
|
444
453
|
});
|
|
445
454
|
|
|
446
455
|
const [marketplaces, installed] = await Promise.all([mgr.listMarketplaces(), mgr.listInstalledPlugins()]);
|
|
@@ -969,25 +978,29 @@ export class SelectorController {
|
|
|
969
978
|
|
|
970
979
|
showSessionObserver(registry: SessionObserverRegistry): void {
|
|
971
980
|
const observeKeys = this.ctx.keybindings.getKeys("app.session.observe");
|
|
981
|
+
let cleanup: (() => void) | undefined;
|
|
982
|
+
let overlayHandle: OverlayHandle | undefined;
|
|
972
983
|
|
|
973
|
-
|
|
974
|
-
|
|
984
|
+
const done = () => {
|
|
985
|
+
cleanup?.();
|
|
986
|
+
overlayHandle?.hide();
|
|
987
|
+
this.ctx.ui.requestRender();
|
|
988
|
+
};
|
|
975
989
|
|
|
976
|
-
|
|
977
|
-
registry,
|
|
978
|
-
() => {
|
|
979
|
-
cleanup?.();
|
|
980
|
-
done();
|
|
981
|
-
},
|
|
982
|
-
observeKeys,
|
|
983
|
-
);
|
|
990
|
+
const selector = new SessionObserverOverlayComponent(registry, done, observeKeys);
|
|
984
991
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
992
|
+
cleanup = registry.onChange(() => {
|
|
993
|
+
selector.refreshFromRegistry();
|
|
994
|
+
this.ctx.ui.requestRender();
|
|
995
|
+
});
|
|
989
996
|
|
|
990
|
-
|
|
997
|
+
overlayHandle = this.ctx.ui.showOverlay(selector, {
|
|
998
|
+
anchor: "bottom-center",
|
|
999
|
+
width: "100%",
|
|
1000
|
+
maxHeight: "100%",
|
|
1001
|
+
margin: 0,
|
|
991
1002
|
});
|
|
1003
|
+
this.ctx.ui.setFocus(selector);
|
|
1004
|
+
this.ctx.ui.requestRender();
|
|
992
1005
|
}
|
|
993
1006
|
}
|
|
@@ -1324,13 +1324,22 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1324
1324
|
this.#commandController.handleToolsCommand();
|
|
1325
1325
|
}
|
|
1326
1326
|
|
|
1327
|
-
|
|
1327
|
+
#prepareSessionSwitch(): void {
|
|
1328
1328
|
this.#btwController.dispose();
|
|
1329
1329
|
this.#extensionUiController.clearExtensionTerminalInputListeners();
|
|
1330
1330
|
this.#planReviewContainer = undefined;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
handleClearCommand(): Promise<void> {
|
|
1334
|
+
this.#prepareSessionSwitch();
|
|
1331
1335
|
return this.#commandController.handleClearCommand();
|
|
1332
1336
|
}
|
|
1333
1337
|
|
|
1338
|
+
handleDropCommand(): Promise<void> {
|
|
1339
|
+
this.#prepareSessionSwitch();
|
|
1340
|
+
return this.#commandController.handleDropCommand();
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1334
1343
|
handleForkCommand(): Promise<void> {
|
|
1335
1344
|
this.#btwController.dispose();
|
|
1336
1345
|
return this.#commandController.handleForkCommand();
|
package/src/modes/types.ts
CHANGED
|
@@ -181,6 +181,7 @@ export interface InteractiveModeContext {
|
|
|
181
181
|
handleDumpCommand(): void;
|
|
182
182
|
handleDebugTranscriptCommand(): Promise<void>;
|
|
183
183
|
handleClearCommand(): Promise<void>;
|
|
184
|
+
handleDropCommand(): Promise<void>;
|
|
184
185
|
handleForkCommand(): Promise<void>;
|
|
185
186
|
handleBashCommand(command: string, excludeFromContext?: boolean): Promise<void>;
|
|
186
187
|
handlePythonCommand(code: string, excludeFromContext?: boolean): Promise<void>;
|
|
@@ -3373,7 +3373,15 @@ export class AgentSession {
|
|
|
3373
3373
|
this.#asyncJobManager?.cancelAll();
|
|
3374
3374
|
this.#closeAllProviderSessions("new session");
|
|
3375
3375
|
this.agent.reset();
|
|
3376
|
-
|
|
3376
|
+
if (options?.drop && previousSessionFile) {
|
|
3377
|
+
try {
|
|
3378
|
+
await this.sessionManager.dropSession(previousSessionFile);
|
|
3379
|
+
} catch (err) {
|
|
3380
|
+
logger.error("Failed to delete session during /drop", { err });
|
|
3381
|
+
}
|
|
3382
|
+
} else {
|
|
3383
|
+
await this.sessionManager.flush();
|
|
3384
|
+
}
|
|
3377
3385
|
await this.sessionManager.newSession(options);
|
|
3378
3386
|
this.setTodoPhases([]);
|
|
3379
3387
|
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
@@ -66,6 +66,8 @@ export interface SessionHeader {
|
|
|
66
66
|
|
|
67
67
|
export interface NewSessionOptions {
|
|
68
68
|
parentSession?: string;
|
|
69
|
+
/** Skip flushing the current session and delete it instead of saving. */
|
|
70
|
+
drop?: boolean;
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
export interface SessionEntryBase {
|
|
@@ -1707,6 +1709,17 @@ export class SessionManager {
|
|
|
1707
1709
|
return this.#newSessionSync(options);
|
|
1708
1710
|
}
|
|
1709
1711
|
|
|
1712
|
+
/** Delete a session file and its artifacts. Drains the persist writer first to avoid EPERM on Windows. ENOENT is treated as success. */
|
|
1713
|
+
async dropSession(sessionPath: string): Promise<void> {
|
|
1714
|
+
await this.#closePersistWriter();
|
|
1715
|
+
try {
|
|
1716
|
+
await this.storage.deleteSessionWithArtifacts(sessionPath);
|
|
1717
|
+
} catch (err) {
|
|
1718
|
+
if (isEnoent(err)) return;
|
|
1719
|
+
throw err;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1710
1723
|
/**
|
|
1711
1724
|
* Fork the current session, creating a new session file with the same entries.
|
|
1712
1725
|
* Returns both the old and new session file paths for artifact copying.
|
|
@@ -32,6 +32,7 @@ export interface SessionStorage {
|
|
|
32
32
|
writeText(path: string, content: string): Promise<void>;
|
|
33
33
|
rename(path: string, nextPath: string): Promise<void>;
|
|
34
34
|
unlink(path: string): Promise<void>;
|
|
35
|
+
deleteSessionWithArtifacts(sessionPath: string): Promise<void>;
|
|
35
36
|
openWriter(path: string, options?: { flags?: "a" | "w"; onError?: (err: Error) => void }): SessionStorageWriter;
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -360,6 +361,9 @@ export class MemorySessionStorage implements SessionStorage {
|
|
|
360
361
|
this.#files.delete(path);
|
|
361
362
|
return Promise.resolve();
|
|
362
363
|
}
|
|
364
|
+
deleteSessionWithArtifacts(_sessionPath: string): Promise<void> {
|
|
365
|
+
return Promise.resolve();
|
|
366
|
+
}
|
|
363
367
|
|
|
364
368
|
openWriter(path: string, options?: { flags?: "a" | "w"; onError?: (err: Error) => void }): SessionStorageWriter {
|
|
365
369
|
return new MemorySessionStorageWriter(this, path, options);
|
|
@@ -512,6 +512,14 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
|
|
|
512
512
|
await runtime.ctx.handleClearCommand();
|
|
513
513
|
},
|
|
514
514
|
},
|
|
515
|
+
{
|
|
516
|
+
name: "drop",
|
|
517
|
+
description: "Delete the current session and start a new one",
|
|
518
|
+
handle: async (_command, runtime) => {
|
|
519
|
+
runtime.ctx.editor.setText("");
|
|
520
|
+
await runtime.ctx.handleDropCommand();
|
|
521
|
+
},
|
|
522
|
+
},
|
|
515
523
|
{
|
|
516
524
|
name: "compact",
|
|
517
525
|
description: "Manually compact the session context",
|
package/src/tools/browser.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type {
|
|
|
15
15
|
Page,
|
|
16
16
|
default as Puppeteer,
|
|
17
17
|
SerializedAXNode,
|
|
18
|
-
} from "puppeteer";
|
|
18
|
+
} from "puppeteer-core";
|
|
19
19
|
import browserDescription from "../prompts/tools/browser.md" with { type: "text" };
|
|
20
20
|
import type { ToolSession } from "../sdk";
|
|
21
21
|
import { resizeImage } from "../utils/image-resize";
|
|
@@ -53,7 +53,7 @@ async function loadPuppeteer(): Promise<typeof Puppeteer> {
|
|
|
53
53
|
await Bun.write(path.join(safeDir, "package.json"), "{}");
|
|
54
54
|
try {
|
|
55
55
|
process.chdir(safeDir);
|
|
56
|
-
puppeteerModule = (await import("puppeteer")).default;
|
|
56
|
+
puppeteerModule = (await import("puppeteer-core")).default;
|
|
57
57
|
return puppeteerModule;
|
|
58
58
|
} finally {
|
|
59
59
|
process.chdir(prev);
|
|
@@ -61,50 +61,172 @@ async function loadPuppeteer(): Promise<typeof Puppeteer> {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
64
|
+
* Lazily download Chromium on first browser launch via @puppeteer/browsers.
|
|
65
|
+
* Skipped when a system Chromium (NixOS) or PUPPETEER_EXECUTABLE_PATH is set.
|
|
66
|
+
* The browser is cached under ~/.omp/puppeteer (getPuppeteerDir).
|
|
67
|
+
*/
|
|
68
|
+
let chromiumExecutablePromise: Promise<string | undefined> | undefined;
|
|
69
|
+
async function ensureChromiumExecutable(): Promise<string | undefined> {
|
|
70
|
+
const sysChrome = resolveSystemChromium();
|
|
71
|
+
if (sysChrome) return sysChrome;
|
|
72
|
+
const envPath = process.env.PUPPETEER_EXECUTABLE_PATH;
|
|
73
|
+
if (envPath) return envPath;
|
|
74
|
+
if (chromiumExecutablePromise) return chromiumExecutablePromise;
|
|
75
|
+
|
|
76
|
+
chromiumExecutablePromise = (async () => {
|
|
77
|
+
const [browsers, revisions] = await Promise.all([
|
|
78
|
+
import("@puppeteer/browsers"),
|
|
79
|
+
import("puppeteer-core/internal/revisions.js"),
|
|
80
|
+
]);
|
|
81
|
+
const platform = browsers.detectBrowserPlatform();
|
|
82
|
+
if (!platform) {
|
|
83
|
+
logger.warn("Could not detect browser platform; relying on puppeteer default resolution");
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
const cacheDir = getPuppeteerDir();
|
|
87
|
+
const buildId = await browsers.resolveBuildId(
|
|
88
|
+
browsers.Browser.CHROME,
|
|
89
|
+
platform,
|
|
90
|
+
revisions.PUPPETEER_REVISIONS.chrome,
|
|
91
|
+
);
|
|
92
|
+
const executablePath = browsers.computeExecutablePath({
|
|
93
|
+
browser: browsers.Browser.CHROME,
|
|
94
|
+
buildId,
|
|
95
|
+
cacheDir,
|
|
96
|
+
platform,
|
|
97
|
+
});
|
|
98
|
+
if (fs.existsSync(executablePath)) return executablePath;
|
|
99
|
+
|
|
100
|
+
logger.warn("Downloading Chromium for puppeteer (first browser use)", {
|
|
101
|
+
buildId,
|
|
102
|
+
platform,
|
|
103
|
+
cacheDir,
|
|
104
|
+
});
|
|
105
|
+
let lastReportedPercent = -1;
|
|
106
|
+
await browsers.install({
|
|
107
|
+
browser: browsers.Browser.CHROME,
|
|
108
|
+
buildId,
|
|
109
|
+
cacheDir,
|
|
110
|
+
platform,
|
|
111
|
+
downloadProgressCallback: (downloaded, total) => {
|
|
112
|
+
if (total <= 0) return;
|
|
113
|
+
const pct = Math.floor((downloaded / total) * 100);
|
|
114
|
+
if (pct >= lastReportedPercent + 10 || downloaded === total) {
|
|
115
|
+
lastReportedPercent = pct;
|
|
116
|
+
logger.debug(
|
|
117
|
+
`Chromium download: ${pct}% (${Math.round(downloaded / 1_000_000)} / ${Math.round(total / 1_000_000)} MB)`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
return executablePath;
|
|
123
|
+
})().catch(err => {
|
|
124
|
+
chromiumExecutablePromise = undefined;
|
|
125
|
+
throw new ToolError(
|
|
126
|
+
`Failed to install Chromium for puppeteer: ${(err as Error).message}. ` +
|
|
127
|
+
"Set PUPPETEER_EXECUTABLE_PATH to use an existing Chrome/Chromium binary, or install one manually.",
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
return chromiumExecutablePromise;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Resolve a system-installed Chrome/Chromium so `puppeteer.launch()` can reuse
|
|
135
|
+
* it instead of forcing a Chromium download. Returns `undefined` when no binary
|
|
136
|
+
* is found, which lets the caller fall back to a managed download.
|
|
67
137
|
*
|
|
68
|
-
* Detection order:
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
138
|
+
* Detection order (per platform):
|
|
139
|
+
* - macOS: Google Chrome → Chromium → Microsoft Edge (system + user Applications)
|
|
140
|
+
* - Linux: PATH lookups (google-chrome, chromium, etc.) → common /usr/bin paths,
|
|
141
|
+
* with NixOS-specific profile paths added when /etc/NIXOS exists
|
|
142
|
+
* - Windows: Program Files / LocalAppData install paths for Chrome and Edge
|
|
73
143
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
144
|
+
* Honored regardless of platform: PUPPETEER_EXECUTABLE_PATH callers should bypass
|
|
145
|
+
* this entirely (handled in ensureChromiumExecutable).
|
|
76
146
|
*/
|
|
77
147
|
let _resolvedChromium: string | null | undefined; // undefined = unchecked; null = not found
|
|
78
|
-
function
|
|
79
|
-
if (_resolvedChromium !== undefined) return _resolvedChromium ?? undefined;
|
|
148
|
+
function isExecutableFile(p: string): boolean {
|
|
80
149
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return undefined;
|
|
84
|
-
}
|
|
150
|
+
const st = fs.statSync(p);
|
|
151
|
+
return st.isFile();
|
|
85
152
|
} catch {
|
|
86
|
-
|
|
87
|
-
return undefined;
|
|
153
|
+
return false;
|
|
88
154
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function systemChromiumCandidates(): string[] {
|
|
158
|
+
const home = os.homedir();
|
|
159
|
+
const candidates: string[] = [];
|
|
160
|
+
switch (process.platform) {
|
|
161
|
+
case "darwin": {
|
|
162
|
+
for (const root of ["/Applications", path.join(home, "Applications")]) {
|
|
163
|
+
candidates.push(
|
|
164
|
+
path.join(root, "Google Chrome.app/Contents/MacOS/Google Chrome"),
|
|
165
|
+
path.join(root, "Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta"),
|
|
166
|
+
path.join(root, "Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev"),
|
|
167
|
+
path.join(root, "Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary"),
|
|
168
|
+
path.join(root, "Chromium.app/Contents/MacOS/Chromium"),
|
|
169
|
+
path.join(root, "Microsoft Edge.app/Contents/MacOS/Microsoft Edge"),
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
case "linux": {
|
|
175
|
+
const names = ["google-chrome-stable", "google-chrome", "chromium", "chromium-browser", "chrome"];
|
|
176
|
+
for (const name of names) {
|
|
177
|
+
const found = $which(name);
|
|
178
|
+
if (found) candidates.push(found);
|
|
179
|
+
}
|
|
180
|
+
candidates.push(
|
|
181
|
+
"/usr/bin/google-chrome-stable",
|
|
182
|
+
"/usr/bin/google-chrome",
|
|
183
|
+
"/usr/bin/chromium",
|
|
184
|
+
"/usr/bin/chromium-browser",
|
|
185
|
+
"/snap/bin/chromium",
|
|
186
|
+
"/var/lib/flatpak/exports/bin/com.google.Chrome",
|
|
187
|
+
"/var/lib/flatpak/exports/bin/org.chromium.Chromium",
|
|
188
|
+
);
|
|
189
|
+
let onNixos = false;
|
|
97
190
|
try {
|
|
98
|
-
|
|
99
|
-
_resolvedChromium = candidate;
|
|
100
|
-
logger.debug("NixOS: using system Chromium", { path: candidate });
|
|
101
|
-
return candidate;
|
|
102
|
-
}
|
|
191
|
+
onNixos = fs.existsSync("/etc/NIXOS");
|
|
103
192
|
} catch {}
|
|
193
|
+
if (onNixos) {
|
|
194
|
+
candidates.push(path.join(home, ".nix-profile/bin/chromium"), "/run/current-system/sw/bin/chromium");
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
case "win32": {
|
|
199
|
+
const programFiles = process.env.ProgramFiles ?? "C:\\Program Files";
|
|
200
|
+
const programFilesX86 = process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
|
|
201
|
+
const localAppData = process.env.LOCALAPPDATA ?? path.join(home, "AppData\\Local");
|
|
202
|
+
candidates.push(
|
|
203
|
+
path.join(programFiles, "Google\\Chrome\\Application\\chrome.exe"),
|
|
204
|
+
path.join(programFilesX86, "Google\\Chrome\\Application\\chrome.exe"),
|
|
205
|
+
path.join(localAppData, "Google\\Chrome\\Application\\chrome.exe"),
|
|
206
|
+
path.join(programFiles, "Chromium\\Application\\chrome.exe"),
|
|
207
|
+
path.join(localAppData, "Chromium\\Application\\chrome.exe"),
|
|
208
|
+
path.join(programFiles, "Microsoft\\Edge\\Application\\msedge.exe"),
|
|
209
|
+
path.join(programFilesX86, "Microsoft\\Edge\\Application\\msedge.exe"),
|
|
210
|
+
);
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return candidates;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function resolveSystemChromium(): string | undefined {
|
|
218
|
+
if (_resolvedChromium !== undefined) return _resolvedChromium ?? undefined;
|
|
219
|
+
const seen = new Set<string>();
|
|
220
|
+
for (const candidate of systemChromiumCandidates()) {
|
|
221
|
+
if (!candidate || seen.has(candidate)) continue;
|
|
222
|
+
seen.add(candidate);
|
|
223
|
+
if (isExecutableFile(candidate)) {
|
|
224
|
+
_resolvedChromium = candidate;
|
|
225
|
+
logger.debug("Using system Chrome/Chromium", { path: candidate });
|
|
226
|
+
return candidate;
|
|
104
227
|
}
|
|
105
228
|
}
|
|
106
229
|
_resolvedChromium = null;
|
|
107
|
-
logger.debug("NixOS detected but no Chromium binary found; Puppeteer may fail to launch");
|
|
108
230
|
return undefined;
|
|
109
231
|
}
|
|
110
232
|
|
|
@@ -674,7 +796,7 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
|
|
|
674
796
|
this.#browser = await puppeteer.launch({
|
|
675
797
|
headless: this.#currentHeadless,
|
|
676
798
|
defaultViewport: this.#currentHeadless ? initialViewport : null,
|
|
677
|
-
executablePath:
|
|
799
|
+
executablePath: await ensureChromiumExecutable(),
|
|
678
800
|
args: launchArgs,
|
|
679
801
|
ignoreDefaultArgs: [...STEALTH_IGNORE_DEFAULT_ARGS],
|
|
680
802
|
});
|
package/src/web/search/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unified Web Search Tool
|
|
3
3
|
*
|
|
4
|
-
* Single tool supporting Anthropic, Perplexity, Exa, Brave, Jina, Kimi, Gemini, Codex, Tavily, Kagi, Z.AI, and Synthetic
|
|
4
|
+
* Single tool supporting Anthropic, Perplexity, Exa, Brave, Jina, Kimi, Gemini, Codex, Tavily, Kagi, Z.AI, SearXNG, and Synthetic
|
|
5
5
|
* providers with provider-specific parameters exposed conditionally.
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
@@ -202,7 +202,7 @@ export async function runSearchQuery(
|
|
|
202
202
|
/**
|
|
203
203
|
* Web search tool implementation.
|
|
204
204
|
*
|
|
205
|
-
* Supports Anthropic, Perplexity, Exa, Brave, Jina, Kimi, Gemini, Codex, Z.AI, and Synthetic providers with automatic fallback.
|
|
205
|
+
* Supports Anthropic, Perplexity, Exa, Brave, Jina, Kimi, Gemini, Codex, Z.AI, SearXNG, and Synthetic providers with automatic fallback.
|
|
206
206
|
* Session is accepted for interface consistency but not used.
|
|
207
207
|
*/
|
|
208
208
|
export class SearchTool implements AgentTool<typeof webSearchSchema, SearchRenderDetails> {
|
|
@@ -9,6 +9,7 @@ import { KagiProvider } from "./providers/kagi";
|
|
|
9
9
|
import { KimiProvider } from "./providers/kimi";
|
|
10
10
|
import { ParallelProvider } from "./providers/parallel";
|
|
11
11
|
import { PerplexityProvider } from "./providers/perplexity";
|
|
12
|
+
import { SearXNGProvider } from "./providers/searxng";
|
|
12
13
|
import { SyntheticProvider } from "./providers/synthetic";
|
|
13
14
|
import { TavilyProvider } from "./providers/tavily";
|
|
14
15
|
import { ZaiProvider } from "./providers/zai";
|
|
@@ -31,6 +32,7 @@ const SEARCH_PROVIDERS: Record<SearchProviderId, SearchProvider> = {
|
|
|
31
32
|
parallel: new ParallelProvider(),
|
|
32
33
|
kagi: new KagiProvider(),
|
|
33
34
|
synthetic: new SyntheticProvider(),
|
|
35
|
+
searxng: new SearXNGProvider(),
|
|
34
36
|
} as const;
|
|
35
37
|
|
|
36
38
|
export const SEARCH_PROVIDER_ORDER: SearchProviderId[] = [
|
|
@@ -47,6 +49,7 @@ export const SEARCH_PROVIDER_ORDER: SearchProviderId[] = [
|
|
|
47
49
|
"parallel",
|
|
48
50
|
"kagi",
|
|
49
51
|
"synthetic",
|
|
52
|
+
"searxng",
|
|
50
53
|
];
|
|
51
54
|
|
|
52
55
|
export function getSearchProvider(provider: SearchProviderId): SearchProvider {
|