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.
@@ -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.31",
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.3",
59
- "@earendil-works/pi-coding-agent": "^0.75.3",
60
- "@earendil-works/pi-tui": "^0.75.3",
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",