@pasko70/pibo 1.14.0 → 1.16.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/context/pibo-native-tooling.md +1 -1
- package/dist/apps/chat/web-app.js +11 -0
- package/dist/apps/chat-ui/assets/{dist-1eQpSfYj.js → dist-4-l8R83g.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-CHdx1Jri.js → chat-ui/assets/dist-B9-pH_ss.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-hoDORUlL.js → dist-BKc5qFOn.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-CntqFySz.js → chat-ui/assets/dist-CfT2OdUN.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-ur4yXrlj.js → dist-ClTBuMxL.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-Cjq6RWEt.js → chat-ui/assets/dist-D4pAJPG3.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-kPuQud7t.js → chat-ui/assets/dist-DCr1bDLp.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-B7gXtTZC.js → chat-ui/assets/dist-D_inCXoq.js} +1 -1
- package/dist/apps/{context-files-ui/assets/dist-C-Jm45P4.js → chat-ui/assets/dist-DbW63bQx.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BIwXuAMQ.js → dist-DyeQntkx.js} +1 -1
- package/dist/apps/chat-ui/assets/index-CMTOlTY9.js +473 -0
- package/dist/apps/chat-ui/assets/index-E0RmuCkY.css +1 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-BeTXIfmQ.js +275 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/apps/context-files-ui/assets/{dist-DxEGebCR.js → dist-4oHMVqMd.js} +1 -1
- package/dist/apps/{chat-ui/assets/dist-LWXXqiGT.js → context-files-ui/assets/dist-B8GCRy-W.js} +1 -1
- package/dist/apps/{chat-ui/assets/dist-CtsdsIpj.js → context-files-ui/assets/dist-BZscbUy_.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-BKuGdcnX.js → dist-C3bTw5ib.js} +1 -1
- package/dist/apps/{chat-ui/assets/dist-BG1J2hGI.js → context-files-ui/assets/dist-CFbqQ7A8.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-BD262Ouw.js → dist-CZOCrCg8.js} +1 -1
- package/dist/apps/{chat-ui/assets/dist-DmqwP_tM.js → context-files-ui/assets/dist-D9wOh84K.js} +1 -1
- package/dist/apps/{chat-ui/assets/dist-BeCHnL7P.js → context-files-ui/assets/dist-D_65A0Jm.js} +1 -1
- package/dist/apps/{chat-ui/assets/dist-DfdQQObC.js → context-files-ui/assets/dist-DsVkw0QH.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-CULMH-fB.js → dist-dXgL1mFo.js} +1 -1
- package/dist/apps/context-files-ui/assets/index-DiJwk6ni.js +438 -0
- package/dist/apps/context-files-ui/index.html +1 -1
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.16.0.vsix +0 -0
- package/dist/core/routed-session.js +17 -0
- package/dist/core/runtime.js +27 -5
- package/dist/core/session-router.js +20 -6
- package/dist/debug/web-streaming-benchmark-analysis.js +23 -0
- package/dist/debug/web-streaming-browser-library.js +0 -1
- package/dist/debug/web.js +4 -2
- package/dist/gateway/server.js +1 -0
- package/dist/plugins/builtin.js +10 -9
- package/dist/session-ui/terminalRows.js +12 -2
- package/dist/shared/trace-event-projection.js +15 -15
- package/dist/shared/trace-live-reducer.js +1 -0
- package/dist/tools/browser-use-leases.js +16 -0
- package/dist/tools/codex-browser-node-repl.js +224 -0
- package/dist/tools/codex-browser-node-worker-source.js +403 -0
- package/dist/tools/codex-browser.js +461 -0
- package/package.json +3 -1
- package/dist/apps/chat-ui/assets/index-CoCl8FG8.css +0 -1
- package/dist/apps/chat-ui/assets/index-DLNTzhdh.js +0 -237
- package/dist/apps/chat-vscode-web/assets/index-R_7kgvvD.js +0 -41
- package/dist/apps/context-files-ui/assets/index-DZWsbuvg.js +0 -202
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.14.0.vsix +0 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { StringEnum, Type } from "@earendil-works/pi-ai";
|
|
4
|
+
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { connectCdpTarget, listCdpTargets } from "./cdp-client.js";
|
|
6
|
+
import { browserPoolPaths, releaseBrowserPoolLease } from "./browser-pool.js";
|
|
7
|
+
import { browserUseLeaseEnvironment, findActiveBrowserUseLeaseForHolder } from "./browser-use-leases.js";
|
|
8
|
+
import { ensureBrowserUseWrapper } from "./browser-use-wrapper.js";
|
|
9
|
+
import { findCliToolEntry, getCliToolStatus } from "./registry.js";
|
|
10
|
+
import { CodexBrowserNodeRepl, } from "./codex-browser-node-repl.js";
|
|
11
|
+
export const BROWSER_USE_OPEN_TABS_TOOL_NAME = "browser_use_open_tabs";
|
|
12
|
+
export const BROWSER_USE_TAKE_SCREENSHOT_TOOL_NAME = "browser_use_take_screenshot";
|
|
13
|
+
export const BROWSER_USE_TOOL_NAME = "browser_use_browser_use";
|
|
14
|
+
export const NODE_REPL_JS_TOOL_NAME = "node_repl_js";
|
|
15
|
+
export const NODE_REPL_JS_RESET_TOOL_NAME = "node_repl_js_reset";
|
|
16
|
+
export const CODEX_BROWSER_TOOL_NAMES = [
|
|
17
|
+
BROWSER_USE_OPEN_TABS_TOOL_NAME,
|
|
18
|
+
BROWSER_USE_TAKE_SCREENSHOT_TOOL_NAME,
|
|
19
|
+
BROWSER_USE_TOOL_NAME,
|
|
20
|
+
NODE_REPL_JS_TOOL_NAME,
|
|
21
|
+
NODE_REPL_JS_RESET_TOOL_NAME,
|
|
22
|
+
];
|
|
23
|
+
export class CodexBrowserSessionController {
|
|
24
|
+
options;
|
|
25
|
+
sessionName;
|
|
26
|
+
browserPoolLeaseId;
|
|
27
|
+
authLeaseId;
|
|
28
|
+
status;
|
|
29
|
+
wrapperPath;
|
|
30
|
+
env;
|
|
31
|
+
runCommand;
|
|
32
|
+
cdpUrl;
|
|
33
|
+
nodeRepl;
|
|
34
|
+
browserLeaseUsed = false;
|
|
35
|
+
constructor(options) {
|
|
36
|
+
this.options = options;
|
|
37
|
+
const entry = findCliToolEntry("browser-use");
|
|
38
|
+
if (!entry)
|
|
39
|
+
throw new Error("Pibo browser-use registry entry is unavailable");
|
|
40
|
+
this.status = options.status ?? getCliToolStatus(entry);
|
|
41
|
+
this.wrapperPath = this.status.installed
|
|
42
|
+
? ensureBrowserUseWrapper(this.status) ?? this.status.executablePath
|
|
43
|
+
: this.status.executablePath;
|
|
44
|
+
const baseEnv = options.env ?? process.env;
|
|
45
|
+
const explicitAuthLease = baseEnv.PIBO_BROWSER_USE_LEASE_ID?.trim();
|
|
46
|
+
const sessionId = options.piboSessionId?.trim() || "local";
|
|
47
|
+
const registeredLease = explicitAuthLease ? undefined : findRegisteredLease(this.status, sessionId);
|
|
48
|
+
const registeredLeaseEnv = registeredLease ? browserUseLeaseEnvironment(this.status, registeredLease) : undefined;
|
|
49
|
+
this.sessionName = explicitAuthLease && baseEnv.PIBO_BROWSER_USE_SESSION
|
|
50
|
+
? baseEnv.PIBO_BROWSER_USE_SESSION
|
|
51
|
+
: registeredLease?.sessionName ?? `pibo-${safeSessionName(sessionId)}`;
|
|
52
|
+
this.browserPoolLeaseId = explicitAuthLease && baseEnv.PIBO_BROWSER_POOL_LEASE_ID
|
|
53
|
+
? baseEnv.PIBO_BROWSER_POOL_LEASE_ID
|
|
54
|
+
: registeredLease?.browserPoolLeaseId ?? `browser-use:${this.sessionName}`;
|
|
55
|
+
this.authLeaseId = explicitAuthLease || registeredLease?.id;
|
|
56
|
+
this.env = {
|
|
57
|
+
...baseEnv,
|
|
58
|
+
...(registeredLeaseEnv ?? {}),
|
|
59
|
+
BROWSER_USE_HOME: this.status.homeDir,
|
|
60
|
+
PIBO_BROWSER_USE_SESSION: this.sessionName,
|
|
61
|
+
PIBO_BROWSER_POOL_LEASE_ID: this.browserPoolLeaseId,
|
|
62
|
+
PIBO_BROWSER_POOL_HOLDER: `pibo-session:${sessionId}`,
|
|
63
|
+
};
|
|
64
|
+
if (!this.authLeaseId) {
|
|
65
|
+
delete this.env.PIBO_BROWSER_USE_LEASE_ID;
|
|
66
|
+
delete this.env.PIBO_BROWSER_USE_CHROME_USER_DATA_DIR;
|
|
67
|
+
}
|
|
68
|
+
this.runCommand = options.runCommand ?? runBrowserUseCommand;
|
|
69
|
+
}
|
|
70
|
+
async openTabs() {
|
|
71
|
+
const cdpUrl = await this.ensureChrome();
|
|
72
|
+
const targets = await listCdpTargets({ cdpUrl });
|
|
73
|
+
return pageTargets(targets).map(toBrowserUseTab);
|
|
74
|
+
}
|
|
75
|
+
async takeScreenshot(input = {}) {
|
|
76
|
+
const timeoutMs = input.timeoutMs ?? 15_000;
|
|
77
|
+
const cdpUrl = await this.ensureChrome(timeoutMs);
|
|
78
|
+
const targets = pageTargets(await listCdpTargets({ cdpUrl, timeoutMs }));
|
|
79
|
+
const target = selectTarget(targets, input.tabId);
|
|
80
|
+
if (!target)
|
|
81
|
+
throw new Error(input.tabId ? `Browser tab "${input.tabId}" was not found` : "No open browser tab was found");
|
|
82
|
+
const client = await connectCdpTarget(target, Math.min(timeoutMs, 5_000));
|
|
83
|
+
try {
|
|
84
|
+
const data = await captureScreenshot(client, input.fullPage === true, timeoutMs);
|
|
85
|
+
return {
|
|
86
|
+
status: "ok",
|
|
87
|
+
sessionName: this.sessionName,
|
|
88
|
+
browserPoolLeaseId: this.browserPoolLeaseId,
|
|
89
|
+
...(this.authLeaseId ? { authLeaseId: this.authLeaseId } : {}),
|
|
90
|
+
tab: toBrowserUseTab(target, targets.indexOf(target)),
|
|
91
|
+
fullPage: input.fullPage === true,
|
|
92
|
+
mimeType: "image/png",
|
|
93
|
+
data,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
client.close();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async use(input, signal) {
|
|
101
|
+
const command = browserUseCommand(input);
|
|
102
|
+
const response = await this.execute(command, input.timeoutMs ?? 30_000, signal);
|
|
103
|
+
if (response.exitCode !== 0) {
|
|
104
|
+
throw new Error(response.stderr.trim() || response.stdout.trim() || `browser-use exited with code ${response.exitCode ?? "unknown"}`);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
status: "ok",
|
|
108
|
+
sessionName: this.sessionName,
|
|
109
|
+
browserPoolLeaseId: this.browserPoolLeaseId,
|
|
110
|
+
...(this.authLeaseId ? { authLeaseId: this.authLeaseId } : {}),
|
|
111
|
+
command,
|
|
112
|
+
output: parseJsonOrText(response.stdout),
|
|
113
|
+
...(response.stderr.trim() ? { stderr: response.stderr.trim() } : {}),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async js(code, timeoutMs = 30_000) {
|
|
117
|
+
const repl = await this.getNodeRepl();
|
|
118
|
+
return repl.exec(code, timeoutMs);
|
|
119
|
+
}
|
|
120
|
+
async jsReset() {
|
|
121
|
+
if (!this.nodeRepl?.isAlive())
|
|
122
|
+
return { status: "ok", reset: true };
|
|
123
|
+
return this.nodeRepl.reset();
|
|
124
|
+
}
|
|
125
|
+
async dispose() {
|
|
126
|
+
this.nodeRepl?.dispose();
|
|
127
|
+
this.nodeRepl = undefined;
|
|
128
|
+
if (!this.browserLeaseUsed)
|
|
129
|
+
return;
|
|
130
|
+
const identity = {
|
|
131
|
+
workerId: this.env.PIBO_BROWSER_POOL_WORKER_ID || this.env.PIBO_COMPUTE_WORKER_ID || this.env.HOSTNAME || "local",
|
|
132
|
+
poolId: this.env.PIBO_BROWSER_POOL_ID || "default",
|
|
133
|
+
maxBrowserProcesses: positiveInteger(this.env.PIBO_BROWSER_POOL_MAX_PROCESSES, 1),
|
|
134
|
+
};
|
|
135
|
+
const rootDir = this.env.PIBO_BROWSER_POOL_ROOT || join(this.status.homeDir, "pibo-browser-pool");
|
|
136
|
+
try {
|
|
137
|
+
await releaseBrowserPoolLease(browserPoolPaths(rootDir, identity), identity, { leaseId: this.browserPoolLeaseId });
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Browser pool cleanup is best-effort during Pi session disposal.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async getNodeRepl() {
|
|
144
|
+
if (this.nodeRepl?.isAlive())
|
|
145
|
+
return this.nodeRepl;
|
|
146
|
+
this.nodeRepl = await CodexBrowserNodeRepl.start({
|
|
147
|
+
openTabs: () => this.openTabs(),
|
|
148
|
+
use: (input) => this.use(requireBrowserUseInput(input)),
|
|
149
|
+
}, this.options.cwd);
|
|
150
|
+
return this.nodeRepl;
|
|
151
|
+
}
|
|
152
|
+
async ensureChrome(timeoutMs = 15_000) {
|
|
153
|
+
if (this.cdpUrl) {
|
|
154
|
+
try {
|
|
155
|
+
await listCdpTargets({ cdpUrl: this.cdpUrl, timeoutMs: Math.min(timeoutMs, 1_000) });
|
|
156
|
+
return this.cdpUrl;
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
this.cdpUrl = undefined;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const response = await this.execute(["--pibo-ensure-chrome"], timeoutMs);
|
|
163
|
+
if (response.exitCode !== 0) {
|
|
164
|
+
throw new Error(response.stderr.trim() || response.stdout.trim() || "browser-use could not start Chrome");
|
|
165
|
+
}
|
|
166
|
+
const cdpUrl = response.stdout.split(/\r?\n/).map((line) => line.trim()).findLast((line) => /^https?:\/\//.test(line));
|
|
167
|
+
if (!cdpUrl)
|
|
168
|
+
throw new Error(`browser-use did not return a CDP URL: ${response.stdout.trim()}`);
|
|
169
|
+
this.cdpUrl = cdpUrl.replace(/\/+$/, "");
|
|
170
|
+
return this.cdpUrl;
|
|
171
|
+
}
|
|
172
|
+
execute(command, timeoutMs, signal) {
|
|
173
|
+
if (!this.status.installed) {
|
|
174
|
+
throw new Error("browser-use is not installed. Run `pibo tools install browser-use`.");
|
|
175
|
+
}
|
|
176
|
+
const args = ["--session", this.sessionName, ...(command[0]?.startsWith("--") ? [] : ["--json"]), ...command];
|
|
177
|
+
this.browserLeaseUsed = true;
|
|
178
|
+
return this.runCommand({
|
|
179
|
+
executable: this.wrapperPath,
|
|
180
|
+
args,
|
|
181
|
+
cwd: this.options.cwd,
|
|
182
|
+
env: this.env,
|
|
183
|
+
timeoutMs,
|
|
184
|
+
signal,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
export function createCodexBrowserToolProfiles() {
|
|
189
|
+
const descriptions = {
|
|
190
|
+
[BROWSER_USE_OPEN_TABS_TOOL_NAME]: "List tabs in the persistent Browser Use session (Codex-compatible label: browser_use.open_tabs).",
|
|
191
|
+
[BROWSER_USE_TAKE_SCREENSHOT_TOOL_NAME]: "Capture a browser tab screenshot (Codex-compatible label: browser_use.take_screenshot).",
|
|
192
|
+
[BROWSER_USE_TOOL_NAME]: "Control the persistent Browser Use session through structured actions (Codex-compatible label: browser_use.browser_use).",
|
|
193
|
+
[NODE_REPL_JS_TOOL_NAME]: "Run JavaScript in a persistent browser-bound Node REPL (Codex-compatible label: node_repl.js).",
|
|
194
|
+
[NODE_REPL_JS_RESET_TOOL_NAME]: "Reset the persistent browser-bound Node REPL (Codex-compatible label: node_repl.js_reset).",
|
|
195
|
+
};
|
|
196
|
+
return CODEX_BROWSER_TOOL_NAMES.map((name) => ({
|
|
197
|
+
name,
|
|
198
|
+
description: descriptions[name],
|
|
199
|
+
yieldable: name === BROWSER_USE_TOOL_NAME,
|
|
200
|
+
builtInPiboTool: "codex_browser",
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
export function createCodexBrowserToolDefinitions(controller, enabledNames = CODEX_BROWSER_TOOL_NAMES) {
|
|
204
|
+
const enabled = new Set(enabledNames);
|
|
205
|
+
const tools = [
|
|
206
|
+
[BROWSER_USE_OPEN_TABS_TOOL_NAME, defineTool({
|
|
207
|
+
name: BROWSER_USE_OPEN_TABS_TOOL_NAME,
|
|
208
|
+
label: "browser_use.open_tabs",
|
|
209
|
+
description: "List open tabs in the persistent Browser Use session. Returns stable target IDs, short tab IDs, titles, URLs, and tab indices.",
|
|
210
|
+
promptSnippet: "Use browser_use_open_tabs (browser_use.open_tabs) to discover tabs before targeting a screenshot or tab-specific action.",
|
|
211
|
+
executionMode: "parallel",
|
|
212
|
+
parameters: Type.Object({}),
|
|
213
|
+
async execute() {
|
|
214
|
+
return toolResult(await controller.openTabs());
|
|
215
|
+
},
|
|
216
|
+
})],
|
|
217
|
+
[BROWSER_USE_TAKE_SCREENSHOT_TOOL_NAME, defineTool({
|
|
218
|
+
name: BROWSER_USE_TAKE_SCREENSHOT_TOOL_NAME,
|
|
219
|
+
label: "browser_use.take_screenshot",
|
|
220
|
+
description: "Take a PNG screenshot from the persistent Browser Use session. Omit tabId to capture the first attachable page tab.",
|
|
221
|
+
promptSnippet: "Use browser_use_take_screenshot (browser_use.take_screenshot) when visual page state matters.",
|
|
222
|
+
executionMode: "parallel",
|
|
223
|
+
parameters: Type.Object({
|
|
224
|
+
tabId: Type.Optional(Type.String({ description: "Full target ID or short tab ID returned by browser_use_open_tabs." })),
|
|
225
|
+
fullPage: Type.Optional(Type.Boolean({ description: "Capture the full scrollable page instead of only the viewport." })),
|
|
226
|
+
timeoutMs: Type.Optional(Type.Number({ description: "Screenshot timeout in milliseconds." })),
|
|
227
|
+
}),
|
|
228
|
+
async execute(_toolCallId, params) {
|
|
229
|
+
const result = await controller.takeScreenshot(params);
|
|
230
|
+
const { data, ...details } = result;
|
|
231
|
+
return {
|
|
232
|
+
content: [
|
|
233
|
+
{ type: "text", text: JSON.stringify(details, null, 2) },
|
|
234
|
+
{ type: "image", data, mimeType: result.mimeType },
|
|
235
|
+
],
|
|
236
|
+
details,
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
})],
|
|
240
|
+
[BROWSER_USE_TOOL_NAME, defineTool({
|
|
241
|
+
name: BROWSER_USE_TOOL_NAME,
|
|
242
|
+
label: "browser_use.browser_use",
|
|
243
|
+
description: "Control one persistent Browser Use session with a structured action. State and element indices persist through the same Pibo session and managed browser-pool lease.",
|
|
244
|
+
promptSnippet: "Use browser_use_browser_use (browser_use.browser_use) for structured navigation and interaction; call state before index-based clicks or input.",
|
|
245
|
+
executionMode: "sequential",
|
|
246
|
+
parameters: Type.Object({
|
|
247
|
+
action: StringEnum(["navigate", "state", "click", "input", "type", "scroll", "back", "switch_tab", "close_tab", "keys", "evaluate", "wait_selector", "wait_text"], { description: "Browser action." }),
|
|
248
|
+
url: Type.Optional(Type.String({ description: "URL for navigate." })),
|
|
249
|
+
index: Type.Optional(Type.Number({ description: "Interactive element index for click or input." })),
|
|
250
|
+
coordinateX: Type.Optional(Type.Number({ description: "Viewport X coordinate for click; use with coordinateY." })),
|
|
251
|
+
coordinateY: Type.Optional(Type.Number({ description: "Viewport Y coordinate for click; use with coordinateX." })),
|
|
252
|
+
text: Type.Optional(Type.String({ description: "Text for input, type, or wait_text." })),
|
|
253
|
+
direction: Type.Optional(StringEnum(["up", "down"], { description: "Scroll direction." })),
|
|
254
|
+
amount: Type.Optional(Type.Number({ description: "Scroll amount in pixels." })),
|
|
255
|
+
tab: Type.Optional(Type.Number({ description: "Browser Use tab index for switch_tab or close_tab." })),
|
|
256
|
+
keys: Type.Optional(Type.String({ description: "Keys to send, such as Enter or Control+a." })),
|
|
257
|
+
js: Type.Optional(Type.String({ description: "JavaScript for evaluate." })),
|
|
258
|
+
selector: Type.Optional(Type.String({ description: "CSS selector for wait_selector." })),
|
|
259
|
+
timeoutMs: Type.Optional(Type.Number({ description: "Action timeout in milliseconds." })),
|
|
260
|
+
}),
|
|
261
|
+
async execute(_toolCallId, params, signal) {
|
|
262
|
+
return toolResult(await controller.use(params, signal));
|
|
263
|
+
},
|
|
264
|
+
})],
|
|
265
|
+
[NODE_REPL_JS_TOOL_NAME, defineTool({
|
|
266
|
+
name: NODE_REPL_JS_TOOL_NAME,
|
|
267
|
+
label: "node_repl.js",
|
|
268
|
+
description: "Execute JavaScript in a persistent, session-scoped Node REPL with top-level await. Top-level bindings persist. The sandbox omits require and process, and exposes browser.openTabs() plus browser.use(action, params) for the same Browser Use session.",
|
|
269
|
+
promptSnippet: "Use node_repl_js (node_repl.js) for persistent JavaScript state and browser-bound analysis. Top-level await and reusable top-level bindings are supported.",
|
|
270
|
+
executionMode: "sequential",
|
|
271
|
+
parameters: Type.Object({
|
|
272
|
+
code: Type.String({ description: "JavaScript to execute. Top-level declarations persist across calls." }),
|
|
273
|
+
timeoutMs: Type.Optional(Type.Number({ description: "Execution timeout in milliseconds." })),
|
|
274
|
+
}),
|
|
275
|
+
async execute(_toolCallId, params) {
|
|
276
|
+
return nodeReplToolResult(await controller.js(params.code, params.timeoutMs));
|
|
277
|
+
},
|
|
278
|
+
})],
|
|
279
|
+
[NODE_REPL_JS_RESET_TOOL_NAME, defineTool({
|
|
280
|
+
name: NODE_REPL_JS_RESET_TOOL_NAME,
|
|
281
|
+
label: "node_repl.js_reset",
|
|
282
|
+
description: "Reset the persistent node_repl.js namespace for the current Pibo session.",
|
|
283
|
+
promptSnippet: "Use node_repl_js_reset (node_repl.js_reset) only when persistent JavaScript state should be discarded.",
|
|
284
|
+
executionMode: "sequential",
|
|
285
|
+
parameters: Type.Object({}),
|
|
286
|
+
async execute() {
|
|
287
|
+
return nodeReplToolResult(await controller.jsReset());
|
|
288
|
+
},
|
|
289
|
+
})],
|
|
290
|
+
];
|
|
291
|
+
return tools.filter(([name]) => enabled.has(name)).map(([, definition]) => definition);
|
|
292
|
+
}
|
|
293
|
+
function safeSessionName(value) {
|
|
294
|
+
return value.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 120) || "local";
|
|
295
|
+
}
|
|
296
|
+
function positiveInteger(value, fallback) {
|
|
297
|
+
const parsed = Number.parseInt(value ?? "", 10);
|
|
298
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
299
|
+
}
|
|
300
|
+
function findRegisteredLease(status, piboSessionId) {
|
|
301
|
+
try {
|
|
302
|
+
return findActiveBrowserUseLeaseForHolder(status, piboSessionId);
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function runBrowserUseCommand(request) {
|
|
309
|
+
return new Promise((resolve, reject) => {
|
|
310
|
+
const child = spawn(request.executable, request.args, {
|
|
311
|
+
cwd: request.cwd,
|
|
312
|
+
env: request.env,
|
|
313
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
314
|
+
});
|
|
315
|
+
let stdout = "";
|
|
316
|
+
let stderr = "";
|
|
317
|
+
let settled = false;
|
|
318
|
+
const abort = () => child.kill("SIGTERM");
|
|
319
|
+
request.signal?.addEventListener("abort", abort, { once: true });
|
|
320
|
+
const timer = setTimeout(() => child.kill("SIGTERM"), request.timeoutMs);
|
|
321
|
+
child.stdout.on("data", (chunk) => { stdout += String(chunk); });
|
|
322
|
+
child.stderr.on("data", (chunk) => { stderr += String(chunk); });
|
|
323
|
+
child.once("error", (error) => {
|
|
324
|
+
if (settled)
|
|
325
|
+
return;
|
|
326
|
+
settled = true;
|
|
327
|
+
clearTimeout(timer);
|
|
328
|
+
request.signal?.removeEventListener("abort", abort);
|
|
329
|
+
reject(error);
|
|
330
|
+
});
|
|
331
|
+
child.once("close", (exitCode) => {
|
|
332
|
+
if (settled)
|
|
333
|
+
return;
|
|
334
|
+
settled = true;
|
|
335
|
+
clearTimeout(timer);
|
|
336
|
+
request.signal?.removeEventListener("abort", abort);
|
|
337
|
+
resolve({ exitCode, stdout, stderr });
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
function browserUseCommand(input) {
|
|
342
|
+
switch (input.action) {
|
|
343
|
+
case "navigate":
|
|
344
|
+
return ["open", requireText(input.url, "url")];
|
|
345
|
+
case "state":
|
|
346
|
+
return ["state"];
|
|
347
|
+
case "click":
|
|
348
|
+
if (input.index !== undefined)
|
|
349
|
+
return ["click", integerText(input.index, "index")];
|
|
350
|
+
if (input.coordinateX !== undefined && input.coordinateY !== undefined) {
|
|
351
|
+
return ["click", integerText(input.coordinateX, "coordinateX"), integerText(input.coordinateY, "coordinateY")];
|
|
352
|
+
}
|
|
353
|
+
throw new Error("browser_use.browser_use click requires index or coordinateX and coordinateY");
|
|
354
|
+
case "input":
|
|
355
|
+
return ["input", integerText(input.index, "index"), requireText(input.text, "text")];
|
|
356
|
+
case "type":
|
|
357
|
+
return ["type", requireText(input.text, "text")];
|
|
358
|
+
case "scroll":
|
|
359
|
+
return ["scroll", input.direction ?? "down", ...(input.amount === undefined ? [] : ["--amount", integerText(input.amount, "amount")])];
|
|
360
|
+
case "back":
|
|
361
|
+
return ["back"];
|
|
362
|
+
case "switch_tab":
|
|
363
|
+
return ["switch", integerText(input.tab, "tab")];
|
|
364
|
+
case "close_tab":
|
|
365
|
+
return ["close-tab", ...(input.tab === undefined ? [] : [integerText(input.tab, "tab")])];
|
|
366
|
+
case "keys":
|
|
367
|
+
return ["keys", requireText(input.keys, "keys")];
|
|
368
|
+
case "evaluate":
|
|
369
|
+
return ["eval", requireText(input.js, "js")];
|
|
370
|
+
case "wait_selector":
|
|
371
|
+
return ["wait", "selector", requireText(input.selector, "selector")];
|
|
372
|
+
case "wait_text":
|
|
373
|
+
return ["wait", "text", requireText(input.text, "text")];
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
function requireBrowserUseInput(value) {
|
|
377
|
+
if (typeof value.action !== "string")
|
|
378
|
+
throw new Error("browser.use requires action");
|
|
379
|
+
return value;
|
|
380
|
+
}
|
|
381
|
+
function requireText(value, field) {
|
|
382
|
+
if (typeof value !== "string" || value.length === 0)
|
|
383
|
+
throw new Error(`browser_use.browser_use ${field} must be a non-empty string`);
|
|
384
|
+
return value;
|
|
385
|
+
}
|
|
386
|
+
function integerText(value, field) {
|
|
387
|
+
if (typeof value !== "number" || !Number.isInteger(value))
|
|
388
|
+
throw new Error(`browser_use.browser_use ${field} must be an integer`);
|
|
389
|
+
return String(value);
|
|
390
|
+
}
|
|
391
|
+
function pageTargets(targets) {
|
|
392
|
+
return targets.filter((target) => target.type === "page" && Boolean(target.webSocketDebuggerUrl));
|
|
393
|
+
}
|
|
394
|
+
function toBrowserUseTab(target, index = 0) {
|
|
395
|
+
return {
|
|
396
|
+
index,
|
|
397
|
+
tabId: target.id.slice(-4),
|
|
398
|
+
targetId: target.id,
|
|
399
|
+
title: target.title,
|
|
400
|
+
url: target.url,
|
|
401
|
+
type: target.type,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function selectTarget(targets, tabId) {
|
|
405
|
+
if (!tabId)
|
|
406
|
+
return targets.find((target) => target.url !== "about:blank") ?? targets[0];
|
|
407
|
+
return targets.find((target) => target.id === tabId || target.id.endsWith(tabId));
|
|
408
|
+
}
|
|
409
|
+
async function captureScreenshot(client, fullPage, timeoutMs) {
|
|
410
|
+
await client.send("Page.enable", undefined, timeoutMs);
|
|
411
|
+
const params = { format: "png", fromSurface: true, captureBeyondViewport: fullPage };
|
|
412
|
+
if (fullPage) {
|
|
413
|
+
const metrics = await client.send("Page.getLayoutMetrics", undefined, timeoutMs);
|
|
414
|
+
const width = metrics.cssContentSize?.width;
|
|
415
|
+
const height = metrics.cssContentSize?.height;
|
|
416
|
+
if (typeof width === "number" && typeof height === "number") {
|
|
417
|
+
params.clip = { x: 0, y: 0, width, height, scale: 1 };
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
const response = await client.send("Page.captureScreenshot", params, timeoutMs);
|
|
421
|
+
if (typeof response.data !== "string" || response.data.length === 0)
|
|
422
|
+
throw new Error("CDP screenshot returned no image data");
|
|
423
|
+
return response.data;
|
|
424
|
+
}
|
|
425
|
+
function parseJsonOrText(stdout) {
|
|
426
|
+
const text = stdout.trim();
|
|
427
|
+
if (!text)
|
|
428
|
+
return "";
|
|
429
|
+
try {
|
|
430
|
+
return JSON.parse(text);
|
|
431
|
+
}
|
|
432
|
+
catch {
|
|
433
|
+
return text;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
function toolResult(result) {
|
|
437
|
+
return {
|
|
438
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
439
|
+
details: result,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function nodeReplToolResult(result) {
|
|
443
|
+
const lines = [`status: ${result.status}`];
|
|
444
|
+
if ("executionCount" in result)
|
|
445
|
+
lines.push(`executionCount: ${result.executionCount}`, `durationMs: ${result.durationMs}`);
|
|
446
|
+
if ("reset" in result)
|
|
447
|
+
lines.push(`reset: ${result.reset}`);
|
|
448
|
+
if ("stdout" in result && result.stdout)
|
|
449
|
+
lines.push("", "stdout:", result.stdout);
|
|
450
|
+
if ("stderr" in result && result.stderr)
|
|
451
|
+
lines.push("", "stderr:", result.stderr);
|
|
452
|
+
if ("result" in result && result.result !== undefined && result.result !== null)
|
|
453
|
+
lines.push("", "result:", JSON.stringify(result.result, null, 2));
|
|
454
|
+
if (result.error)
|
|
455
|
+
lines.push("", "error:", JSON.stringify(result.error, null, 2));
|
|
456
|
+
return {
|
|
457
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
458
|
+
details: result,
|
|
459
|
+
isError: result.status !== "ok",
|
|
460
|
+
};
|
|
461
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pasko70/pibo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
"packages/workflows",
|
|
@@ -69,6 +69,8 @@
|
|
|
69
69
|
"@uiw/react-json-view": "^2.0.0-alpha.41",
|
|
70
70
|
"@vscode/ripgrep": "^1.18.0",
|
|
71
71
|
"@xyflow/react": "12.10.2",
|
|
72
|
+
"acorn": "8.16.0",
|
|
73
|
+
"acorn-walk": "8.3.4",
|
|
72
74
|
"better-auth": "^1.6.9",
|
|
73
75
|
"commander": "^14.0.3",
|
|
74
76
|
"ink": "^7.0.3",
|