pi-agent-browser-native 0.5.0 → 0.6.5
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 +138 -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 +0 -3
- 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 +1 -8
- 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
package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js
CHANGED
|
@@ -2,6 +2,7 @@ import { isRecord } from "../../../parsing.js";
|
|
|
2
2
|
import { buildAgentBrowserResultCategoryDetails } from "../../../results/categories.js";
|
|
3
3
|
import { buildSnapshotPresentation } from "../../../results/snapshot.js";
|
|
4
4
|
import { extractRefSnapshotFromData } from "../../../session-page-state.js";
|
|
5
|
+
import { redactSensitiveText } from "../../../runtime.js";
|
|
5
6
|
import { collectScrollPositionSnapshot } from "../diagnostics.js";
|
|
6
7
|
import { buildSessionDetailFields, runSessionCommandData } from "../session-state.js";
|
|
7
8
|
function parseSnapshotFilterRequest(commandTokens) {
|
|
@@ -42,6 +43,116 @@ function parseSnapshotFilterRequest(commandTokens) {
|
|
|
42
43
|
return undefined;
|
|
43
44
|
return { cleanArgs, diff, role, search, viewport };
|
|
44
45
|
}
|
|
46
|
+
const RENDERED_TEXT_SEARCH_MAX_MATCHES = 8;
|
|
47
|
+
function buildRenderedTextSearchEval(search) {
|
|
48
|
+
return `(() => {
|
|
49
|
+
const query = ${JSON.stringify(search.trim().toLowerCase())};
|
|
50
|
+
const limit = ${RENDERED_TEXT_SEARCH_MAX_MATCHES};
|
|
51
|
+
const normalize = (value) => String(value ?? "").replace(/\\s+/g, " ").trim();
|
|
52
|
+
const attributeTextOf = (element) => [element.getAttribute("aria-label"), element.getAttribute("title"), element.getAttribute("placeholder")].filter(Boolean).join(" ");
|
|
53
|
+
const searchableTextOf = (element) => normalize([attributeTextOf(element), element.textContent].filter(Boolean).join(" "));
|
|
54
|
+
const renderedTextOf = (element) => normalize([attributeTextOf(element), element instanceof HTMLElement ? element.innerText : element.textContent].filter(Boolean).join(" "));
|
|
55
|
+
const isRendered = (element) => {
|
|
56
|
+
if (!(element instanceof Element) || element.getClientRects().length === 0) return false;
|
|
57
|
+
const visibility = getComputedStyle(element).visibility;
|
|
58
|
+
if (visibility === "hidden" || visibility === "collapse") return false;
|
|
59
|
+
for (let current = element; current; current = current.parentElement) {
|
|
60
|
+
if (Number(getComputedStyle(current).opacity) === 0) return false;
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
};
|
|
64
|
+
const validationPattern = /(?:^|[-_\\s])(?:error|invalid|validation|warning)(?:$|[-_\\s])/i;
|
|
65
|
+
const validationAncestor = (element) => {
|
|
66
|
+
for (let current = element; current && current !== document.body; current = current.parentElement) {
|
|
67
|
+
const role = current.getAttribute("role");
|
|
68
|
+
const live = current.getAttribute("aria-live");
|
|
69
|
+
const descriptor = [current.id, current.className, current.getAttribute("data-testid")].map(normalize).join(" ");
|
|
70
|
+
if (["alert", "alertdialog", "status"].includes(role || "") || current.getAttribute("aria-invalid") === "true" || (live && live !== "off") || validationPattern.test(descriptor)) return current;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
};
|
|
74
|
+
const candidates = Array.from(document.querySelectorAll("body *")).filter((element) => searchableTextOf(element).toLowerCase().includes(query));
|
|
75
|
+
const candidateSet = new Set(candidates);
|
|
76
|
+
const minimal = candidates.filter((element) => !Array.from(element.children).some((child) => candidateSet.has(child)));
|
|
77
|
+
const rendered = minimal.filter((element) => isRendered(element) && renderedTextOf(element).toLowerCase().includes(query));
|
|
78
|
+
const matches = rendered.map((element, index) => {
|
|
79
|
+
const semantic = validationAncestor(element);
|
|
80
|
+
const source = semantic || element;
|
|
81
|
+
const text = renderedTextOf(element);
|
|
82
|
+
const matchIndex = text.toLowerCase().indexOf(query);
|
|
83
|
+
const start = Math.max(0, matchIndex - 80);
|
|
84
|
+
const snippet = text.slice(start, start + 240);
|
|
85
|
+
const rect = element.getBoundingClientRect();
|
|
86
|
+
const name = normalize(element.getAttribute("aria-label") || element.getAttribute("title") || element.getAttribute("placeholder")) || undefined;
|
|
87
|
+
return {
|
|
88
|
+
index,
|
|
89
|
+
kind: semantic ? "validation" : "text",
|
|
90
|
+
name,
|
|
91
|
+
offscreen: rect.bottom < 0 || rect.right < 0 || rect.top > innerHeight || rect.left > innerWidth,
|
|
92
|
+
role: source.getAttribute("role") || undefined,
|
|
93
|
+
tagName: element.tagName.toLowerCase(),
|
|
94
|
+
text: snippet,
|
|
95
|
+
};
|
|
96
|
+
}).sort((left, right) => (left.kind === right.kind ? left.index - right.index : left.kind === "validation" ? -1 : 1));
|
|
97
|
+
const unique = [];
|
|
98
|
+
const seen = new Set();
|
|
99
|
+
for (const match of matches) {
|
|
100
|
+
const key = [match.kind, match.role, match.name, match.text].join("\\n");
|
|
101
|
+
if (seen.has(key)) continue;
|
|
102
|
+
seen.add(key);
|
|
103
|
+
const { index, ...visible } = match;
|
|
104
|
+
unique.push(visible);
|
|
105
|
+
}
|
|
106
|
+
return { matches: unique.slice(0, limit), totalMatches: unique.length, truncated: unique.length > limit };
|
|
107
|
+
})()`;
|
|
108
|
+
}
|
|
109
|
+
function extractRenderedTextSearchResult(data) {
|
|
110
|
+
const result = isRecord(data) && isRecord(data.result) ? data.result : data;
|
|
111
|
+
if (!isRecord(result) || !Array.isArray(result.matches))
|
|
112
|
+
return undefined;
|
|
113
|
+
const matches = result.matches.flatMap((value) => {
|
|
114
|
+
if (!isRecord(value) || (value.kind !== "text" && value.kind !== "validation") || typeof value.offscreen !== "boolean" || typeof value.tagName !== "string" || typeof value.text !== "string")
|
|
115
|
+
return [];
|
|
116
|
+
return [{
|
|
117
|
+
kind: value.kind,
|
|
118
|
+
...(typeof value.name === "string" ? { name: redactSensitiveText(value.name) } : {}),
|
|
119
|
+
offscreen: value.offscreen,
|
|
120
|
+
...(typeof value.role === "string" ? { role: value.role } : {}),
|
|
121
|
+
tagName: value.tagName,
|
|
122
|
+
text: redactSensitiveText(value.text),
|
|
123
|
+
}];
|
|
124
|
+
}).slice(0, RENDERED_TEXT_SEARCH_MAX_MATCHES);
|
|
125
|
+
const totalMatches = typeof result.totalMatches === "number" && Number.isInteger(result.totalMatches) && result.totalMatches >= matches.length ? result.totalMatches : matches.length;
|
|
126
|
+
return { matches, totalMatches, truncated: result.truncated === true || totalMatches > matches.length };
|
|
127
|
+
}
|
|
128
|
+
function attachRenderedTextMatchRefs(matches, snapshotData) {
|
|
129
|
+
if (!isRecord(snapshotData) || !isRecord(snapshotData.refs))
|
|
130
|
+
return matches;
|
|
131
|
+
const refs = snapshotData.refs;
|
|
132
|
+
return matches.map((match) => {
|
|
133
|
+
if (!match.name)
|
|
134
|
+
return match;
|
|
135
|
+
const normalizedName = match.name.replace(/\s+/g, " ").trim().toLowerCase();
|
|
136
|
+
const candidates = Object.entries(refs).filter(([, value]) => {
|
|
137
|
+
if (!isRecord(value) || typeof value.name !== "string")
|
|
138
|
+
return false;
|
|
139
|
+
const nameMatches = value.name.replace(/\s+/g, " ").trim().toLowerCase() === normalizedName;
|
|
140
|
+
return nameMatches && (!match.role || typeof value.role !== "string" || value.role.toLowerCase() === match.role.toLowerCase());
|
|
141
|
+
});
|
|
142
|
+
return candidates.length === 1 ? { ...match, ref: candidates[0][0] } : match;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function formatRenderedTextSearchMatches(result) {
|
|
146
|
+
if (!result || result.matches.length === 0)
|
|
147
|
+
return undefined;
|
|
148
|
+
const lines = result.matches.map((match) => {
|
|
149
|
+
const context = [match.kind === "validation" ? "validation" : undefined, match.offscreen ? "outside viewport" : undefined, match.role, match.tagName, match.ref ? `@${match.ref}` : undefined].filter(Boolean).join(", ");
|
|
150
|
+
return `- ${JSON.stringify(match.text)}${context ? ` (${context})` : ""}`;
|
|
151
|
+
});
|
|
152
|
+
if (result.truncated)
|
|
153
|
+
lines.push(`- ... (${result.totalMatches - result.matches.length} additional rendered-text matches omitted)`);
|
|
154
|
+
return ["Rendered page text matches:", ...lines].join("\n");
|
|
155
|
+
}
|
|
45
156
|
function buildSnapshotDiff(previous, current) {
|
|
46
157
|
if (!current)
|
|
47
158
|
return undefined;
|
|
@@ -113,6 +224,11 @@ export async function trySnapshotFilter(options) {
|
|
|
113
224
|
const filtered = request.role || request.search ? filterSnapshotData(snapshotData, request) : isRecord(snapshotData) ? { data: snapshotData, matchedRefs: isRecord(snapshotData.refs) ? Object.keys(snapshotData.refs).length : 0, totalLines: typeof snapshotData.snapshot === "string" ? snapshotData.snapshot.split(/\r?\n/).filter((line) => line.length > 0).length : 0, totalRefs: isRecord(snapshotData.refs) ? Object.keys(snapshotData.refs).length : 0, visibleLines: typeof snapshotData.snapshot === "string" ? snapshotData.snapshot.split(/\r?\n/).filter((line) => line.length > 0).length : 0 } : undefined;
|
|
114
225
|
if (!filtered)
|
|
115
226
|
return undefined;
|
|
227
|
+
const renderedTextSearch = request.search
|
|
228
|
+
? extractRenderedTextSearchResult(await runSessionCommandData({ args: ["eval", "--stdin"], cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal, stdin: buildRenderedTextSearchEval(request.search) }))
|
|
229
|
+
: undefined;
|
|
230
|
+
if (renderedTextSearch)
|
|
231
|
+
renderedTextSearch.matches = attachRenderedTextMatchRefs(renderedTextSearch.matches, snapshotData);
|
|
116
232
|
const viewport = request.viewport ? await collectScrollPositionSnapshot({ cwd: options.cwd, namespace: options.namespace, sessionName: options.sessionName, signal: options.signal }) : undefined;
|
|
117
233
|
const fullSnapshot = extractRefSnapshotFromData(snapshotData);
|
|
118
234
|
const diff = request.diff ? buildSnapshotDiff(options.previousRefSnapshot, fullSnapshot) : undefined;
|
|
@@ -126,7 +242,8 @@ export async function trySnapshotFilter(options) {
|
|
|
126
242
|
: "Snapshot viewport metadata collected.";
|
|
127
243
|
const viewportText = viewport ? `Viewport: ${viewport.innerWidth}×${viewport.innerHeight}, scroll ${viewport.scrollX},${viewport.scrollY}, document ${viewport.scrollWidth}×${viewport.scrollHeight}, sampled scroll containers ${viewport.containers.length}/${viewport.containerCount}.` : undefined;
|
|
128
244
|
const diffText = diff && (request.role || request.search) ? diff.summary : undefined;
|
|
129
|
-
const
|
|
245
|
+
const renderedTextSearchText = formatRenderedTextSearchMatches(renderedTextSearch);
|
|
246
|
+
const prefix = [summary, renderedTextSearchText, diffText, viewportText].filter((line) => line !== undefined).join("\n\n");
|
|
130
247
|
if (presentation.content[0]?.type === "text")
|
|
131
248
|
presentation.content[0] = { ...presentation.content[0], text: `${prefix}\n\n${presentation.content[0].text}` };
|
|
132
249
|
return {
|
|
@@ -146,7 +263,7 @@ export async function trySnapshotFilter(options) {
|
|
|
146
263
|
refSnapshot: fullSnapshot,
|
|
147
264
|
sessionMode: options.sessionMode,
|
|
148
265
|
snapshotDiff: diff,
|
|
149
|
-
snapshotFilter: request.role || request.search ? { cleanArgs: request.cleanArgs, matchedRefs: filtered.matchedRefs, role: request.role, search: request.search, totalLines: filtered.totalLines, totalRefs: filtered.totalRefs, visibleLines: filtered.visibleLines } : undefined,
|
|
266
|
+
snapshotFilter: request.role || request.search ? { cleanArgs: request.cleanArgs, matchedRefs: filtered.matchedRefs, renderedTextMatches: renderedTextSearch?.matches, renderedTextTotalMatches: renderedTextSearch?.totalMatches, renderedTextTruncated: renderedTextSearch?.truncated, role: request.role, search: request.search, totalLines: filtered.totalLines, totalRefs: filtered.totalRefs, visibleLines: filtered.visibleLines } : undefined,
|
|
150
267
|
snapshotViewport: viewport,
|
|
151
268
|
...buildAgentBrowserResultCategoryDetails({ args: options.effectiveArgs, command: "snapshot", succeeded: true }),
|
|
152
269
|
...buildSessionDetailFields(options.sessionName, options.usedImplicitSession, options.namespace, options.managedSessionRestoreDisabled()),
|
package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES, VALUE_FLAGS } from "../../../argv-grammar.js";
|
|
2
2
|
import { isOpenNavigationCommand } from "../../../command-taxonomy.js";
|
|
3
3
|
import { getAgentBrowserProcessTimeoutMs } from "../../../process.js";
|
|
4
|
-
import {
|
|
4
|
+
import { getUpstreamEffectiveBatchSteps } from "../../batch-stdin.js";
|
|
5
5
|
const POSITIONAL_VALUE_FLAGS = new Set([...VALUE_FLAGS, "--llms"]);
|
|
6
6
|
const COMMAND_PROCESS_TIMEOUT_GRACE_MS = 5_000;
|
|
7
7
|
function parseMillisecondsToken(token) {
|
|
@@ -12,7 +12,8 @@ function parseMillisecondsToken(token) {
|
|
|
12
12
|
return Number.isSafeInteger(parsed) ? parsed : undefined;
|
|
13
13
|
}
|
|
14
14
|
function findCommandTimeoutMs(commandTokens) {
|
|
15
|
-
|
|
15
|
+
const [command, subcommand] = commandTokens;
|
|
16
|
+
if (command !== "wait" && command !== "read" && !(command === "webmcp" && ["invoke", "result"].includes(subcommand ?? "")))
|
|
16
17
|
return undefined;
|
|
17
18
|
for (let index = 1; index < commandTokens.length; index += 1) {
|
|
18
19
|
const token = commandTokens[index];
|
|
@@ -73,11 +74,11 @@ function findCommandTimeoutBudgetMs(commandTokens, stdin, activePageUrl) {
|
|
|
73
74
|
const directTimeout = commandTimeoutBudgetMs(commandTokens, activePageUrl);
|
|
74
75
|
if (directTimeout !== undefined)
|
|
75
76
|
return directTimeout;
|
|
76
|
-
if (commandTokens[0] !== "batch"
|
|
77
|
+
if (commandTokens[0] !== "batch")
|
|
77
78
|
return undefined;
|
|
78
79
|
let batchTimeoutTotal = 0;
|
|
79
80
|
let batchPageUrl = activePageUrl;
|
|
80
|
-
for (const
|
|
81
|
+
for (const step of getUpstreamEffectiveBatchSteps(commandTokens, stdin)) {
|
|
81
82
|
batchTimeoutTotal += commandTimeoutBudgetMs(step, batchPageUrl) ?? 0;
|
|
82
83
|
if (isOpenNavigationCommand(step[0]))
|
|
83
84
|
batchPageUrl = findFirstPositionalArgument(step);
|
|
@@ -87,10 +88,10 @@ function findCommandTimeoutBudgetMs(commandTokens, stdin, activePageUrl) {
|
|
|
87
88
|
export function commandTimeoutNeedsActivePageUrl(commandTokens, stdin) {
|
|
88
89
|
if (commandTokens[0] === "read")
|
|
89
90
|
return findCommandTimeoutMs(commandTokens) !== undefined && readUsesActivePageUrl(commandTokens);
|
|
90
|
-
if (commandTokens[0] !== "batch"
|
|
91
|
+
if (commandTokens[0] !== "batch")
|
|
91
92
|
return false;
|
|
92
93
|
let hasKnownPageUrl = false;
|
|
93
|
-
for (const
|
|
94
|
+
for (const step of getUpstreamEffectiveBatchSteps(commandTokens, stdin)) {
|
|
94
95
|
if (isOpenNavigationCommand(step[0]) && findFirstPositionalArgument(step))
|
|
95
96
|
hasKnownPageUrl = true;
|
|
96
97
|
if (!hasKnownPageUrl && findCommandTimeoutMs(step) !== undefined && step[0] === "read" && readUsesActivePageUrl(step))
|
|
@@ -18,15 +18,15 @@ import { buildSessionAwareStaleRefNextActions, buildSessionTabRecoveryNextAction
|
|
|
18
18
|
import { resolveVisibleRefActionFromSnapshot } from "../../results/selector-recovery.js";
|
|
19
19
|
import { extractRefSnapshotFromData } from "../../session-page-state.js";
|
|
20
20
|
import { buildExecutionPlan, canUseHeadlessCompatibilityUserAgent, createFreshSessionName, extractCommandTokens, extractUpstreamCommandTokens, getDefaultHeadlessCompatUserAgent, parseWaitCommandTokens, redactInvocationArgs, } from "../../runtime.js";
|
|
21
|
-
import { buildOwnedManagedSessionRestoreContext,
|
|
21
|
+
import { buildOwnedManagedSessionRestoreContext, resolveExplicitAutosaveInterval, withOwnedManagedSessionContext, } from "../../managed-session-restore.js";
|
|
22
22
|
import { getAgentBrowserProcessEnvironment } from "../../process-environment.js";
|
|
23
|
-
import {
|
|
23
|
+
import { getExplicitSessionPageVerificationRequirement, getPageTargetValidationError, } from "../../page-target-validation.js";
|
|
24
24
|
import { acquireOwnedManagedSessionDaemonPolicy, getRunningHeadedAutosavePolicyChangeError } from "./managed-session-daemon-policy.js";
|
|
25
25
|
import { applyOpenResultTabCorrection, buildManagedSessionOutcome, buildPinnedBatchPlan, buildSessionDetailFields, buildStaleRefPreflight, getSessionContextKey, extractStringResultField, collectAnySessionTabSelection, collectSessionTabSelection, getGuardedRefUsage, getTraceOwnerGuardMessage, runSessionCommandData, shouldPinSessionTabForCommand, } from "./session-state.js";
|
|
26
26
|
import { getUpstreamEffectiveBatchSteps, parseBatchStdinJsonArray } from "../batch-stdin.js";
|
|
27
|
-
import { buildElectronHostFailureResult, getElectronLaunchFailureCategory, redactRecoveryHint } from "./final-result.js";
|
|
27
|
+
import { buildElectronHostFailureResult, formatAgentBrowserNextActionsText, getElectronLaunchFailureCategory, redactRecoveryHint } from "./final-result.js";
|
|
28
28
|
import { prepareClickDispatchProbe } from "./click-dispatch.js";
|
|
29
|
-
import { collectScrollPositionSnapshot, validateQaAttachedPrecondition } from "./diagnostics.js";
|
|
29
|
+
import { buildUnsupportedScrollIntoViewRecovery, collectScrollPositionSnapshot, validateQaAttachedPrecondition } from "./diagnostics.js";
|
|
30
30
|
import { getScreenshotPathTokenIndex } from "./artifact-paths.js";
|
|
31
31
|
import { findRequestedArtifactCloseViolation } from "./prompt-guards.js";
|
|
32
32
|
export function normalizeRunInput(input) {
|
|
@@ -283,10 +283,9 @@ async function collectSamePageRefFreshnessPreflight(options) {
|
|
|
283
283
|
}
|
|
284
284
|
function getIdleTimeoutMismatch(args, configuredValue) {
|
|
285
285
|
for (let index = 0; index < args.length; index += 1) {
|
|
286
|
-
|
|
287
|
-
if (token !== "--idle-timeout" && !token.startsWith("--idle-timeout="))
|
|
286
|
+
if (args[index] !== "--idle-timeout")
|
|
288
287
|
continue;
|
|
289
|
-
const requestedToken =
|
|
288
|
+
const requestedToken = args[++index];
|
|
290
289
|
if (!requestedToken || !/^\d+$/.test(requestedToken) || Number(requestedToken) === Number(configuredValue))
|
|
291
290
|
continue;
|
|
292
291
|
return `--idle-timeout ${requestedToken} conflicts with this Pi process's managed-session idle timeout (${configuredValue} ms). Restart Pi with PI_AGENT_BROWSER_IMPLICIT_SESSION_IDLE_TIMEOUT_MS=${requestedToken} and omit --idle-timeout; changing the launch value for one call can restart the upstream browser and discard the active tab.`;
|
|
@@ -353,22 +352,20 @@ export async function prepareBrowserRun(options) {
|
|
|
353
352
|
let electronLaunch;
|
|
354
353
|
const sessionMode = compiledElectron?.action === "launch" ? "fresh" : params.sessionMode ?? "auto";
|
|
355
354
|
const freshSessionName = createFreshSessionName(managedSessionBaseName, ephemeralSessionSeed, freshSessionOrdinal + 1);
|
|
356
|
-
const
|
|
355
|
+
const rawPageTargetError = getPageTargetValidationError({
|
|
357
356
|
args: runtimeToolArgs,
|
|
358
|
-
cwd,
|
|
359
|
-
parentEnv: agentBrowserProcessEnv,
|
|
360
357
|
stdin: runtimeToolStdin,
|
|
361
358
|
trustedFirstBatchTabSelection: true,
|
|
362
359
|
});
|
|
363
|
-
if (
|
|
360
|
+
if (rawPageTargetError) {
|
|
364
361
|
return {
|
|
365
362
|
kind: "early-result",
|
|
366
363
|
result: {
|
|
367
|
-
content: [{ type: "text", text:
|
|
364
|
+
content: [{ type: "text", text: rawPageTargetError }],
|
|
368
365
|
details: {
|
|
369
366
|
args: redactedArgs,
|
|
370
|
-
...buildAgentBrowserResultCategoryDetails({ args: redactedArgs, errorText:
|
|
371
|
-
validationError:
|
|
367
|
+
...buildAgentBrowserResultCategoryDetails({ args: redactedArgs, errorText: rawPageTargetError, succeeded: false, validationError: rawPageTargetError }),
|
|
368
|
+
validationError: rawPageTargetError,
|
|
372
369
|
},
|
|
373
370
|
isError: true,
|
|
374
371
|
},
|
|
@@ -419,16 +416,14 @@ export async function prepareBrowserRun(options) {
|
|
|
419
416
|
executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: idleTimeoutMismatch };
|
|
420
417
|
const ownedSessionKey = getSessionContextKey(executionPlan.sessionName, executionPlan.namespace);
|
|
421
418
|
const plannedSessionPageState = sessionPageState.get(ownedSessionKey);
|
|
422
|
-
const
|
|
419
|
+
const pageTargetError = getPageTargetValidationError({
|
|
423
420
|
args: executionPlan.effectiveArgs,
|
|
424
421
|
currentPageUrl: plannedSessionPageState.tabTarget?.url,
|
|
425
422
|
pageUrlUnknown: plannedSessionPageState.tabTargetUnknown === true,
|
|
426
|
-
cwd,
|
|
427
|
-
parentEnv: agentBrowserProcessEnv,
|
|
428
423
|
stdin: runtimeToolStdin,
|
|
429
424
|
});
|
|
430
|
-
if (!executionPlan.validationError &&
|
|
431
|
-
executionPlan = { ...executionPlan, recoveryHint: undefined, validationError:
|
|
425
|
+
if (!executionPlan.validationError && pageTargetError)
|
|
426
|
+
executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: pageTargetError };
|
|
432
427
|
const recordedOwnedSession = ownedSessionKey ? state.ownedManagedSessions.get(ownedSessionKey) : undefined;
|
|
433
428
|
const targetsCurrentManagedSession = state.managedSessionActive
|
|
434
429
|
&& ownedSessionKey === getSessionContextKey(state.managedSessionName, state.managedSessionNamespace);
|
|
@@ -462,6 +457,7 @@ export async function prepareBrowserRun(options) {
|
|
|
462
457
|
executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: autosavePolicyChangeError };
|
|
463
458
|
}
|
|
464
459
|
const headedLaunch = getBooleanFlagValue(executionPlan.effectiveArgs, "--headed") ?? isUpstreamEnvFlagEnabled(agentBrowserProcessEnv.AGENT_BROWSER_HEADED);
|
|
460
|
+
const providerLaunch = executionPlan.startupScopedFlags.some((flag) => flag === "--provider" || flag === "-p") || agentBrowserProcessEnv.AGENT_BROWSER_PROVIDER !== undefined;
|
|
465
461
|
const headedManagedAutosaveDisabled = retainedHeadedAutosaveDisabled || (explicitAutosaveInterval === undefined && headedLaunch);
|
|
466
462
|
const headedManagedAutosaveInterval = retainedHeadedAutosaveInterval ?? (headedLaunch ? explicitAutosaveInterval ?? "0" : undefined);
|
|
467
463
|
const compatibilityUserAgent = executionPlan.compatibilityWorkaround ? getDefaultHeadlessCompatUserAgent() : undefined;
|
|
@@ -484,9 +480,6 @@ export async function prepareBrowserRun(options) {
|
|
|
484
480
|
compatibilityUserAgent: compatibilityUserAgentApplied ? compatibilityUserAgent : undefined,
|
|
485
481
|
wrapperInjectedUserAgent: compatibilityUserAgentApplied,
|
|
486
482
|
});
|
|
487
|
-
const managedSessionTargetError = getManagedSessionTargetAccessValidationError(executionPlan.effectiveArgs, ownedManagedSession !== undefined, agentBrowserProcessEnv);
|
|
488
|
-
if (!executionPlan.validationError && managedSessionTargetError)
|
|
489
|
-
executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: managedSessionTargetError };
|
|
490
483
|
if (!executionPlan.validationError && ownedManagedSession) {
|
|
491
484
|
const closeCommand = isCloseCommand(executionPlan.commandInfo.command);
|
|
492
485
|
const policy = await acquireOwnedManagedSessionDaemonPolicy({
|
|
@@ -523,17 +516,6 @@ export async function prepareBrowserRun(options) {
|
|
|
523
516
|
effectiveArgs: ["--user-agent", compatibilityUserAgent, ...executionPlan.effectiveArgs],
|
|
524
517
|
};
|
|
525
518
|
}
|
|
526
|
-
else if (closeCommand && managedSessionPolicyLock) {
|
|
527
|
-
executionPlan = {
|
|
528
|
-
...executionPlan,
|
|
529
|
-
effectiveArgs: canonicalizeOwnedManagedSessionCloseArgs({
|
|
530
|
-
args: executionPlan.effectiveArgs,
|
|
531
|
-
cwd,
|
|
532
|
-
ownedManagedSession: true,
|
|
533
|
-
restoreState: state.managedSessionRestoreState,
|
|
534
|
-
}, true),
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
519
|
}
|
|
538
520
|
return await withOwnedManagedSessionContext(ownedManagedSession, async () => {
|
|
539
521
|
const managedSessionRestoreDisabled = () => state.managedSessionRestoreState.isDisabled(executionPlan.sessionName, executionPlan.namespace);
|
|
@@ -577,12 +559,10 @@ export async function prepareBrowserRun(options) {
|
|
|
577
559
|
executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: request.requirement };
|
|
578
560
|
return;
|
|
579
561
|
}
|
|
580
|
-
const livePageValidationError =
|
|
562
|
+
const livePageValidationError = getPageTargetValidationError({
|
|
581
563
|
args: request.args,
|
|
582
564
|
currentPageUrl: liveUrl,
|
|
583
|
-
cwd,
|
|
584
565
|
pageUrlUnknown: false,
|
|
585
|
-
parentEnv: agentBrowserProcessEnv,
|
|
586
566
|
stdin: request.stdin,
|
|
587
567
|
});
|
|
588
568
|
if (livePageValidationError) {
|
|
@@ -598,7 +578,7 @@ export async function prepareBrowserRun(options) {
|
|
|
598
578
|
if (!executionPlan.validationError && mayResolveSemanticVisibleRef && requiresLivePageVerification()) {
|
|
599
579
|
await verifyLivePage({
|
|
600
580
|
args: ["snapshot", "-i"],
|
|
601
|
-
requirement:
|
|
581
|
+
requirement: getExplicitSessionPageVerificationRequirement({ args: ["snapshot", "-i"] }),
|
|
602
582
|
});
|
|
603
583
|
}
|
|
604
584
|
if (!executionPlan.validationError && mayResolveSemanticVisibleRef) {
|
|
@@ -632,6 +612,13 @@ export async function prepareBrowserRun(options) {
|
|
|
632
612
|
});
|
|
633
613
|
}
|
|
634
614
|
const commandTokens = semanticActionVisibleRefResolution ? extractUpstreamCommandTokens(semanticActionVisibleRefResolution.args) : extractUpstreamCommandTokens(preparedArgs.args);
|
|
615
|
+
const unsupportedScrollIntoViewRecovery = executionPlan.validationError || executionPlan.plainTextInspection
|
|
616
|
+
? undefined
|
|
617
|
+
: [commandTokens, ...getUpstreamEffectiveBatchSteps(commandTokens, runtimeToolStdin)]
|
|
618
|
+
.map((tokens) => buildUnsupportedScrollIntoViewRecovery({ commandTokens: tokens, sessionName: executionPlan.sessionName }))
|
|
619
|
+
.find((recovery) => recovery !== undefined);
|
|
620
|
+
if (unsupportedScrollIntoViewRecovery)
|
|
621
|
+
executionPlan = { ...executionPlan, recoveryHint: undefined, validationError: unsupportedScrollIntoViewRecovery.error };
|
|
635
622
|
const resolvedSemanticActionRefSnapshot = semanticActionVisibleRefResolution?.snapshot
|
|
636
623
|
? { ...semanticActionVisibleRefResolution.snapshot, target: semanticActionVisibleRefResolution.snapshot.target ?? priorSessionTabTarget }
|
|
637
624
|
: undefined;
|
|
@@ -648,9 +635,8 @@ export async function prepareBrowserRun(options) {
|
|
|
648
635
|
&& requiresLivePageVerification();
|
|
649
636
|
const livePageRequirement = livePageAccessEligible
|
|
650
637
|
&& !livePageVerified
|
|
651
|
-
?
|
|
638
|
+
? getExplicitSessionPageVerificationRequirement({
|
|
652
639
|
args: executionPlan.effectiveArgs,
|
|
653
|
-
cwd,
|
|
654
640
|
stdin: runtimeToolStdin,
|
|
655
641
|
})
|
|
656
642
|
: undefined;
|
|
@@ -669,8 +655,10 @@ export async function prepareBrowserRun(options) {
|
|
|
669
655
|
freshSessionOrdinal += 1;
|
|
670
656
|
}
|
|
671
657
|
if (executionPlan.validationError) {
|
|
658
|
+
const nextActions = applyNamespaceToNextActions(unsupportedScrollIntoViewRecovery?.nextActions, executionPlan.namespace);
|
|
659
|
+
const nextActionsText = formatAgentBrowserNextActionsText(nextActions);
|
|
672
660
|
return { kind: "early-result", statePatch, result: {
|
|
673
|
-
content: [{ type: "text", text: executionPlan.validationError }],
|
|
661
|
+
content: [{ type: "text", text: [executionPlan.validationError, nextActionsText].filter((text) => text !== undefined).join("\n\n") }],
|
|
674
662
|
details: {
|
|
675
663
|
args: redactedArgs,
|
|
676
664
|
compiledElectron: redactedCompiledElectron,
|
|
@@ -679,6 +667,7 @@ export async function prepareBrowserRun(options) {
|
|
|
679
667
|
compiledSourceLookup: redactedCompiledSourceLookup,
|
|
680
668
|
compiledNetworkSourceLookup: redactedCompiledNetworkSourceLookup,
|
|
681
669
|
invalidValueFlag: executionPlan.invalidValueFlag,
|
|
670
|
+
nextActions,
|
|
682
671
|
sessionMode,
|
|
683
672
|
sessionRecoveryHint: redactedRecoveryHint,
|
|
684
673
|
startupScopedFlags: executionPlan.startupScopedFlags,
|
|
@@ -1058,6 +1047,8 @@ export async function prepareBrowserRun(options) {
|
|
|
1058
1047
|
kind: "ready",
|
|
1059
1048
|
prepared: {
|
|
1060
1049
|
commandTokens,
|
|
1050
|
+
headedLaunch,
|
|
1051
|
+
providerLaunch,
|
|
1061
1052
|
managedSessionPolicyLock,
|
|
1062
1053
|
compiledElectron,
|
|
1063
1054
|
compiledJob,
|