pi-agent-browser-native 0.5.0 → 0.6.6
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 +144 -0
- package/README.md +75 -42
- package/dist/extensions/agent-browser/index.js +13 -83
- package/dist/extensions/agent-browser/lib/argv-grammar.js +8 -2
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +1 -1
- package/dist/extensions/agent-browser/lib/command-policy.js +4 -7
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +19 -11
- package/dist/extensions/agent-browser/lib/config-policy.js +25 -1
- package/dist/extensions/agent-browser/lib/config.js +1 -1
- package/dist/extensions/agent-browser/lib/input-modes/job.js +0 -9
- package/dist/extensions/agent-browser/lib/input-modes/params.js +1 -1
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +18 -4
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +3 -138
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +1 -81
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +4 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +31 -26
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +60 -8
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +1 -4
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +119 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +7 -6
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +31 -40
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +76 -70
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +6 -8
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +5 -10
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +41 -21
- package/dist/extensions/agent-browser/lib/page-target-validation.js +270 -0
- package/dist/extensions/agent-browser/lib/playbook.js +13 -12
- package/dist/extensions/agent-browser/lib/process-identity.js +11 -10
- package/dist/extensions/agent-browser/lib/process.js +18 -82
- package/dist/extensions/agent-browser/lib/recording-reservations.js +11 -78
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +1 -1
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +27 -14
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +20 -2
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +11 -16
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +12 -9
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +2 -2
- package/dist/extensions/agent-browser/lib/results/presentation.js +31 -4
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +1 -1
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +9 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +3 -3
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +2 -1
- package/dist/extensions/agent-browser/lib/results/snapshot.js +4 -4
- package/dist/extensions/agent-browser/lib/runtime.js +73 -72
- package/dist/extensions/agent-browser/lib/session-page-state.js +12 -3
- package/dist/extensions/agent-browser/lib/temp.js +1 -2
- package/dist/extensions/agent-browser/lib/upstream-version.js +5 -5
- package/dist/extensions/agent-browser/lib/web-search.js +108 -24
- package/dist/scripts/agent-browser-target.mjs +19 -1
- package/docs/ARCHITECTURE.md +24 -20
- package/docs/COMMAND_REFERENCE.md +181 -49
- package/docs/ELECTRON.md +2 -2
- package/docs/RELEASE.md +10 -8
- package/docs/REQUIREMENTS.md +8 -7
- package/docs/SUPPORT_MATRIX.md +31 -26
- package/docs/TOOL_CONTRACT.md +89 -56
- package/package.json +1 -1
- package/scripts/agent-browser-capability-baseline.mjs +65 -5
- package/scripts/agent-browser-target.mjs +19 -1
- package/scripts/config.mjs +1 -0
- package/scripts/doctor.mjs +15 -9
- package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -20
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +0 -601
- package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -78
- package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -37
|
@@ -50,9 +50,6 @@ function getPolicyLockDigest(sessionName, namespace) {
|
|
|
50
50
|
export function getManagedSessionPolicyLockPath(sessionName, namespace) {
|
|
51
51
|
return join(getCoordinationDirectory(), `.pi-agent-browser-policy-${getPolicyLockDigest(sessionName, namespace)}.lock-v3`);
|
|
52
52
|
}
|
|
53
|
-
export function getLegacyManagedSessionPolicyLockPath(sessionName, namespace) {
|
|
54
|
-
return join(getCoordinationDirectory(), `.pi-agent-browser-policy-${getPolicyLockDigest(sessionName, namespace)}.lock-v2`);
|
|
55
|
-
}
|
|
56
53
|
function parseOwner(content) {
|
|
57
54
|
if (Buffer.byteLength(content) > POLICY_LOCK_MAX_BYTES)
|
|
58
55
|
return undefined;
|
|
@@ -69,22 +66,6 @@ function parseOwner(content) {
|
|
|
69
66
|
return undefined;
|
|
70
67
|
}
|
|
71
68
|
}
|
|
72
|
-
function parseLegacyBridgeOwner(content) {
|
|
73
|
-
if (Buffer.byteLength(content) > POLICY_LOCK_MAX_BYTES)
|
|
74
|
-
return undefined;
|
|
75
|
-
try {
|
|
76
|
-
const parsed = JSON.parse(content);
|
|
77
|
-
return (parsed.version === 2 || parsed.version === 3)
|
|
78
|
-
&& Number.isSafeInteger(parsed.pid) && (parsed.pid ?? 0) > 0
|
|
79
|
-
&& typeof parsed.startIdentity === "string" && parsed.startIdentity.length > 0
|
|
80
|
-
&& typeof parsed.token === "string" && parsed.token.length > 0
|
|
81
|
-
? parsed
|
|
82
|
-
: undefined;
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
return undefined;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
69
|
function parseTicket(content, token) {
|
|
89
70
|
if (Buffer.byteLength(content) > POLICY_LOCK_MAX_BYTES)
|
|
90
71
|
return undefined;
|
|
@@ -139,26 +120,6 @@ async function readClaim(path) {
|
|
|
139
120
|
return undefined;
|
|
140
121
|
}
|
|
141
122
|
}
|
|
142
|
-
async function readLegacyBridgeOwner(path) {
|
|
143
|
-
try {
|
|
144
|
-
const directory = await lstat(path);
|
|
145
|
-
const ownerPath = join(path, LOCK_OWNER_FILE);
|
|
146
|
-
const ownerEntry = await lstat(ownerPath);
|
|
147
|
-
if (!directory.isDirectory() || directory.isSymbolicLink() || !ownerEntry.isFile() || ownerEntry.isSymbolicLink())
|
|
148
|
-
return undefined;
|
|
149
|
-
if (ownerEntry.size > POLICY_LOCK_MAX_BYTES)
|
|
150
|
-
return undefined;
|
|
151
|
-
if (process.platform !== "win32") {
|
|
152
|
-
const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
153
|
-
if (uid === undefined || directory.uid !== uid || ownerEntry.uid !== uid || (directory.mode & 0o077) !== 0 || (ownerEntry.mode & 0o177) !== 0)
|
|
154
|
-
return undefined;
|
|
155
|
-
}
|
|
156
|
-
return parseLegacyBridgeOwner(await readFile(ownerPath, "utf8"));
|
|
157
|
-
}
|
|
158
|
-
catch {
|
|
159
|
-
return undefined;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
123
|
async function readClaims(basePath) {
|
|
163
124
|
const directory = dirname(basePath);
|
|
164
125
|
const prefix = `${basename(basePath)}.claim-`;
|
|
@@ -225,28 +186,6 @@ async function removeClaimOwnedBy(path, token) {
|
|
|
225
186
|
await rm(movedPath, { force: true, recursive: true });
|
|
226
187
|
return true;
|
|
227
188
|
}
|
|
228
|
-
async function removeLegacyBridgeOwnedBy(path, token) {
|
|
229
|
-
const current = await readLegacyBridgeOwner(path);
|
|
230
|
-
if (current?.version !== 3 || current.token !== token)
|
|
231
|
-
return false;
|
|
232
|
-
const movedPath = join(dirname(path), `.pi-agent-browser-policy-bridge-remove-${token}-${randomUUID()}`);
|
|
233
|
-
try {
|
|
234
|
-
await rename(path, movedPath);
|
|
235
|
-
}
|
|
236
|
-
catch (error) {
|
|
237
|
-
return error.code === "ENOENT";
|
|
238
|
-
}
|
|
239
|
-
const moved = await readLegacyBridgeOwner(movedPath);
|
|
240
|
-
if (moved?.version !== 3 || moved.token !== token) {
|
|
241
|
-
try {
|
|
242
|
-
await rename(movedPath, path);
|
|
243
|
-
}
|
|
244
|
-
catch { }
|
|
245
|
-
return false;
|
|
246
|
-
}
|
|
247
|
-
await rm(movedPath, { force: true, recursive: true });
|
|
248
|
-
return true;
|
|
249
|
-
}
|
|
250
189
|
async function cleanDeadPolicyArtifacts(directory) {
|
|
251
190
|
let names;
|
|
252
191
|
try {
|
|
@@ -256,72 +195,13 @@ async function cleanDeadPolicyArtifacts(directory) {
|
|
|
256
195
|
return;
|
|
257
196
|
}
|
|
258
197
|
for (const name of names.filter((candidate) => candidate.startsWith(".pi-agent-browser-policy-remove-")
|
|
259
|
-
|| candidate.startsWith(".pi-agent-browser-policy-bridge-remove-")
|
|
260
|
-
|| candidate.includes(".lock-v2.bridge-candidate-")
|
|
261
|
-
|| candidate.includes(".lock-v2.candidate-")
|
|
262
198
|
|| candidate.includes(".lock-v3.candidate-"))) {
|
|
263
199
|
const path = join(directory, name);
|
|
264
|
-
const
|
|
265
|
-
if (
|
|
200
|
+
const claim = await readClaim(path);
|
|
201
|
+
if (claim && await ownerAlive(claim.owner) === false)
|
|
266
202
|
await rm(path, { force: true, recursive: true }).catch(() => undefined);
|
|
267
203
|
}
|
|
268
204
|
}
|
|
269
|
-
async function hasLegacyV2Contender(path) {
|
|
270
|
-
try {
|
|
271
|
-
const prefix = `${basename(path)}.candidate-`;
|
|
272
|
-
return (await readdir(dirname(path))).some((name) => name.startsWith(prefix));
|
|
273
|
-
}
|
|
274
|
-
catch {
|
|
275
|
-
return undefined;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
async function acquireLegacyPolicyBridge(options) {
|
|
279
|
-
const path = getLegacyManagedSessionPolicyLockPath(options.sessionName, options.namespace);
|
|
280
|
-
const candidatePath = `${path}.bridge-candidate-${options.owner.token}`;
|
|
281
|
-
try {
|
|
282
|
-
await mkdir(candidatePath, { mode: 0o700 });
|
|
283
|
-
await writeFile(join(candidatePath, LOCK_OWNER_FILE), JSON.stringify(options.owner), { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
284
|
-
while (!options.signal?.aborted) {
|
|
285
|
-
const legacyContender = await hasLegacyV2Contender(path);
|
|
286
|
-
if (legacyContender === undefined)
|
|
287
|
-
return undefined;
|
|
288
|
-
if (legacyContender) {
|
|
289
|
-
if (Date.now() >= options.deadline)
|
|
290
|
-
return undefined;
|
|
291
|
-
await waitForRetry(options.signal);
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
try {
|
|
295
|
-
await rename(candidatePath, path);
|
|
296
|
-
const installed = await readLegacyBridgeOwner(path);
|
|
297
|
-
return installed?.version === 3 && installed.token === options.owner.token
|
|
298
|
-
? { release: async () => { await removeLegacyBridgeOwnedBy(path, options.owner.token); } }
|
|
299
|
-
: undefined;
|
|
300
|
-
}
|
|
301
|
-
catch (error) {
|
|
302
|
-
if (!["EACCES", "EEXIST", "ENOTEMPTY", "EPERM"].includes(error.code ?? ""))
|
|
303
|
-
return undefined;
|
|
304
|
-
}
|
|
305
|
-
const observed = await readLegacyBridgeOwner(path);
|
|
306
|
-
if (!observed)
|
|
307
|
-
return undefined;
|
|
308
|
-
if (observed.version === 3 && await ownerAlive(observed) === false) {
|
|
309
|
-
if (await removeLegacyBridgeOwnedBy(path, observed.token))
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
if (Date.now() >= options.deadline)
|
|
313
|
-
return undefined;
|
|
314
|
-
await waitForRetry(options.signal);
|
|
315
|
-
}
|
|
316
|
-
return undefined;
|
|
317
|
-
}
|
|
318
|
-
catch {
|
|
319
|
-
return undefined;
|
|
320
|
-
}
|
|
321
|
-
finally {
|
|
322
|
-
await rm(candidatePath, { force: true, recursive: true }).catch(() => undefined);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
205
|
function claimPrecedes(left, right) {
|
|
326
206
|
if (left.ticket === null)
|
|
327
207
|
return true;
|
|
@@ -358,7 +238,6 @@ export async function acquireManagedSessionPolicyLock(options) {
|
|
|
358
238
|
const candidatePath = `${basePath}.candidate-${token}`;
|
|
359
239
|
const claimPath = `${basePath}.claim-${token}`;
|
|
360
240
|
let claimPublished = false;
|
|
361
|
-
let legacyBridge;
|
|
362
241
|
let lockAcquired = false;
|
|
363
242
|
try {
|
|
364
243
|
await mkdir(candidatePath, { mode: 0o700 });
|
|
@@ -397,20 +276,8 @@ export async function acquireManagedSessionPolicyLock(options) {
|
|
|
397
276
|
}
|
|
398
277
|
if (!blocked) {
|
|
399
278
|
await cleanDeadPolicyArtifacts(directory);
|
|
400
|
-
legacyBridge = await acquireLegacyPolicyBridge({
|
|
401
|
-
deadline,
|
|
402
|
-
owner,
|
|
403
|
-
signal: options.signal,
|
|
404
|
-
sessionName: options.sessionName,
|
|
405
|
-
namespace: options.namespace,
|
|
406
|
-
});
|
|
407
|
-
if (!legacyBridge)
|
|
408
|
-
return undefined;
|
|
409
279
|
lockAcquired = true;
|
|
410
|
-
return { release: async () => {
|
|
411
|
-
await legacyBridge?.release();
|
|
412
|
-
await removeClaimOwnedBy(claimPath, token);
|
|
413
|
-
} };
|
|
280
|
+
return { release: async () => { await removeClaimOwnedBy(claimPath, token); } };
|
|
414
281
|
}
|
|
415
282
|
if (Date.now() >= deadline)
|
|
416
283
|
return undefined;
|
|
@@ -423,8 +290,6 @@ export async function acquireManagedSessionPolicyLock(options) {
|
|
|
423
290
|
}
|
|
424
291
|
finally {
|
|
425
292
|
await rm(candidatePath, { force: true, recursive: true }).catch(() => undefined);
|
|
426
|
-
if (!lockAcquired)
|
|
427
|
-
await legacyBridge?.release();
|
|
428
293
|
if (claimPublished && !lockAcquired)
|
|
429
294
|
await removeClaimOwnedBy(claimPath, token);
|
|
430
295
|
}
|
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
import { chmodSync, lstatSync, readFileSync, unlinkSync } from "node:fs";
|
|
3
2
|
import { extractUpstreamCommandTokens, parseCommandInfo } from "./argv-descriptor.js";
|
|
4
3
|
import { canonicalizeAgentBrowserNamespace, extractExplicitNamespace, extractExplicitSessionName, extractRequestedRestoreKey, getAgentBrowserSessionIdentityKey, isUpstreamEnvFlagEnabled, scanUpstreamGlobalFlagOccurrences, } from "./argv-grammar.js";
|
|
5
4
|
import { hasLaunchScopedFlagToken, MANAGED_RESTORE_INCOMPATIBLE_BOOLEAN_ENVS, MANAGED_RESTORE_INCOMPATIBLE_ENVS, MANAGED_RESTORE_INCOMPATIBLE_FLAGS, } from "./launch-scoped-flags.js";
|
|
6
5
|
import { createManagedSessionRestoreKey, ensureManagedSessionRestoreStorageIsSecure, getManagedSessionRestoreScope, getManagedSessionRestoreProtectedStorageEnv, hasManagedSessionRestoreProjectIdentity, resolveManagedSessionRestoreHome, } from "./managed-session-storage.js";
|
|
7
6
|
import { parseUserBatchStdin } from "./orchestration/batch-stdin.js";
|
|
8
7
|
import { getAgentBrowserProcessEnvironment } from "./process-environment.js";
|
|
9
|
-
import { writeSecureTempFile } from "./temp.js";
|
|
10
8
|
export { createManagedSessionRestoreKey, ensureManagedSessionRestoreStorageIsSecure, getManagedSessionRestoreScope } from "./managed-session-storage.js";
|
|
11
9
|
export { pruneOwnedManagedSessionRestoreSnapshots } from "./managed-session-snapshots.js";
|
|
12
10
|
const AGENT_BROWSER_CONFIG_ENV = "AGENT_BROWSER_CONFIG";
|
|
13
11
|
const AGENT_BROWSER_RESTORE_ENV = "AGENT_BROWSER_RESTORE";
|
|
14
12
|
const MANAGED_SESSION_RESTORE_ENV = "PI_AGENT_BROWSER_MANAGED_SESSION_RESTORE";
|
|
15
13
|
export const MANAGED_SESSION_NAME_PREFIX = "piab-";
|
|
16
|
-
const MANAGED_SESSION_RESTORE_EMPTY_CONFIG_CONTENT = "{}\n";
|
|
17
|
-
const MANAGED_SESSION_RESTORE_EMPTY_CONFIG_NAME = ".pi-agent-browser-managed-restore-config-v1.json";
|
|
18
14
|
const MANAGED_SESSION_RESTORE_SPAWN_PINNED_ENVS = new Set([AGENT_BROWSER_CONFIG_ENV, AGENT_BROWSER_RESTORE_ENV, "AGENT_BROWSER_NAMESPACE"]);
|
|
19
|
-
let managedSessionRestoreEmptyConfigPath;
|
|
20
|
-
let managedSessionRestoreEmptyConfigPromise;
|
|
21
15
|
function isDisabledEnvFlag(value) {
|
|
22
16
|
if (value === undefined)
|
|
23
17
|
return false;
|
|
@@ -108,7 +102,7 @@ function closesBrowserSession(args) {
|
|
|
108
102
|
export function agentBrowserExplicitConfigIsPresent(parentEnv = getAgentBrowserProcessEnvironment(), args = []) {
|
|
109
103
|
return hasExplicitConfigArg(args) || hasUpstreamEnvValue(parentEnv, AGENT_BROWSER_CONFIG_ENV);
|
|
110
104
|
}
|
|
111
|
-
/**
|
|
105
|
+
/** Caller-selected upstream config disables the wrapper's automatic restore injection without blocking that config. */
|
|
112
106
|
export function agentBrowserConfigBlocksManagedRestore(_cwd, parentEnv = getAgentBrowserProcessEnvironment(), args = [], platform = process.platform) {
|
|
113
107
|
return !resolveManagedSessionRestoreHome(parentEnv, platform) || agentBrowserExplicitConfigIsPresent(parentEnv, args);
|
|
114
108
|
}
|
|
@@ -203,80 +197,6 @@ export function getOwnedManagedSessionCompatibilityEnv(options) {
|
|
|
203
197
|
...(ownedContext.headedManagedAutosaveInterval !== undefined && !explicitIntervalMatches ? { AGENT_BROWSER_AUTOSAVE_INTERVAL_MS: ownedContext.headedManagedAutosaveInterval } : {}),
|
|
204
198
|
};
|
|
205
199
|
}
|
|
206
|
-
export function shouldOmitOwnedManagedSessionRestoreEnv(options) {
|
|
207
|
-
return resolveManagedSessionRestorePolicy(options).owned && closesBrowserSession(options.args);
|
|
208
|
-
}
|
|
209
|
-
export function canonicalizeOwnedManagedSessionCloseArgs(options, force = false) {
|
|
210
|
-
const policy = resolveManagedSessionRestorePolicy(options);
|
|
211
|
-
if (!policy.owned || !closesBrowserSession(options.args))
|
|
212
|
-
return options.args;
|
|
213
|
-
const sessionName = policy.ownedContext?.sessionName ?? policy.sessionName;
|
|
214
|
-
if (!sessionName)
|
|
215
|
-
return options.args;
|
|
216
|
-
const namespace = canonicalizeAgentBrowserNamespace(policy.ownedContext?.namespace ?? policy.namespace) ?? "";
|
|
217
|
-
const command = options.args.at(-1);
|
|
218
|
-
const prefix = options.args.slice(0, -1);
|
|
219
|
-
const safePrefixes = [
|
|
220
|
-
["--session", sessionName],
|
|
221
|
-
["--json", "--session", sessionName],
|
|
222
|
-
["--namespace", namespace, "--session", sessionName],
|
|
223
|
-
["--json", "--namespace", namespace, "--session", sessionName],
|
|
224
|
-
];
|
|
225
|
-
if (!force && command && ["close", "exit", "quit"].includes(command)
|
|
226
|
-
&& safePrefixes.some((candidate) => candidate.length === prefix.length && candidate.every((token, index) => token === prefix[index]))) {
|
|
227
|
-
return options.args;
|
|
228
|
-
}
|
|
229
|
-
return ["--json", "--namespace", namespace, "--session", sessionName, "close"];
|
|
230
|
-
}
|
|
231
|
-
export function cleanupManagedSessionRestoreConfig() {
|
|
232
|
-
if (managedSessionRestoreEmptyConfigPath) {
|
|
233
|
-
try {
|
|
234
|
-
unlinkSync(managedSessionRestoreEmptyConfigPath);
|
|
235
|
-
}
|
|
236
|
-
catch { }
|
|
237
|
-
}
|
|
238
|
-
managedSessionRestoreEmptyConfigPath = undefined;
|
|
239
|
-
managedSessionRestoreEmptyConfigPromise = undefined;
|
|
240
|
-
}
|
|
241
|
-
async function ensureManagedSessionRestoreEmptyConfig(platform) {
|
|
242
|
-
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
243
|
-
try {
|
|
244
|
-
managedSessionRestoreEmptyConfigPromise ??= writeSecureTempFile({
|
|
245
|
-
content: MANAGED_SESSION_RESTORE_EMPTY_CONFIG_CONTENT,
|
|
246
|
-
prefix: MANAGED_SESSION_RESTORE_EMPTY_CONFIG_NAME.replace(/\.json$/, ""),
|
|
247
|
-
suffix: ".json",
|
|
248
|
-
}).then((path) => {
|
|
249
|
-
if (platform !== "win32")
|
|
250
|
-
chmodSync(path, 0o400);
|
|
251
|
-
managedSessionRestoreEmptyConfigPath = path;
|
|
252
|
-
return path;
|
|
253
|
-
});
|
|
254
|
-
const path = await managedSessionRestoreEmptyConfigPromise;
|
|
255
|
-
let entry = lstatSync(path);
|
|
256
|
-
if (entry.isSymbolicLink() || !entry.isFile())
|
|
257
|
-
throw new Error("Managed restore config is not a regular file.");
|
|
258
|
-
if (platform !== "win32" && (entry.mode & 0o777) !== 0o400) {
|
|
259
|
-
chmodSync(path, 0o400);
|
|
260
|
-
entry = lstatSync(path);
|
|
261
|
-
}
|
|
262
|
-
if (entry.isSymbolicLink() || !entry.isFile() || (platform !== "win32" && (entry.mode & 0o777) !== 0o400))
|
|
263
|
-
throw new Error("Managed restore config permissions are unsafe.");
|
|
264
|
-
if (readFileSync(path, "utf8") !== MANAGED_SESSION_RESTORE_EMPTY_CONFIG_CONTENT)
|
|
265
|
-
throw new Error("Managed restore config content changed.");
|
|
266
|
-
return path;
|
|
267
|
-
}
|
|
268
|
-
catch {
|
|
269
|
-
cleanupManagedSessionRestoreConfig();
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return undefined;
|
|
273
|
-
}
|
|
274
|
-
export async function getManagedSessionRestoreConfigEnv(restoreEnv, pinForOwnedClose = false) {
|
|
275
|
-
if (restoreEnv[AGENT_BROWSER_RESTORE_ENV] === undefined && !pinForOwnedClose)
|
|
276
|
-
return {};
|
|
277
|
-
const path = await ensureManagedSessionRestoreEmptyConfig(process.platform);
|
|
278
|
-
return path ? { [AGENT_BROWSER_CONFIG_ENV]: path } : undefined;
|
|
279
|
-
}
|
|
280
200
|
export function getManagedSessionRestoreProtectedEnv(options, restoreEnv) {
|
|
281
201
|
const { ownedContext } = resolveManagedSessionRestorePolicy(options);
|
|
282
202
|
if (restoreEnv[AGENT_BROWSER_RESTORE_ENV] === undefined)
|
|
@@ -3,8 +3,11 @@ import { linkSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpathSync
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, isAbsolute, join, parse, resolve, win32 } from "node:path";
|
|
5
5
|
import { canonicalizeAgentBrowserNamespace } from "./argv-grammar.js";
|
|
6
|
-
|
|
6
|
+
const MANAGED_SESSION_RESTORE_KEY_PATTERN = /^piab-r2-[a-f\d]{32}$/i;
|
|
7
7
|
const MANAGED_SESSION_NAME_PREFIX = "piab-r2-";
|
|
8
|
+
export function isManagedSessionRestoreKey(value) {
|
|
9
|
+
return typeof value === "string" && MANAGED_SESSION_RESTORE_KEY_PATTERN.test(value);
|
|
10
|
+
}
|
|
8
11
|
const MANAGED_SESSION_FRESH_SUFFIX_PATTERN = /-fresh-[a-f\d]{10}$/i;
|
|
9
12
|
const MANAGED_SESSION_RESTORE_KEY_HASH_LENGTH = 32;
|
|
10
13
|
const PROJECT_GENERATION_MARKER_NAME = "pi-agent-browser-project-generation-v1.json";
|
|
@@ -3,14 +3,12 @@ import { isAbsolute, resolve } from "node:path";
|
|
|
3
3
|
import { isCloseCommand, isOpenNavigationCommand } from "../../command-taxonomy.js";
|
|
4
4
|
import { boundElectronProbeString } from "../../electron/cdp.js";
|
|
5
5
|
import { executableExistsOnPath } from "../../executable-path.js";
|
|
6
|
-
import { isHttpOrHttpsUrl } from "../../input-modes/job.js";
|
|
7
6
|
import { formatSessionArtifactRetentionSummary } from "../../results/artifact-manifest.js";
|
|
8
7
|
import { buildNextToolAction, withOptionalSessionArgs } from "../../results/next-actions.js";
|
|
9
8
|
import { buildVisibleRefFallbackDiagnosticFromSnapshot, getVisibleRefFallbackTarget } from "../../results/selector-recovery.js";
|
|
10
9
|
import { extractRefSnapshotFromData, isAboutBlankUrl, normalizeComparableUrl } from "../../session-page-state.js";
|
|
11
10
|
import { extractUpstreamCommandTokens, parseWaitCommandTokens, redactInvocationArgs, redactSensitiveText } from "../../runtime.js";
|
|
12
11
|
import { isRecord } from "../../parsing.js";
|
|
13
|
-
import { getManagedSessionStateAccessValidationError, isFileUrl } from "../../managed-session-state-policy.js";
|
|
14
12
|
import { extractBatchResultCommand, extractNavigationSummaryFromData, extractStringResultField, findElectronLaunchRecordForSession, runSessionCommandData, } from "./session-state.js";
|
|
15
13
|
import { parseValidBatchStepEntries } from "../batch-stdin.js";
|
|
16
14
|
import { getScreenshotPathTokenIndex } from "./artifact-paths.js";
|
|
@@ -22,16 +20,17 @@ export async function collectNavigationSummary(options) {
|
|
|
22
20
|
const url = extractStringResultField(await runSessionCommandData({ args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal }), "url");
|
|
23
21
|
if (!url || !/^[a-z][a-z0-9+.-]*:/i.test(url))
|
|
24
22
|
return undefined;
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const urlChanged = options.priorTarget?.url ? normalizeComparableUrl(options.priorTarget.url) !== normalizeComparableUrl(url) : undefined;
|
|
24
|
+
if (isAboutBlankUrl(url))
|
|
25
|
+
return { url, ...(urlChanged !== undefined ? { urlChanged } : {}) };
|
|
27
26
|
// Reuse the title already observed for this exact URL instead of spending a second probe. Titles can
|
|
28
27
|
// change without a URL change on SPAs, but this summary is only a "last observed" page label; the URL
|
|
29
28
|
// stays live-probed on every call.
|
|
30
|
-
if (options.priorTarget?.title && normalizeComparableUrl(options.priorTarget.url) === normalizeComparableUrl(url)) {
|
|
31
|
-
return { title: options.priorTarget.title, url };
|
|
29
|
+
if (options.reusePriorTitle !== false && options.priorTarget?.title && normalizeComparableUrl(options.priorTarget.url) === normalizeComparableUrl(url)) {
|
|
30
|
+
return { title: options.priorTarget.title, url, urlChanged: false };
|
|
32
31
|
}
|
|
33
32
|
const title = extractStringResultField(await runSessionCommandData({ args: ["get", "title"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal }), "title");
|
|
34
|
-
return { title, url };
|
|
33
|
+
return { title, url, ...(urlChanged !== undefined ? { urlChanged } : {}) };
|
|
35
34
|
}
|
|
36
35
|
function extractScrollPositionSnapshot(data) {
|
|
37
36
|
const result = isRecord(data) && isRecord(data.result) ? data.result : data;
|
|
@@ -89,6 +88,21 @@ function sameScrollPositionSnapshot(left, right) {
|
|
|
89
88
|
return other?.id === container.id && other.scrollTop === container.scrollTop && other.scrollLeft === container.scrollLeft;
|
|
90
89
|
});
|
|
91
90
|
}
|
|
91
|
+
export function buildUnsupportedScrollIntoViewRecovery(options) {
|
|
92
|
+
if (!["scrollintoview", "scrollinto"].includes(options.commandTokens[0] ?? "") || options.commandTokens.some((token) => token === "--help" || token === "-h"))
|
|
93
|
+
return undefined;
|
|
94
|
+
const match = /^text=(.+)$/s.exec(options.commandTokens[1] ?? "");
|
|
95
|
+
if (!match)
|
|
96
|
+
return undefined;
|
|
97
|
+
const text = redactSensitiveText(match[1]);
|
|
98
|
+
return {
|
|
99
|
+
error: "scrollintoview accepts a CSS selector, xpath=..., or a current @e… ref; text=... is not supported and can falsely report success without moving the page.",
|
|
100
|
+
nextActions: [
|
|
101
|
+
{ id: "scroll-semantic-text-target", params: { args: withOptionalSessionArgs(options.sessionName, ["find", "text", text, "hover"]) }, reason: "Use the upstream semantic text locator; hover resolves and scrolls the matched element into view.", safety: "Hover may open a tooltip or menu. Use the snapshot/ref recovery instead when hover state could affect the workflow.", tool: "agent_browser" },
|
|
102
|
+
{ id: "refresh-refs-for-scroll-target", params: { args: withOptionalSessionArgs(options.sessionName, ["snapshot", "-i"]) }, reason: "Capture a current element ref, then retry scrollintoview with that @e… ref.", safety: "Read-only snapshot; choose the intended current ref before retrying the scroll.", tool: "agent_browser" },
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
92
106
|
export function buildScrollNoopDiagnostic(before, after) {
|
|
93
107
|
if (!before || !after || !sameScrollPositionSnapshot(before, after))
|
|
94
108
|
return undefined;
|
|
@@ -531,14 +545,11 @@ async function collectElectronManagedSessionUrl(options) {
|
|
|
531
545
|
export async function collectElectronManagedSessionTarget(options) {
|
|
532
546
|
if (!options.sessionName)
|
|
533
547
|
return undefined;
|
|
534
|
-
const urlResult = await collectManagedSessionCommandData({
|
|
548
|
+
const urlResult = await collectManagedSessionCommandData({ args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, timeoutMs: options.timeoutMs });
|
|
535
549
|
const url = boundElectronProbeString(extractStringResultField(urlResult.data, "result") ?? extractStringResultField(urlResult.data, "url"), 300);
|
|
536
550
|
if (urlResult.error || !url)
|
|
537
551
|
return { error: urlResult.error ?? "get url returned no active page URL.", sessionName: options.sessionName };
|
|
538
|
-
const
|
|
539
|
-
if (fileAccessError)
|
|
540
|
-
return { error: fileAccessError, sessionName: options.sessionName, url };
|
|
541
|
-
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 });
|
|
552
|
+
const titleResult = await collectManagedSessionCommandData({ args: ["get", "title"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, timeoutMs: options.timeoutMs });
|
|
542
553
|
const title = boundElectronProbeString(extractStringResultField(titleResult.data, "result") ?? extractStringResultField(titleResult.data, "title"), 160);
|
|
543
554
|
return { sessionName: options.sessionName, title, url, ...(titleResult.error ? { error: titleResult.error } : {}) };
|
|
544
555
|
}
|
|
@@ -566,7 +577,7 @@ export function buildQaAttachedRecoveryNextActions(sessionName) {
|
|
|
566
577
|
buildNextToolAction({
|
|
567
578
|
args: sessionArgs(["snapshot", "-i"]),
|
|
568
579
|
id: "snapshot-before-qa-attached",
|
|
569
|
-
reason: "Capture interactive refs on the active
|
|
580
|
+
reason: "Capture interactive refs on the active page before retrying qa.attached.",
|
|
570
581
|
safety: "Read-only snapshot; confirms a renderable page is selected.",
|
|
571
582
|
}),
|
|
572
583
|
];
|
|
@@ -588,13 +599,7 @@ export async function validateQaAttachedPrecondition(options) {
|
|
|
588
599
|
const url = urlProbe.url?.trim();
|
|
589
600
|
if (!url) {
|
|
590
601
|
return {
|
|
591
|
-
error: "qa.attached requires an attached session with a readable
|
|
592
|
-
nextActions: buildQaAttachedRecoveryNextActions(options.sessionName),
|
|
593
|
-
};
|
|
594
|
-
}
|
|
595
|
-
if (!isHttpOrHttpsUrl(url)) {
|
|
596
|
-
return {
|
|
597
|
-
error: `qa.attached requires an http(s) page URL; the current attached URL is "${url}". Use tab list and snapshot -i to recover a web surface before retrying.`,
|
|
602
|
+
error: "qa.attached requires an attached session with a readable page URL. Run tab list, select a stable tab, then snapshot -i before retrying.",
|
|
598
603
|
nextActions: buildQaAttachedRecoveryNextActions(options.sessionName),
|
|
599
604
|
};
|
|
600
605
|
}
|
|
@@ -686,9 +691,6 @@ export async function collectElectronHandoff(options) {
|
|
|
686
691
|
const url = extractStringResultField(urlData, "result") ?? extractStringResultField(urlData, "url");
|
|
687
692
|
if (!url)
|
|
688
693
|
throw new Error("Electron handoff get url returned no active page URL.");
|
|
689
|
-
const fileAccessError = getManagedSessionStateAccessValidationError({ args: ["snapshot", "-i"], currentPageUrl: url, cwd: options.cwd });
|
|
690
|
-
if (fileAccessError)
|
|
691
|
-
return { error: fileAccessError, failureCategory: "validation-error", handoff: options.handoff };
|
|
692
694
|
const tabs = await runSessionCommandData({ args: ["tab", "list"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, throwOnFailure: true });
|
|
693
695
|
if (options.signal?.aborted)
|
|
694
696
|
throw new Error("Electron handoff was aborted.");
|
|
@@ -875,7 +877,7 @@ export async function collectTimeoutPartialProgress(options) {
|
|
|
875
877
|
const artifacts = await collectTimeoutArtifactEvidence(options.cwd, rawSteps);
|
|
876
878
|
const urlData = await runSessionCommandData({ args: ["get", "url"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName });
|
|
877
879
|
const recoveredUrl = extractStringResultField(urlData, "result") ?? extractStringResultField(urlData, "url");
|
|
878
|
-
const titleData = recoveredUrl
|
|
880
|
+
const titleData = recoveredUrl
|
|
879
881
|
? await runSessionCommandData({ args: ["get", "title"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName })
|
|
880
882
|
: undefined;
|
|
881
883
|
const title = extractStringResultField(titleData, "result") ?? extractStringResultField(titleData, "title");
|
|
@@ -910,7 +912,7 @@ function sanitizeCurrentPageUrlForTimeoutDiagnostic(url) {
|
|
|
910
912
|
return redactSensitivePathSegmentsForDiagnostic(redactSensitiveText(url));
|
|
911
913
|
}
|
|
912
914
|
}
|
|
913
|
-
export function formatTimeoutPartialProgressText(progress) {
|
|
915
|
+
export function formatTimeoutPartialProgressText(progress, pageTargetUnknown = false) {
|
|
914
916
|
const lines = [`Timeout partial progress: ${progress.summary}`];
|
|
915
917
|
const currentPageTitle = progress.currentPage?.title ? redactSensitivePathSegmentsForDiagnostic(redactSensitiveText(progress.currentPage.title)) : undefined;
|
|
916
918
|
const currentPageUrl = progress.currentPage?.url ? sanitizeCurrentPageUrlForTimeoutDiagnostic(progress.currentPage.url) : undefined;
|
|
@@ -928,7 +930,10 @@ export function formatTimeoutPartialProgressText(progress) {
|
|
|
928
930
|
lines.push(`- ... ${progress.steps.length - shownSteps.length} more step${progress.steps.length - shownSteps.length === 1 ? "" : "s"} omitted`);
|
|
929
931
|
}
|
|
930
932
|
if (progress.retryStep?.retry?.args) {
|
|
931
|
-
|
|
933
|
+
const payload = JSON.stringify({ args: redactInvocationArgs(progress.retryStep.retry.args) });
|
|
934
|
+
lines.push(pageTargetUnknown
|
|
935
|
+
? `Retry candidate for step ${progress.retryStep.index}: ${payload}. Verify the current URL before running it.`
|
|
936
|
+
: `Retry failed step: ${payload}`);
|
|
932
937
|
}
|
|
933
938
|
for (const artifact of progress.artifacts)
|
|
934
939
|
lines.push(`Artifact from step ${artifact.stepIndex}: ${redactSensitivePathSegmentsForDiagnostic(artifact.path)} (${artifact.exists ? `exists${typeof artifact.sizeBytes === "number" ? `, ${artifact.sizeBytes} bytes` : ""}` : "missing"})`);
|
|
@@ -3,9 +3,10 @@ import { getCompiledSemanticActionCommandIndex, getCompiledSemanticActionSession
|
|
|
3
3
|
import { redactNetworkSourceLookupSurface } from "../../input-modes/lookups.js";
|
|
4
4
|
import { buildAgentBrowserNextActions } from "../../results/action-recommendations.js";
|
|
5
5
|
import { buildAgentBrowserResultCategoryDetails } from "../../results/categories.js";
|
|
6
|
+
import { extractAgentBrowserLifecycle } from "../../results/presentation/common.js";
|
|
6
7
|
import { formatSessionArtifactRetentionSummary } from "../../results/artifact-manifest.js";
|
|
7
8
|
import { alignPageChangeSummaryNextActionIds, appendUniqueAgentBrowserNextActions, applyNamespaceToNextActions, isStandaloneSnapshotNextAction, withOptionalSessionArgs, } from "../../results/next-actions.js";
|
|
8
|
-
import { buildConnectedSessionNextActions, buildNoActivePageNextActions, buildSessionAwareStaleRefNextActions, buildSessionTabRecoveryNextActions, } from "../../results/recovery-next-actions.js";
|
|
9
|
+
import { buildConnectedSessionNextActions, buildNoActivePageNextActions, buildPendingWebMcpNextActions, buildSessionAwareStaleRefNextActions, buildSessionTabRecoveryNextActions, } from "../../results/recovery-next-actions.js";
|
|
9
10
|
import { buildRichInputRecoveryDiagnostic, buildRichInputRecoveryNextActions, buildVisibleRefFallbackNextActions, formatRichInputRecoveryText, formatVisibleRefFallbackText, sanitizeVisibleRefFallbackDiagnostic, } from "../../results/selector-recovery.js";
|
|
10
11
|
import { buildNoActivePageRefSnapshotInvalidation, isNoActivePageSnapshotFailure, } from "../../session-page-state.js";
|
|
11
12
|
import { extractExplicitSessionName } from "../../argv-grammar.js";
|
|
@@ -208,6 +209,18 @@ function buildTimeoutPartialProgressNextActions(options) {
|
|
|
208
209
|
const retryArgs = options.timeoutPartialProgress?.retryStep?.retry?.args;
|
|
209
210
|
const stepIndex = options.timeoutPartialProgress?.retryStep?.index;
|
|
210
211
|
const freshSessionAbandoned = options.sessionMode === "fresh" && options.timeoutPartialProgress?.liveUrlRecovered !== true;
|
|
212
|
+
if (options.currentSessionTabTargetUnknown && !freshSessionAbandoned && options.executionPlan.sessionName) {
|
|
213
|
+
return [{
|
|
214
|
+
id: "verify-page-target-after-timeout",
|
|
215
|
+
params: {
|
|
216
|
+
args: withOptionalSessionArgs(options.executionPlan.sessionName, ["batch", "--bail"]),
|
|
217
|
+
stdin: JSON.stringify([["get", "url"], ["snapshot", "-i"]]),
|
|
218
|
+
},
|
|
219
|
+
reason: `Verify the current URL, then inspect the page after timeout${stepIndex === undefined ? "" : ` before resuming from incomplete step ${stepIndex}`}.`,
|
|
220
|
+
safety: "Fail-fast read-only recovery: snapshot runs only after get url succeeds, satisfying the wrapper page-target guard without trusting the planned URL.",
|
|
221
|
+
tool: "agent_browser",
|
|
222
|
+
}];
|
|
223
|
+
}
|
|
211
224
|
if (retryArgs) {
|
|
212
225
|
return [{
|
|
213
226
|
id: "retry-timeout-step",
|
|
@@ -267,6 +280,10 @@ function buildResultNextActions(options) {
|
|
|
267
280
|
const appendUnique = (actions) => {
|
|
268
281
|
appendUniqueAgentBrowserNextActions(nextActions, actions);
|
|
269
282
|
};
|
|
283
|
+
if (options.unsettledWebMcpMutation && options.currentSessionTabTargetUnknown) {
|
|
284
|
+
nextActions = nextActions.filter((action) => !isStandaloneSnapshotNextAction(action));
|
|
285
|
+
appendUnique(buildPendingWebMcpNextActions(options.executionPlan.sessionName));
|
|
286
|
+
}
|
|
270
287
|
if (options.categoryDetails.resultCategory === "success" && options.executionPlan.commandInfo.command === "connect" && !options.electronLaunchRecord)
|
|
271
288
|
appendUnique(buildConnectedSessionNextActions(options.executionPlan.sessionName));
|
|
272
289
|
if (options.noActivePageSnapshotFailure)
|
|
@@ -320,6 +337,8 @@ function buildResultNextActions(options) {
|
|
|
320
337
|
if (options.managedSessionOutcome)
|
|
321
338
|
appendUnique(buildManagedSessionFreshFailureNextActions(options.managedSessionOutcome));
|
|
322
339
|
if (options.categoryDetails.failureCategory === "timeout" && options.processResult.timedOut) {
|
|
340
|
+
if (options.currentSessionTabTargetUnknown)
|
|
341
|
+
nextActions = nextActions.filter((action) => !isStandaloneSnapshotNextAction(action));
|
|
323
342
|
appendUnique(buildTimeoutPartialProgressNextActions(options));
|
|
324
343
|
appendUnique(buildDialogTimeoutNextActions({ command: options.executionPlan.commandInfo.command, sessionName: options.executionPlan.sessionName }));
|
|
325
344
|
}
|
|
@@ -329,19 +348,45 @@ function buildResultNextActions(options) {
|
|
|
329
348
|
append(buildAgentBrowserNextActions({ electron: { launchId: options.electronLaunchRecord.launchId, sessionName: options.electronLaunchRecord.sessionName, status: options.electronLaunchRecord.cleanupState }, failureCategory: options.categoryDetails.failureCategory, resultCategory: options.categoryDetails.resultCategory, successCategory: options.categoryDetails.successCategory }));
|
|
330
349
|
return nextActions.length > 0 ? nextActions : undefined;
|
|
331
350
|
}
|
|
332
|
-
function
|
|
333
|
-
if (
|
|
351
|
+
export function formatAgentBrowserNextActionsText(nextActions) {
|
|
352
|
+
if (!nextActions || nextActions.length === 0)
|
|
334
353
|
return undefined;
|
|
335
354
|
const lines = nextActions.slice(0, 6).map((action) => {
|
|
336
355
|
const params = action.params
|
|
337
|
-
? { ...action.params, ...(action.params.stdin
|
|
356
|
+
? { ...action.params, ...(action.params.stdin !== undefined && action.params.stdin.length > 500 ? { stdin: "[omitted; use details.nextActions]" } : {}) }
|
|
338
357
|
: undefined;
|
|
339
358
|
const payload = action.artifactPath ? { artifactPath: action.artifactPath } : params;
|
|
340
|
-
return `- ${action.id}${payload ? ` ${JSON.stringify(payload)}` : ""}: ${action.reason}`;
|
|
359
|
+
return `- ${action.id}${payload ? ` ${redactSensitiveText(JSON.stringify(payload))}` : ""}: ${redactSensitiveText(action.reason)}`;
|
|
341
360
|
});
|
|
342
|
-
return ["Next actions:", ...lines, "
|
|
361
|
+
return ["Next actions:", ...lines, "The same redacted payloads are available in details.nextActions."].join("\n");
|
|
362
|
+
}
|
|
363
|
+
function formatFailureNextActionsText(options, nextActions) {
|
|
364
|
+
return options.categoryDetails.resultCategory === "failure" ? formatAgentBrowserNextActionsText(nextActions) : undefined;
|
|
365
|
+
}
|
|
366
|
+
function getReadSource(options) {
|
|
367
|
+
return options.executionPlan.commandInfo.command === "read" && isRecord(options.presentationEnvelope?.data) && typeof options.presentationEnvelope.data.source === "string"
|
|
368
|
+
? options.presentationEnvelope.data.source
|
|
369
|
+
: undefined;
|
|
370
|
+
}
|
|
371
|
+
function formatReadExecutionText(options, lifecycle) {
|
|
372
|
+
const source = getReadSource(options);
|
|
373
|
+
if (!source)
|
|
374
|
+
return undefined;
|
|
375
|
+
return `Read execution: source ${source}; CLI started: ${options.processResult.agentBrowserStarted ? "yes" : "no"}; managed browser lifecycle active: ${lifecycle?.effectiveLaunch.browserLaunched === true ? "yes" : "no"}; managed session outcome: ${options.managedSessionOutcome?.status ?? "not managed"}.`;
|
|
376
|
+
}
|
|
377
|
+
function buildBrowserWindowStatus(options, lifecycle) {
|
|
378
|
+
if (!options.headedLaunch || options.preserveAttachedBrowserSession || options.providerLaunch || !options.succeeded || lifecycle?.effectiveLaunch.browserLaunched !== true || !options.executionPlan.managedSessionName || !options.managedSessionOutcome || !["created", "replaced"].includes(options.managedSessionOutcome.status))
|
|
379
|
+
return undefined;
|
|
380
|
+
return { mode: "headed", ownership: "wrapper-managed", sessionName: options.executionPlan.managedSessionName, visibility: "unverified" };
|
|
381
|
+
}
|
|
382
|
+
function formatBrowserWindowText(browserWindow) {
|
|
383
|
+
if (!browserWindow)
|
|
384
|
+
return undefined;
|
|
385
|
+
return "Headed browser handoff: wrapper-managed headed window requested; desktop visibility unverified. If login is needed, ask the user to confirm they can see the window and finish signing in there, then continue with sessionMode auto.";
|
|
343
386
|
}
|
|
344
387
|
function buildAgentBrowserResultDetails(options, nextActions) {
|
|
388
|
+
const lifecycle = extractAgentBrowserLifecycle(options.presentationEnvelope?.data);
|
|
389
|
+
const browserWindow = buildBrowserWindowStatus(options, lifecycle);
|
|
345
390
|
const publicVisibleRefFallbackDiagnostic = options.visibleRefFallbackDiagnostic ? sanitizeVisibleRefFallbackDiagnostic(options.visibleRefFallbackDiagnostic) : undefined;
|
|
346
391
|
const rawPageChangeSummary = (options.scrollNoopDiagnostic || options.comboboxFocusDiagnostic) && options.presentation.pageChangeSummary ? { ...options.presentation.pageChangeSummary, nextActionIds: nextActions?.map((action) => action.id) } : options.presentation.pageChangeSummary;
|
|
347
392
|
const pageChangeSummary = alignPageChangeSummaryNextActionIds(rawPageChangeSummary, nextActions);
|
|
@@ -370,6 +415,9 @@ function buildAgentBrowserResultDetails(options, nextActions) {
|
|
|
370
415
|
electron: options.electronLaunchRecord ? { action: "launch", cleanup: options.electronFailedConnectCleanup, handoff: options.electronHandoff, identifiers: buildElectronIdentifiers(options.electronLaunchRecord), launch: options.electronLaunchRecord, profileIsolation: options.electronProfileIsolationDetails, status: options.succeeded ? "succeeded" : "failed", targets: options.electronLaunch?.targets, version: options.electronLaunch?.version } : undefined,
|
|
371
416
|
...options.categoryDetails,
|
|
372
417
|
agentBrowserStarted: options.processResult.agentBrowserStarted,
|
|
418
|
+
browserWindow,
|
|
419
|
+
lifecycle,
|
|
420
|
+
readSource: getReadSource(options),
|
|
373
421
|
aboutBlankSessionMismatch: options.aboutBlankSessionMismatch,
|
|
374
422
|
electronPostCommandHealth: options.electronPostCommandHealth,
|
|
375
423
|
electronRefFreshness: options.electronRefFreshnessDiagnostic,
|
|
@@ -429,6 +477,8 @@ function buildAgentBrowserResultDetails(options, nextActions) {
|
|
|
429
477
|
}
|
|
430
478
|
export function buildFinalAgentBrowserToolResult(options) {
|
|
431
479
|
const nextActions = applyNamespaceToNextActions(buildResultNextActions(options), options.executionPlan.namespace);
|
|
480
|
+
const lifecycle = extractAgentBrowserLifecycle(options.presentationEnvelope?.data);
|
|
481
|
+
const browserWindow = buildBrowserWindowStatus(options, lifecycle);
|
|
432
482
|
const details = buildAgentBrowserResultDetails(options, nextActions);
|
|
433
483
|
const visibleRefFallbackText = formatVisibleRefFallbackText(options.visibleRefFallbackDiagnostic);
|
|
434
484
|
const richInputRecoveryText = formatRichInputRecoveryText(options.richInputRecoveryDiagnostic);
|
|
@@ -445,10 +495,12 @@ export function buildFinalAgentBrowserToolResult(options) {
|
|
|
445
495
|
const evalStdinHintText = formatEvalStdinHintText(options.evalStdinHint);
|
|
446
496
|
const evalResultWarningText = formatEvalResultWarningText(options.evalResultWarning);
|
|
447
497
|
const artifactCleanupText = formatArtifactCleanupGuidanceText(options.artifactCleanup);
|
|
448
|
-
const timeoutPartialProgressText = options.timeoutPartialProgress ? formatTimeoutPartialProgressText(options.timeoutPartialProgress) : undefined;
|
|
498
|
+
const timeoutPartialProgressText = options.timeoutPartialProgress ? formatTimeoutPartialProgressText(options.timeoutPartialProgress, options.currentSessionTabTargetUnknown === true && !(options.sessionMode === "fresh" && options.timeoutPartialProgress.liveUrlRecovered !== true)) : undefined;
|
|
449
499
|
const managedSessionOutcomeText = formatManagedSessionOutcomeText(options.managedSessionOutcome);
|
|
500
|
+
const readExecutionText = formatReadExecutionText(options, lifecycle);
|
|
501
|
+
const browserWindowText = formatBrowserWindowText(browserWindow);
|
|
450
502
|
const failureNextActionsText = formatFailureNextActionsText(options, nextActions);
|
|
451
|
-
const rawAppendedDiagnosticText = [visibleRefFallbackText, richInputRecoveryText, semanticActionCandidateText, clickDispatchText, overlayBlockerText, fillVerificationText, electronRefFreshnessText, selectorTextVisibilityText, electronBroadGetTextScopeText, scrollNoopDiagnosticText, comboboxFocusDiagnosticText, recordingDependencyWarningText, evalStdinHintText, evalResultWarningText, artifactCleanupText, timeoutPartialProgressText, managedSessionOutcomeText, failureNextActionsText].filter((item) => item !== undefined).join("\n\n");
|
|
503
|
+
const rawAppendedDiagnosticText = [visibleRefFallbackText, richInputRecoveryText, semanticActionCandidateText, clickDispatchText, overlayBlockerText, fillVerificationText, electronRefFreshnessText, selectorTextVisibilityText, electronBroadGetTextScopeText, scrollNoopDiagnosticText, comboboxFocusDiagnosticText, recordingDependencyWarningText, evalStdinHintText, evalResultWarningText, artifactCleanupText, timeoutPartialProgressText, managedSessionOutcomeText, readExecutionText, browserWindowText, failureNextActionsText].filter((item) => item !== undefined).join("\n\n");
|
|
452
504
|
const appendedDiagnosticText = redactSensitiveText(redactExactSensitiveText(rawAppendedDiagnosticText, options.exactSensitiveValues));
|
|
453
505
|
const shouldAppendDiagnosticText = appendedDiagnosticText.length > 0 && (!options.userRequestedJson || options.plainTextInspection);
|
|
454
506
|
let content = shouldAppendDiagnosticText && options.redactedContent[0]?.type === "text" ? [{ ...options.redactedContent[0], text: `${options.redactedContent[0].text}\n\n${appendedDiagnosticText}` }, ...options.redactedContent.slice(1)] : options.redactedContent;
|
package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js
CHANGED
|
@@ -20,7 +20,6 @@ function getHeadedManagedAutosaveEnv(interval) {
|
|
|
20
20
|
}
|
|
21
21
|
export async function inspectManagedSessionDaemon(options) {
|
|
22
22
|
const processResult = await runAgentBrowserProcess({
|
|
23
|
-
allowManagedSessionTarget: options.allowManagedSessionTarget,
|
|
24
23
|
args: ["--json", "--namespace", options.namespace ?? "", "--session", options.sessionName, "session", "info"],
|
|
25
24
|
cwd: options.cwd,
|
|
26
25
|
env: getHeadedManagedAutosaveEnv(options.headedManagedAutosaveInterval),
|
|
@@ -63,12 +62,11 @@ export async function acquireOwnedManagedSessionDaemonPolicy(options) {
|
|
|
63
62
|
return signal?.aborted
|
|
64
63
|
? {}
|
|
65
64
|
: {
|
|
66
|
-
error: "Managed-session policy coordination is unavailable or busy. Retry after the current operation finishes, repair the private policy-lock directory, and on POSIX verify that /bin/ps
|
|
65
|
+
error: "Managed-session policy coordination is unavailable or busy. Retry after the current operation finishes, repair the private policy-lock directory, and on POSIX verify that /bin/ps, /usr/bin/ps, or ps through PATH is available.",
|
|
67
66
|
};
|
|
68
67
|
}
|
|
69
68
|
try {
|
|
70
69
|
const daemon = await inspectManagedSessionDaemon({
|
|
71
|
-
allowManagedSessionTarget: true,
|
|
72
70
|
cwd: context.cwd,
|
|
73
71
|
headedManagedAutosaveInterval: context.headedManagedAutosaveInterval,
|
|
74
72
|
namespace: context.namespace,
|
|
@@ -133,7 +131,6 @@ export async function closeManagedSession(options) {
|
|
|
133
131
|
}
|
|
134
132
|
try {
|
|
135
133
|
const daemon = await inspectManagedSessionDaemon({
|
|
136
|
-
allowManagedSessionTarget: true,
|
|
137
134
|
cwd: options.cwd,
|
|
138
135
|
headedManagedAutosaveInterval: options.headedManagedAutosaveInterval,
|
|
139
136
|
namespace: options.namespace,
|