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
|
@@ -197,7 +197,7 @@ export function formatPresentationSummary(commandInfo, data, compiledSemanticAct
|
|
|
197
197
|
return semanticSummary;
|
|
198
198
|
}
|
|
199
199
|
const navigationSummary = getNavigationSummary(data);
|
|
200
|
-
if (navigationSummary && isNavigationObservableCommand(presentationCommandInfo.command)) {
|
|
200
|
+
if (navigationSummary && isNavigationObservableCommand(presentationCommandInfo.command, presentationCommandInfo.subcommand)) {
|
|
201
201
|
const navigationText = formatNavigationSummary(navigationSummary);
|
|
202
202
|
if (navigationText) {
|
|
203
203
|
return `${presentationCommandInfo.command ?? "navigation"} → ${navigationText.split("\n", 1)[0] ?? navigationText}`;
|
|
@@ -242,7 +242,7 @@ export function formatPresentationContentText(commandInfo, data, compiledSemanti
|
|
|
242
242
|
}
|
|
243
243
|
const presentationCommandInfo = resolvePresentationCommandInfo(commandInfo, compiledSemanticAction);
|
|
244
244
|
const navigationSummary = getNavigationSummary(data);
|
|
245
|
-
if (navigationSummary && isNavigationObservableCommand(presentationCommandInfo.command)) {
|
|
245
|
+
if (navigationSummary && isNavigationObservableCommand(presentationCommandInfo.command, presentationCommandInfo.subcommand)) {
|
|
246
246
|
const navigationText = formatNavigationSummary(navigationSummary);
|
|
247
247
|
if (navigationText) {
|
|
248
248
|
const actionText = formatNavigationActionResult(data);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { isRecord } from "../parsing.js";
|
|
2
|
-
import { extractUpstreamCommandTokens } from "../runtime.js";
|
|
2
|
+
import { extractUpstreamCommandTokens, parseCommandInfo, redactInvocationArgs } from "../runtime.js";
|
|
3
3
|
import { buildAgentBrowserNextActions } from "./action-recommendations.js";
|
|
4
4
|
import { buildAgentBrowserResultCategoryDetails } from "./categories.js";
|
|
5
5
|
import { detectConfirmationRequired } from "./confirmation.js";
|
|
6
6
|
import { buildSnapshotPresentation } from "./snapshot.js";
|
|
7
7
|
import { parseJsonPreviewString, redactModelFacingText, stringifyModelFacing } from "./presentation/common.js";
|
|
8
8
|
import { applyArtifactManifest, attachInlineImage, buildArtifactVerificationSummary, buildManifestEntriesForFileArtifacts, classifyPresentationSuccessCategory, extractFileArtifacts, extractImagePath, formatArtifactMetadataLines, formatArtifactSummary, formatMissingArtifactFailureText, getSavedFileDetails, hasMissingFileArtifact, isManifestFileArtifact, } from "./presentation/artifacts.js";
|
|
9
|
-
import { buildBatchPresentation, isAgentBrowserBatchResultArray } from "./presentation/batch.js";
|
|
10
|
-
import { getPresentationPaths } from "./presentation/content.js";
|
|
9
|
+
import { buildBatchPresentation, isAgentBrowserBatchResultArray, redactBatchStepErrorData } from "./presentation/batch.js";
|
|
10
|
+
import { getPresentationPaths, isStringArray } from "./presentation/content.js";
|
|
11
11
|
import { buildNetworkRequestsNextActions, buildStreamNextActions, enrichStreamStatusData, formatNetworkRouteDiagnosticsText, redactPresentationData, } from "./presentation/diagnostics.js";
|
|
12
12
|
import { buildErrorPresentation } from "./presentation/errors.js";
|
|
13
13
|
import { compactLargePresentationOutput } from "./presentation/large-output.js";
|
|
@@ -31,6 +31,23 @@ function mergeNextActions(...groups) {
|
|
|
31
31
|
function shouldAddAnnotatedScreenshotGuidance(commandInfo, args) {
|
|
32
32
|
return commandInfo.command === "screenshot" && (args?.includes("--annotate") ?? false);
|
|
33
33
|
}
|
|
34
|
+
function getKeyboardInsertTextWarning(commandInfo) {
|
|
35
|
+
if (commandInfo.command !== "keyboard" || commandInfo.subcommand !== "inserttext")
|
|
36
|
+
return undefined;
|
|
37
|
+
return "Input dispatch warning: keyboard inserttext skips key events. A DOM value change does not prove a framework-controlled editor accepted it; verify application state before saving, or use keyboard type when real key events are required.";
|
|
38
|
+
}
|
|
39
|
+
function redactBatchSpillData(data) {
|
|
40
|
+
return data.map((row) => {
|
|
41
|
+
const command = isStringArray(row.command) ? row.command : undefined;
|
|
42
|
+
const commandInfo = parseCommandInfo(command ?? []);
|
|
43
|
+
return {
|
|
44
|
+
...row,
|
|
45
|
+
command: command ? redactInvocationArgs(command) : row.command,
|
|
46
|
+
error: row.error === undefined ? undefined : redactBatchStepErrorData(command, row.error),
|
|
47
|
+
result: row.result === undefined ? undefined : redactPresentationData(commandInfo, row.result),
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
}
|
|
34
51
|
export async function buildToolPresentation(options) {
|
|
35
52
|
const { args, artifactManifest, artifactRequest, commandInfo, compiledSemanticAction, cwd, envelope, errorText, namespace, networkRouteDiagnostics, networkRoutes, persistentArtifactStore, sessionName, } = options;
|
|
36
53
|
const commandInfoWithTokens = commandInfo.commandTokens || !args ? commandInfo : { ...commandInfo, commandTokens: extractUpstreamCommandTokens(args) };
|
|
@@ -39,7 +56,9 @@ export async function buildToolPresentation(options) {
|
|
|
39
56
|
return buildErrorPresentation({ args, commandInfo, errorText, sessionName });
|
|
40
57
|
}
|
|
41
58
|
const data = enrichStreamStatusData(commandInfoWithTokens, envelope?.data);
|
|
42
|
-
const presentationData =
|
|
59
|
+
const presentationData = commandInfo.command === "batch" && isAgentBrowserBatchResultArray(data)
|
|
60
|
+
? redactBatchSpillData(data)
|
|
61
|
+
: redactPresentationData(commandInfoWithTokens, data);
|
|
43
62
|
const artifacts = await extractFileArtifacts({ artifactManifest, artifactMaxUpdatedAtMs: options.artifactMaxUpdatedAtMs, artifactMinUpdatedAtMs: options.artifactMinUpdatedAtMs, artifactRequest, commandInfo: presentationCommandInfo, cwd, data, namespace, sessionName });
|
|
44
63
|
const artifactVerification = buildArtifactVerificationSummary(artifacts);
|
|
45
64
|
const artifactSummary = formatArtifactSummary(artifacts);
|
|
@@ -95,6 +114,10 @@ export async function buildToolPresentation(options) {
|
|
|
95
114
|
const guidance = "Annotated screenshot note: dense pages can produce overlapping labels. If the labels are noisy, capture a scoped element screenshot, take a non-annotated screenshot, or use snapshot -i high-value refs as the machine-readable map.";
|
|
96
115
|
presentation.content[0] = { ...presentation.content[0], text: `${presentation.content[0].text}\n\n${guidance}` };
|
|
97
116
|
}
|
|
117
|
+
const keyboardInsertTextWarning = getKeyboardInsertTextWarning(commandInfoWithTokens);
|
|
118
|
+
if (keyboardInsertTextWarning && presentation.content[0]?.type === "text") {
|
|
119
|
+
presentation.content[0] = { ...presentation.content[0], text: `${presentation.content[0].text}\n\n${keyboardInsertTextWarning}` };
|
|
120
|
+
}
|
|
98
121
|
const imagePath = artifactRequest?.absolutePath ?? extractImagePath(commandInfo, cwd, data);
|
|
99
122
|
const presentationWithImage = imagePath ? await attachInlineImage(presentation, imagePath) : presentation;
|
|
100
123
|
const compactedPresentation = await compactLargePresentationOutput({
|
|
@@ -159,6 +182,7 @@ export async function buildToolPresentation(options) {
|
|
|
159
182
|
resultCategory: presentationWithManifest.resultCategory ?? "success",
|
|
160
183
|
savedFilePath: presentationWithManifest.savedFilePath,
|
|
161
184
|
sessionName,
|
|
185
|
+
subcommand: presentationCommandInfo.subcommand,
|
|
162
186
|
successCategory: presentationWithManifest.successCategory,
|
|
163
187
|
});
|
|
164
188
|
const networkNextActions = commandInfoWithTokens.command === "network" && commandInfoWithTokens.subcommand === "requests" && presentationWithManifest.resultCategory === "success"
|
|
@@ -174,5 +198,8 @@ export async function buildToolPresentation(options) {
|
|
|
174
198
|
savedFilePath: presentationWithManifest.savedFilePath,
|
|
175
199
|
summary: presentationWithManifest.summary,
|
|
176
200
|
});
|
|
201
|
+
if (presentationWithManifest.pageChangeSummary?.observed === false && presentationCommandInfo.command !== "batch" && presentationWithManifest.content[0]?.type === "text") {
|
|
202
|
+
presentationWithManifest.content[0] = { ...presentationWithManifest.content[0], text: `${presentationWithManifest.content[0].text}\n\nAction dispatched; application change unverified. Verify the expected URL, text, state, or external receipt before relying on it.` };
|
|
203
|
+
}
|
|
177
204
|
return sanitizeModelFacingPresentation(presentationWithManifest);
|
|
178
205
|
}
|
|
@@ -60,7 +60,7 @@ export function buildRecoveryNextActions(recovery) {
|
|
|
60
60
|
args: sessionArgs(["get", "url"]),
|
|
61
61
|
id: AGENT_BROWSER_RECOVERY_NEXT_ACTION_IDS.connectedSessionGetUrl,
|
|
62
62
|
reason: "Verify the attached page URL before any page-content inspection.",
|
|
63
|
-
safety: "Read-only URL lookup. The wrapper keeps page inspection blocked if the active target
|
|
63
|
+
safety: "Read-only URL lookup. The wrapper keeps page inspection blocked if the active target URL cannot be verified.",
|
|
64
64
|
}),
|
|
65
65
|
buildNextToolAction({
|
|
66
66
|
args: sessionArgs(["tab", "list"]),
|
|
@@ -17,6 +17,15 @@ export function buildNoActivePageNextActions(sessionName) {
|
|
|
17
17
|
resultCategory: "failure",
|
|
18
18
|
}) ?? [];
|
|
19
19
|
}
|
|
20
|
+
export function buildPendingWebMcpNextActions(sessionName) {
|
|
21
|
+
return [{
|
|
22
|
+
id: "verify-page-target-after-pending-webmcp",
|
|
23
|
+
params: { args: withOptionalSessionArgs(sessionName, ["get", "url"]) },
|
|
24
|
+
reason: "Verify the current page target before taking a fresh snapshot; use webmcp result or cancel to settle the detached invocation.",
|
|
25
|
+
safety: "Read-only URL inspection. It does not settle the pending page tool, which may still mutate or navigate later.",
|
|
26
|
+
tool: "agent_browser",
|
|
27
|
+
}];
|
|
28
|
+
}
|
|
20
29
|
export function buildSessionTabRecoveryNextActions(options) {
|
|
21
30
|
const resultCategory = options.resultCategory ?? "success";
|
|
22
31
|
return buildAgentBrowserNextActions({
|
|
@@ -9,7 +9,7 @@ const SELECTOR_RECOVERY_ACTION_NAMES = new Set(["check", "click", "fill", "selec
|
|
|
9
9
|
const VISIBLE_REF_FALLBACK_CANDIDATE_LIMIT = 3;
|
|
10
10
|
const EDITABLE_CONTROL_ROLES = new Set(["combobox", "searchbox", "textbox"]);
|
|
11
11
|
const RICH_INPUT_RECOVERY_EDITABLE_ROLES = new Set(["searchbox", "textbox"]);
|
|
12
|
-
const RICH_INPUT_RECOVERY_HINT = "After the editable ref is focused, use keyboard
|
|
12
|
+
const RICH_INPUT_RECOVERY_HINT = "After the editable ref is focused, use keyboard type when a framework-controlled editor requires real key events. keyboard inserttext is paste-like and needs separate application-state verification. Do not press Enter or otherwise submit unless the user flow explicitly calls for it.";
|
|
13
13
|
function isSelectorRecoveryActionName(action) {
|
|
14
14
|
return SELECTOR_RECOVERY_ACTION_NAMES.has(action);
|
|
15
15
|
}
|
|
@@ -225,7 +225,7 @@ export function buildRichInputRecoveryNextActions(options) {
|
|
|
225
225
|
const clickId = getAgentBrowserRichInputRecoveryNextActionId("click", index, candidateCount);
|
|
226
226
|
const safety = ambiguous
|
|
227
227
|
? `Several editable refs share the same exact name. Inspect the current snapshot and use only the ${candidate.ref} ${candidate.role} if it is clearly the intended input. No fill text or submit key is included.`
|
|
228
|
-
: "Does not include fill text or submit the form. After focus/click succeeds, use keyboard
|
|
228
|
+
: "Does not include fill text or submit the form. After focus/click succeeds, use keyboard type for framework-controlled editors; use keyboard inserttext only with separate application-state verification.";
|
|
229
229
|
return [
|
|
230
230
|
{
|
|
231
231
|
id: focusId,
|
|
@@ -251,7 +251,7 @@ export function formatRichInputRecoveryText(diagnostic) {
|
|
|
251
251
|
"Rich input recovery:",
|
|
252
252
|
...diagnostic.candidates.map((candidate, index) => {
|
|
253
253
|
const [focusId, clickId] = diagnostic.nextActionIds.slice(index * 2, index * 2 + 2);
|
|
254
|
-
return `- ${candidate.ref} ${candidate.role} ${JSON.stringify(candidate.name)}: use ${focusId} or ${clickId}; then use keyboard
|
|
254
|
+
return `- ${candidate.ref} ${candidate.role} ${JSON.stringify(candidate.name)}: use ${focusId} or ${clickId}; then use keyboard type for framework-controlled editors, or paste-like keyboard inserttext only with separate application-state verification.`;
|
|
255
255
|
}),
|
|
256
256
|
`- ${diagnostic.inputMethodHint}`,
|
|
257
257
|
].join("\n");
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { redactSensitiveValue } from "../runtime.js";
|
|
1
2
|
import { writePersistentSessionArtifactFile, writeSecureTempFile } from "../temp.js";
|
|
2
3
|
import { buildEvictedSessionArtifactEntries, formatSessionArtifactRetentionSummary, mergeSessionArtifactManifest, } from "./artifact-manifest.js";
|
|
3
4
|
const SNAPSHOT_SPILL_FILE_PREFIX = "pi-agent-browser-snapshot";
|
|
4
5
|
export async function writeSnapshotSpillFile(data, persistentArtifactStore) {
|
|
5
6
|
const options = {
|
|
6
|
-
content: JSON.stringify(data, null, 2),
|
|
7
|
+
content: JSON.stringify(redactSensitiveValue(data), null, 2),
|
|
7
8
|
prefix: SNAPSHOT_SPILL_FILE_PREFIX,
|
|
8
9
|
suffix: ".json",
|
|
9
10
|
};
|
|
@@ -162,12 +162,12 @@ export async function buildSnapshotPresentation(data, persistentArtifactStore =
|
|
|
162
162
|
...(roleCountsText ? [`Top roles: ${roleCountsText}`] : []),
|
|
163
163
|
"",
|
|
164
164
|
"Compact snapshot view.",
|
|
165
|
-
"Viewport note: compact snapshots are DOM/signal-prioritized, not guaranteed to start with the currently scrolled viewport; use the full
|
|
165
|
+
"Viewport note: compact snapshots are DOM/signal-prioritized, not guaranteed to start with the currently scrolled viewport; use the full redacted snapshot, a screenshot, or listed high-value refs when viewport context matters.",
|
|
166
166
|
];
|
|
167
167
|
if (fallbackPreview) {
|
|
168
168
|
lines.push("", "Compact outline:", ...(fallbackPreview.lines.length > 0 ? fallbackPreview.lines : ["(no interactive elements)"]));
|
|
169
169
|
if (fallbackPreview.omittedCount > 0) {
|
|
170
|
-
lines.push(`- ... (${fallbackPreview.omittedCount} additional snapshot lines omitted; ${fullOutputPath ? `full output path: ${fullOutputPath}` : "the full
|
|
170
|
+
lines.push(`- ... (${fallbackPreview.omittedCount} additional snapshot lines omitted; ${fullOutputPath ? `full output path: ${fullOutputPath}` : "the full redacted snapshot was omitted"})`);
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
else {
|
|
@@ -204,8 +204,8 @@ export async function buildSnapshotPresentation(data, persistentArtifactStore =
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
lines.push("", fullOutputPath
|
|
207
|
-
? `Full
|
|
208
|
-
: `Full
|
|
207
|
+
? `Full redacted snapshot path: ${fullOutputPath}`
|
|
208
|
+
: `Full redacted snapshot unavailable: ${spillErrorText ?? "temp spill file could not be created."}`);
|
|
209
209
|
const manifestFields = applySnapshotArtifactManifest({
|
|
210
210
|
baseManifest: artifactManifest,
|
|
211
211
|
command: "snapshot",
|
|
@@ -2,9 +2,8 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { basename } from "node:path";
|
|
3
3
|
import { extractUpstreamCommandTokens, findCommandStartIndex, parseArgvDescriptor, parseCommandInfo, } from "./argv-descriptor.js";
|
|
4
4
|
import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./batch-lifecycle.js";
|
|
5
|
-
import { canonicalizeAgentBrowserNamespace, extractExplicitNamespace, extractExplicitSessionName, getAgentBrowserSessionIdentityKey, getBooleanFlagValue, GLOBAL_VALUE_FLAGS_ALLOWING_DASH_VALUE, isAgentBrowserSessionIdentityKeyInNamespace, isUpstreamEnvFlagEnabled, PREVALIDATED_VALUE_FLAGS, resolveAgentBrowserNamespace, scanUpstreamGlobalFlagOccurrences, } from "./argv-grammar.js";
|
|
5
|
+
import { canonicalizeAgentBrowserNamespace, extractExplicitNamespace, extractExplicitSessionName, getAgentBrowserSessionIdentityKey, getBooleanFlagValue, GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES, GLOBAL_VALUE_FLAGS_ALLOWING_DASH_VALUE, isAgentBrowserSessionIdentityKeyInNamespace, isUpstreamEnvFlagEnabled, PREVALIDATED_VALUE_FLAGS, resolveAgentBrowserNamespace, scanUpstreamGlobalFlagOccurrences, } from "./argv-grammar.js";
|
|
6
6
|
import { needsManagedSession } from "./command-policy.js";
|
|
7
|
-
import { isWrapperManagedSessionName, redactManagedSessionRestoreKeys } from "./managed-session-capabilities.js";
|
|
8
7
|
import { isCloseAllCommand, isCloseCommand, isOpenNavigationCommand } from "./command-taxonomy.js";
|
|
9
8
|
import { hasLaunchScopedFlagToken, LAUNCH_SCOPED_FLAG_DEFINITIONS, LAUNCH_SCOPED_FLAG_LABEL, } from "./launch-scoped-flags.js";
|
|
10
9
|
import { MANAGED_SESSION_NAME_PREFIX, } from "./managed-session-restore.js";
|
|
@@ -21,7 +20,9 @@ const DEFAULT_IMPLICIT_SESSION_IDLE_TIMEOUT_MS = 15 * 60 * 1000;
|
|
|
21
20
|
const DEFAULT_IMPLICIT_SESSION_CLOSE_TIMEOUT_MS = 5_000;
|
|
22
21
|
const INSPECTION_FLAGS = new Set(["--help", "-h", "--version", "-V"]);
|
|
23
22
|
const SENSITIVE_VALUE_FLAGS = new Set(["--body", "--headers", "--password", "--proxy"]);
|
|
24
|
-
const SENSITIVE_QUERY_PARAM_PATTERN = /^(?:access(?:_|-)?token|api(?:_|-)?key|auth|authorization|bearer|client(?:_|-)?secret|code|cookie|id(?:_|-)?token|key|pass(?:word)?|refresh(?:_|-)?token|secret|sentry(?:_|-)?key|session(?:_|-)?id|sig(?:nature)?|token|write(?:_|-)?key)$/i;
|
|
23
|
+
const SENSITIVE_QUERY_PARAM_PATTERN = /^(?:access(?:_|-)?token|api(?:_|-)?key|auth|authorization|bearer|client(?:_|-)?secret|code|cookie|id(?:_|-)?token|key|pass(?:word)?|refresh(?:_|-)?token|relay(?:_|-)?state|saml(?:_|-)?request|saml(?:_|-)?response|secret|sentry(?:_|-)?key|session(?:_|-)?id|sig(?:nature)?|token|write(?:_|-)?key)$/i;
|
|
24
|
+
const AUTH_STATE_QUERY_PARAM_PATTERN = /^(?:nonce|state)$/i;
|
|
25
|
+
const AUTH_URL_CONTEXT_PATTERN = /(?:^|[./_-])(?:auth|authorize|callback|login|oauth2?|oidc|saml|sso)(?:[./?#_-]|$)/i;
|
|
25
26
|
const SENSITIVE_FIELD_NAME_PATTERN = /^(?:[A-Za-z0-9_-]*(?:api[_-]?key|access[_-]?key|private[_-]?key|secret(?:[_-]?(?:key|access[_-]?key))?|token|password|passwd|credentials?|database[_-]?url|db[_-]?url|connection[_-]?string|mongo(?:db)?[_-]?uri|redis[_-]?url)|[A-Za-z0-9]*(?:apiKey|ApiKey|apikey|privateKey|PrivateKey|databaseUrl|DatabaseUrl|dbUrl|DbUrl|connectionString|ConnectionString|mongoUri|MongoUri|mongodbUri|MongodbUri|mongoDbUri|MongoDbUri|redisUrl|RedisUrl|Token|Secret|Password|Credential|Credentials)|auth(?:orization)?|bearer|client(?:_|-)?secret|cookie|id(?:_|-)?token|pass(?:word)?|proxy(?:_|-)?authorization|refresh(?:_|-)?token|sentry(?:_|-)?key|session(?:_|-)?id|set(?:_|-)?cookie|sig(?:nature)?|write(?:_|-)?key|x(?:_|-)?api(?:_|-)?key)$/i;
|
|
26
27
|
const ENV_SECRET_ASSIGNMENT_PATTERN = /\b((?:export\s+)?([A-Za-z_][A-Za-z0-9_-]*)(\s*[:=]\s*))(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,;]+)/g;
|
|
27
28
|
const DEFAULT_HEADLESS_COMPAT_USER_AGENT_BY_PLATFORM = {
|
|
@@ -49,48 +50,57 @@ function redactUrlToken(token) {
|
|
|
49
50
|
catch {
|
|
50
51
|
return token;
|
|
51
52
|
}
|
|
52
|
-
|
|
53
|
-
if (parsed.username.length > 0) {
|
|
53
|
+
if (parsed.username.length > 0)
|
|
54
54
|
parsed.username = "[REDACTED]";
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
if (parsed.password.length > 0) {
|
|
55
|
+
if (parsed.password.length > 0)
|
|
58
56
|
parsed.password = "[REDACTED]";
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
const hashText = parsed.hash.startsWith("#") ? parsed.hash.slice(1) : parsed.hash;
|
|
58
|
+
const hashParams = hashText.includes("=") ? new URLSearchParams(hashText) : undefined;
|
|
59
|
+
const authContext = AUTH_URL_CONTEXT_PATTERN.test(`${parsed.hostname}${parsed.pathname}`)
|
|
60
|
+
|| [...parsed.searchParams.keys(), ...(hashParams?.keys() ?? [])].some(shouldRedactQueryParam);
|
|
61
61
|
for (const [name] of parsed.searchParams) {
|
|
62
|
-
if (shouldRedactQueryParam(name)) {
|
|
62
|
+
if (shouldRedactQueryParam(name) || (authContext && AUTH_STATE_QUERY_PARAM_PATTERN.test(name))) {
|
|
63
63
|
parsed.searchParams.set(name, "[REDACTED]");
|
|
64
|
-
mutated = true;
|
|
65
64
|
}
|
|
66
65
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const hashParams = new URLSearchParams(hashText);
|
|
66
|
+
if (hashParams) {
|
|
67
|
+
let hashMutated = false;
|
|
70
68
|
for (const [name] of hashParams) {
|
|
71
|
-
if (shouldRedactQueryParam(name)) {
|
|
69
|
+
if (shouldRedactQueryParam(name) || (authContext && AUTH_STATE_QUERY_PARAM_PATTERN.test(name))) {
|
|
72
70
|
hashParams.set(name, "[REDACTED]");
|
|
73
|
-
|
|
71
|
+
hashMutated = true;
|
|
74
72
|
}
|
|
75
73
|
}
|
|
76
|
-
if (
|
|
74
|
+
if (hashMutated)
|
|
77
75
|
parsed.hash = `#${hashParams.toString()}`;
|
|
78
|
-
}
|
|
79
76
|
}
|
|
80
77
|
return parsed.toString();
|
|
81
78
|
}
|
|
82
79
|
function redactLooseUrlParameterText(text) {
|
|
83
|
-
return text.replace(/([
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return
|
|
80
|
+
return text.replace(/(?<![^\s"'`<>\])}])[^\s"'`<>\])}]*[?#&][^\s"'`<>\])}]*/g, (token) => {
|
|
81
|
+
const queryNames = [...token.matchAll(/[?#&]([^=&#\s"'`<>\])}]+)=/g)].map((match) => {
|
|
82
|
+
try {
|
|
83
|
+
return decodeURIComponent((match[1] ?? "").replace(/\+/g, " "));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return match[1] ?? "";
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
const authContext = AUTH_URL_CONTEXT_PATTERN.test(token) || queryNames.some(shouldRedactQueryParam);
|
|
90
|
+
return token.replace(/([?#&])([^=&#\s"'`<>\])}]+)=([^&#\s"'`<>\])}]*)/g, (match, separator, rawName, rawValue) => {
|
|
91
|
+
if (rawValue === "[REDACTED" || rawValue === "[REDACTED]" || /%5Bredacted%5D/i.test(rawValue))
|
|
92
|
+
return match;
|
|
93
|
+
let name = rawName;
|
|
94
|
+
try {
|
|
95
|
+
name = decodeURIComponent(rawName.replace(/\+/g, " "));
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Keep the raw name when percent decoding fails.
|
|
99
|
+
}
|
|
100
|
+
if (!shouldRedactQueryParam(name) && !(authContext && AUTH_STATE_QUERY_PARAM_PATTERN.test(name)))
|
|
101
|
+
return match;
|
|
102
|
+
return `${separator}${rawName}=[REDACTED]`;
|
|
103
|
+
});
|
|
94
104
|
});
|
|
95
105
|
}
|
|
96
106
|
function redactLooseUrlUserinfo(text) {
|
|
@@ -226,7 +236,7 @@ function redactEnvSecretAssignments(text) {
|
|
|
226
236
|
});
|
|
227
237
|
}
|
|
228
238
|
export function redactSensitiveText(text) {
|
|
229
|
-
return redactEmbeddedStructuredText(redactEnvSecretAssignments(redactStandaloneBasicCredential(redactBearerCredentials(redactLooseUrlUserinfo(redactLooseUrlMatches(
|
|
239
|
+
return redactEmbeddedStructuredText(redactEnvSecretAssignments(redactStandaloneBasicCredential(redactBearerCredentials(redactLooseUrlParameterText(redactLooseUrlUserinfo(redactLooseUrlMatches(text))))
|
|
230
240
|
.replace(/\b(Authorization\s*:\s*Basic)\s+[^\s",]+/gi, "$1 [REDACTED]")
|
|
231
241
|
.replace(/\b(Cookie|Set-Cookie)\s*:\s*[^\n\r"]+/gi, "$1: [REDACTED]"))));
|
|
232
242
|
}
|
|
@@ -323,20 +333,6 @@ export function getImplicitSessionIdleTimeoutMs(env = getAgentBrowserProcessEnvi
|
|
|
323
333
|
function countExplicitGlobalFlags(args, targetFlag) {
|
|
324
334
|
return scanUpstreamGlobalFlagOccurrences(args, targetFlag).length;
|
|
325
335
|
}
|
|
326
|
-
function getUnsupportedLeadingIdentityAssignment(args) {
|
|
327
|
-
const commandStartIndex = findCommandStartIndex(args) ?? args.length;
|
|
328
|
-
for (let index = 0; index < commandStartIndex; index += 1) {
|
|
329
|
-
const token = args[index];
|
|
330
|
-
if (token.startsWith("--session="))
|
|
331
|
-
return "--session";
|
|
332
|
-
if (token.startsWith("--namespace="))
|
|
333
|
-
return "--namespace";
|
|
334
|
-
const flag = token.split("=", 1)[0] ?? token;
|
|
335
|
-
if (!token.includes("=") && PREVALIDATED_VALUE_FLAGS.has(flag))
|
|
336
|
-
index += 1;
|
|
337
|
-
}
|
|
338
|
-
return undefined;
|
|
339
|
-
}
|
|
340
336
|
export function getImplicitSessionCloseTimeoutMs(env = getAgentBrowserProcessEnvironment()) {
|
|
341
337
|
return parseTimeoutMs(env[IMPLICIT_SESSION_CLOSE_TIMEOUT_ENV], 0) ?? DEFAULT_IMPLICIT_SESSION_CLOSE_TIMEOUT_MS;
|
|
342
338
|
}
|
|
@@ -611,7 +607,7 @@ function getUnsupportedInlineWaitDownloadError(args) {
|
|
|
611
607
|
return undefined;
|
|
612
608
|
return `agent-browser ${TARGET_AGENT_BROWSER_VERSION} does not support \`wait --download=<path>\`. Pass the optional path as a separate argument: \`wait --download <path>\` (or \`wait -d <path>\`).`;
|
|
613
609
|
}
|
|
614
|
-
export function validateToolArgs(args) {
|
|
610
|
+
export function validateToolArgs(args, options = {}) {
|
|
615
611
|
if (args.length === 0) {
|
|
616
612
|
return "`args` must contain at least one agent-browser command token.";
|
|
617
613
|
}
|
|
@@ -623,27 +619,30 @@ export function validateToolArgs(args) {
|
|
|
623
619
|
if (sessionModeArg) {
|
|
624
620
|
return "Do not pass `--session-mode` in args. Use the top-level agent_browser `sessionMode` field instead, for example { args: [\"--profile\", \"Default\", \"open\", \"https://example.com\"], sessionMode: \"fresh\" }.";
|
|
625
621
|
}
|
|
622
|
+
const inspection = !options.batchStep && isPlainTextInspectionArgs(args);
|
|
623
|
+
const invalidValueFlag = inspection ? undefined : getInvalidValueFlagDetails(args, !options.batchStep);
|
|
624
|
+
if (invalidValueFlag?.reason === "unsupported-assignment")
|
|
625
|
+
return formatInvalidValueFlagError(invalidValueFlag, options.batchStep);
|
|
626
626
|
return getBareMcpValidationError(args) ?? getSingleKeyCommandValidationError(args) ?? getUnsupportedInlineWaitDownloadError(args);
|
|
627
627
|
}
|
|
628
|
-
function getInvalidValueFlagDetails(args) {
|
|
628
|
+
function getInvalidValueFlagDetails(args, allowRestoreAssignment = true) {
|
|
629
629
|
for (let index = 0; index < args.length; index += 1) {
|
|
630
630
|
const token = args[index];
|
|
631
631
|
if (!token.startsWith("-")) {
|
|
632
632
|
continue;
|
|
633
633
|
}
|
|
634
634
|
const normalizedToken = token.split("=", 1)[0] ?? token;
|
|
635
|
-
if (
|
|
636
|
-
|
|
635
|
+
if (token.includes("=")
|
|
636
|
+
&& (PREVALIDATED_VALUE_FLAGS.has(normalizedToken)
|
|
637
|
+
|| GLOBAL_BOOLEAN_FLAGS_WITH_OPTIONAL_VALUES.has(normalizedToken)
|
|
638
|
+
|| (!allowRestoreAssignment && normalizedToken === "--restore"))) {
|
|
639
|
+
return {
|
|
640
|
+
flag: normalizedToken,
|
|
641
|
+
index,
|
|
642
|
+
reason: "unsupported-assignment",
|
|
643
|
+
};
|
|
637
644
|
}
|
|
638
|
-
if (
|
|
639
|
-
const value = token.slice(token.indexOf("=") + 1).trim();
|
|
640
|
-
if (value.length === 0) {
|
|
641
|
-
return {
|
|
642
|
-
flag: normalizedToken,
|
|
643
|
-
index,
|
|
644
|
-
reason: "missing-value",
|
|
645
|
-
};
|
|
646
|
-
}
|
|
645
|
+
if (!PREVALIDATED_VALUE_FLAGS.has(normalizedToken)) {
|
|
647
646
|
continue;
|
|
648
647
|
}
|
|
649
648
|
const receivedToken = args[index + 1];
|
|
@@ -666,7 +665,15 @@ function getInvalidValueFlagDetails(args) {
|
|
|
666
665
|
}
|
|
667
666
|
return undefined;
|
|
668
667
|
}
|
|
669
|
-
function formatInvalidValueFlagError(details) {
|
|
668
|
+
function formatInvalidValueFlagError(details, batchStep = false) {
|
|
669
|
+
if (details.reason === "unsupported-assignment") {
|
|
670
|
+
if (batchStep) {
|
|
671
|
+
return details.flag === "--restore"
|
|
672
|
+
? "Global `--restore=<key>` belongs before `batch` in top-level args, not inside a batch step."
|
|
673
|
+
: `Global \`${details.flag}=<value>\` is not supported inside a batch step. Move \`${details.flag}\` and its value before \`batch\` as separate top-level args.`;
|
|
674
|
+
}
|
|
675
|
+
return `agent-browser ${TARGET_AGENT_BROWSER_VERSION} does not support \`${details.flag}=<value>\`. Pass \`${details.flag}\` and its value as separate arguments.`;
|
|
676
|
+
}
|
|
670
677
|
if (details.reason === "unexpected-flag" && details.receivedToken) {
|
|
671
678
|
return `Flag \`${details.flag}\` requires a value, but received \`${details.receivedToken}\` instead. Pass a non-flag value immediately after \`${details.flag}\`.`;
|
|
672
679
|
}
|
|
@@ -779,7 +786,6 @@ export function getStartupScopedFlags(args) {
|
|
|
779
786
|
}
|
|
780
787
|
export function buildExecutionPlan(args, options) {
|
|
781
788
|
const invalidValueFlag = getInvalidValueFlagDetails(args);
|
|
782
|
-
const unsupportedIdentityAssignment = getUnsupportedLeadingIdentityAssignment(args);
|
|
783
789
|
const explicitNamespacePresent = scanUpstreamGlobalFlagOccurrences(args, "--namespace").length > 0;
|
|
784
790
|
const explicitNamespace = extractExplicitNamespace(args);
|
|
785
791
|
const managedSessionNamespace = canonicalizeAgentBrowserNamespace(options.managedSessionNamespace);
|
|
@@ -811,16 +817,6 @@ export function buildExecutionPlan(args, options) {
|
|
|
811
817
|
validationError: formatInvalidValueFlagError(invalidValueFlag),
|
|
812
818
|
};
|
|
813
819
|
}
|
|
814
|
-
if (unsupportedIdentityAssignment) {
|
|
815
|
-
return {
|
|
816
|
-
commandInfo: {},
|
|
817
|
-
effectiveArgs,
|
|
818
|
-
plainTextInspection: false,
|
|
819
|
-
startupScopedFlags: [],
|
|
820
|
-
usedImplicitSession: false,
|
|
821
|
-
validationError: `${unsupportedIdentityAssignment}=... is not supported by agent-browser ${TARGET_AGENT_BROWSER_VERSION}. Pass ${unsupportedIdentityAssignment} and its value as separate arguments.`,
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
820
|
for (const flag of ["--session", "--namespace"]) {
|
|
825
821
|
if (countExplicitGlobalFlags(args, flag) <= 1)
|
|
826
822
|
continue;
|
|
@@ -834,8 +830,13 @@ export function buildExecutionPlan(args, options) {
|
|
|
834
830
|
};
|
|
835
831
|
}
|
|
836
832
|
const explicitSessionName = extractExplicitSessionName(args);
|
|
837
|
-
if (explicitSessionName && !
|
|
838
|
-
|
|
833
|
+
if (explicitSessionName && !explicitNamespacePresent) {
|
|
834
|
+
const targetsCurrentManagedSession = options.managedSessionActive
|
|
835
|
+
&& getAgentBrowserSessionIdentityKey(explicitSessionName, managedSessionNamespace)
|
|
836
|
+
=== getAgentBrowserSessionIdentityKey(options.managedSessionName, managedSessionNamespace);
|
|
837
|
+
namespace = targetsCurrentManagedSession
|
|
838
|
+
? managedSessionNamespace
|
|
839
|
+
: resolveAgentBrowserNamespace(args, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE);
|
|
839
840
|
}
|
|
840
841
|
const shouldCreateFreshManagedSession = !explicitSessionName && options.sessionMode === "fresh" && commandInfo.command !== undefined && !isCloseCommand(commandInfo.command);
|
|
841
842
|
let argsToAppend = args;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { extractUpstreamCommandTokens } from "./argv-descriptor.js";
|
|
2
2
|
import { getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace } from "./argv-grammar.js";
|
|
3
3
|
import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./batch-lifecycle.js";
|
|
4
|
-
import { isCloseAllCommand, isCloseCommand, isReadOnlyDiagnosticSessionTargetCommand, isRecordPageTransitionCommand, isUnverifiedPageTransitionCommand } from "./command-taxonomy.js";
|
|
4
|
+
import { isCloseAllCommand, isCloseCommand, isReadOnlyDiagnosticSessionTargetCommand, isRecordPageTransitionCommand, isUnverifiedPageTransitionCommand, isWebMcpPageMutationCommand } from "./command-taxonomy.js";
|
|
5
5
|
import { isRecord } from "./parsing.js";
|
|
6
6
|
import { getEditableRefEvidence } from "./results/editable-ref-evidence.js";
|
|
7
7
|
import { enrichSnapshotRefEntries, getSnapshotRefEntries } from "./results/snapshot-refs.js";
|
|
@@ -210,6 +210,14 @@ export function buildPageTransitionRefSnapshotInvalidation(summary) {
|
|
|
210
210
|
summary: summary ?? "A recording command (record start, or record restart with a URL) replaced or navigated the active page and invalidated the prior snapshot. Run snapshot -i before using page-scoped refs.",
|
|
211
211
|
};
|
|
212
212
|
}
|
|
213
|
+
export function getCommandRefSnapshotInvalidation(commandTokens) {
|
|
214
|
+
if (isRecordPageTransitionCommand(commandTokens))
|
|
215
|
+
return buildPageTransitionRefSnapshotInvalidation();
|
|
216
|
+
if (isWebMcpPageMutationCommand(commandTokens)) {
|
|
217
|
+
return buildPageTransitionRefSnapshotInvalidation("A WebMCP invoke, result, or cancel command can mutate, rerender, or navigate the page, so the prior snapshot refs were invalidated. Run snapshot -i before using page-scoped refs.");
|
|
218
|
+
}
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
213
221
|
export function isNoActivePageSnapshotFailure(command, text) {
|
|
214
222
|
return command === "snapshot" && /\bno active page\b/i.test(text ?? "");
|
|
215
223
|
}
|
|
@@ -226,8 +234,9 @@ export function extractLatestRefSnapshotStateFromBatchResults(data) {
|
|
|
226
234
|
latestState = undefined;
|
|
227
235
|
continue;
|
|
228
236
|
}
|
|
229
|
-
|
|
230
|
-
|
|
237
|
+
const transitionInvalidation = getCommandRefSnapshotInvalidation(commandTokens);
|
|
238
|
+
if (transitionInvalidation) {
|
|
239
|
+
latestState = { invalidation: transitionInvalidation };
|
|
231
240
|
continue;
|
|
232
241
|
}
|
|
233
242
|
if (name !== "snapshot")
|
|
@@ -8,7 +8,6 @@ import { processStartIdentitiesMatch, readProcessStartIdentity } from "./process
|
|
|
8
8
|
const TEMP_ROOT_PREFIX = "pi-agent-browser-";
|
|
9
9
|
const TEMP_ROOT_MARKER_FILE_NAME = ".pi-agent-browser-owner.json";
|
|
10
10
|
const TEMP_ROOT_MARKER_KIND = "pi-agent-browser-temp-root";
|
|
11
|
-
const TEMP_ROOT_LEGACY_MARKER_VERSION = 1;
|
|
12
11
|
const TEMP_ROOT_MARKER_VERSION = 2;
|
|
13
12
|
const STALE_TEMP_ROOT_MAX_AGE_MS = 24 * 60 * 60 * 1_000;
|
|
14
13
|
const TEMP_ROOT_MAX_BYTES_ENV = "PI_AGENT_BROWSER_TEMP_ROOT_MAX_BYTES";
|
|
@@ -39,7 +38,7 @@ function isProtectedTempChildName(value) {
|
|
|
39
38
|
function isTempRootOwnershipRecord(value) {
|
|
40
39
|
if (!isRecord(value))
|
|
41
40
|
return false;
|
|
42
|
-
if (value.kind !== TEMP_ROOT_MARKER_KIND ||
|
|
41
|
+
if (value.kind !== TEMP_ROOT_MARKER_KIND || value.version !== TEMP_ROOT_MARKER_VERSION)
|
|
43
42
|
return false;
|
|
44
43
|
if (!isPositiveFiniteNumber(value.createdAtMs))
|
|
45
44
|
return false;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { TARGET_AGENT_BROWSER_VERSION, TARGET_AGENT_BROWSER_VERSION_LABEL } from "../../../scripts/agent-browser-target.mjs";
|
|
2
|
-
export { TARGET_AGENT_BROWSER_VERSION, TARGET_AGENT_BROWSER_VERSION_LABEL };
|
|
1
|
+
import { MINIMUM_AGENT_BROWSER_VERSION, MINIMUM_AGENT_BROWSER_VERSION_LABEL, SUPPORTED_AGENT_BROWSER_VERSION_LABEL, TARGET_AGENT_BROWSER_VERSION, TARGET_AGENT_BROWSER_VERSION_LABEL, isSupportedAgentBrowserVersion, } from "../../../scripts/agent-browser-target.mjs";
|
|
2
|
+
export { MINIMUM_AGENT_BROWSER_VERSION, MINIMUM_AGENT_BROWSER_VERSION_LABEL, SUPPORTED_AGENT_BROWSER_VERSION_LABEL, TARGET_AGENT_BROWSER_VERSION, TARGET_AGENT_BROWSER_VERSION_LABEL, isSupportedAgentBrowserVersion, };
|
|
3
3
|
export function parseAgentBrowserVersionOutput(stdout) {
|
|
4
4
|
const match = stdout.trim().match(/^agent-browser\s+(\S+)$/);
|
|
5
5
|
return match?.[1];
|
|
6
6
|
}
|
|
7
7
|
export function getAgentBrowserVersionValidationError(stdout) {
|
|
8
8
|
const observed = parseAgentBrowserVersionOutput(stdout);
|
|
9
|
-
if (observed
|
|
9
|
+
if (observed && isSupportedAgentBrowserVersion(observed))
|
|
10
10
|
return undefined;
|
|
11
11
|
return observed
|
|
12
|
-
? `Installed agent-browser ${observed}
|
|
13
|
-
: `agent-browser --version returned an unrecognized value; expected
|
|
12
|
+
? `Installed agent-browser ${observed} is unsupported; stable versions must be at least ${MINIMUM_AGENT_BROWSER_VERSION_LABEL}. Install ${TARGET_AGENT_BROWSER_VERSION_LABEL} (recommended), run pi-agent-browser-doctor, then reload Pi.`
|
|
13
|
+
: `agent-browser --version returned an unrecognized value; expected ${SUPPORTED_AGENT_BROWSER_VERSION_LABEL}. Run pi-agent-browser-doctor and install a supported upstream version.`;
|
|
14
14
|
}
|