pi-agent-browser-native 0.8.2 → 0.8.3
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 +23 -0
- package/README.md +16 -7
- package/dist/extensions/agent-browser/index.js +430 -157
- package/dist/extensions/agent-browser/lib/browser-journal.js +418 -0
- package/dist/extensions/agent-browser/lib/browser-session-conversion.js +381 -0
- package/dist/extensions/agent-browser/lib/browser-transcript.js +119 -19
- package/dist/extensions/agent-browser/lib/input-modes/job.js +49 -2
- package/dist/extensions/agent-browser/lib/input-modes/params.js +32 -35
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +25 -5
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +20 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +97 -78
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +23 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +52 -17
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +13 -8
- package/dist/extensions/agent-browser/lib/playbook.js +9 -2
- package/dist/extensions/agent-browser/lib/prompt-policy.js +0 -14
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +2 -1
- package/dist/extensions/agent-browser/lib/session-page-state.js +95 -157
- package/dist/extensions/agent-browser/lib/temp.js +14 -6
- package/dist/extensions/agent-browser/lib/tool-surface.js +12 -23
- package/dist/extensions/agent-browser/lib/web-search.js +0 -8
- package/docs/ARCHITECTURE.md +13 -11
- package/docs/COMMAND_REFERENCE.md +9 -7
- package/docs/RELEASE.md +3 -3
- package/docs/REQUIREMENTS.md +1 -1
- package/docs/SESSION_STORAGE.md +60 -0
- package/docs/SUPPORT_MATRIX.md +8 -5
- package/docs/TOOL_CONTRACT.md +17 -14
- package/package.json +9 -5
- package/scripts/build.mjs +4 -3
- package/scripts/convert-browser-session.mjs +22 -0
|
@@ -79,41 +79,38 @@ export function createAgentBrowserQaParamsSchema(Type = JsonSchema, StringEnum =
|
|
|
79
79
|
export function createAgentBrowserElectronParamsSchema(Type = JsonSchema, StringEnum = localStringEnum) {
|
|
80
80
|
const common = { outputPath: outputProperties(Type).outputPath };
|
|
81
81
|
const timeoutMs = Type.Optional(Type.Integer({ minimum: 1 }));
|
|
82
|
-
|
|
83
|
-
Type.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Type.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
...common,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
action: StringEnum(["probe"]),
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
...common,
|
|
115
|
-
}, { additionalProperties: false }),
|
|
116
|
-
]));
|
|
82
|
+
const list = {
|
|
83
|
+
query: Type.Optional(Type.String({ description: "Case-insensitive app filter.", minLength: 1 })),
|
|
84
|
+
maxResults: Type.Optional(Type.Integer({ description: `Result cap; default ${ELECTRON_DISCOVERY_DEFAULT_MAX_RESULTS}, clamped to ${ELECTRON_DISCOVERY_MAX_RESULTS}.`, minimum: 1 })),
|
|
85
|
+
};
|
|
86
|
+
const launch = {
|
|
87
|
+
appPath: Type.Optional(Type.String({ description: "macOS .app path.", minLength: 1 })),
|
|
88
|
+
appName: Type.Optional(Type.String({ description: "Name from agent_browser_electron list.", minLength: 1 })),
|
|
89
|
+
bundleId: Type.Optional(Type.String({ minLength: 1 })),
|
|
90
|
+
executablePath: Type.Optional(Type.String({ minLength: 1 })),
|
|
91
|
+
appArgs: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
92
|
+
handoff: Type.Optional(StringEnum(AGENT_BROWSER_ELECTRON_HANDOFFS)),
|
|
93
|
+
targetType: Type.Optional(StringEnum(AGENT_BROWSER_ELECTRON_TARGET_TYPES)),
|
|
94
|
+
timeoutMs,
|
|
95
|
+
allow: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
96
|
+
deny: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
97
|
+
};
|
|
98
|
+
const probe = { launchId: Type.Optional(Type.String({ minLength: 1 })), timeoutMs };
|
|
99
|
+
const lifecycle = { ...probe, all: Type.Optional(Type.Literal(true)) };
|
|
100
|
+
// Strict providers need an object root; Pi needs root properties to normalize optional nulls.
|
|
101
|
+
// Each branch retains its original action-specific field boundary.
|
|
102
|
+
return Type.Unsafe(Type.Object({
|
|
103
|
+
action: StringEnum(["list", "launch", "status", "cleanup", "probe"]),
|
|
104
|
+
...list, ...launch, ...lifecycle, ...common,
|
|
105
|
+
}, {
|
|
106
|
+
additionalProperties: false,
|
|
107
|
+
anyOf: [
|
|
108
|
+
Type.Object({ action: StringEnum(["list"]), ...list, ...common }, { additionalProperties: false }),
|
|
109
|
+
Type.Object({ action: StringEnum(["launch"]), ...launch, ...common }, { additionalProperties: false }),
|
|
110
|
+
Type.Object({ action: StringEnum(["status", "cleanup"]), ...lifecycle, ...common }, { additionalProperties: false, not: { required: ["launchId", "all"] } }),
|
|
111
|
+
Type.Object({ action: StringEnum(["probe"]), ...probe, ...common }, { additionalProperties: false }),
|
|
112
|
+
],
|
|
113
|
+
}));
|
|
117
114
|
}
|
|
118
115
|
export function createAgentBrowserSourceParamsSchema(Type = JsonSchema, StringEnum = localStringEnum) {
|
|
119
116
|
return Type.Unsafe(Type.Object({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { createHash, randomInt, randomUUID } from "node:crypto";
|
|
3
3
|
import { lstat, mkdir, readFile, readdir, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { basename, dirname, join } from "node:path";
|
|
@@ -11,6 +11,9 @@ const POLICY_LOCK_RETRY_MS = 10;
|
|
|
11
11
|
const POLICY_LOCK_MAX_BYTES = 1_048_576; // One claim may cover an entire owned-browser cleanup set.
|
|
12
12
|
const LOCK_OWNER_FILE = "owner.json";
|
|
13
13
|
const LOCK_TICKET_FILE = "ticket.json";
|
|
14
|
+
const STALE_CLAIM_MIN_AGE_MS = 30 * 60 * 1_000;
|
|
15
|
+
const POLICY_GC_BATCH_SIZE = 8;
|
|
16
|
+
let lastPolicyGcName;
|
|
14
17
|
function getCoordinationDirectory(platform = process.platform) {
|
|
15
18
|
if (platform !== "win32") {
|
|
16
19
|
const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
@@ -175,6 +178,7 @@ function parseOwner(content) {
|
|
|
175
178
|
&& Number.isSafeInteger(parsed.pid) && (parsed.pid ?? 0) > 0
|
|
176
179
|
&& typeof parsed.startIdentity === "string" && parsed.startIdentity.length > 0
|
|
177
180
|
&& typeof parsed.token === "string" && parsed.token.length > 0
|
|
181
|
+
&& !parsed.token.includes("/") && !parsed.token.includes("\\") && !parsed.token.includes("\0")
|
|
178
182
|
? parsed
|
|
179
183
|
: undefined;
|
|
180
184
|
}
|
|
@@ -323,14 +327,30 @@ async function cleanDeadPolicyArtifacts(directory, budget) {
|
|
|
323
327
|
catch {
|
|
324
328
|
return;
|
|
325
329
|
}
|
|
326
|
-
|
|
327
|
-
|
|
330
|
+
// ponytail: list/sort all names; stream directories if enumeration becomes the bottleneck.
|
|
331
|
+
const candidates = names.filter((name) => name.startsWith(".pi-agent-browser-policy-remove-")
|
|
332
|
+
|| /^\.pi-agent-browser-policy-[a-f0-9]{64}\.lock-v4\.(?:candidate|claim)-/.test(name)).sort();
|
|
333
|
+
if (candidates.length === 0)
|
|
334
|
+
return;
|
|
335
|
+
const start = lastPolicyGcName === undefined ? randomInt(candidates.length)
|
|
336
|
+
: Math.max(0, candidates.findIndex((name) => name > lastPolicyGcName));
|
|
337
|
+
const batch = Array.from({ length: Math.min(POLICY_GC_BATCH_SIZE, candidates.length) }, (_, offset) => candidates[(start + offset) % candidates.length]);
|
|
338
|
+
lastPolicyGcName = batch.at(-1);
|
|
339
|
+
for (const name of batch) {
|
|
328
340
|
if (budget.signal?.aborted || Date.now() >= (budget.deadline ?? Infinity))
|
|
329
341
|
return;
|
|
330
342
|
const path = join(directory, name);
|
|
343
|
+
const published = name.includes(".lock-v4.claim-");
|
|
344
|
+
if (published) {
|
|
345
|
+
const entry = await lstat(path).catch(() => undefined);
|
|
346
|
+
if (!entry || entry.mtimeMs >= Date.now() - STALE_CLAIM_MIN_AGE_MS)
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
331
349
|
const claim = await readClaim(path);
|
|
332
|
-
if (claim &&
|
|
333
|
-
|
|
350
|
+
if (!claim || (published && !name.endsWith(`.claim-${claim.owner.token}`)))
|
|
351
|
+
continue;
|
|
352
|
+
if (await ownerAlive(claim.owner, budget) === false)
|
|
353
|
+
await removeClaimOwnedBy(path, claim.owner.token).catch(() => false);
|
|
334
354
|
}
|
|
335
355
|
}
|
|
336
356
|
function claimPrecedes(left, right) {
|
|
@@ -4,6 +4,7 @@ import { canonicalizeAgentBrowserNamespace, extractExplicitNamespace, extractExp
|
|
|
4
4
|
import { hasLaunchScopedFlagToken, MANAGED_RESTORE_INCOMPATIBLE_BOOLEAN_ENVS, MANAGED_RESTORE_INCOMPATIBLE_ENVS, MANAGED_RESTORE_INCOMPATIBLE_FLAGS, } from "./launch-scoped-flags.js";
|
|
5
5
|
import { createManagedSessionRestoreKey, ensureManagedSessionRestoreStorageIsSecure, getManagedSessionRestoreScope, getManagedSessionRestoreProtectedStorageEnv, hasManagedSessionRestoreProjectIdentity, resolveManagedSessionRestoreHome, } from "./managed-session-storage.js";
|
|
6
6
|
import { parseUserBatchStdin } from "./orchestration/batch-stdin.js";
|
|
7
|
+
import { isRecord } from "./parsing.js";
|
|
7
8
|
import { getAgentBrowserProcessEnvironment } from "./process-environment.js";
|
|
8
9
|
export { createManagedSessionRestoreKey, ensureManagedSessionRestoreStorageIsSecure, getManagedSessionRestoreScope } from "./managed-session-storage.js";
|
|
9
10
|
export { pruneOwnedManagedSessionRestoreSnapshots } from "./managed-session-snapshots.js";
|
|
@@ -39,10 +40,11 @@ export class ManagedSessionRestoreState {
|
|
|
39
40
|
this.#disabled.add(getAgentBrowserSessionIdentityKey(sessionName, namespace));
|
|
40
41
|
}
|
|
41
42
|
getDaemonRestoreKey(sessionName, namespace) {
|
|
42
|
-
return typeof sessionName === "string" ? this.#daemonRestoreKeys.get(getAgentBrowserSessionIdentityKey(sessionName, namespace)) : undefined;
|
|
43
|
+
return typeof sessionName === "string" ? this.#daemonRestoreKeys.get(getAgentBrowserSessionIdentityKey(sessionName, namespace))?.restoreKey : undefined;
|
|
43
44
|
}
|
|
44
45
|
hasDaemonRestoreKey(sessionName, namespace) {
|
|
45
|
-
return typeof sessionName === "string" && this.#daemonRestoreKeys.
|
|
46
|
+
return typeof sessionName === "string" && this.#daemonRestoreKeys.get(getAgentBrowserSessionIdentityKey(sessionName, namespace))?.restored !== true
|
|
47
|
+
&& this.#daemonRestoreKeys.has(getAgentBrowserSessionIdentityKey(sessionName, namespace));
|
|
46
48
|
}
|
|
47
49
|
forgetDaemonRestoreKey(sessionName, namespace) {
|
|
48
50
|
if (sessionName)
|
|
@@ -51,9 +53,22 @@ export class ManagedSessionRestoreState {
|
|
|
51
53
|
isDisabled(sessionName, namespace) {
|
|
52
54
|
return typeof sessionName === "string" && this.#disabled.has(getAgentBrowserSessionIdentityKey(sessionName, namespace));
|
|
53
55
|
}
|
|
54
|
-
recordDaemonRestoreKey(sessionName, namespace, restoreKey) {
|
|
55
|
-
if (sessionName)
|
|
56
|
-
|
|
56
|
+
recordDaemonRestoreKey(sessionName, namespace, restoreKey, generation) {
|
|
57
|
+
if (sessionName) {
|
|
58
|
+
const key = getAgentBrowserSessionIdentityKey(sessionName, namespace);
|
|
59
|
+
const previous = this.#daemonRestoreKeys.get(key);
|
|
60
|
+
this.#daemonRestoreKeys.set(key, { restoreKey, generation: generation ?? (previous?.restoreKey === restoreKey ? previous.generation : undefined) });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
getDaemonReceipt(sessionName, namespace) {
|
|
64
|
+
const receipt = sessionName ? this.#daemonRestoreKeys.get(getAgentBrowserSessionIdentityKey(sessionName, namespace)) : undefined;
|
|
65
|
+
return receipt?.generation ? { restoreKey: receipt.restoreKey, generation: receipt.generation } : undefined;
|
|
66
|
+
}
|
|
67
|
+
restoreDaemonReceipt(sessionName, namespace, receipt) {
|
|
68
|
+
if (isRecord(receipt) && typeof receipt.generation === "string" && receipt.generation.length > 0
|
|
69
|
+
&& (receipt.restoreKey === null || typeof receipt.restoreKey === "string")) {
|
|
70
|
+
this.#daemonRestoreKeys.set(getAgentBrowserSessionIdentityKey(sessionName, namespace), { restoreKey: receipt.restoreKey, generation: receipt.generation, restored: true });
|
|
71
|
+
}
|
|
57
72
|
}
|
|
58
73
|
replace(identities = [], options = {}) {
|
|
59
74
|
if (!options.preserveDaemonRestoreKeys)
|
package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js
CHANGED
|
@@ -5,7 +5,8 @@ import { acquireManagedSessionPolicyLock } from "../../managed-session-policy-lo
|
|
|
5
5
|
import { pruneOwnedManagedSessionRestoreSnapshots, resolveExplicitAutosaveInterval, withOwnedManagedSessionContext, } from "../../managed-session-restore.js";
|
|
6
6
|
import { isManagedSessionRestoreKey } from "../../managed-session-storage.js";
|
|
7
7
|
import { isRecord } from "../../parsing.js";
|
|
8
|
-
import {
|
|
8
|
+
import { readProcessStartIdentity } from "../../process-identity.js";
|
|
9
|
+
import { getAgentBrowserProcessEnvironment, withAgentBrowserProcessEnvironment } from "../../process-environment.js";
|
|
9
10
|
import { runAgentBrowserProcess, withAttachedBrowserSessionContext } from "../../process.js";
|
|
10
11
|
import { getAgentBrowserErrorText, parseAgentBrowserEnvelope } from "../../results/envelope.js";
|
|
11
12
|
import { redactInvocationArgs } from "../../runtime.js";
|
|
@@ -43,9 +44,16 @@ export async function inspectManagedSessionDaemon(options) {
|
|
|
43
44
|
return { status: "inactive" };
|
|
44
45
|
if (!isRecord(data.runtime))
|
|
45
46
|
return { status: "unknown" };
|
|
47
|
+
let generation;
|
|
48
|
+
if (options.includeGeneration && Number.isSafeInteger(data.runtime.backgroundPid) && Number(data.runtime.backgroundPid) > 0
|
|
49
|
+
&& typeof data.runtime.socketDir === "string" && data.runtime.browserLaunched !== false) {
|
|
50
|
+
const start = await readProcessStartIdentity(Number(data.runtime.backgroundPid), process.platform, { signal: options.signal, deadline: Date.now() + (options.timeoutMs ?? 5_000) });
|
|
51
|
+
if (start)
|
|
52
|
+
generation = JSON.stringify({ pid: data.runtime.backgroundPid, start, socketDir: data.runtime.socketDir });
|
|
53
|
+
}
|
|
46
54
|
if (typeof data.runtime.restoreKey === "string" && data.runtime.restoreKey.length > 0)
|
|
47
|
-
return { restoreKey: data.runtime.restoreKey, status: "active" };
|
|
48
|
-
return data.runtime.restoreKey === null ? { restoreKey: null, status: "active" } : { status: "unknown" };
|
|
55
|
+
return { restoreKey: data.runtime.restoreKey, status: "active", generation };
|
|
56
|
+
return data.runtime.restoreKey === null ? { restoreKey: null, status: "active", generation } : { status: "unknown" };
|
|
49
57
|
}
|
|
50
58
|
finally {
|
|
51
59
|
if (processResult.stdoutSpillPath)
|
|
@@ -86,8 +94,10 @@ export async function acquireOwnedManagedSessionDaemonPolicy(options) {
|
|
|
86
94
|
};
|
|
87
95
|
}
|
|
88
96
|
try {
|
|
97
|
+
const receipt = context.restoreState.getDaemonReceipt(context.sessionName, context.namespace);
|
|
89
98
|
const daemon = await inspectManagedSessionDaemon({
|
|
90
99
|
cwd: context.cwd,
|
|
100
|
+
includeGeneration: receipt !== undefined && !context.restoreState.hasDaemonRestoreKey(context.sessionName, context.namespace),
|
|
91
101
|
headedManagedAutosaveInterval: context.headedManagedAutosaveInterval,
|
|
92
102
|
namespace: context.namespace,
|
|
93
103
|
sessionName: context.sessionName,
|
|
@@ -95,6 +105,13 @@ export async function acquireOwnedManagedSessionDaemonPolicy(options) {
|
|
|
95
105
|
});
|
|
96
106
|
if (daemon.status === "inactive")
|
|
97
107
|
context.restoreState.forgetDaemonRestoreKey(context.sessionName, context.namespace);
|
|
108
|
+
if (daemon.status === "active" && receipt && !context.restoreState.hasDaemonRestoreKey(context.sessionName, context.namespace)) {
|
|
109
|
+
if (daemon.generation === receipt.generation && daemon.restoreKey === receipt.restoreKey) {
|
|
110
|
+
context.restoreState.recordDaemonRestoreKey(context.sessionName, context.namespace, daemon.restoreKey, daemon.generation);
|
|
111
|
+
}
|
|
112
|
+
else
|
|
113
|
+
context.restoreState.forgetDaemonRestoreKey(context.sessionName, context.namespace);
|
|
114
|
+
}
|
|
98
115
|
if (options.mode === "close") {
|
|
99
116
|
if (daemon.status === "active")
|
|
100
117
|
context.restoreState.recordDaemonRestoreKey(context.sessionName, context.namespace, daemon.restoreKey);
|
|
@@ -141,87 +158,89 @@ export async function acquireOwnedManagedSessionDaemonPolicy(options) {
|
|
|
141
158
|
}
|
|
142
159
|
}
|
|
143
160
|
export async function closeManagedSession(options) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
sessionName: options.sessionName,
|
|
157
|
-
signal: controller.signal,
|
|
158
|
-
timeoutMs: Math.min(options.timeoutMs, 1_000),
|
|
159
|
-
});
|
|
160
|
-
if (!policyLock) {
|
|
161
|
-
clearTimeout(timer);
|
|
162
|
-
return timeoutError ?? "Managed-session policy coordination is unavailable or busy; cleanup did not run. Retry after the current operation finishes or repair the private policy-lock directory.";
|
|
163
|
-
}
|
|
164
|
-
try {
|
|
165
|
-
phase = "daemon inspection";
|
|
166
|
-
phaseStartedAt = Date.now();
|
|
167
|
-
const daemon = await inspectManagedSessionDaemon({
|
|
168
|
-
cwd: options.cwd,
|
|
169
|
-
headedManagedAutosaveInterval: options.headedManagedAutosaveInterval,
|
|
161
|
+
return withAgentBrowserProcessEnvironment(options.socketDir ? { PI_AGENT_BROWSER_SOCKET_DIR: options.socketDir } : {}, async () => {
|
|
162
|
+
const controller = new AbortController();
|
|
163
|
+
let phase = "policy coordination";
|
|
164
|
+
let phaseStartedAt = Date.now();
|
|
165
|
+
let timeoutError;
|
|
166
|
+
const timer = setTimeout(() => {
|
|
167
|
+
timeoutError = `Managed-session cleanup timed out after ${options.timeoutMs} ms during ${phase} (${Date.now() - phaseStartedAt} ms in this phase).`;
|
|
168
|
+
controller.abort();
|
|
169
|
+
}, options.timeoutMs);
|
|
170
|
+
let stdoutSpillPath;
|
|
171
|
+
const closeArgs = [...(options.namespace !== undefined ? ["--namespace", options.namespace] : []), "--session", options.sessionName, "close"];
|
|
172
|
+
const policyLock = options.policyLock ?? await acquireManagedSessionPolicyLock({
|
|
170
173
|
namespace: options.namespace,
|
|
171
|
-
preserveAttachedBrowserSession: options.preserveAttachedBrowserSession,
|
|
172
174
|
sessionName: options.sessionName,
|
|
173
175
|
signal: controller.signal,
|
|
174
|
-
timeoutMs: Math.min(options.timeoutMs,
|
|
175
|
-
});
|
|
176
|
-
if (daemon.status === "active")
|
|
177
|
-
options.restoreState.recordDaemonRestoreKey(options.sessionName, options.namespace, daemon.restoreKey);
|
|
178
|
-
const daemonRestoreKey = options.restoreState.getDaemonRestoreKey(options.sessionName, options.namespace);
|
|
179
|
-
const ownedRestoreKey = !options.restoreState.isDisabled(options.sessionName, options.namespace)
|
|
180
|
-
&& isManagedSessionRestoreKey(daemonRestoreKey) ? daemonRestoreKey : null;
|
|
181
|
-
phase = "native close";
|
|
182
|
-
phaseStartedAt = Date.now();
|
|
183
|
-
const processResult = await runAgentBrowserProcess({
|
|
184
|
-
args: closeArgs,
|
|
185
|
-
cwd: options.cwd,
|
|
186
|
-
env: { AGENT_BROWSER_JSON: "1", ...getHeadedManagedAutosaveEnv(options.headedManagedAutosaveInterval) },
|
|
187
|
-
managedSessionRestoreState: options.restoreState,
|
|
188
|
-
ownedManagedSession: true,
|
|
189
|
-
preserveAttachedBrowserSession: options.preserveAttachedBrowserSession,
|
|
190
|
-
signal: controller.signal,
|
|
176
|
+
timeoutMs: Math.min(options.timeoutMs, 1_000),
|
|
191
177
|
});
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
178
|
+
if (!policyLock) {
|
|
179
|
+
clearTimeout(timer);
|
|
180
|
+
return timeoutError ?? "Managed-session policy coordination is unavailable or busy; cleanup did not run. Retry after the current operation finishes or repair the private policy-lock directory.";
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
phase = "daemon inspection";
|
|
184
|
+
phaseStartedAt = Date.now();
|
|
185
|
+
const daemon = await inspectManagedSessionDaemon({
|
|
199
186
|
cwd: options.cwd,
|
|
187
|
+
headedManagedAutosaveInterval: options.headedManagedAutosaveInterval,
|
|
200
188
|
namespace: options.namespace,
|
|
201
|
-
|
|
202
|
-
|
|
189
|
+
preserveAttachedBrowserSession: options.preserveAttachedBrowserSession,
|
|
190
|
+
sessionName: options.sessionName,
|
|
191
|
+
signal: controller.signal,
|
|
192
|
+
timeoutMs: Math.min(options.timeoutMs, 2_000),
|
|
193
|
+
});
|
|
194
|
+
if (daemon.status === "active")
|
|
195
|
+
options.restoreState.recordDaemonRestoreKey(options.sessionName, options.namespace, daemon.restoreKey);
|
|
196
|
+
const daemonRestoreKey = options.restoreState.getDaemonRestoreKey(options.sessionName, options.namespace);
|
|
197
|
+
const ownedRestoreKey = !options.restoreState.isDisabled(options.sessionName, options.namespace)
|
|
198
|
+
&& isManagedSessionRestoreKey(daemonRestoreKey) ? daemonRestoreKey : null;
|
|
199
|
+
phase = "native close";
|
|
200
|
+
phaseStartedAt = Date.now();
|
|
201
|
+
const processResult = await runAgentBrowserProcess({
|
|
202
|
+
args: closeArgs,
|
|
203
|
+
cwd: options.cwd,
|
|
204
|
+
env: { AGENT_BROWSER_JSON: "1", ...getHeadedManagedAutosaveEnv(options.headedManagedAutosaveInterval) },
|
|
205
|
+
managedSessionRestoreState: options.restoreState,
|
|
206
|
+
ownedManagedSession: true,
|
|
207
|
+
preserveAttachedBrowserSession: options.preserveAttachedBrowserSession,
|
|
208
|
+
signal: controller.signal,
|
|
209
|
+
});
|
|
210
|
+
clearTimeout(timer);
|
|
211
|
+
stdoutSpillPath = processResult.stdoutSpillPath;
|
|
212
|
+
if (!processResult.aborted && !processResult.spawnError && processResult.exitCode === 0) {
|
|
213
|
+
const parsed = await parseAgentBrowserEnvelope({ stdout: processResult.stdout, stdoutPath: processResult.stdoutSpillPath });
|
|
214
|
+
const data = parsed.envelope?.success === true && isRecord(parsed.envelope.data) ? parsed.envelope.data : undefined;
|
|
215
|
+
options.restoreState.clear(options.sessionName, options.namespace);
|
|
216
|
+
pruneOwnedManagedSessionRestoreSnapshots({
|
|
217
|
+
cwd: options.cwd,
|
|
218
|
+
namespace: options.namespace,
|
|
219
|
+
restoreKey: ownedRestoreKey,
|
|
220
|
+
statePath: typeof data?.statePath === "string" ? data.statePath : undefined,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return timeoutError ?? getAgentBrowserErrorText({
|
|
224
|
+
aborted: processResult.aborted,
|
|
225
|
+
command: "close",
|
|
226
|
+
effectiveArgs: redactInvocationArgs(closeArgs),
|
|
227
|
+
exitCode: processResult.exitCode,
|
|
228
|
+
plainTextInspection: false,
|
|
229
|
+
spawnError: processResult.spawnError,
|
|
230
|
+
stderr: processResult.stderr,
|
|
231
|
+
timedOut: processResult.timedOut,
|
|
232
|
+
timeoutMs: processResult.timeoutMs,
|
|
203
233
|
});
|
|
204
234
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
catch (error) {
|
|
218
|
-
return timeoutError ?? (error instanceof Error ? error.message : String(error));
|
|
219
|
-
}
|
|
220
|
-
finally {
|
|
221
|
-
clearTimeout(timer);
|
|
222
|
-
if (stdoutSpillPath)
|
|
223
|
-
await rm(stdoutSpillPath, { force: true }).catch(() => undefined);
|
|
224
|
-
if (!options.policyLock)
|
|
225
|
-
await policyLock.release();
|
|
226
|
-
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
return timeoutError ?? (error instanceof Error ? error.message : String(error));
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
clearTimeout(timer);
|
|
240
|
+
if (stdoutSpillPath)
|
|
241
|
+
await rm(stdoutSpillPath, { force: true }).catch(() => undefined);
|
|
242
|
+
if (!options.policyLock)
|
|
243
|
+
await policyLock.release();
|
|
244
|
+
}
|
|
245
|
+
});
|
|
227
246
|
}
|
|
@@ -392,6 +392,7 @@ export async function prepareBrowserRun(options) {
|
|
|
392
392
|
}) };
|
|
393
393
|
}
|
|
394
394
|
electronLaunch = launchResult.value;
|
|
395
|
+
electronLaunch.record.ownerSessionId = options.ctx.sessionManager.getSessionId();
|
|
395
396
|
runtimeToolArgs = ["connect", electronLaunch.connectArg];
|
|
396
397
|
runtimeToolStdin = undefined;
|
|
397
398
|
}
|
|
@@ -570,7 +571,18 @@ export async function prepareBrowserRun(options) {
|
|
|
570
571
|
const sessionTabPinningReason = priorSessionPageState.pinningReason;
|
|
571
572
|
let priorRefSnapshotState = priorSessionPageState.refSnapshot;
|
|
572
573
|
let priorRefSnapshotInvalidation = priorSessionPageState.refSnapshotInvalidation;
|
|
573
|
-
|
|
574
|
+
let nativeGenerationChanged = false;
|
|
575
|
+
if (!browserIndependent && !isCloseCommand(executionPlan.commandInfo.command) && priorRefSnapshotState?.refIds.length && sessionStateKey && executionPlan.sessionName) {
|
|
576
|
+
const daemon = await inspectManagedSessionDaemon({ cwd, namespace: executionPlan.namespace, sessionName: executionPlan.sessionName, signal, includeGeneration: true, headedManagedAutosaveInterval: ownedManagedSession?.headedManagedAutosaveInterval ?? (ownedManagedSession?.headedManagedAutosaveDisabled ? "0" : undefined), timeoutMs: params.timeoutMs });
|
|
577
|
+
sessionPageState.bindSnapshotGeneration(sessionStateKey, daemon.status === "active" ? daemon.generation : undefined);
|
|
578
|
+
if (daemon.status !== "active" || daemon.generation === undefined || priorRefSnapshotState.generation !== daemon.generation) {
|
|
579
|
+
nativeGenerationChanged = daemon.status === "active" && daemon.generation !== undefined && priorRefSnapshotState.generation !== undefined && priorRefSnapshotState.generation !== daemon.generation;
|
|
580
|
+
priorRefSnapshotState = undefined;
|
|
581
|
+
priorRefSnapshotInvalidation = buildPageTransitionRefSnapshotInvalidation("The native browser generation changed or could not be verified. Take a new complete snapshot before using refs, even when the URL is unchanged.");
|
|
582
|
+
sessionPageState.applyRefSnapshotInvalidation({ invalidation: priorRefSnapshotInvalidation, sessionName: sessionStateKey, update: options.sessionPageStateUpdate });
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const coldManagedSession = !browserIndependent && (managedSessionDaemonInactive || nativeGenerationChanged || priorSessionPageState.tabReopenPending === true)
|
|
574
586
|
&& recordedOwnedSession !== undefined
|
|
575
587
|
&& sessionTabPinningReason === "restore"
|
|
576
588
|
&& ownedManagedSession?.restoreDecision === "enabled"
|
|
@@ -960,8 +972,17 @@ export async function prepareBrowserRun(options) {
|
|
|
960
972
|
signal,
|
|
961
973
|
usedImplicitSession: executionPlan.usedImplicitSession,
|
|
962
974
|
});
|
|
963
|
-
if (snapshotFilter)
|
|
975
|
+
if (snapshotFilter) {
|
|
976
|
+
if (sessionStateKey && executionPlan.sessionName) {
|
|
977
|
+
const daemon = await inspectManagedSessionDaemon({ cwd, namespace: executionPlan.namespace, sessionName: executionPlan.sessionName, signal, includeGeneration: true, headedManagedAutosaveInterval: ownedManagedSession?.headedManagedAutosaveInterval ?? (ownedManagedSession?.headedManagedAutosaveDisabled ? "0" : undefined), timeoutMs: params.timeoutMs });
|
|
978
|
+
sessionPageState.bindSnapshotGeneration(sessionStateKey, daemon.status === "active" ? daemon.generation : undefined);
|
|
979
|
+
if (ownedManagedSession && daemon.status === "active" && daemon.generation)
|
|
980
|
+
state.managedSessionRestoreState.recordDaemonRestoreKey(executionPlan.sessionName, executionPlan.namespace, daemon.restoreKey, daemon.generation);
|
|
981
|
+
if (isRecord(snapshotFilter.result.details))
|
|
982
|
+
snapshotFilter.result.details.refSnapshot = sessionPageState.get(sessionStateKey).refSnapshot;
|
|
983
|
+
}
|
|
964
984
|
return { kind: "early-result", statePatch: { ...statePatch, artifactManifest: snapshotFilter.artifactManifest ?? statePatch.artifactManifest }, result: snapshotFilter.result };
|
|
985
|
+
}
|
|
965
986
|
const networkRequestsPageFilter = await tryNetworkRequestsPageFilter({
|
|
966
987
|
commandTokens,
|
|
967
988
|
compatibilityWorkaround,
|
|
@@ -15,7 +15,7 @@ import { buildToolPresentation } from "../../results/presentation.js";
|
|
|
15
15
|
import { compactLargePresentationOutput } from "../../results/presentation/large-output.js";
|
|
16
16
|
import { extractEnvelopeErrorText, getAgentBrowserErrorText, parseAgentBrowserEnvelope } from "../../results/envelope.js";
|
|
17
17
|
import { detectConfirmationRequired } from "../../results/confirmation.js";
|
|
18
|
-
import { omitUpstreamLifecycle } from "../../results/presentation/common.js";
|
|
18
|
+
import { extractAgentBrowserLifecycle, omitUpstreamLifecycle } from "../../results/presentation/common.js";
|
|
19
19
|
import { getClipboardWritePayloadCandidates, redactClipboardPermissionEcho, redactClipboardPermissionErrorValue } from "../../results/presentation/errors.js";
|
|
20
20
|
import { shouldCaptureSemanticActionNavigationSummary } from "../../results/presentation/semantic-action.js";
|
|
21
21
|
import { buildPageTransitionRefSnapshotInvalidation, commandExplicitlyTargetsAboutBlank, getCommandRefSnapshotInvalidation, deriveSessionTabTarget, extractLatestRefSnapshotStateFromBatchResults, extractRefSnapshotFromData, extractSessionTabTargetFromBatchResults, extractSessionTabTargetFromCommandData, isAboutBlankSessionTabTarget, normalizeSessionTabTarget, } from "../../session-page-state.js";
|
|
@@ -23,10 +23,10 @@ import { isRecord } from "../../parsing.js";
|
|
|
23
23
|
import { buildReadConfirmationNextActions, nextReadConfirmation } from "../../read-confirmation.js";
|
|
24
24
|
import { pruneOwnedManagedSessionRestoreSnapshots } from "../../managed-session-restore.js";
|
|
25
25
|
import { isManagedSessionRestoreKey } from "../../managed-session-storage.js";
|
|
26
|
-
import { createFreshSessionName, extractUpstreamCommandTokens, resolveManagedSessionState } from "../../runtime.js";
|
|
26
|
+
import { createFreshSessionName, extractUpstreamCommandTokens, redactInvocationArgs, resolveManagedSessionState } from "../../runtime.js";
|
|
27
27
|
import { getUpstreamEffectiveBatchSteps } from "../batch-stdin.js";
|
|
28
28
|
import { closeManagedSession, inspectManagedSessionDaemon } from "./managed-session-daemon-policy.js";
|
|
29
|
-
import { applyOpenResultTabCorrection, buildAboutBlankRecoveryHint, buildAboutBlankWarning, buildElectronPostCommandHealthDiagnostic, buildElectronRefFreshnessDiagnostic, buildElectronSessionMismatch, buildManagedSessionOutcome, collectOpenResultTabCorrection, collectSessionTabSelection, collectSessionTabTarget, commandChoosesSessionTabTarget, extractNavigationSummaryFromData, extractStringResultField, findElectronLaunchRecordForSession, formatElectronPostCommandHealthText, formatElectronSessionMismatchText, getSessionContextKey, getStaleRefArgs, mergeNavigationSummaryIntoData, runSessionCommandData, shouldCaptureNavigationSummary, shouldCorrectSessionTabAfterCommand, shouldInspectElectronPostCommandHealth, updateTraceOwnerState, } from "./session-state.js";
|
|
29
|
+
import { applyOpenResultTabCorrection, applyBrowserRunStatePatch, buildAboutBlankRecoveryHint, buildAboutBlankWarning, buildElectronPostCommandHealthDiagnostic, buildElectronRefFreshnessDiagnostic, buildElectronSessionMismatch, buildManagedSessionOutcome, collectOpenResultTabCorrection, collectSessionTabSelection, collectSessionTabTarget, commandChoosesSessionTabTarget, extractNavigationSummaryFromData, extractStringResultField, findElectronLaunchRecordForSession, formatElectronPostCommandHealthText, formatElectronSessionMismatchText, getSessionContextKey, getStaleRefArgs, mergeNavigationSummaryIntoData, runSessionCommandData, shouldCaptureNavigationSummary, shouldCorrectSessionTabAfterCommand, shouldInspectElectronPostCommandHealth, updateTraceOwnerState, } from "./session-state.js";
|
|
30
30
|
import { collectClickDispatchDiagnostic } from "./click-dispatch.js";
|
|
31
31
|
import { buildScrollNoopDiagnostic, collectComboboxFocusDiagnostic, collectElectronBroadGetTextScopeDiagnostics, collectElectronHandoff, collectFillVerificationDiagnostic, collectNavigationSummary, collectOverlayBlockerDiagnostic, collectQaAttachedTarget, collectSnapshotOverlayBlockerDiagnostic, collectRecordingDependencyWarning, collectScrollPositionSnapshot, collectSelectorTextVisibilityDiagnostics, collectTimeoutPartialProgress, sleepMs, formatQaAttachedTargetText, getArtifactCleanupGuidance, getEvalResultWarning, getEvalStdinHint, getSourceLookupElectronContext, } from "./diagnostics.js";
|
|
32
32
|
import { repairScreenshotData } from "./prepare.js";
|
|
@@ -617,7 +617,7 @@ export async function processBrowserOutput(input) {
|
|
|
617
617
|
networkRoutesBySession.delete(replacedSessionStateKey ?? replacedManagedSessionName);
|
|
618
618
|
sessionPageState.clearSession(replacedSessionStateKey ?? replacedManagedSessionName);
|
|
619
619
|
const replacedSessionKey = replacedSessionStateKey ?? replacedManagedSessionName;
|
|
620
|
-
const replacedCloseError = await closeManagedSession({ cwd: priorManagedSessionCwd, headedManagedAutosaveInterval: priorManagedSessionHeadedAutosaveInterval, namespace: priorManagedSessionNamespace, preserveAttachedBrowserSession: state.attachedSessionKeys.has(replacedSessionKey), restoreState: state.managedSessionRestoreState, sessionName: replacedManagedSessionName, timeoutMs: implicitSessionCloseTimeoutMs });
|
|
620
|
+
const replacedCloseError = await closeManagedSession({ cwd: priorManagedSessionCwd, headedManagedAutosaveInterval: priorManagedSessionHeadedAutosaveInterval, namespace: priorManagedSessionNamespace, preserveAttachedBrowserSession: state.attachedSessionKeys.has(replacedSessionKey), restoreState: state.managedSessionRestoreState, sessionName: replacedManagedSessionName, socketDir: state.ownedManagedSessions.get(replacedSessionKey)?.socketDir, timeoutMs: implicitSessionCloseTimeoutMs });
|
|
621
621
|
if (managedSessionOutcome) {
|
|
622
622
|
managedSessionOutcome = {
|
|
623
623
|
...managedSessionOutcome,
|
|
@@ -709,6 +709,30 @@ export async function processBrowserOutput(input) {
|
|
|
709
709
|
}
|
|
710
710
|
if (plainTextUpgrade && errorText)
|
|
711
711
|
presentationEnvelope = { ...presentationEnvelope, success: false, error: errorText };
|
|
712
|
+
const resultRetainsPreparedManagedSession = !managedSessionOutcome || (managedSessionOutcome.activeAfter
|
|
713
|
+
&& managedSessionOutcome.attemptedSessionName === managedSessionOutcome.currentSessionName);
|
|
714
|
+
const resultHeadedManagedAutosaveDisabled = prepared.ownedManagedSessionContext?.headedManagedAutosaveDisabled === true
|
|
715
|
+
&& resultRetainsPreparedManagedSession
|
|
716
|
+
&& !(commandClosesSession && succeeded);
|
|
717
|
+
const resultHeadedManagedAutosaveInterval = resultRetainsPreparedManagedSession && !(commandClosesSession && succeeded)
|
|
718
|
+
? prepared.ownedManagedSessionContext?.headedManagedAutosaveInterval
|
|
719
|
+
: undefined;
|
|
720
|
+
// Commit native lifecycle facts to the execution state before rendering/export can fail.
|
|
721
|
+
applyBrowserRunStatePatch(state, { artifactManifest, freshSessionOrdinal, managedSessionActive, managedSessionCompatibilityWorkaround, managedSessionHeadedAutosaveDisabled, managedSessionHeadedAutosaveInterval, managedSessionCwd, managedSessionName, managedSessionNamespace, networkRoutesBySession });
|
|
722
|
+
state.observedBrowserEffects = {
|
|
723
|
+
args: prepared.redactedArgs, command: prepared.executionPlan.commandInfo.command, subcommand: prepared.executionPlan.commandInfo.subcommand,
|
|
724
|
+
sessionName: prepared.executionPlan.sessionName, namespace: prepared.executionPlan.namespace, sessionMode: prepared.sessionMode,
|
|
725
|
+
usedImplicitSession: prepared.executionPlan.usedImplicitSession, agentBrowserStarted: processResult.agentBrowserStarted,
|
|
726
|
+
nativeSucceeded: succeeded, managedSessionOutcome, managedSessionCwd: prepared.ownedManagedSessionContext?.cwd ?? managedSessionCwd, compatibilityWorkaround: prepared.compatibilityWorkaround,
|
|
727
|
+
managedSessionHeadedAutosaveDisabled: resultHeadedManagedAutosaveDisabled, managedSessionHeadedAutosaveInterval: resultHeadedManagedAutosaveInterval,
|
|
728
|
+
managedSessionRestoreDisabled: state.managedSessionRestoreState.isDisabled(prepared.executionPlan.sessionName, prepared.executionPlan.namespace),
|
|
729
|
+
closeAllApplied, readConfirmation: readConfirmationEvent ?? prepared.readConfirmation,
|
|
730
|
+
electron: electronLaunchRecord ? { launch: electronLaunchRecord, cleanup: electronFailedConnectCleanup } : undefined,
|
|
731
|
+
batchSteps: Array.isArray(presentationEnvelope?.data) ? presentationEnvelope.data.filter(isRecord).map(row => ({
|
|
732
|
+
command: Array.isArray(row.command) && row.command.every(token => typeof token === "string") ? redactInvocationArgs(row.command) : undefined,
|
|
733
|
+
success: row.success, lifecycle: extractAgentBrowserLifecycle(row.result),
|
|
734
|
+
})) : undefined,
|
|
735
|
+
};
|
|
712
736
|
let presentation = plainTextInspection ? { artifacts: undefined, batchFailure: undefined, batchSteps: undefined, content: [{ type: "text", text: inspectionText ?? "" }], data: undefined, fullOutputPath: undefined, fullOutputPaths: undefined, imagePath: undefined, imagePaths: undefined, savedFile: undefined, savedFilePath: undefined, summary: `${prepared.redactedArgs.join(" ")} completed` } : recordingStopRecovery && !recordingStopRecovery.batch ? recordingStopRecovery.presentation : await buildToolPresentation({ modelVisible: input.modelVisible, args: prepared.redactedProcessArgs, artifactManifest, artifactMaxUpdatedAtMs: Date.now(), artifactMinUpdatedAtMs: input.artifactRunStartedAtMs, artifactRequest: screenshotArtifactRequest, batchArtifactRequests: batchScreenshotArtifactRequests, commandInfo: prepared.executionPlan.commandInfo, compiledSemanticAction: prepared.compiledSemanticAction, cwd: operationCwd, envelope: presentationEnvelope, errorText, namespace: prepared.executionPlan.namespace, networkRouteDiagnostics, networkRoutes: activeNetworkRoutes, persistentArtifactStore, previousRecordingContactSheetPath: sessionStateKey ? state.activeRecordingReservations?.get(sessionStateKey)?.contactSheetPath : undefined, piCleanupOwnership: sessionStateKey && (state.ownedManagedSessions.has(sessionStateKey) || prepared.executionPlan.managedSessionName !== undefined) ? "wrapper-managed" : "caller-owned", sessionName: prepared.executionPlan.sessionName });
|
|
713
737
|
if (recordingStopRecovery)
|
|
714
738
|
presentation = mergeRecordingRecoveryPresentation(presentation, recordingStopRecovery);
|
|
@@ -794,13 +818,14 @@ export async function processBrowserOutput(input) {
|
|
|
794
818
|
presentation.failureCategory = "qa-failure";
|
|
795
819
|
presentation.summary = qaPreset.summary;
|
|
796
820
|
const compactText = buildQaCompactFailureText({
|
|
797
|
-
|
|
798
|
-
|
|
821
|
+
causalError: presentation.batchFailure?.failedStep.text,
|
|
822
|
+
executedStepCount: presentation.batchSteps?.length,
|
|
799
823
|
page: extractQaPageContext({
|
|
800
824
|
attachedTarget: qaAttachedTarget,
|
|
801
825
|
batchData: presentationEnvelope?.data,
|
|
802
826
|
compiled: prepared.compiledQaPreset,
|
|
803
827
|
}),
|
|
828
|
+
plannedStepCount: prepared.compiledQaPreset.steps.length,
|
|
804
829
|
qaPreset,
|
|
805
830
|
});
|
|
806
831
|
const nonTextContent = presentation.content.filter((item) => item.type !== "text");
|
|
@@ -829,7 +854,12 @@ export async function processBrowserOutput(input) {
|
|
|
829
854
|
const qaAttachedBannerText = [qaAttachedTargetText, qaAttachedDiagnosticsText].filter((part) => typeof part === "string" && part.length > 0).join("\n");
|
|
830
855
|
const skipAttachedTargetBanner = qaPreset?.passed && prepared.compiledQaPreset?.checks.attached;
|
|
831
856
|
if (!skipAttachedTargetBanner && qaAttachedBannerText && presentation.content[0]?.type === "text")
|
|
832
|
-
presentation.content[0] = {
|
|
857
|
+
presentation.content[0] = {
|
|
858
|
+
...presentation.content[0],
|
|
859
|
+
text: qaPreset?.passed === false
|
|
860
|
+
? `${presentation.content[0].text}\n\n${qaAttachedBannerText}`
|
|
861
|
+
: `${qaAttachedBannerText}\n\n${presentation.content[0].text}`,
|
|
862
|
+
};
|
|
833
863
|
else if (!skipAttachedTargetBanner && qaAttachedBannerText)
|
|
834
864
|
presentation.content.unshift({ type: "text", text: qaAttachedBannerText });
|
|
835
865
|
if (managedSessionOutcome && managedSessionOutcome.succeeded !== succeeded)
|
|
@@ -856,19 +886,24 @@ export async function processBrowserOutput(input) {
|
|
|
856
886
|
currentRefSnapshot = finalRecoveryState.currentRefSnapshot;
|
|
857
887
|
currentRefSnapshotInvalidation = finalRecoveryState.currentRefSnapshotInvalidation;
|
|
858
888
|
const authoritativePageState = sessionStateKey ? sessionPageState.get(sessionStateKey) : undefined;
|
|
889
|
+
const newSnapshot = authoritativePageState?.refSnapshot && authoritativePageState.refSnapshot.snapshotId !== prepared.priorRefSnapshotState?.snapshotId;
|
|
890
|
+
const needsDaemonReceipt = prepared.ownedManagedSessionContext && !prepared.ownedManagedSessionContext.reuseOnly && !commandClosesSession && processResult.agentBrowserStarted
|
|
891
|
+
&& (succeeded || managedSessionOutcome?.activeAfter === true && managedSessionOutcome.currentSessionName === prepared.executionPlan.sessionName)
|
|
892
|
+
&& !state.managedSessionRestoreState.getDaemonReceipt(prepared.executionPlan.sessionName, prepared.executionPlan.namespace);
|
|
893
|
+
if (sessionStateKey && prepared.executionPlan.sessionName && (newSnapshot || needsDaemonReceipt)) {
|
|
894
|
+
const daemon = await inspectManagedSessionDaemon({ cwd, namespace: prepared.executionPlan.namespace, sessionName: prepared.executionPlan.sessionName, signal, includeGeneration: true, headedManagedAutosaveInterval: prepared.ownedManagedSessionContext?.headedManagedAutosaveInterval ?? (prepared.ownedManagedSessionContext?.headedManagedAutosaveDisabled ? "0" : undefined), timeoutMs: prepared.processTimeoutMs });
|
|
895
|
+
if (newSnapshot) {
|
|
896
|
+
sessionPageState.bindSnapshotGeneration(sessionStateKey, daemon.status === "active" ? daemon.generation : undefined);
|
|
897
|
+
currentRefSnapshot = sessionPageState.get(sessionStateKey).refSnapshot;
|
|
898
|
+
}
|
|
899
|
+
if (prepared.ownedManagedSessionContext && daemon.status === "active" && daemon.generation) {
|
|
900
|
+
state.managedSessionRestoreState.recordDaemonRestoreKey(prepared.executionPlan.sessionName, prepared.executionPlan.namespace, daemon.restoreKey, daemon.generation);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
859
903
|
if (sessionStateKey)
|
|
860
904
|
currentSessionTabTarget = authoritativePageState?.tabTarget;
|
|
861
905
|
const currentSessionTabTargetUnknown = authoritativePageState?.tabTargetUnknown === true ? true : undefined;
|
|
862
|
-
const
|
|
863
|
-
&& managedSessionOutcome.attemptedSessionName === managedSessionOutcome.currentSessionName);
|
|
864
|
-
const resultHeadedManagedAutosaveDisabled = prepared.ownedManagedSessionContext?.headedManagedAutosaveDisabled === true
|
|
865
|
-
&& !prepared.ownedManagedSessionContext.reuseOnly
|
|
866
|
-
&& resultRetainsPreparedManagedSession
|
|
867
|
-
&& !(commandClosesSession && succeeded);
|
|
868
|
-
const resultHeadedManagedAutosaveInterval = resultRetainsPreparedManagedSession && !prepared.ownedManagedSessionContext?.reuseOnly && !(commandClosesSession && succeeded)
|
|
869
|
-
? prepared.ownedManagedSessionContext?.headedManagedAutosaveInterval
|
|
870
|
-
: undefined;
|
|
871
|
-
const result = buildFinalAgentBrowserToolResult({ modelVisible: input.modelVisible, aboutBlankSessionMismatch, artifactCleanup, categoryDetails: finalRecoveryState.categoryDetails, clickDispatchDiagnostic, commandTokens: prepared.commandTokens, comboboxFocusDiagnostic, compiledNetworkSourceLookup: prepared.compiledNetworkSourceLookup, compiledSemanticAction: prepared.compiledSemanticAction, compatibilityWorkaround: prepared.compatibilityWorkaround, currentRefSnapshot, currentRefSnapshotInvalidation, currentSessionTabTarget, currentSessionTabTargetUnknown, electronBroadGetTextScopeDiagnostics, electronFailedConnectCleanup, electronHandoff, electronLaunch: prepared.electronLaunch, electronLaunchRecord, electronLaunchRecords, electronPostCommandHealth, electronProfileIsolationDetails: input.electronProfileIsolationDetails, electronRefFreshnessDiagnostic, electronSessionMismatch, errorText, evalResultWarning, evalStdinHint, exactSensitiveValues: prepared.exactSensitiveValues, executionPlan: prepared.executionPlan, fillVerificationDiagnostic, headedLaunch: prepared.headedLaunch, inspectionText, preserveAttachedBrowserSession: input.preserveAttachedBrowserSession === true, providerLaunch: prepared.providerLaunch, managedSessionHeadedAutosaveDisabled: resultHeadedManagedAutosaveDisabled || undefined, managedSessionHeadedAutosaveInterval: resultHeadedManagedAutosaveInterval, managedSessionOutcome, managedSessionRestoreDisabled: state.managedSessionRestoreState.isDisabled(prepared.executionPlan.sessionName, prepared.executionPlan.namespace), navigationSummary, networkSourceLookup, noActivePageSnapshotFailure: finalRecoveryState.noActivePageSnapshotFailure, openResultTabCorrection, overlayBlockerDiagnostic, parseError, parseFailureOutput, parseSucceeded, plainTextInspection, presentation, presentationEnvelope, priorSessionTabTarget: prepared.priorSessionTabTarget, processResult, qaAttachedTarget, qaPreset, recordingDependencyWarning, redactedArgs: prepared.redactedArgs, redactedCompiledElectron: prepared.redactedCompiledElectron, redactedCompiledJob: prepared.redactedCompiledJob, redactedCompiledNetworkSourceLookup: prepared.redactedCompiledNetworkSourceLookup, redactedCompiledQaPreset: prepared.redactedCompiledQaPreset, redactedCompiledSemanticAction: prepared.redactedCompiledSemanticAction, redactedCompiledSourceLookup: prepared.redactedCompiledSourceLookup, redactedContent, redactedProcessArgs: prepared.redactedProcessArgs, redactedRecoveryHint: prepared.redactedRecoveryHint, resultArtifactManifest, richInputRecoveryDiagnostic: finalRecoveryState.richInputRecoveryDiagnostic, scrollNoopDiagnostic, selectorTextVisibilityDiagnostics, sessionMode: prepared.sessionMode, sessionTabCorrection, sourceLookup, succeeded, timeoutPartialProgress, unsettledWebMcpMutation, userRequestedJson: prepared.userRequestedJson, visibleRefFallbackDiagnostic: finalRecoveryState.visibleRefFallbackDiagnostic, visibleRefFallbackSessionName: finalRecoveryState.visibleRefFallbackSessionName });
|
|
906
|
+
const result = buildFinalAgentBrowserToolResult({ modelVisible: input.modelVisible, aboutBlankSessionMismatch, artifactCleanup, categoryDetails: finalRecoveryState.categoryDetails, clickDispatchDiagnostic, commandTokens: prepared.commandTokens, comboboxFocusDiagnostic, compiledNetworkSourceLookup: prepared.compiledNetworkSourceLookup, compiledSemanticAction: prepared.compiledSemanticAction, compatibilityWorkaround: prepared.compatibilityWorkaround, currentRefSnapshot, currentRefSnapshotInvalidation, currentSessionTabTarget, currentSessionTabTargetUnknown, electronBroadGetTextScopeDiagnostics, electronFailedConnectCleanup, electronHandoff, electronLaunch: prepared.electronLaunch, electronLaunchRecord, electronLaunchRecords, electronPostCommandHealth, electronProfileIsolationDetails: input.electronProfileIsolationDetails, electronRefFreshnessDiagnostic, electronSessionMismatch, errorText, evalResultWarning, evalStdinHint, exactSensitiveValues: prepared.exactSensitiveValues, executionPlan: prepared.executionPlan, fillVerificationDiagnostic, headedLaunch: prepared.headedLaunch, inspectionText, preserveAttachedBrowserSession: input.preserveAttachedBrowserSession === true, providerLaunch: prepared.providerLaunch, managedSessionHeadedAutosaveDisabled: !prepared.ownedManagedSessionContext?.reuseOnly && resultHeadedManagedAutosaveDisabled || undefined, managedSessionHeadedAutosaveInterval: prepared.ownedManagedSessionContext?.reuseOnly ? undefined : resultHeadedManagedAutosaveInterval, managedSessionOutcome, managedSessionRestoreDisabled: state.managedSessionRestoreState.isDisabled(prepared.executionPlan.sessionName, prepared.executionPlan.namespace), navigationSummary, networkSourceLookup, noActivePageSnapshotFailure: finalRecoveryState.noActivePageSnapshotFailure, openResultTabCorrection, overlayBlockerDiagnostic, parseError, parseFailureOutput, parseSucceeded, plainTextInspection, presentation, presentationEnvelope, priorSessionTabTarget: prepared.priorSessionTabTarget, processResult, qaAttachedTarget, qaPreset, recordingDependencyWarning, redactedArgs: prepared.redactedArgs, redactedCompiledElectron: prepared.redactedCompiledElectron, redactedCompiledJob: prepared.redactedCompiledJob, redactedCompiledNetworkSourceLookup: prepared.redactedCompiledNetworkSourceLookup, redactedCompiledQaPreset: prepared.redactedCompiledQaPreset, redactedCompiledSemanticAction: prepared.redactedCompiledSemanticAction, redactedCompiledSourceLookup: prepared.redactedCompiledSourceLookup, redactedContent, redactedProcessArgs: prepared.redactedProcessArgs, redactedRecoveryHint: prepared.redactedRecoveryHint, resultArtifactManifest, richInputRecoveryDiagnostic: finalRecoveryState.richInputRecoveryDiagnostic, scrollNoopDiagnostic, selectorTextVisibilityDiagnostics, sessionMode: prepared.sessionMode, sessionTabCorrection, sourceLookup, succeeded, timeoutPartialProgress, unsettledWebMcpMutation, userRequestedJson: prepared.userRequestedJson, visibleRefFallbackDiagnostic: finalRecoveryState.visibleRefFallbackDiagnostic, visibleRefFallbackSessionName: finalRecoveryState.visibleRefFallbackSessionName });
|
|
872
907
|
const resultWithCloseAll = closeAllApplied
|
|
873
908
|
? { ...result, details: { ...(isRecord(result.details) ? result.details : {}), closeAllApplied: true } }
|
|
874
909
|
: result;
|
|
@@ -603,7 +603,7 @@ export async function cleanupTrackedElectronHostLaunches(options) {
|
|
|
603
603
|
const sessionKey = getSessionPageStateKey(record.sessionName, record.namespace) ?? record.sessionName;
|
|
604
604
|
const managedSessionOwner = sessionKey ? options.ownedManagedSessions.get(sessionKey) : undefined;
|
|
605
605
|
const managedSessionCloseError = record.sessionName
|
|
606
|
-
? await closeManagedSession({ cwd: options.cwd, headedManagedAutosaveInterval: managedSessionOwner?.headedManagedAutosaveInterval, namespace: record.namespace, preserveAttachedBrowserSession: options.attachedSessionKeys.has(sessionKey ?? record.sessionName), restoreState: options.managedSessionRestoreState, sessionName: record.sessionName, timeoutMs: options.timeoutMs })
|
|
606
|
+
? await closeManagedSession({ cwd: options.cwd, headedManagedAutosaveInterval: managedSessionOwner?.headedManagedAutosaveInterval, namespace: record.namespace, preserveAttachedBrowserSession: options.attachedSessionKeys.has(sessionKey ?? record.sessionName), restoreState: options.managedSessionRestoreState, sessionName: record.sessionName, socketDir: managedSessionOwner?.socketDir, timeoutMs: options.timeoutMs })
|
|
607
607
|
: undefined;
|
|
608
608
|
const managedSessionStep = record.sessionName
|
|
609
609
|
? managedSessionCloseError
|