pi-agent-browser-native 0.6.14 → 0.6.16
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 +36 -0
- package/README.md +21 -9
- package/dist/extensions/agent-browser/index.js +79 -4
- package/dist/extensions/agent-browser/lib/electron/cleanup.js +21 -6
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +47 -11
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +37 -4
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +13 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +4 -3
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +0 -17
- package/dist/extensions/agent-browser/lib/playbook.js +5 -5
- package/dist/extensions/agent-browser/lib/process-identity.js +2 -1
- package/dist/extensions/agent-browser/lib/process.js +56 -20
- package/dist/extensions/agent-browser/lib/runtime.js +1 -1
- package/dist/extensions/agent-browser/lib/windows-stock-launcher.js +70 -0
- package/docs/ARCHITECTURE.md +17 -4
- package/docs/COMMAND_REFERENCE.md +6 -6
- package/docs/ELECTRON.md +4 -2
- package/docs/RELEASE.md +35 -1
- package/docs/REQUIREMENTS.md +1 -1
- package/docs/SUPPORT_MATRIX.md +18 -6
- package/docs/TOOL_CONTRACT.md +13 -9
- package/docs/platform-smoke.md +5 -3
- package/package.json +10 -6
- package/scripts/config.mjs +1 -1
- package/scripts/doctor.mjs +2 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +0 -142
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { mkdir, stat, writeFile } from "node:fs/promises";
|
|
2
|
-
import { dirname, resolve } from "node:path";
|
|
3
|
-
import { isRecord } from "../../../parsing.js";
|
|
4
|
-
import { buildAgentBrowserResultCategoryDetails } from "../../../results/categories.js";
|
|
5
|
-
import { formatSessionArtifactRetentionSummary, mergeSessionArtifactManifest } from "../../../results/artifact-manifest.js";
|
|
6
|
-
import { redactSensitiveText } from "../../../runtime.js";
|
|
7
|
-
import { buildSessionDetailFields, runSessionCommandData } from "../session-state.js";
|
|
8
|
-
const DIRECT_ANCHOR_DOWNLOAD_MAX_BYTES = 2 * 1024 * 1024;
|
|
9
|
-
function getDirectDownloadRequest(commandTokens) {
|
|
10
|
-
if (commandTokens[0] !== "download" || commandTokens.length !== 3)
|
|
11
|
-
return undefined;
|
|
12
|
-
const selector = commandTokens[1];
|
|
13
|
-
const path = commandTokens[2];
|
|
14
|
-
if (!selector || !path || selector.startsWith("@"))
|
|
15
|
-
return undefined;
|
|
16
|
-
return { path, selector };
|
|
17
|
-
}
|
|
18
|
-
function buildAnchorDownloadProbe(selector) {
|
|
19
|
-
return `(async () => {\n const selector = ${JSON.stringify(selector)};\n const maxBytes = ${DIRECT_ANCHOR_DOWNLOAD_MAX_BYTES};\n const isLoopbackHttpUrl = (url) => (url.protocol === "http:" || url.protocol === "https:") && (url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]");\n const element = document.querySelector(selector);\n const anchor = element?.closest?.("a[href]");\n const pageUrl = location.href;\n const page = new URL(pageUrl);\n if (!anchor) return { status: "no-anchor", pageUrl };\n const href = anchor.href;\n const anchorUrl = new URL(href, pageUrl);\n if (!isLoopbackHttpUrl(page)) return { download: anchor.getAttribute("download") || "", href, pageUrl, status: "not-loopback-page" };\n if (anchorUrl.origin !== page.origin) return { download: anchor.getAttribute("download") || "", href, pageUrl, status: "not-same-origin" };\n if (!isLoopbackHttpUrl(anchorUrl)) return { download: anchor.getAttribute("download") || "", href, pageUrl, status: "not-loopback-href" };\n const response = await fetch(anchorUrl.href, { credentials: "include", redirect: "manual" });\n if (!response.ok) return { download: anchor.getAttribute("download") || "", href, pageUrl, responseUrl: response.url, status: "fetch-failed", statusCode: response.status };\n const responseUrl = new URL(response.url);\n if (!isLoopbackHttpUrl(responseUrl) || responseUrl.origin !== page.origin) return { download: anchor.getAttribute("download") || "", href, pageUrl, responseUrl: response.url, status: "not-loopback-response" };\n const buffer = await response.arrayBuffer();\n if (buffer.byteLength > maxBytes) return { download: anchor.getAttribute("download") || "", href, pageUrl, responseUrl: response.url, sizeBytes: buffer.byteLength, status: "too-large" };\n const bytes = new Uint8Array(buffer);\n let binary = "";\n for (let index = 0; index < bytes.length; index += 32768) binary += String.fromCharCode(...bytes.subarray(index, index + 32768));\n return { bodyBase64: btoa(binary), contentType: response.headers.get("content-type") || "", download: anchor.getAttribute("download") || "", href, pageUrl, responseUrl: response.url, sizeBytes: buffer.byteLength, status: "fetched-anchor" };\n})()`;
|
|
20
|
-
}
|
|
21
|
-
function isLoopbackHttpUrl(url) {
|
|
22
|
-
return (url.protocol === "http:" || url.protocol === "https:") && (url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]");
|
|
23
|
-
}
|
|
24
|
-
export async function tryDirectAnchorDownload(options) {
|
|
25
|
-
const request = getDirectDownloadRequest(options.commandTokens);
|
|
26
|
-
if (!request || !options.sessionName)
|
|
27
|
-
return undefined;
|
|
28
|
-
try {
|
|
29
|
-
const probeData = await runSessionCommandData({
|
|
30
|
-
args: ["eval", "--stdin"],
|
|
31
|
-
cwd: options.cwd,
|
|
32
|
-
namespace: options.namespace,
|
|
33
|
-
sessionName: options.sessionName,
|
|
34
|
-
signal: options.signal,
|
|
35
|
-
stdin: buildAnchorDownloadProbe(request.selector),
|
|
36
|
-
});
|
|
37
|
-
const probe = isRecord(probeData) && isRecord(probeData.result) ? probeData.result : probeData;
|
|
38
|
-
if (!isRecord(probe) || probe.status !== "fetched-anchor" || typeof probe.href !== "string" || typeof probe.pageUrl !== "string" || typeof probe.bodyBase64 !== "string")
|
|
39
|
-
return undefined;
|
|
40
|
-
const href = new URL(probe.href);
|
|
41
|
-
const pageUrl = new URL(probe.pageUrl);
|
|
42
|
-
const responseUrl = typeof probe.responseUrl === "string" ? new URL(probe.responseUrl) : href;
|
|
43
|
-
if (!isLoopbackHttpUrl(pageUrl) || !isLoopbackHttpUrl(href) || !isLoopbackHttpUrl(responseUrl) || href.origin !== pageUrl.origin || responseUrl.origin !== pageUrl.origin)
|
|
44
|
-
return undefined;
|
|
45
|
-
const body = Buffer.from(probe.bodyBase64, "base64");
|
|
46
|
-
if (body.byteLength > DIRECT_ANCHOR_DOWNLOAD_MAX_BYTES)
|
|
47
|
-
return undefined;
|
|
48
|
-
if (typeof probe.sizeBytes === "number" && probe.sizeBytes !== body.byteLength)
|
|
49
|
-
return undefined;
|
|
50
|
-
const absolutePath = resolve(options.cwd, request.path);
|
|
51
|
-
await mkdir(dirname(absolutePath), { recursive: true });
|
|
52
|
-
await writeFile(absolutePath, body);
|
|
53
|
-
const fileStat = await stat(absolutePath);
|
|
54
|
-
const mediaType = typeof probe.contentType === "string" && probe.contentType.length > 0 ? probe.contentType : undefined;
|
|
55
|
-
const manifestEntry = {
|
|
56
|
-
absolutePath,
|
|
57
|
-
command: "download",
|
|
58
|
-
createdAtMs: Date.now(),
|
|
59
|
-
cwd: options.cwd,
|
|
60
|
-
exists: true,
|
|
61
|
-
kind: "download",
|
|
62
|
-
mediaType,
|
|
63
|
-
path: absolutePath,
|
|
64
|
-
requestedPath: request.path,
|
|
65
|
-
retentionState: "live",
|
|
66
|
-
session: options.sessionName,
|
|
67
|
-
sizeBytes: fileStat.size,
|
|
68
|
-
storageScope: "explicit-path",
|
|
69
|
-
};
|
|
70
|
-
const artifactManifest = mergeSessionArtifactManifest({ base: options.artifactManifest, entries: [manifestEntry] });
|
|
71
|
-
const artifactRetentionSummary = artifactManifest ? formatSessionArtifactRetentionSummary(artifactManifest) : undefined;
|
|
72
|
-
const artifact = {
|
|
73
|
-
absolutePath,
|
|
74
|
-
artifactType: "download",
|
|
75
|
-
command: "download",
|
|
76
|
-
cwd: options.cwd,
|
|
77
|
-
exists: true,
|
|
78
|
-
kind: "download",
|
|
79
|
-
mediaType,
|
|
80
|
-
path: absolutePath,
|
|
81
|
-
requestedPath: request.path,
|
|
82
|
-
session: options.sessionName,
|
|
83
|
-
sizeBytes: fileStat.size,
|
|
84
|
-
status: "saved",
|
|
85
|
-
};
|
|
86
|
-
const artifactVerification = {
|
|
87
|
-
artifacts: [{
|
|
88
|
-
absolutePath,
|
|
89
|
-
exists: true,
|
|
90
|
-
kind: "download",
|
|
91
|
-
mediaType,
|
|
92
|
-
path: absolutePath,
|
|
93
|
-
requestedPath: request.path,
|
|
94
|
-
sizeBytes: fileStat.size,
|
|
95
|
-
state: "verified",
|
|
96
|
-
status: "saved",
|
|
97
|
-
}],
|
|
98
|
-
missingCount: 0,
|
|
99
|
-
pendingCount: 0,
|
|
100
|
-
unverifiedCount: 0,
|
|
101
|
-
verified: true,
|
|
102
|
-
verifiedCount: 1,
|
|
103
|
-
};
|
|
104
|
-
const savedFile = { command: "download", kind: "download", metadata: { download: probe.download, href: redactSensitiveText(href.href), method: "direct-anchor-fetch" }, path: absolutePath };
|
|
105
|
-
return {
|
|
106
|
-
artifactManifest,
|
|
107
|
-
result: {
|
|
108
|
-
content: [{
|
|
109
|
-
type: "text",
|
|
110
|
-
text: [
|
|
111
|
-
`Download completed: ${absolutePath}`,
|
|
112
|
-
`Requested path: ${request.path}`,
|
|
113
|
-
`Source: ${redactSensitiveText(href.href)}`,
|
|
114
|
-
`Size: ${fileStat.size} bytes`,
|
|
115
|
-
"Method: direct anchor fetch before upstream download fallback.",
|
|
116
|
-
].join("\n"),
|
|
117
|
-
}],
|
|
118
|
-
details: {
|
|
119
|
-
args: options.redactedArgs,
|
|
120
|
-
artifactManifest,
|
|
121
|
-
artifactRetentionSummary,
|
|
122
|
-
artifacts: [artifact],
|
|
123
|
-
artifactVerification,
|
|
124
|
-
command: "download",
|
|
125
|
-
compatibilityWorkaround: options.compatibilityWorkaround,
|
|
126
|
-
downloadRecovery: { href: redactSensitiveText(href.href), method: "direct-anchor-fetch", selector: request.selector },
|
|
127
|
-
effectiveArgs: options.effectiveArgs,
|
|
128
|
-
savedFile,
|
|
129
|
-
savedFilePath: absolutePath,
|
|
130
|
-
sessionMode: options.sessionMode,
|
|
131
|
-
...buildAgentBrowserResultCategoryDetails({ artifacts: [artifact], args: options.effectiveArgs, command: "download", savedFile, succeeded: true }),
|
|
132
|
-
...buildSessionDetailFields(options.sessionName, options.usedImplicitSession, options.namespace, options.managedSessionRestoreDisabled()),
|
|
133
|
-
summary: `Download completed: ${absolutePath}`,
|
|
134
|
-
},
|
|
135
|
-
isError: false,
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
catch {
|
|
140
|
-
return undefined;
|
|
141
|
-
}
|
|
142
|
-
}
|