bladebro 4.0.3 → 4.1.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 +1 -0
- package/package.json +6 -6
- package/pi-extension.ts +64 -17
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bladebro",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"description": "Stealthy and efficient agentic browser driver for AI agents. Few tools, full control, real stealth, max token efficiency.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -48,11 +48,11 @@
|
|
|
48
48
|
"LICENSE"
|
|
49
49
|
],
|
|
50
50
|
"optionalDependencies": {
|
|
51
|
-
"bladebro-linux-x64": "4.
|
|
52
|
-
"bladebro-linux-arm64": "4.
|
|
53
|
-
"bladebro-darwin-x64": "4.
|
|
54
|
-
"bladebro-darwin-arm64": "4.
|
|
55
|
-
"bladebro-windows-x64": "4.
|
|
51
|
+
"bladebro-linux-x64": "4.1.1",
|
|
52
|
+
"bladebro-linux-arm64": "4.1.1",
|
|
53
|
+
"bladebro-darwin-x64": "4.1.1",
|
|
54
|
+
"bladebro-darwin-arm64": "4.1.1",
|
|
55
|
+
"bladebro-windows-x64": "4.1.1"
|
|
56
56
|
},
|
|
57
57
|
"engines": {
|
|
58
58
|
"node": ">=14"
|
package/pi-extension.ts
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
26
26
|
import { Type } from "typebox";
|
|
27
27
|
import { Text } from "@earendil-works/pi-tui";
|
|
28
|
+
import { StringDecoder } from "node:string_decoder";
|
|
28
29
|
import { spawn, type ChildProcess } from "node:child_process";
|
|
29
30
|
import { existsSync } from "node:fs";
|
|
30
31
|
import { dirname, join } from "node:path";
|
|
@@ -247,16 +248,21 @@ class McpStdio {
|
|
|
247
248
|
private pending = new Map<number, PendingReq>();
|
|
248
249
|
private buffer = "";
|
|
249
250
|
private alive = false;
|
|
251
|
+
private decoder = new StringDecoder("utf8");
|
|
250
252
|
|
|
251
253
|
start(binaryPath: string): Promise<void> {
|
|
252
254
|
return new Promise((resolve, reject) => {
|
|
253
|
-
this.
|
|
255
|
+
this.buffer = "";
|
|
256
|
+
this.decoder = new StringDecoder("utf8");
|
|
257
|
+
const proc = this.proc = spawn(binaryPath, ["mcp"], {
|
|
254
258
|
stdio: ["pipe", "pipe", "pipe"],
|
|
255
259
|
env: { ...process.env },
|
|
256
260
|
});
|
|
257
261
|
|
|
258
262
|
const onExit = () => {
|
|
263
|
+
if (this.proc !== proc) return;
|
|
259
264
|
this.alive = false;
|
|
265
|
+
this.proc = null;
|
|
260
266
|
for (const [, p] of this.pending) {
|
|
261
267
|
clearTimeout(p.timer);
|
|
262
268
|
p.reject(new Error("bladebro process exited"));
|
|
@@ -264,7 +270,12 @@ class McpStdio {
|
|
|
264
270
|
this.pending.clear();
|
|
265
271
|
};
|
|
266
272
|
|
|
267
|
-
|
|
273
|
+
proc.on("exit", onExit);
|
|
274
|
+
proc.stdin!.on("error", (err) => {
|
|
275
|
+
onExit();
|
|
276
|
+
proc.kill();
|
|
277
|
+
reject(err);
|
|
278
|
+
});
|
|
268
279
|
this.proc.on("error", (err) => {
|
|
269
280
|
onExit();
|
|
270
281
|
reject(err);
|
|
@@ -292,12 +303,25 @@ class McpStdio {
|
|
|
292
303
|
this.alive = true;
|
|
293
304
|
resolve();
|
|
294
305
|
})
|
|
295
|
-
.catch(
|
|
306
|
+
.catch((err) => {
|
|
307
|
+
onExit();
|
|
308
|
+
proc.kill();
|
|
309
|
+
reject(err);
|
|
310
|
+
});
|
|
296
311
|
});
|
|
297
312
|
}
|
|
298
313
|
|
|
299
314
|
private onData(data: Buffer): void {
|
|
300
|
-
this.buffer +=
|
|
315
|
+
this.buffer += this.decoder.write(data);
|
|
316
|
+
if (Buffer.byteLength(this.buffer, "utf8") > 64 * 1024 * 1024) {
|
|
317
|
+
for (const [, p] of this.pending) {
|
|
318
|
+
clearTimeout(p.timer);
|
|
319
|
+
p.reject(new Error("bladebro MCP response exceeds 64 MiB"));
|
|
320
|
+
}
|
|
321
|
+
this.pending.clear();
|
|
322
|
+
this.proc?.kill();
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
301
325
|
let idx: number;
|
|
302
326
|
while ((idx = this.buffer.indexOf("\n")) >= 0) {
|
|
303
327
|
const line = this.buffer.slice(0, idx).trim();
|
|
@@ -308,18 +332,35 @@ class McpStdio {
|
|
|
308
332
|
|
|
309
333
|
private onMessage(line: string): void {
|
|
310
334
|
let msg: any;
|
|
311
|
-
try { msg = JSON.parse(line); } catch {
|
|
335
|
+
try { msg = JSON.parse(line); } catch {
|
|
336
|
+
for (const [, p] of this.pending) {
|
|
337
|
+
clearTimeout(p.timer);
|
|
338
|
+
p.reject(new Error("Invalid MCP JSON from bladebro"));
|
|
339
|
+
}
|
|
340
|
+
this.pending.clear();
|
|
341
|
+
this.proc?.kill();
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (!msg || typeof msg !== "object" || Array.isArray(msg)) {
|
|
345
|
+
this.proc?.kill();
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
312
348
|
if (msg.id !== undefined && this.pending.has(msg.id)) {
|
|
313
349
|
const entry = this.pending.get(msg.id)!;
|
|
314
350
|
this.pending.delete(msg.id);
|
|
315
351
|
clearTimeout(entry.timer);
|
|
316
|
-
if (msg.error) entry.reject(msg.error);
|
|
352
|
+
if (msg.error) entry.reject(new Error(msg.error.message || "MCP request failed"));
|
|
353
|
+
else if (!("result" in msg)) entry.reject(new Error("MCP response missing result"));
|
|
317
354
|
else entry.resolve(msg.result);
|
|
318
355
|
}
|
|
319
356
|
}
|
|
320
357
|
|
|
321
358
|
private request(method: string, params: any, timeoutMs = 180000): Promise<any> {
|
|
322
359
|
return new Promise((resolve, reject) => {
|
|
360
|
+
if (!this.proc || this.proc.exitCode !== null || this.proc.signalCode !== null || !this.proc.stdin?.writable) {
|
|
361
|
+
reject(new Error("bladebro process is not running"));
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
323
364
|
const id = this.nextId++;
|
|
324
365
|
const timer = setTimeout(() => {
|
|
325
366
|
this.pending.delete(id);
|
|
@@ -348,18 +389,23 @@ class McpStdio {
|
|
|
348
389
|
isAlive(): boolean { return this.alive && this.proc !== null; }
|
|
349
390
|
|
|
350
391
|
async stop(): Promise<void> {
|
|
351
|
-
|
|
392
|
+
const proc = this.proc;
|
|
393
|
+
if (!proc) return;
|
|
352
394
|
this.alive = false;
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
395
|
+
for (const [, p] of this.pending) {
|
|
396
|
+
clearTimeout(p.timer);
|
|
397
|
+
p.reject(new Error("bladebro session stopped"));
|
|
398
|
+
}
|
|
399
|
+
this.pending.clear();
|
|
400
|
+
if (proc.exitCode === null && proc.signalCode === null) {
|
|
401
|
+
await new Promise<void>((resolve) => {
|
|
402
|
+
const t = setTimeout(() => { proc.kill("SIGKILL"); resolve(); }, 5000);
|
|
403
|
+
proc.once("exit", () => { clearTimeout(t); resolve(); });
|
|
404
|
+
proc.stdin?.end();
|
|
405
|
+
proc.kill("SIGTERM");
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
if (this.proc === proc) this.proc = null;
|
|
363
409
|
}
|
|
364
410
|
}
|
|
365
411
|
|
|
@@ -452,6 +498,7 @@ export default function bladebroExtension(pi: ExtensionAPI) {
|
|
|
452
498
|
},
|
|
453
499
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
454
500
|
const c = await ensureClient();
|
|
501
|
+
if (_signal?.aborted) throw new Error("Bladebro tool call cancelled before dispatch");
|
|
455
502
|
const result = await c.callTool(tool.name, params);
|
|
456
503
|
return {
|
|
457
504
|
content: result.content || [],
|