pi-agent-browser-native 0.2.72 → 0.2.75
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 +33 -0
- package/README.md +14 -12
- package/dist/extensions/agent-browser/index.js +148 -16
- package/dist/extensions/agent-browser/lib/argv-grammar.js +122 -0
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +11 -0
- package/dist/extensions/agent-browser/lib/electron/cdp.js +2 -2
- package/dist/extensions/agent-browser/lib/electron/launch.js +48 -12
- package/dist/extensions/agent-browser/lib/input-modes/params.js +96 -98
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +88 -2
- package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +22 -0
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +432 -0
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +374 -0
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +367 -0
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +589 -0
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +299 -0
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +35 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +9 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +40 -22
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +15 -6
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +54 -33
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +182 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +629 -430
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +148 -57
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +30 -40
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +102 -19
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +13 -1
- package/dist/extensions/agent-browser/lib/playbook.js +9 -8
- package/dist/extensions/agent-browser/lib/process-identity.js +82 -0
- package/dist/extensions/agent-browser/lib/process.js +271 -34
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +5 -3
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +2 -1
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +35 -12
- package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +42 -0
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +7 -0
- package/dist/extensions/agent-browser/lib/runtime.js +114 -107
- package/dist/extensions/agent-browser/lib/session-page-state.js +48 -17
- package/dist/extensions/agent-browser/lib/temp.js +13 -25
- package/docs/ARCHITECTURE.md +10 -9
- package/docs/COMMAND_REFERENCE.md +43 -23
- package/docs/ELECTRON.md +10 -10
- package/docs/RELEASE.md +3 -2
- package/docs/REQUIREMENTS.md +1 -1
- package/docs/SUPPORT_MATRIX.md +19 -18
- package/docs/TOOL_CONTRACT.md +29 -26
- package/docs/platform-smoke.md +2 -2
- package/package.json +1 -1
- package/platform-smoke.config.mjs +1 -1
- package/scripts/agent-browser-capability-baseline.mjs +11 -3
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Purpose: Resolve and secure the filesystem identity used by automatic managed-session restore.
|
|
3
|
+
* Responsibilities: Bind restore keys to one Git checkout generation, validate trusted HOME ancestry, and prepare owner-only upstream state directories.
|
|
4
|
+
* Scope: Filesystem policy only; argv/session ownership and snapshot retention live in sibling modules.
|
|
5
|
+
*/
|
|
6
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
7
|
+
import { linkSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { dirname, isAbsolute, join, parse, resolve, win32 } from "node:path";
|
|
10
|
+
import { canonicalizeAgentBrowserNamespace } from "./argv-grammar.js";
|
|
11
|
+
export { isManagedSessionRestoreKey } from "./managed-session-capabilities.js";
|
|
12
|
+
const MANAGED_SESSION_NAME_PREFIX = "piab-r2-";
|
|
13
|
+
const MANAGED_SESSION_RESTORE_KEY_HASH_LENGTH = 32;
|
|
14
|
+
const PROJECT_GENERATION_MARKER_NAME = "pi-agent-browser-project-generation-v1.json";
|
|
15
|
+
const PROJECT_GENERATION_MARKER_MAX_BYTES = 1_024;
|
|
16
|
+
const projectGenerationCache = new Map();
|
|
17
|
+
function isAbsoluteHome(path, platform) {
|
|
18
|
+
return platform === "win32" ? win32.isAbsolute(path) : isAbsolute(path);
|
|
19
|
+
}
|
|
20
|
+
function currentUid() {
|
|
21
|
+
return typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
22
|
+
}
|
|
23
|
+
function isTrustedPosixDirectory(path, requireCurrentOwner) {
|
|
24
|
+
const uid = currentUid();
|
|
25
|
+
if (uid === undefined)
|
|
26
|
+
return false;
|
|
27
|
+
const root = parse(path).root;
|
|
28
|
+
let cursor = root;
|
|
29
|
+
for (const component of path.slice(root.length).split("/").filter(Boolean)) {
|
|
30
|
+
cursor = join(cursor, component);
|
|
31
|
+
let entry;
|
|
32
|
+
try {
|
|
33
|
+
entry = lstatSync(cursor);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (entry.isSymbolicLink() || !entry.isDirectory())
|
|
39
|
+
return false;
|
|
40
|
+
if (entry.uid !== 0 && entry.uid !== uid)
|
|
41
|
+
return false;
|
|
42
|
+
const writableByOthers = (entry.mode & 0o022) !== 0;
|
|
43
|
+
const rootOwnedStickyDirectory = entry.uid === 0 && (entry.mode & 0o1000) !== 0;
|
|
44
|
+
if (writableByOthers && !rootOwnedStickyDirectory)
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const leaf = lstatSync(path);
|
|
49
|
+
return !requireCurrentOwner || leaf.uid === uid;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function resolveManagedSessionRestoreHome(parentEnv, platform = process.platform) {
|
|
56
|
+
const configuredHome = platform === "win32" ? parentEnv.USERPROFILE : parentEnv.HOME;
|
|
57
|
+
const candidate = configuredHome ?? homedir();
|
|
58
|
+
if (!candidate || candidate.trim() !== candidate || !isAbsoluteHome(candidate, platform))
|
|
59
|
+
return undefined;
|
|
60
|
+
if (platform === "win32")
|
|
61
|
+
return candidate;
|
|
62
|
+
try {
|
|
63
|
+
const canonical = realpathSync(candidate);
|
|
64
|
+
return isTrustedPosixDirectory(canonical, true) ? canonical : undefined;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function ensureOwnerOnlyDirectory(path, platform = process.platform) {
|
|
71
|
+
try {
|
|
72
|
+
try {
|
|
73
|
+
mkdirSync(path, { mode: 0o700 });
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (error.code !== "EEXIST")
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
const entry = lstatSync(path);
|
|
80
|
+
if (entry.isSymbolicLink() || !entry.isDirectory())
|
|
81
|
+
return false;
|
|
82
|
+
if (platform === "win32")
|
|
83
|
+
return true;
|
|
84
|
+
const uid = currentUid();
|
|
85
|
+
return uid !== undefined && entry.uid === uid && (entry.mode & 0o077) === 0;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export function directoryContainsSymlink(path) {
|
|
92
|
+
try {
|
|
93
|
+
return readdirSync(path, { withFileTypes: true }).some((entry) => entry.isSymbolicLink());
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function resolveGitCheckout(cwd, platform) {
|
|
100
|
+
let directory = cwd;
|
|
101
|
+
while (true) {
|
|
102
|
+
const dotGit = join(directory, ".git");
|
|
103
|
+
try {
|
|
104
|
+
const entry = lstatSync(dotGit);
|
|
105
|
+
if (entry.isDirectory() && !entry.isSymbolicLink())
|
|
106
|
+
return { gitDirectory: realpathSync(dotGit), worktreeDirectory: realpathSync(directory) };
|
|
107
|
+
if (!entry.isFile() || entry.isSymbolicLink() || entry.size > PROJECT_GENERATION_MARKER_MAX_BYTES)
|
|
108
|
+
return undefined;
|
|
109
|
+
if (platform !== "win32") {
|
|
110
|
+
const uid = currentUid();
|
|
111
|
+
if (uid === undefined || entry.uid !== uid || (entry.mode & 0o022) !== 0)
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
const match = /^gitdir:\s*(.+)\s*$/i.exec(readFileSync(dotGit, "utf8"));
|
|
115
|
+
if (!match?.[1])
|
|
116
|
+
return undefined;
|
|
117
|
+
return { gitDirectory: realpathSync(resolve(directory, match[1])), worktreeDirectory: realpathSync(directory) };
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
if (error.code !== "ENOENT")
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
const parent = dirname(directory);
|
|
124
|
+
if (parent === directory)
|
|
125
|
+
return undefined;
|
|
126
|
+
directory = parent;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function readProjectGenerationMarker(path, platform) {
|
|
130
|
+
try {
|
|
131
|
+
const entry = lstatSync(path);
|
|
132
|
+
if (entry.isSymbolicLink() || !entry.isFile() || entry.size > PROJECT_GENERATION_MARKER_MAX_BYTES)
|
|
133
|
+
return undefined;
|
|
134
|
+
if (platform !== "win32") {
|
|
135
|
+
const uid = currentUid();
|
|
136
|
+
if (uid === undefined || entry.uid !== uid || (entry.mode & 0o177) !== 0)
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
140
|
+
return parsed.version === 1 && typeof parsed.id === "string" && /^[a-f\d]{8}(?:-[a-f\d]{4}){3}-[a-f\d]{12}$/i.test(parsed.id) ? parsed.id : undefined;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function getDirectoryFilesystemIdentity(path) {
|
|
147
|
+
try {
|
|
148
|
+
const entry = statSync(path, { bigint: true });
|
|
149
|
+
return entry.isDirectory() && entry.dev > 0n && entry.ino > 0n && entry.birthtimeNs > 0n
|
|
150
|
+
? `${entry.dev}:${entry.ino}:${entry.birthtimeNs}`
|
|
151
|
+
: undefined;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function resolveManagedSessionRestoreProjectCheckout(cwd, platform) {
|
|
158
|
+
let canonicalCwd;
|
|
159
|
+
try {
|
|
160
|
+
canonicalCwd = realpathSync(cwd);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
if (platform !== "win32" && !isTrustedPosixDirectory(canonicalCwd, false))
|
|
166
|
+
return undefined;
|
|
167
|
+
const checkout = resolveGitCheckout(canonicalCwd, platform);
|
|
168
|
+
if (!checkout)
|
|
169
|
+
return undefined;
|
|
170
|
+
if (platform !== "win32" && (!isTrustedPosixDirectory(checkout.worktreeDirectory, true)
|
|
171
|
+
|| !isTrustedPosixDirectory(checkout.gitDirectory, true)))
|
|
172
|
+
return undefined;
|
|
173
|
+
return { canonicalCwd, ...checkout };
|
|
174
|
+
}
|
|
175
|
+
export function resolveManagedSessionRestoreCheckoutRoot(cwd, platform = process.platform) {
|
|
176
|
+
return resolveManagedSessionRestoreProjectCheckout(cwd, platform)?.worktreeDirectory;
|
|
177
|
+
}
|
|
178
|
+
function resolveProjectGenerationIdentity(cwd, platform = process.platform) {
|
|
179
|
+
const checkout = resolveManagedSessionRestoreProjectCheckout(cwd, platform);
|
|
180
|
+
if (!checkout)
|
|
181
|
+
return undefined;
|
|
182
|
+
const { canonicalCwd } = checkout;
|
|
183
|
+
const gitFilesystemIdentity = getDirectoryFilesystemIdentity(checkout.gitDirectory);
|
|
184
|
+
const worktreeFilesystemIdentity = getDirectoryFilesystemIdentity(checkout.worktreeDirectory);
|
|
185
|
+
if (!gitFilesystemIdentity || !worktreeFilesystemIdentity)
|
|
186
|
+
return undefined;
|
|
187
|
+
const markerPath = join(checkout.gitDirectory, PROJECT_GENERATION_MARKER_NAME);
|
|
188
|
+
let marker = readProjectGenerationMarker(markerPath, platform);
|
|
189
|
+
const cached = projectGenerationCache.get(canonicalCwd);
|
|
190
|
+
if (cached
|
|
191
|
+
&& cached.gitDirectory === checkout.gitDirectory
|
|
192
|
+
&& cached.worktreeDirectory === checkout.worktreeDirectory
|
|
193
|
+
&& cached.gitFilesystemIdentity === gitFilesystemIdentity
|
|
194
|
+
&& cached.worktreeFilesystemIdentity === worktreeFilesystemIdentity
|
|
195
|
+
&& cached.marker === marker)
|
|
196
|
+
return cached.identity;
|
|
197
|
+
projectGenerationCache.delete(canonicalCwd);
|
|
198
|
+
try {
|
|
199
|
+
if (!marker) {
|
|
200
|
+
const candidatePath = `${markerPath}.candidate-${process.pid}-${randomUUID()}`;
|
|
201
|
+
try {
|
|
202
|
+
writeFileSync(candidatePath, JSON.stringify({ id: randomUUID(), version: 1 }), { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
203
|
+
try {
|
|
204
|
+
linkSync(candidatePath, markerPath);
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
if (error.code !== "EEXIST")
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
try {
|
|
213
|
+
unlinkSync(candidatePath);
|
|
214
|
+
}
|
|
215
|
+
catch { }
|
|
216
|
+
}
|
|
217
|
+
marker = readProjectGenerationMarker(markerPath, platform);
|
|
218
|
+
}
|
|
219
|
+
if (!marker)
|
|
220
|
+
return undefined;
|
|
221
|
+
const identity = `${platform}:${worktreeFilesystemIdentity}:${gitFilesystemIdentity}:${marker}`;
|
|
222
|
+
projectGenerationCache.set(canonicalCwd, {
|
|
223
|
+
gitDirectory: checkout.gitDirectory,
|
|
224
|
+
gitFilesystemIdentity,
|
|
225
|
+
identity,
|
|
226
|
+
marker,
|
|
227
|
+
worktreeDirectory: checkout.worktreeDirectory,
|
|
228
|
+
worktreeFilesystemIdentity,
|
|
229
|
+
});
|
|
230
|
+
return identity;
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
export function hasManagedSessionRestoreProjectIdentity(cwd) {
|
|
237
|
+
return resolveProjectGenerationIdentity(cwd) !== undefined;
|
|
238
|
+
}
|
|
239
|
+
/** Stable for one checkout generation; deliberately changes when a path is replaced by another checkout. */
|
|
240
|
+
export function createManagedSessionRestoreKey(cwd) {
|
|
241
|
+
let canonicalCwd = resolve(cwd);
|
|
242
|
+
try {
|
|
243
|
+
canonicalCwd = realpathSync(canonicalCwd);
|
|
244
|
+
}
|
|
245
|
+
catch { }
|
|
246
|
+
const identity = resolveProjectGenerationIdentity(canonicalCwd);
|
|
247
|
+
const material = identity ?? `unavailable:${canonicalCwd}`;
|
|
248
|
+
const digest = createHash("sha256").update(`restore-v2:${material}`).digest("hex").slice(0, MANAGED_SESSION_RESTORE_KEY_HASH_LENGTH);
|
|
249
|
+
return `${MANAGED_SESSION_NAME_PREFIX}${digest}`;
|
|
250
|
+
}
|
|
251
|
+
function hasValidEncryptionKey(parentEnv) {
|
|
252
|
+
const value = parentEnv.AGENT_BROWSER_ENCRYPTION_KEY;
|
|
253
|
+
return typeof value === "string" && /^[a-f\d]{64}$/i.test(value);
|
|
254
|
+
}
|
|
255
|
+
export function getManagedRestoreSessionsDirectory(home, namespace) {
|
|
256
|
+
const canonicalNamespace = canonicalizeAgentBrowserNamespace(namespace);
|
|
257
|
+
return canonicalNamespace
|
|
258
|
+
? join(home, ".agent-browser", "namespaces", canonicalNamespace, "state", "sessions")
|
|
259
|
+
: join(home, ".agent-browser", "sessions");
|
|
260
|
+
}
|
|
261
|
+
/** Require the upstream 256-bit key format and secure every directory that can receive restore snapshots. */
|
|
262
|
+
export function ensureManagedSessionRestoreStorageIsSecure(parentEnv = process.env, platform = process.platform, namespace) {
|
|
263
|
+
const encryptionKey = parentEnv.AGENT_BROWSER_ENCRYPTION_KEY;
|
|
264
|
+
if (encryptionKey !== undefined && !hasValidEncryptionKey(parentEnv))
|
|
265
|
+
return false;
|
|
266
|
+
if (platform === "win32")
|
|
267
|
+
return hasValidEncryptionKey(parentEnv);
|
|
268
|
+
const home = resolveManagedSessionRestoreHome(parentEnv, platform);
|
|
269
|
+
if (!home)
|
|
270
|
+
return false;
|
|
271
|
+
const root = join(home, ".agent-browser");
|
|
272
|
+
if (!ensureOwnerOnlyDirectory(root, platform))
|
|
273
|
+
return false;
|
|
274
|
+
const canonicalNamespace = canonicalizeAgentBrowserNamespace(namespace);
|
|
275
|
+
const stateComponents = canonicalNamespace
|
|
276
|
+
? ["namespaces", canonicalNamespace, "state", "sessions"]
|
|
277
|
+
: ["sessions"];
|
|
278
|
+
let path = root;
|
|
279
|
+
for (const component of stateComponents) {
|
|
280
|
+
path = join(path, component);
|
|
281
|
+
if (!ensureOwnerOnlyDirectory(path, platform))
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
if (directoryContainsSymlink(path))
|
|
285
|
+
return false;
|
|
286
|
+
const temporaryDirectory = join(path, ".tmp");
|
|
287
|
+
return ensureOwnerOnlyDirectory(temporaryDirectory, platform) && !directoryContainsSymlink(temporaryDirectory);
|
|
288
|
+
}
|
|
289
|
+
export function getManagedSessionRestoreProtectedStorageEnv(restoreEnabled, parentEnv, platform = process.platform) {
|
|
290
|
+
if (!restoreEnabled)
|
|
291
|
+
return {};
|
|
292
|
+
const home = resolveManagedSessionRestoreHome(parentEnv, platform);
|
|
293
|
+
if (!home)
|
|
294
|
+
return {};
|
|
295
|
+
return {
|
|
296
|
+
AGENT_BROWSER_ENCRYPTION_KEY: parentEnv.AGENT_BROWSER_ENCRYPTION_KEY,
|
|
297
|
+
...(platform === "win32" ? { USERPROFILE: home } : { HOME: home }),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
// Mirror upstream commands::shell_words_split so policy inspection sees the same argv.
|
|
2
|
+
export function parseBatchCommandArgument(command) {
|
|
3
|
+
const tokens = [];
|
|
4
|
+
let token = "";
|
|
5
|
+
let inDoubleQuote = false;
|
|
6
|
+
let inSingleQuote = false;
|
|
7
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
8
|
+
const character = command[index];
|
|
9
|
+
if (character === "\\" && !inSingleQuote) {
|
|
10
|
+
const next = command[index + 1];
|
|
11
|
+
if (next !== undefined) {
|
|
12
|
+
token += next;
|
|
13
|
+
index += 1;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
else if (character === '"' && !inSingleQuote) {
|
|
17
|
+
inDoubleQuote = !inDoubleQuote;
|
|
18
|
+
}
|
|
19
|
+
else if (character === "'" && !inDoubleQuote) {
|
|
20
|
+
inSingleQuote = !inSingleQuote;
|
|
21
|
+
}
|
|
22
|
+
else if (character === " " && !inDoubleQuote && !inSingleQuote) {
|
|
23
|
+
if (token !== "") {
|
|
24
|
+
tokens.push(token);
|
|
25
|
+
token = "";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
token += character;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (token !== "")
|
|
33
|
+
tokens.push(token);
|
|
34
|
+
return tokens.length > 0 ? { step: tokens } : { error: "batch command is empty" };
|
|
35
|
+
}
|
|
1
36
|
function validateUserBatchStep(step, index) {
|
|
2
37
|
if (!Array.isArray(step)) {
|
|
3
38
|
return {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { extname, isAbsolute } from "node:path";
|
|
2
|
+
import { GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES, VALUE_FLAGS } from "../../argv-grammar.js";
|
|
3
|
+
const SCREENSHOT_BOOLEAN_FLAGS = new Set(["--annotate", "--full", "-f"]);
|
|
2
4
|
const SCREENSHOT_VALUE_FLAGS = new Set(["--screenshot-dir", "--screenshot-format", "--screenshot-quality"]);
|
|
3
5
|
const SCREENSHOT_IMAGE_EXTENSIONS = new Set([".jpeg", ".jpg", ".png", ".webp"]);
|
|
4
6
|
function isImagePathToken(token) {
|
|
@@ -20,10 +22,15 @@ export function getScreenshotPathTokenIndex(commandTokens) {
|
|
|
20
22
|
}
|
|
21
23
|
if (token.startsWith("-")) {
|
|
22
24
|
const normalizedToken = token.split("=", 1)[0] ?? token;
|
|
23
|
-
if (SCREENSHOT_VALUE_FLAGS.has(normalizedToken) && !token.includes("=")) {
|
|
25
|
+
if ((SCREENSHOT_VALUE_FLAGS.has(normalizedToken) || VALUE_FLAGS.has(normalizedToken)) && !token.includes("=")) {
|
|
24
26
|
index += 1;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (SCREENSHOT_BOOLEAN_FLAGS.has(normalizedToken) || GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES.has(normalizedToken)) {
|
|
30
|
+
if (["true", "false"].includes(commandTokens[index + 1] ?? ""))
|
|
31
|
+
index += 1;
|
|
32
|
+
continue;
|
|
25
33
|
}
|
|
26
|
-
continue;
|
|
27
34
|
}
|
|
28
35
|
positionalIndices.push(index);
|
|
29
36
|
}
|
|
@@ -10,6 +10,7 @@ import { buildVisibleRefFallbackDiagnosticFromSnapshot, getVisibleRefFallbackTar
|
|
|
10
10
|
import { extractRefSnapshotFromData, normalizeComparableUrl } from "../../session-page-state.js";
|
|
11
11
|
import { redactInvocationArgs, redactSensitiveText } from "../../runtime.js";
|
|
12
12
|
import { isRecord } from "../../parsing.js";
|
|
13
|
+
import { getManagedSessionStateAccessValidationError, isFileUrl } from "../../managed-session-state-policy.js";
|
|
13
14
|
import { extractBatchResultCommand, extractNavigationSummaryFromData, extractStringResultField, findElectronLaunchRecordForSession, runSessionCommandData, } from "./session-state.js";
|
|
14
15
|
import { parseValidBatchStepEntries } from "../batch-stdin.js";
|
|
15
16
|
import { getScreenshotPathTokenIndex } from "./artifact-paths.js";
|
|
@@ -19,17 +20,12 @@ export function sleepMs(ms) {
|
|
|
19
20
|
}
|
|
20
21
|
export async function collectNavigationSummary(options) {
|
|
21
22
|
const url = extractStringResultField(await runSessionCommandData({ args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal }), "url");
|
|
23
|
+
if (!url || !/^[a-z][a-z0-9+.-]*:/i.test(url))
|
|
24
|
+
return undefined;
|
|
25
|
+
if (isFileUrl(url))
|
|
26
|
+
return { url };
|
|
22
27
|
const title = extractStringResultField(await runSessionCommandData({ args: ["get", "title"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal }), "title");
|
|
23
|
-
|
|
24
|
-
return { title, url };
|
|
25
|
-
return extractNavigationSummaryFromData(await runSessionCommandData({
|
|
26
|
-
args: ["eval", "--stdin"],
|
|
27
|
-
cwd: options.cwd,
|
|
28
|
-
namespace: options.namespace,
|
|
29
|
-
sessionName: options.sessionName,
|
|
30
|
-
signal: options.signal,
|
|
31
|
-
stdin: `({ title: document.title, url: location.href })`,
|
|
32
|
-
}));
|
|
28
|
+
return { title, url };
|
|
33
29
|
}
|
|
34
30
|
function extractScrollPositionSnapshot(data) {
|
|
35
31
|
const result = isRecord(data) && isRecord(data.result) ? data.result : data;
|
|
@@ -507,7 +503,7 @@ export function formatArtifactCleanupGuidanceText(guidance) {
|
|
|
507
503
|
}
|
|
508
504
|
async function collectManagedSessionCommandData(options) {
|
|
509
505
|
try {
|
|
510
|
-
return { data: await runSessionCommandData(options) };
|
|
506
|
+
return { data: await runSessionCommandData({ ...options, pinNamespace: true }) };
|
|
511
507
|
}
|
|
512
508
|
catch (error) {
|
|
513
509
|
return { error: error instanceof Error ? error.message : String(error) };
|
|
@@ -528,14 +524,16 @@ async function collectElectronManagedSessionUrl(options) {
|
|
|
528
524
|
export async function collectElectronManagedSessionTarget(options) {
|
|
529
525
|
if (!options.sessionName)
|
|
530
526
|
return undefined;
|
|
531
|
-
const [
|
|
532
|
-
collectManagedSessionCommandData({ args: ["get", "title"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, timeoutMs: options.timeoutMs }),
|
|
533
|
-
collectManagedSessionCommandData({ args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, timeoutMs: options.timeoutMs }),
|
|
534
|
-
]);
|
|
535
|
-
const title = boundElectronProbeString(extractStringResultField(titleResult.data, "result") ?? extractStringResultField(titleResult.data, "title"), 160);
|
|
527
|
+
const urlResult = await collectManagedSessionCommandData({ allowManagedSessionTarget: options.allowManagedSessionTarget, args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, timeoutMs: options.timeoutMs });
|
|
536
528
|
const url = boundElectronProbeString(extractStringResultField(urlResult.data, "result") ?? extractStringResultField(urlResult.data, "url"), 300);
|
|
537
|
-
|
|
538
|
-
|
|
529
|
+
if (urlResult.error || !url)
|
|
530
|
+
return { error: urlResult.error ?? "get url returned no active page URL.", sessionName: options.sessionName };
|
|
531
|
+
const fileAccessError = getManagedSessionStateAccessValidationError({ args: ["get", "title"], currentPageUrl: url, cwd: options.cwd });
|
|
532
|
+
if (fileAccessError)
|
|
533
|
+
return { error: fileAccessError, sessionName: options.sessionName, url };
|
|
534
|
+
const titleResult = await collectManagedSessionCommandData({ allowManagedSessionTarget: options.allowManagedSessionTarget, args: ["get", "title"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, timeoutMs: options.timeoutMs });
|
|
535
|
+
const title = boundElectronProbeString(extractStringResultField(titleResult.data, "result") ?? extractStringResultField(titleResult.data, "title"), 160);
|
|
536
|
+
return { sessionName: options.sessionName, title, url, ...(titleResult.error ? { error: titleResult.error } : {}) };
|
|
539
537
|
}
|
|
540
538
|
export async function collectQaAttachedTarget(options) {
|
|
541
539
|
if (!options.sessionName)
|
|
@@ -673,16 +671,33 @@ export async function collectVisibleRefFallbackDiagnostic(options) {
|
|
|
673
671
|
export async function collectElectronHandoff(options) {
|
|
674
672
|
if (options.handoff === "connect")
|
|
675
673
|
return { handoff: "connect" };
|
|
676
|
-
|
|
674
|
+
if (options.signal?.aborted)
|
|
675
|
+
throw new Error("Electron handoff was aborted.");
|
|
676
|
+
const urlData = await runSessionCommandData({ args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, throwOnFailure: true });
|
|
677
|
+
if (options.signal?.aborted)
|
|
678
|
+
throw new Error("Electron handoff was aborted.");
|
|
679
|
+
const url = extractStringResultField(urlData, "result") ?? extractStringResultField(urlData, "url");
|
|
680
|
+
if (!url)
|
|
681
|
+
throw new Error("Electron handoff get url returned no active page URL.");
|
|
682
|
+
const fileAccessError = getManagedSessionStateAccessValidationError({ args: ["snapshot", "-i"], currentPageUrl: url, cwd: options.cwd });
|
|
683
|
+
if (fileAccessError)
|
|
684
|
+
return { error: fileAccessError, failureCategory: "validation-error", handoff: options.handoff };
|
|
685
|
+
const tabs = await runSessionCommandData({ args: ["tab", "list"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, throwOnFailure: true });
|
|
686
|
+
if (options.signal?.aborted)
|
|
687
|
+
throw new Error("Electron handoff was aborted.");
|
|
677
688
|
if (options.handoff === "tabs")
|
|
678
689
|
return { handoff: "tabs", tabs };
|
|
679
|
-
let snapshot = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal });
|
|
690
|
+
let snapshot = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, throwOnFailure: true });
|
|
680
691
|
let refSnapshot = extractRefSnapshotFromData(snapshot);
|
|
681
692
|
let snapshotRetryCount = 0;
|
|
682
693
|
while ((!refSnapshot || refSnapshot.refIds.length === 0) && snapshotRetryCount < 2) {
|
|
694
|
+
if (options.signal?.aborted)
|
|
695
|
+
throw new Error("Electron handoff was aborted.");
|
|
683
696
|
snapshotRetryCount += 1;
|
|
684
697
|
await sleepMs(250);
|
|
685
|
-
|
|
698
|
+
if (options.signal?.aborted)
|
|
699
|
+
throw new Error("Electron handoff was aborted.");
|
|
700
|
+
snapshot = await runSessionCommandData({ args: ["snapshot", "-i"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, throwOnFailure: true });
|
|
686
701
|
refSnapshot = extractRefSnapshotFromData(snapshot);
|
|
687
702
|
}
|
|
688
703
|
return { handoff: "snapshot", refSnapshot, snapshot, ...(snapshotRetryCount > 0 ? { snapshotRetryCount } : {}), tabs };
|
|
@@ -857,8 +872,11 @@ function buildTimeoutProgressSteps(options) {
|
|
|
857
872
|
export async function collectTimeoutPartialProgress(options) {
|
|
858
873
|
const rawSteps = getTimeoutProgressSteps(options.compiledJob, options.command, options.stdin);
|
|
859
874
|
const artifacts = await collectTimeoutArtifactEvidence(options.cwd, rawSteps);
|
|
860
|
-
const
|
|
875
|
+
const urlData = await runSessionCommandData({ args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName });
|
|
861
876
|
const recoveredUrl = extractStringResultField(urlData, "result") ?? extractStringResultField(urlData, "url");
|
|
877
|
+
const titleData = recoveredUrl && !isFileUrl(recoveredUrl)
|
|
878
|
+
? await runSessionCommandData({ args: ["get", "title"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName })
|
|
879
|
+
: undefined;
|
|
862
880
|
const title = extractStringResultField(titleData, "result") ?? extractStringResultField(titleData, "title");
|
|
863
881
|
const plannedUrl = recoveredUrl ? undefined : getPlannedCurrentPageUrl(rawSteps);
|
|
864
882
|
const url = recoveredUrl ?? plannedUrl;
|
|
@@ -6,7 +6,8 @@ import { AgentBrowserNextActionCollector, alignPageChangeSummaryNextActionIds, a
|
|
|
6
6
|
import { buildConnectedSessionNextActions, buildNoActivePageNextActions, buildSessionAwareStaleRefNextActions, buildSessionTabRecoveryNextActions, } from "../../results/recovery-next-actions.js";
|
|
7
7
|
import { buildRichInputRecoveryDiagnostic, buildRichInputRecoveryNextActions, buildVisibleRefFallbackNextActions, formatRichInputRecoveryText, formatVisibleRefFallbackText, sanitizeVisibleRefFallbackDiagnostic, } from "../../results/selector-recovery.js";
|
|
8
8
|
import { buildNoActivePageRefSnapshotInvalidation, isNoActivePageSnapshotFailure, } from "../../session-page-state.js";
|
|
9
|
-
import { extractExplicitSessionName
|
|
9
|
+
import { extractExplicitSessionName } from "../../argv-grammar.js";
|
|
10
|
+
import { redactInvocationArgs, redactSensitiveText, redactSensitiveValue } from "../../runtime.js";
|
|
10
11
|
import { isRecord } from "../../parsing.js";
|
|
11
12
|
import { buildClickDispatchNextActions, formatClickDispatchDiagnosticText } from "./click-dispatch.js";
|
|
12
13
|
import { buildComboboxFocusNextActions, buildElectronBroadGetTextScopeNextActions, buildFillVerificationNextActions, buildOverlayBlockerNextActions, buildScrollNoopNextActions, buildSelectorTextVisibilityNextActions, buildSourceLookupElectronNextActions, collectVisibleRefFallbackDiagnostic, formatArtifactCleanupGuidanceText, formatComboboxFocusDiagnosticText, formatElectronBroadGetTextScopeText, formatEvalResultWarningText, formatEvalStdinHintText, formatFillVerificationText, formatOverlayBlockerText, formatRecordingDependencyWarningText, formatScrollNoopDiagnosticText, formatSelectorTextVisibilityText, formatTimeoutPartialProgressText, } from "./diagnostics.js";
|
|
@@ -90,6 +91,8 @@ export function buildJsonVisibleContent(options) {
|
|
|
90
91
|
return [{ type: "text", text: JSON.stringify(payload, null, 2) }, ...images];
|
|
91
92
|
}
|
|
92
93
|
export function getElectronLaunchFailureCategory(failure) {
|
|
94
|
+
if (failure.reason === "aborted")
|
|
95
|
+
return "aborted";
|
|
93
96
|
if (failure.reason === "policy-blocked")
|
|
94
97
|
return "policy-blocked";
|
|
95
98
|
if (failure.reason === "timeout")
|
|
@@ -122,7 +125,8 @@ function formatElectronLaunchFailureDiagnostics(failure) {
|
|
|
122
125
|
lines.push(`- Timing: ${diagnostics.elapsedMs ?? "unknown"}ms elapsed${diagnostics.timeoutMs !== undefined ? ` of ${diagnostics.timeoutMs}ms timeout` : ""}.`);
|
|
123
126
|
if (diagnostics.outputCaptured === false)
|
|
124
127
|
lines.push("- App stdout/stderr: not captured by this wrapper launch path.");
|
|
125
|
-
|
|
128
|
+
if (failure?.reason !== "aborted")
|
|
129
|
+
lines.push("Retry guidance: increase electron.timeoutMs, try targetType:'any', pass an explicit appPath/executablePath, quit any already-running singleton instance, then retry launch.");
|
|
126
130
|
return lines.join("\n");
|
|
127
131
|
}
|
|
128
132
|
export function buildElectronHostFailureResult(options) {
|
|
@@ -385,14 +389,15 @@ function buildAgentBrowserResultDetails(options, nextActions) {
|
|
|
385
389
|
sessionMode: options.sessionMode,
|
|
386
390
|
sessionTabCorrection: options.sessionTabCorrection,
|
|
387
391
|
sessionTabTarget: options.currentSessionTabTarget,
|
|
392
|
+
sessionTabTargetUnknown: options.currentSessionTabTargetUnknown,
|
|
388
393
|
refSnapshot: options.currentRefSnapshot,
|
|
389
394
|
refSnapshotInvalidation: options.currentRefSnapshotInvalidation,
|
|
390
395
|
namespace: options.executionPlan.namespace,
|
|
391
|
-
...buildSessionDetailFields(options.executionPlan.sessionName, options.executionPlan.usedImplicitSession),
|
|
396
|
+
...buildSessionDetailFields(options.executionPlan.sessionName, options.executionPlan.usedImplicitSession, options.executionPlan.namespace, options.managedSessionRestoreDisabled),
|
|
392
397
|
sessionRecoveryHint: options.redactedRecoveryHint,
|
|
393
398
|
startupScopedFlags: options.executionPlan.startupScopedFlags,
|
|
394
399
|
stderr: options.processResult.stderr,
|
|
395
|
-
stdout: options.plainTextInspection ? options.inspectionText ?? "" :
|
|
400
|
+
stdout: options.plainTextInspection ? options.inspectionText ?? "" : undefined,
|
|
396
401
|
summary: options.presentation.summary,
|
|
397
402
|
timedOut: options.processResult.timedOut || undefined,
|
|
398
403
|
timeoutMs: options.processResult.timeoutMs,
|
|
@@ -428,9 +433,13 @@ export function buildFinalAgentBrowserToolResult(options) {
|
|
|
428
433
|
const result = { content, details: redactToolDetails(details, options.exactSensitiveValues), isError: !options.succeeded };
|
|
429
434
|
return options.compiledNetworkSourceLookup ? redactNetworkSourceLookupSurface(result) : result;
|
|
430
435
|
}
|
|
436
|
+
export function isMissingAgentBrowserBinary(processResult) {
|
|
437
|
+
return processResult.spawnError?.message.includes("ENOENT") === true;
|
|
438
|
+
}
|
|
431
439
|
export async function buildMissingBinaryFailureResult(options) {
|
|
432
|
-
if (!options.processResult
|
|
440
|
+
if (!isMissingAgentBrowserBinary(options.processResult))
|
|
433
441
|
return undefined;
|
|
442
|
+
const spawnError = options.processResult.spawnError.message;
|
|
434
443
|
const errorText = buildMissingBinaryMessage();
|
|
435
444
|
const managedSessionOutcome = buildManagedSessionOutcome({ activeAfter: options.managedSessionActive, activeBefore: options.managedSessionActive, attemptedSessionName: options.executionPlan.managedSessionName, command: options.executionPlan.commandInfo.command, currentSessionName: options.managedSessionName, currentSessionNamespace: options.managedSessionNamespace, previousSessionName: options.managedSessionName, sessionMode: options.sessionMode, succeeded: false });
|
|
436
445
|
const managedSessionOutcomeText = formatManagedSessionOutcomeText(managedSessionOutcome);
|
|
@@ -442,5 +451,5 @@ export async function buildMissingBinaryFailureResult(options) {
|
|
|
442
451
|
missingBinaryElectronRecord = missingBinaryElectronCleanup.record;
|
|
443
452
|
}
|
|
444
453
|
const textParts = [errorText, managedSessionOutcomeText, missingBinaryElectronCleanup ? `Electron cleanup after failed attach: ${missingBinaryElectronCleanup.summary}` : undefined].filter((part) => part !== undefined && part.length > 0);
|
|
445
|
-
return { content: [{ type: "text", text: textParts.join("\n\n") }], details: { args: options.redactedArgs, compatibilityWorkaround: options.compatibilityWorkaround, effectiveArgs: options.redactedProcessArgs, electron: missingBinaryElectronRecord ? { action: "launch", cleanup: missingBinaryElectronCleanup, launch: missingBinaryElectronRecord, status: "failed", targets: options.electronLaunch?.targets, version: options.electronLaunch?.version } : undefined, managedSessionOutcome, namespace: options.executionPlan.namespace, nextActions: managedSessionRecoveryNextActions.length > 0 ? managedSessionRecoveryNextActions : undefined, sessionMode: options.sessionMode, sessionTabCorrection: options.sessionTabCorrection, ...buildAgentBrowserResultCategoryDetails({ args: options.redactedProcessArgs, command: options.executionPlan.commandInfo.command, errorText, failureCategory: "missing-binary", spawnError
|
|
454
|
+
return { content: [{ type: "text", text: textParts.join("\n\n") }], details: { args: options.redactedArgs, compatibilityWorkaround: options.compatibilityWorkaround, effectiveArgs: options.redactedProcessArgs, electron: missingBinaryElectronRecord ? { action: "launch", cleanup: missingBinaryElectronCleanup, launch: missingBinaryElectronRecord, status: "failed", targets: options.electronLaunch?.targets, version: options.electronLaunch?.version } : undefined, managedSessionOutcome, namespace: options.executionPlan.namespace, nextActions: managedSessionRecoveryNextActions.length > 0 ? managedSessionRecoveryNextActions : undefined, sessionMode: options.sessionMode, sessionTabCorrection: options.sessionTabCorrection, ...buildAgentBrowserResultCategoryDetails({ args: options.redactedProcessArgs, command: options.executionPlan.commandInfo.command, errorText, failureCategory: "missing-binary", spawnError, succeeded: false }), spawnError }, isError: true };
|
|
446
455
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { runAgentBrowserProcess } from "../../process.js";
|
|
2
|
+
import { withOwnedManagedSessionContext } from "../../managed-session-restore.js";
|
|
2
3
|
import { cleanupClickDispatchProbe } from "./click-dispatch.js";
|
|
3
4
|
import { applyBrowserRunStatePatch } from "./session-state.js";
|
|
4
5
|
import { buildMissingBinaryFailureResult } from "./final-result.js";
|
|
5
6
|
import { prepareBrowserRun } from "./prepare.js";
|
|
6
7
|
import { processBrowserOutput } from "./process-output.js";
|
|
7
|
-
export { closeManagedSession
|
|
8
|
+
export { closeManagedSession } from "./managed-session-daemon-policy.js";
|
|
9
|
+
export { getSessionContextKey } from "./session-state.js";
|
|
8
10
|
export async function runAgentBrowserTool(options) {
|
|
9
11
|
const preparedResult = await prepareBrowserRun(options);
|
|
10
12
|
applyBrowserRunStatePatch(options.state, preparedResult.kind === "ready" ? preparedResult.prepared.statePatch : preparedResult.statePatch);
|
|
@@ -12,37 +14,56 @@ export async function runAgentBrowserTool(options) {
|
|
|
12
14
|
return preparedResult.result;
|
|
13
15
|
}
|
|
14
16
|
const { prepared } = preparedResult;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
17
|
+
const ownedManagedSession = prepared.ownedManagedSessionContext;
|
|
18
|
+
return await withOwnedManagedSessionContext(ownedManagedSession, async () => {
|
|
19
|
+
try {
|
|
20
|
+
const processResult = await runAgentBrowserProcess({
|
|
21
|
+
args: prepared.processArgs,
|
|
22
|
+
cwd: options.cwd,
|
|
23
|
+
env: ownedManagedSession
|
|
24
|
+
? { AGENT_BROWSER_IDLE_TIMEOUT_MS: options.implicitSessionIdleTimeoutMs }
|
|
25
|
+
: undefined,
|
|
26
|
+
managedSessionRestoreState: options.state.managedSessionRestoreState,
|
|
27
|
+
managedStateCurrentPageUrl: prepared.priorSessionTabTarget?.url,
|
|
28
|
+
managedStatePageUrlUnknown: prepared.priorSessionTabTargetUnknown === true,
|
|
29
|
+
ownedManagedSession: ownedManagedSession !== undefined,
|
|
30
|
+
signal: options.signal,
|
|
31
|
+
stdin: prepared.processStdin,
|
|
32
|
+
timeoutMs: prepared.processTimeoutMs,
|
|
33
|
+
trustedFirstBatchTabSelection: prepared.pinnedBatchUnwrapMode !== undefined,
|
|
34
|
+
});
|
|
35
|
+
const missingBinaryResult = await buildMissingBinaryFailureResult({
|
|
36
|
+
compatibilityWorkaround: prepared.compatibilityWorkaround,
|
|
37
|
+
electronLaunch: prepared.electronLaunch,
|
|
38
|
+
executionPlan: prepared.executionPlan,
|
|
39
|
+
implicitSessionCloseTimeoutMs: options.implicitSessionCloseTimeoutMs,
|
|
40
|
+
managedSessionActive: options.state.managedSessionActive,
|
|
41
|
+
managedSessionName: options.state.managedSessionName,
|
|
42
|
+
managedSessionNamespace: options.state.managedSessionNamespace,
|
|
43
|
+
processResult,
|
|
44
|
+
redactedArgs: prepared.redactedArgs,
|
|
45
|
+
redactedProcessArgs: prepared.redactedProcessArgs,
|
|
46
|
+
sessionMode: prepared.sessionMode,
|
|
47
|
+
sessionTabCorrection: prepared.sessionTabCorrection,
|
|
48
|
+
});
|
|
49
|
+
if (missingBinaryResult)
|
|
50
|
+
return missingBinaryResult;
|
|
51
|
+
const output = await processBrowserOutput({ ...options, prepared, processResult });
|
|
52
|
+
applyBrowserRunStatePatch(options.state, output.statePatch);
|
|
53
|
+
return output.result;
|
|
40
54
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
55
|
+
finally {
|
|
56
|
+
try {
|
|
57
|
+
await cleanupClickDispatchProbe({
|
|
58
|
+
cwd: options.cwd,
|
|
59
|
+
namespace: prepared.executionPlan.namespace,
|
|
60
|
+
probe: prepared.clickDispatchProbe,
|
|
61
|
+
sessionName: prepared.executionPlan.sessionName,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
await prepared.managedSessionPolicyLock?.release();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
48
69
|
}
|