pi-agent-browser-native 0.2.31 → 0.2.32
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 +18 -0
- package/README.md +41 -6
- package/docs/ARCHITECTURE.md +10 -8
- package/docs/COMMAND_REFERENCE.md +56 -9
- package/docs/ELECTRON.md +368 -0
- package/docs/RELEASE.md +30 -2
- package/docs/REQUIREMENTS.md +4 -2
- package/docs/SUPPORT_MATRIX.md +8 -5
- package/docs/TOOL_CONTRACT.md +172 -19
- package/extensions/agent-browser/index.ts +2225 -159
- package/extensions/agent-browser/lib/electron/cleanup.ts +287 -0
- package/extensions/agent-browser/lib/electron/discovery.ts +717 -0
- package/extensions/agent-browser/lib/electron/launch.ts +553 -0
- package/extensions/agent-browser/lib/playbook.ts +7 -6
- package/extensions/agent-browser/lib/results/presentation.ts +37 -7
- package/extensions/agent-browser/lib/results/shared.ts +88 -0
- package/extensions/agent-browser/lib/temp.ts +26 -0
- package/package.json +5 -4
|
@@ -25,10 +25,12 @@ export type AgentBrowserSuccessCategory = "artifact-saved" | "artifact-unverifie
|
|
|
25
25
|
|
|
26
26
|
export type AgentBrowserFailureCategory =
|
|
27
27
|
| "aborted"
|
|
28
|
+
| "cleanup-failed"
|
|
28
29
|
| "confirmation-required"
|
|
29
30
|
| "download-not-verified"
|
|
30
31
|
| "missing-binary"
|
|
31
32
|
| "parse-failure"
|
|
33
|
+
| "policy-blocked"
|
|
32
34
|
| "qa-failure"
|
|
33
35
|
| "selector-not-found"
|
|
34
36
|
| "selector-unsupported"
|
|
@@ -60,6 +62,12 @@ export interface AgentBrowserNextAction {
|
|
|
60
62
|
id: string;
|
|
61
63
|
params?: {
|
|
62
64
|
args?: string[];
|
|
65
|
+
electron?: {
|
|
66
|
+
action: "cleanup" | "list" | "launch" | "probe" | "status";
|
|
67
|
+
all?: boolean;
|
|
68
|
+
handoff?: "connect" | "snapshot" | "tabs";
|
|
69
|
+
launchId?: string;
|
|
70
|
+
};
|
|
63
71
|
networkSourceLookup?: {
|
|
64
72
|
filter?: string;
|
|
65
73
|
requestId?: string;
|
|
@@ -359,6 +367,8 @@ export function classifyAgentBrowserFailureCategory(options: {
|
|
|
359
367
|
if (/ENOENT|not found on PATH|could not find.*agent-browser|agent-browser is required but was not found/i.test(text)) return "missing-binary";
|
|
360
368
|
if (options.parseError || /invalid JSON|missing boolean success|success field must be boolean|returned no JSON output/i.test(text)) return "parse-failure";
|
|
361
369
|
if (/aborted/i.test(text)) return "aborted";
|
|
370
|
+
if (/policy[- ]blocked|blocked by caller policy|caller deny policy|caller allow policy/i.test(text)) return "policy-blocked";
|
|
371
|
+
if (/cleanup failed|cleanup.*partial|partial cleanup|remaining resources/i.test(text)) return "cleanup-failed";
|
|
362
372
|
if (options.tabDrift || /could not re-select the intended tab|about:blank|selected tab looks wrong|tab drift|tab.*wrong/i.test(text)) return "tab-drift";
|
|
363
373
|
if (/\bUnknown ref\b|\bstale ref\b|@ref may be stale|\bref\b.*\b(?:not found|missing|expired)\b/i.test(text)) return "stale-ref";
|
|
364
374
|
if (usedRef && /could not locate element|element not found|no element/i.test(text)) return "stale-ref";
|
|
@@ -424,6 +434,22 @@ function buildArtifactVerificationAction(artifact: FileArtifactMetadata): AgentB
|
|
|
424
434
|
};
|
|
425
435
|
}
|
|
426
436
|
|
|
437
|
+
function buildElectronToolAction(options: {
|
|
438
|
+
action: "cleanup" | "probe" | "status";
|
|
439
|
+
id: string;
|
|
440
|
+
launchId: string;
|
|
441
|
+
reason: string;
|
|
442
|
+
safety?: string;
|
|
443
|
+
}): AgentBrowserNextAction {
|
|
444
|
+
return {
|
|
445
|
+
id: options.id,
|
|
446
|
+
params: { electron: { action: options.action, launchId: options.launchId } },
|
|
447
|
+
reason: options.reason,
|
|
448
|
+
...(options.safety ? { safety: options.safety } : {}),
|
|
449
|
+
tool: "agent_browser",
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
427
453
|
const MUTATING_COMMANDS = new Set([
|
|
428
454
|
"back",
|
|
429
455
|
"check",
|
|
@@ -465,12 +491,74 @@ export function buildAgentBrowserNextActions(options: {
|
|
|
465
491
|
args?: string[];
|
|
466
492
|
command?: string;
|
|
467
493
|
confirmationId?: string;
|
|
494
|
+
electron?: {
|
|
495
|
+
launchId?: string;
|
|
496
|
+
sessionName?: string;
|
|
497
|
+
status?: "active" | "cleaned" | "dead" | "failed" | "partial" | "succeeded";
|
|
498
|
+
};
|
|
468
499
|
failureCategory?: AgentBrowserFailureCategory;
|
|
469
500
|
resultCategory: AgentBrowserResultCategory;
|
|
470
501
|
savedFilePath?: string;
|
|
471
502
|
successCategory?: AgentBrowserSuccessCategory;
|
|
472
503
|
}): AgentBrowserNextAction[] | undefined {
|
|
473
504
|
const actions: AgentBrowserNextAction[] = [];
|
|
505
|
+
if (options.electron?.launchId) {
|
|
506
|
+
const { launchId, sessionName, status } = options.electron;
|
|
507
|
+
if (options.resultCategory === "success" && status !== "cleaned") {
|
|
508
|
+
actions.push(
|
|
509
|
+
buildElectronToolAction({
|
|
510
|
+
action: "status",
|
|
511
|
+
id: "status-electron-launch",
|
|
512
|
+
launchId,
|
|
513
|
+
reason: "Check the wrapper-tracked Electron launch liveness and current CDP targets without mutating the app.",
|
|
514
|
+
}),
|
|
515
|
+
buildElectronToolAction({
|
|
516
|
+
action: "probe",
|
|
517
|
+
id: "probe-electron-launch",
|
|
518
|
+
launchId,
|
|
519
|
+
reason: "Probe the attached Electron managed session and carry the wrapper launchId for follow-up diagnostics.",
|
|
520
|
+
}),
|
|
521
|
+
buildElectronToolAction({
|
|
522
|
+
action: "cleanup",
|
|
523
|
+
id: "cleanup-electron-launch",
|
|
524
|
+
launchId,
|
|
525
|
+
reason: "Clean the wrapper-owned Electron process and isolated userDataDir when the run is complete.",
|
|
526
|
+
safety: "Only operates on the launchId created by electron.launch; explicit artifacts and manually launched apps remain host-owned.",
|
|
527
|
+
}),
|
|
528
|
+
);
|
|
529
|
+
if (sessionName) {
|
|
530
|
+
actions.push(
|
|
531
|
+
buildNextToolAction({
|
|
532
|
+
args: ["--session", sessionName, "tab", "list"],
|
|
533
|
+
id: "list-electron-tabs",
|
|
534
|
+
reason: "Inspect attached Electron page/webview targets before choosing the active tab.",
|
|
535
|
+
}),
|
|
536
|
+
buildNextToolAction({
|
|
537
|
+
args: ["--session", sessionName, "snapshot", "-i"],
|
|
538
|
+
id: "snapshot-electron-session",
|
|
539
|
+
reason: "Refresh interactive refs for the attached Electron session.",
|
|
540
|
+
safety: "Use current Electron refs only after a fresh snapshot for this session.",
|
|
541
|
+
}),
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
} else if (options.resultCategory === "failure" && options.failureCategory === "cleanup-failed") {
|
|
545
|
+
actions.push(
|
|
546
|
+
buildElectronToolAction({
|
|
547
|
+
action: "status",
|
|
548
|
+
id: "status-electron-launch",
|
|
549
|
+
launchId,
|
|
550
|
+
reason: "Inspect which wrapper-tracked Electron resources remain after partial cleanup.",
|
|
551
|
+
}),
|
|
552
|
+
buildElectronToolAction({
|
|
553
|
+
action: "cleanup",
|
|
554
|
+
id: "retry-electron-cleanup",
|
|
555
|
+
launchId,
|
|
556
|
+
reason: "Retry cleanup for the same wrapper-owned Electron launch after reviewing remaining resources.",
|
|
557
|
+
safety: "Only retry for the same launchId; do not use cleanup for manually launched Electron apps.",
|
|
558
|
+
}),
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
474
562
|
if (options.resultCategory === "success") {
|
|
475
563
|
if (options.command === "open") {
|
|
476
564
|
actions.push(buildNextToolAction({
|
|
@@ -390,6 +390,32 @@ export async function writeSecureTempFile(options: {
|
|
|
390
390
|
return path;
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
+
export async function createSecureTempDirectory(prefix: string): Promise<string> {
|
|
394
|
+
const tempRoot = await getSessionTempRoot();
|
|
395
|
+
await assertSecureTempRootBudget(tempRoot, 0);
|
|
396
|
+
const directory = await mkdtemp(join(tempRoot, prefix));
|
|
397
|
+
await chmod(directory, 0o700).catch(() => undefined);
|
|
398
|
+
await refreshSecureTempRootLease(tempRoot).catch(() => undefined);
|
|
399
|
+
return directory;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export async function getSecureTempChildDirectoryValidationError(path: string, childPrefix: string): Promise<string | undefined> {
|
|
403
|
+
const parentDirectory = dirname(path);
|
|
404
|
+
const childName = path.slice(parentDirectory.length + 1);
|
|
405
|
+
if (!childName.startsWith(childPrefix)) {
|
|
406
|
+
return `Refusing to remove ${path}; expected wrapper temp child prefix ${childPrefix}.`;
|
|
407
|
+
}
|
|
408
|
+
const ownershipMarker = await readTempRootOwnershipMarker(parentDirectory);
|
|
409
|
+
if (!ownershipMarker) {
|
|
410
|
+
return `Refusing to remove ${path}; parent directory is not a pi-agent-browser owned temp root.`;
|
|
411
|
+
}
|
|
412
|
+
const currentUid = getCurrentProcessUid();
|
|
413
|
+
if (currentUid !== undefined && ownershipMarker.ownerUid !== undefined && ownershipMarker.ownerUid !== currentUid) {
|
|
414
|
+
return `Refusing to remove ${path}; parent temp root is owned by uid ${ownershipMarker.ownerUid}, not current uid ${currentUid}.`;
|
|
415
|
+
}
|
|
416
|
+
return undefined;
|
|
417
|
+
}
|
|
418
|
+
|
|
393
419
|
export async function writePersistentSessionArtifactFile(options: {
|
|
394
420
|
content: string | Uint8Array;
|
|
395
421
|
prefix: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-agent-browser-native",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.32",
|
|
4
4
|
"description": "pi extension that exposes agent-browser as a native tool for browser automation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Mitch Fultz (https://github.com/fitchmultz)",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"LICENSE",
|
|
39
39
|
"docs/ARCHITECTURE.md",
|
|
40
40
|
"docs/COMMAND_REFERENCE.md",
|
|
41
|
+
"docs/ELECTRON.md",
|
|
41
42
|
"docs/RELEASE.md",
|
|
42
43
|
"docs/REQUIREMENTS.md",
|
|
43
44
|
"docs/SUPPORT_MATRIX.md",
|
|
@@ -55,9 +56,9 @@
|
|
|
55
56
|
"typebox": "*"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
|
-
"@earendil-works/pi-ai": "^0.75.
|
|
59
|
-
"@earendil-works/pi-coding-agent": "^0.75.
|
|
60
|
-
"@earendil-works/pi-tui": "^0.75.
|
|
59
|
+
"@earendil-works/pi-ai": "^0.75.4",
|
|
60
|
+
"@earendil-works/pi-coding-agent": "^0.75.4",
|
|
61
|
+
"@earendil-works/pi-tui": "^0.75.4",
|
|
61
62
|
"@types/node": "^25.6.1",
|
|
62
63
|
"tsx": "^4.21.0",
|
|
63
64
|
"typebox": "^1.1.38",
|