pi-agent-browser-native 0.6.9 → 0.6.11
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 +48 -0
- package/README.md +62 -19
- package/dist/extensions/agent-browser/index.js +424 -451
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +6 -7
- package/dist/extensions/agent-browser/lib/argv-grammar.js +7 -1
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +4 -8
- package/dist/extensions/agent-browser/lib/command-policy.js +41 -2
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +15 -2
- package/dist/extensions/agent-browser/lib/input-modes/params.js +1 -1
- package/dist/extensions/agent-browser/lib/input-modes/script.js +3 -2
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +42 -12
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +3 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +6 -14
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +14 -25
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +12 -6
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +3 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +38 -31
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +60 -20
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/recording-recovery.js +161 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +5 -5
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +2 -4
- package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +68 -0
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +41 -6
- package/dist/extensions/agent-browser/lib/page-target-validation.js +9 -5
- package/dist/extensions/agent-browser/lib/playbook.js +13 -12
- package/dist/extensions/agent-browser/lib/process-environment.js +26 -8
- package/dist/extensions/agent-browser/lib/process.js +8 -5
- package/dist/extensions/agent-browser/lib/read-confirmation.js +59 -0
- package/dist/extensions/agent-browser/lib/recording-reservations.js +11 -1
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +8 -0
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +6 -5
- package/dist/extensions/agent-browser/lib/results/categories.js +4 -2
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +76 -57
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +19 -8
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +5 -25
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +40 -38
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +1 -0
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +3 -3
- package/dist/extensions/agent-browser/lib/results/presentation.js +38 -9
- package/dist/extensions/agent-browser/lib/results/recording.js +50 -0
- package/dist/extensions/agent-browser/lib/runtime.js +72 -20
- package/dist/extensions/agent-browser/lib/session-page-state.js +24 -8
- package/dist/extensions/agent-browser/lib/temp.js +4 -0
- package/dist/scripts/agent-browser-target.mjs +1 -1
- package/docs/ARCHITECTURE.md +29 -12
- package/docs/COMMAND_REFERENCE.md +67 -31
- package/docs/RELEASE.md +6 -4
- package/docs/SUPPORT_MATRIX.md +24 -16
- package/docs/TOOL_CONTRACT.md +82 -28
- package/package.json +1 -1
- package/scripts/agent-browser-capability-baseline.mjs +10 -3
- package/scripts/agent-browser-target.mjs +1 -1
- package/scripts/prepare.mjs +2 -4
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { isRecord } from "../../parsing.js";
|
|
2
|
-
import {
|
|
2
|
+
import { redactSensitiveText, redactSensitiveValue } from "../../runtime.js";
|
|
3
3
|
import { classifyNetworkRequestFailure, isApiLikeNetworkRequest, isNetworkArtifactNoiseRequest, summarizeNetworkFailures } from "../network.js";
|
|
4
4
|
import { withOptionalSessionArgs } from "../next-actions.js";
|
|
5
5
|
import { stringifyUnknown, truncateText } from "../text.js";
|
|
6
|
-
import { firstLine, formatCount, getArrayField, getStringField,
|
|
6
|
+
import { firstLine, formatCount, getArrayField, getStringField, redactModelFacingText, stringifyModelFacing, } from "./common.js";
|
|
7
7
|
const DIAGNOSTIC_REQUEST_PREVIEW_LIMIT = 40;
|
|
8
8
|
const DIAGNOSTIC_LOG_PREVIEW_LIMIT = 80;
|
|
9
9
|
const NETWORK_BODY_PREVIEW_MAX_CHARS = 280;
|
|
@@ -185,11 +185,11 @@ export function formatDiagnosticSummary(commandInfo, data) {
|
|
|
185
185
|
}
|
|
186
186
|
if (commandInfo.subcommand === "route") {
|
|
187
187
|
const routed = getStringField(data, "routed") ?? getStringField(data, "url") ?? getStringField(data, "pattern");
|
|
188
|
-
return routed ? `Network route: ${
|
|
188
|
+
return routed ? `Network route: ${redactSensitiveText(routed)}` : "Network route configured";
|
|
189
189
|
}
|
|
190
190
|
if (commandInfo.subcommand === "unroute") {
|
|
191
191
|
const unrouted = getStringField(data, "unrouted") ?? getStringField(data, "url") ?? getStringField(data, "pattern");
|
|
192
|
-
return unrouted ? `Network unroute: ${
|
|
192
|
+
return unrouted ? `Network unroute: ${redactSensitiveText(unrouted)}` : "Network route removed";
|
|
193
193
|
}
|
|
194
194
|
if (commandInfo.subcommand === "har") {
|
|
195
195
|
const state = getStringField(data, "state") ?? getStringField(data, "status") ?? commandInfo.subcommand;
|
|
@@ -273,14 +273,33 @@ function formatSessionText(data) {
|
|
|
273
273
|
const metadata = [
|
|
274
274
|
`active=${active ? "true" : "false"}`,
|
|
275
275
|
label ? `label=${redactModelFacingText(label)}` : undefined,
|
|
276
|
-
title ? `title=${
|
|
277
|
-
url ? `url=${
|
|
276
|
+
title ? `title=${redactSensitiveText(title)}` : undefined,
|
|
277
|
+
url ? `url=${redactSensitiveText(url)}` : undefined,
|
|
278
278
|
tabCount,
|
|
279
279
|
].filter(Boolean).join("; ");
|
|
280
280
|
return `${index + 1}. name=${name}${active ? " *active*" : ""}; ${metadata}`;
|
|
281
281
|
})
|
|
282
282
|
.join("\n");
|
|
283
283
|
}
|
|
284
|
+
if (typeof data.active === "boolean") {
|
|
285
|
+
const runtime = data.runtime;
|
|
286
|
+
const browser = isRecord(runtime) && isRecord(runtime.browser) ? runtime.browser : undefined;
|
|
287
|
+
const identity = [
|
|
288
|
+
`Daemon: ${data.active ? "active" : "inactive"}; PID: ${data.pid ?? "unknown"}`,
|
|
289
|
+
`Browser: ${browser?.status ?? "unknown"}; alive: ${browser?.alive ?? "unknown"}; Chrome PID: ${browser?.pid ?? "unknown"}`,
|
|
290
|
+
`Exact profile: ${browser?.userDataDir ?? "unknown"}`,
|
|
291
|
+
`Native browser ownership: ${browser?.ownership ?? "unknown"}; Pi cleanup ownership: ${data.piCleanupOwnership ?? "unknown"}`,
|
|
292
|
+
].join("\n");
|
|
293
|
+
return `${redactModelFacingText(identity)}\n\n${stringifyModelFacing({
|
|
294
|
+
...Object.fromEntries(["session", "namespace", "socketDir", "active", "pid", "version", "runtimeError"].map((key) => [key, data[key]])),
|
|
295
|
+
// Native runtime also includes restore check URLs, text and code; show status/identity only.
|
|
296
|
+
runtime: isRecord(runtime) ? Object.fromEntries([
|
|
297
|
+
"session", "namespace", "socketDir", "backgroundPid", "browserLaunched", "browser", "recording", "capabilities", "pageCount", "engine", "launchHash",
|
|
298
|
+
"compatibilityStatus", "restoreKey", "restoreStatus", "restoreLoadedPath", "restoreValidationPending",
|
|
299
|
+
"restoreSave", "saveStatus", "restoreSavedPath",
|
|
300
|
+
].map((key) => [key, runtime[key]])) : runtime,
|
|
301
|
+
})}`;
|
|
302
|
+
}
|
|
284
303
|
const session = getStringField(data, "session");
|
|
285
304
|
return session ? `Current session: ${redactModelFacingText(session)}` : undefined;
|
|
286
305
|
}
|
|
@@ -315,8 +334,7 @@ function getPreviewCandidate(item, keys) {
|
|
|
315
334
|
function formatNetworkPreviewValue(value, maxChars) {
|
|
316
335
|
if (value === undefined || value === null)
|
|
317
336
|
return undefined;
|
|
318
|
-
const
|
|
319
|
-
const redacted = redactSensitiveValue(previewValue);
|
|
337
|
+
const redacted = redactSensitiveValue(value);
|
|
320
338
|
const raw = typeof redacted === "string" ? redacted : stringifyUnknown(redacted);
|
|
321
339
|
const normalized = raw.replace(/\s+/g, " ").trim();
|
|
322
340
|
if (normalized.length === 0)
|
|
@@ -797,7 +815,7 @@ function valueContainsStorageSecret(value) {
|
|
|
797
815
|
return true;
|
|
798
816
|
}
|
|
799
817
|
catch { }
|
|
800
|
-
if (redactSensitiveText(trimmed) !== trimmed
|
|
818
|
+
if (redactSensitiveText(trimmed) !== trimmed)
|
|
801
819
|
return true;
|
|
802
820
|
try {
|
|
803
821
|
return valueContainsStorageSecret(JSON.parse(trimmed));
|
|
@@ -830,13 +848,13 @@ function formatStorageValue(key, value) {
|
|
|
830
848
|
}
|
|
831
849
|
function redactStorageEntryValue(item) {
|
|
832
850
|
if (!Object.hasOwn(item, "value"))
|
|
833
|
-
return
|
|
851
|
+
return redactSensitiveValue(item);
|
|
834
852
|
const key = getStringField(item, "key") ?? getStringField(item, "name");
|
|
835
853
|
const value = item.value;
|
|
836
854
|
if (shouldRevealStorageValue(key, value))
|
|
837
|
-
return
|
|
855
|
+
return redactSensitiveValue(item);
|
|
838
856
|
return {
|
|
839
|
-
...
|
|
857
|
+
...redactSensitiveValue({ ...item, value: undefined }),
|
|
840
858
|
value: "[REDACTED]",
|
|
841
859
|
valueRedacted: true,
|
|
842
860
|
valueRedactionReason: key && STORAGE_SECRET_KEY_PATTERN.test(key) ? "sensitive-key" : "sensitive-value",
|
|
@@ -846,15 +864,15 @@ function redactStorageData(value) {
|
|
|
846
864
|
if (Array.isArray(value))
|
|
847
865
|
return value.map((item) => redactStorageData(item));
|
|
848
866
|
if (!isRecord(value))
|
|
849
|
-
return
|
|
867
|
+
return redactSensitiveValue(value);
|
|
850
868
|
const entries = Object.fromEntries(Object.entries(value).map(([key, entryValue]) => {
|
|
851
869
|
if ((key === "entries" || key === "items") && Array.isArray(entryValue))
|
|
852
|
-
return [key, entryValue.map((item) => isRecord(item) ? redactStorageEntryValue(item) :
|
|
870
|
+
return [key, entryValue.map((item) => isRecord(item) ? redactStorageEntryValue(item) : redactSensitiveValue(item))];
|
|
853
871
|
if (key === "value") {
|
|
854
872
|
const itemKey = getStringField(value, "key") ?? getStringField(value, "name");
|
|
855
|
-
return [key, shouldRevealStorageValue(itemKey, entryValue) ?
|
|
873
|
+
return [key, shouldRevealStorageValue(itemKey, entryValue) ? redactSensitiveValue(entryValue) : "[REDACTED]"];
|
|
856
874
|
}
|
|
857
|
-
return [key,
|
|
875
|
+
return [key, redactSensitiveValue(entryValue)];
|
|
858
876
|
}));
|
|
859
877
|
if (Object.hasOwn(value, "value")) {
|
|
860
878
|
const key = getStringField(value, "key") ?? getStringField(value, "name");
|
|
@@ -899,7 +917,7 @@ function formatDialogText(data) {
|
|
|
899
917
|
lines.push(`Type: ${redactModelFacingText(type)}`);
|
|
900
918
|
const message = getStringField(data, "message");
|
|
901
919
|
if (message)
|
|
902
|
-
lines.push(`Message: ${
|
|
920
|
+
lines.push(`Message: ${redactModelFacingText(message)}`);
|
|
903
921
|
if (data.accepted === true)
|
|
904
922
|
lines.push("Accepted.");
|
|
905
923
|
if (data.dismissed === true)
|
|
@@ -910,7 +928,7 @@ function formatFrameText(data) {
|
|
|
910
928
|
const frame = getStringField(data, "frame") ?? getStringField(data, "name") ?? getStringField(data, "selector");
|
|
911
929
|
const url = getStringField(data, "url");
|
|
912
930
|
const title = getStringField(data, "title");
|
|
913
|
-
const lines = [frame ? `Frame: ${redactModelFacingText(frame)}` : undefined, title ? `Title: ${redactModelFacingText(title)}` : undefined, url ? `URL: ${
|
|
931
|
+
const lines = [frame ? `Frame: ${redactModelFacingText(frame)}` : undefined, title ? `Title: ${redactModelFacingText(title)}` : undefined, url ? `URL: ${redactSensitiveText(url)}` : undefined].filter(Boolean);
|
|
914
932
|
return lines.length > 0 ? lines.join("\n") : undefined;
|
|
915
933
|
}
|
|
916
934
|
function formatStateText(data, subcommand) {
|
|
@@ -933,10 +951,10 @@ function formatStateText(data, subcommand) {
|
|
|
933
951
|
return states
|
|
934
952
|
.map((item, index) => {
|
|
935
953
|
if (!isRecord(item))
|
|
936
|
-
return `${index + 1}. ${
|
|
954
|
+
return `${index + 1}. ${stringifyModelFacing(item)}`;
|
|
937
955
|
const name = getStringField(item, "name") ?? getStringField(item, "file") ?? getStringField(item, "path") ?? `(state ${index + 1})`;
|
|
938
956
|
const url = getStringField(item, "url");
|
|
939
|
-
return url ? `${index + 1}. ${redactModelFacingText(name)} — ${
|
|
957
|
+
return url ? `${index + 1}. ${redactModelFacingText(name)} — ${redactSensitiveText(url)}` : `${index + 1}. ${redactModelFacingText(name)}`;
|
|
940
958
|
})
|
|
941
959
|
.join("\n");
|
|
942
960
|
}
|
|
@@ -946,23 +964,11 @@ function formatStateText(data, subcommand) {
|
|
|
946
964
|
return "State cleared.";
|
|
947
965
|
return undefined;
|
|
948
966
|
}
|
|
949
|
-
function redactStructuredPresentationValue(value) {
|
|
950
|
-
if (typeof value === "string")
|
|
951
|
-
return redactModelFacingTextIfSensitive(value);
|
|
952
|
-
if (Array.isArray(value))
|
|
953
|
-
return value.map((item) => redactStructuredPresentationValue(item));
|
|
954
|
-
if (!isRecord(value))
|
|
955
|
-
return value;
|
|
956
|
-
return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [
|
|
957
|
-
key,
|
|
958
|
-
isSensitiveFieldName(key) ? "[REDACTED]" : redactStructuredPresentationValue(entryValue),
|
|
959
|
-
]));
|
|
960
|
-
}
|
|
961
967
|
function redactStatefulValues(value, sensitiveKeys) {
|
|
962
968
|
if (Array.isArray(value))
|
|
963
969
|
return value.map((item) => redactStatefulValues(item, sensitiveKeys));
|
|
964
970
|
if (!isRecord(value))
|
|
965
|
-
return
|
|
971
|
+
return redactSensitiveValue(value);
|
|
966
972
|
return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [
|
|
967
973
|
key,
|
|
968
974
|
sensitiveKeys.has(key.toLowerCase()) ? "[REDACTED]" : redactStatefulValues(entryValue, sensitiveKeys),
|
|
@@ -973,13 +979,9 @@ export function redactPresentationData(commandInfo, data) {
|
|
|
973
979
|
return redactStatefulValues(data, new Set(["value"]));
|
|
974
980
|
if (commandInfo.command === "storage")
|
|
975
981
|
return redactStorageData(data);
|
|
976
|
-
if (commandInfo.command === "session" && commandInfo.subcommand === "list")
|
|
977
|
-
return redactStructuredPresentationValue(data);
|
|
978
|
-
if (commandInfo.command === "state" && commandInfo.subcommand === "list")
|
|
979
|
-
return redactStructuredPresentationValue(data);
|
|
980
982
|
if (commandInfo.command === "state" && commandInfo.subcommand === "show")
|
|
981
983
|
return redactStatefulValues(data, new Set(["value"]));
|
|
982
|
-
return
|
|
984
|
+
return redactSensitiveValue(data);
|
|
983
985
|
}
|
|
984
986
|
export function formatDiagnosticText(commandInfo, data) {
|
|
985
987
|
if (commandInfo.command === "session")
|
|
@@ -203,6 +203,7 @@ export function buildErrorPresentation(options) {
|
|
|
203
203
|
overlayBlockedClick: isOverlayBlockedClickError(presentationCommand ?? commandInfo.command, safeErrorText, args ?? commandInfo.commandTokens),
|
|
204
204
|
resultCategory: "failure",
|
|
205
205
|
sessionName,
|
|
206
|
+
subcommand: commandInfo.subcommand,
|
|
206
207
|
}) ?? []),
|
|
207
208
|
];
|
|
208
209
|
return {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isNavigationObservableCommandName, isOpenNavigationCommand, isPageChangeSummaryCommand } from "../../command-taxonomy.js";
|
|
2
2
|
import { isRecord } from "../../parsing.js";
|
|
3
3
|
import { detectConfirmationRequired } from "../confirmation.js";
|
|
4
|
-
import { omitUpstreamLifecycle, redactModelFacingText, stringifyModelFacing } from "./common.js";
|
|
4
|
+
import { firstLine, omitUpstreamLifecycle, redactModelFacingText, stringifyModelFacing } from "./common.js";
|
|
5
5
|
const NAVIGATION_SUMMARY_FIELD = "navigationSummary";
|
|
6
6
|
const GET_RESULT_FIELDS = {
|
|
7
7
|
attr: "value",
|
|
@@ -25,7 +25,7 @@ function getScalarExtractionResult(commandInfo, data) {
|
|
|
25
25
|
if (result === null || result === undefined)
|
|
26
26
|
return "null";
|
|
27
27
|
if (typeof result === "object")
|
|
28
|
-
return JSON.stringify(result);
|
|
28
|
+
return JSON.stringify(result, null, 2);
|
|
29
29
|
return undefined;
|
|
30
30
|
}
|
|
31
31
|
function getExtractionOrigin(data) {
|
|
@@ -52,7 +52,7 @@ export function formatExtractionSummary(commandInfo, data) {
|
|
|
52
52
|
return undefined;
|
|
53
53
|
}
|
|
54
54
|
const safeScalarResult = redactModelFacingText(scalarResult);
|
|
55
|
-
const firstResultLine = safeScalarResult
|
|
55
|
+
const firstResultLine = firstLine(safeScalarResult);
|
|
56
56
|
if (commandInfo.command === "get") {
|
|
57
57
|
return `${formatGetSummaryLabel(commandInfo.subcommand)}: ${firstResultLine}`;
|
|
58
58
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { isRecord } from "../parsing.js";
|
|
2
|
+
import { buildReadConfirmationNextActions, nextReadConfirmation } from "../read-confirmation.js";
|
|
2
3
|
import { extractUpstreamCommandTokens, parseCommandInfo, redactInvocationArgs } from "../runtime.js";
|
|
3
4
|
import { buildAgentBrowserNextActions } from "./action-recommendations.js";
|
|
4
5
|
import { buildAgentBrowserResultCategoryDetails } from "./categories.js";
|
|
5
6
|
import { detectConfirmationRequired } from "./confirmation.js";
|
|
6
7
|
import { buildSnapshotPresentation } from "./snapshot.js";
|
|
7
|
-
import {
|
|
8
|
+
import { redactModelFacingText } from "./presentation/common.js";
|
|
8
9
|
import { applyArtifactManifest, attachInlineImage, buildArtifactVerificationSummary, buildManifestEntriesForFileArtifacts, classifyPresentationSuccessCategory, extractFileArtifacts, extractImagePath, formatArtifactMetadataLines, formatArtifactSummary, formatMissingArtifactFailureText, getSavedFileDetails, hasMissingFileArtifact, isManifestFileArtifact, } from "./presentation/artifacts.js";
|
|
9
10
|
import { buildBatchPresentation, isAgentBrowserBatchResultArray, redactBatchStepErrorData } from "./presentation/batch.js";
|
|
10
11
|
import { getPresentationPaths, isStringArray } from "./presentation/content.js";
|
|
@@ -18,8 +19,7 @@ function sanitizeModelFacingPresentation(presentation) {
|
|
|
18
19
|
presentation.content = presentation.content.map((item) => {
|
|
19
20
|
if (item.type !== "text")
|
|
20
21
|
return item;
|
|
21
|
-
|
|
22
|
-
return parsed === item.text ? item : { ...item, text: stringifyModelFacing(parsed) };
|
|
22
|
+
return { ...item, text: redactModelFacingText(item.text) };
|
|
23
23
|
});
|
|
24
24
|
presentation.summary = redactModelFacingText(presentation.summary);
|
|
25
25
|
return presentation;
|
|
@@ -52,7 +52,10 @@ export async function buildToolPresentation(options) {
|
|
|
52
52
|
const { args, artifactManifest, artifactRequest, commandInfo, compiledSemanticAction, cwd, envelope, errorText, namespace, networkRouteDiagnostics, networkRoutes, persistentArtifactStore, sessionName, } = options;
|
|
53
53
|
const commandInfoWithTokens = commandInfo.commandTokens || !args ? commandInfo : { ...commandInfo, commandTokens: extractUpstreamCommandTokens(args) };
|
|
54
54
|
const presentationCommandInfo = resolvePresentationCommandInfo(commandInfoWithTokens, compiledSemanticAction);
|
|
55
|
-
|
|
55
|
+
const recordingCommand = commandInfo.command === "record";
|
|
56
|
+
const recordingBatch = commandInfo.command === "batch" && isAgentBrowserBatchResultArray(envelope?.data)
|
|
57
|
+
&& envelope.data.some((row) => row.command?.[0] === "record");
|
|
58
|
+
if (errorText && !recordingCommand && !recordingBatch) {
|
|
56
59
|
return buildErrorPresentation({
|
|
57
60
|
args,
|
|
58
61
|
commandInfo,
|
|
@@ -61,11 +64,15 @@ export async function buildToolPresentation(options) {
|
|
|
61
64
|
sessionName,
|
|
62
65
|
});
|
|
63
66
|
}
|
|
64
|
-
|
|
67
|
+
let data = enrichStreamStatusData(commandInfoWithTokens, envelope?.data);
|
|
68
|
+
if (commandInfo.command === "session" && commandInfo.subcommand === "info" && isRecord(data)) {
|
|
69
|
+
data = { ...data, piCleanupOwnership: options.piCleanupOwnership ?? "unknown" };
|
|
70
|
+
}
|
|
71
|
+
const readConfirmation = nextReadConfirmation({ commandTokens: commandInfoWithTokens.commandTokens ?? [], data, namespace, sessionName: sessionName ?? "default", succeeded: envelope?.success !== false });
|
|
65
72
|
const presentationData = commandInfo.command === "batch" && isAgentBrowserBatchResultArray(data)
|
|
66
73
|
? redactBatchSpillData(data)
|
|
67
74
|
: redactPresentationData(commandInfoWithTokens, data);
|
|
68
|
-
const artifacts = await extractFileArtifacts({ artifactManifest, artifactMaxUpdatedAtMs: options.artifactMaxUpdatedAtMs, artifactMinUpdatedAtMs: options.artifactMinUpdatedAtMs, artifactRequest, commandInfo: presentationCommandInfo, cwd, data, namespace, sessionName });
|
|
75
|
+
const artifacts = await extractFileArtifacts({ artifactManifest, artifactMaxUpdatedAtMs: options.artifactMaxUpdatedAtMs, artifactMinUpdatedAtMs: options.artifactMinUpdatedAtMs, artifactRequest, commandInfo: presentationCommandInfo, cwd, data, namespace, recordingOutcome: recordingCommand ? envelope?.success : undefined, recordingPending: options.recordingPending, sessionName });
|
|
69
76
|
const artifactVerification = buildArtifactVerificationSummary(artifacts);
|
|
70
77
|
const artifactSummary = formatArtifactSummary(artifacts);
|
|
71
78
|
const summary = artifactSummary ?? formatPresentationSummary(commandInfoWithTokens, data, compiledSemanticAction);
|
|
@@ -83,6 +90,7 @@ export async function buildToolPresentation(options) {
|
|
|
83
90
|
namespace,
|
|
84
91
|
networkRoutes,
|
|
85
92
|
persistentArtifactStore,
|
|
93
|
+
piCleanupOwnership: options.piCleanupOwnership,
|
|
86
94
|
sessionName,
|
|
87
95
|
summary,
|
|
88
96
|
});
|
|
@@ -99,6 +107,12 @@ export async function buildToolPresentation(options) {
|
|
|
99
107
|
summary,
|
|
100
108
|
};
|
|
101
109
|
}
|
|
110
|
+
if (errorText && (recordingCommand || recordingBatch)) {
|
|
111
|
+
const errorPresentation = buildErrorPresentation({ args, commandInfo, errorText, presentationCommand: presentationCommandInfo.command, sessionName });
|
|
112
|
+
presentation = { ...presentation, resultCategory: "failure", failureCategory: errorPresentation.failureCategory, summary: errorPresentation.summary,
|
|
113
|
+
content: [{ type: "text", text: `${errorPresentation.content[0]?.type === "text" ? errorPresentation.content[0].text : errorText}\n\n${presentation.content[0]?.type === "text" ? presentation.content[0].text : ""}` }],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
102
116
|
if (networkRouteDiagnostics && networkRouteDiagnostics.length > 0 && presentation.content[0]?.type === "text") {
|
|
103
117
|
const diagnosticText = formatNetworkRouteDiagnosticsText(networkRouteDiagnostics);
|
|
104
118
|
if (diagnosticText)
|
|
@@ -141,7 +155,7 @@ export async function buildToolPresentation(options) {
|
|
|
141
155
|
presentationWithManifest.artifactVerification = buildArtifactVerificationSummary(artifacts, presentationWithManifest.artifactManifest, currentSpillPaths) ?? presentationWithManifest.artifactVerification;
|
|
142
156
|
const confirmationRequired = detectConfirmationRequired(data);
|
|
143
157
|
const missingArtifactFailureText = formatMissingArtifactFailureText(presentationWithManifest.artifacts);
|
|
144
|
-
if (missingArtifactFailureText && hasMissingFileArtifact(presentationWithManifest.artifacts)) {
|
|
158
|
+
if (!errorText && missingArtifactFailureText && hasMissingFileArtifact(presentationWithManifest.artifacts)) {
|
|
145
159
|
presentationWithManifest.resultCategory = "failure";
|
|
146
160
|
presentationWithManifest.failureCategory = "artifact-missing";
|
|
147
161
|
presentationWithManifest.successCategory = undefined;
|
|
@@ -153,6 +167,21 @@ export async function buildToolPresentation(options) {
|
|
|
153
167
|
presentationWithManifest.content.unshift({ type: "text", text: missingArtifactFailureText });
|
|
154
168
|
}
|
|
155
169
|
}
|
|
170
|
+
const failedRecording = presentationWithManifest.artifacts?.find((artifact) => artifact.recording?.success === false || artifact.recording?.output.encoderSucceeded === false);
|
|
171
|
+
if (failedRecording && !errorText) {
|
|
172
|
+
const failure = `Recording failed: ${failedRecording.recording?.error ?? "native capture/encoder failure"}`;
|
|
173
|
+
presentationWithManifest.resultCategory = "failure";
|
|
174
|
+
presentationWithManifest.failureCategory = "upstream-error";
|
|
175
|
+
presentationWithManifest.successCategory = undefined;
|
|
176
|
+
presentationWithManifest.summary = failure;
|
|
177
|
+
presentationWithManifest.content.unshift({ type: "text", text: failure });
|
|
178
|
+
}
|
|
179
|
+
if (readConfirmation?.state === "pending") {
|
|
180
|
+
presentationWithManifest.readConfirmation = readConfirmation;
|
|
181
|
+
presentationWithManifest.resultCategory = "failure";
|
|
182
|
+
presentationWithManifest.failureCategory = "confirmation-required";
|
|
183
|
+
presentationWithManifest.successCategory = undefined;
|
|
184
|
+
}
|
|
156
185
|
if (!presentationWithManifest.resultCategory) {
|
|
157
186
|
const categoryDetails = buildAgentBrowserResultCategoryDetails({
|
|
158
187
|
artifacts: presentationWithManifest.artifacts,
|
|
@@ -160,7 +189,7 @@ export async function buildToolPresentation(options) {
|
|
|
160
189
|
confirmationRequired: confirmationRequired !== undefined,
|
|
161
190
|
errorText: envelope?.success === false ? presentationWithManifest.summary : undefined,
|
|
162
191
|
savedFile: presentationWithManifest.savedFile,
|
|
163
|
-
succeeded: envelope?.success !== false,
|
|
192
|
+
succeeded: envelope?.success !== false && confirmationRequired === undefined,
|
|
164
193
|
});
|
|
165
194
|
presentationWithManifest.resultCategory = categoryDetails.resultCategory;
|
|
166
195
|
presentationWithManifest.successCategory = categoryDetails.resultCategory === "success"
|
|
@@ -196,7 +225,7 @@ export async function buildToolPresentation(options) {
|
|
|
196
225
|
? buildNetworkRequestsNextActions(data, sessionName, presentationWithManifest.networkRouteDiagnostics)
|
|
197
226
|
: undefined;
|
|
198
227
|
const streamNextActions = presentationWithManifest.resultCategory === "success" ? buildStreamNextActions(commandInfoWithTokens, data, sessionName) : undefined;
|
|
199
|
-
presentationWithManifest.nextActions = mergeNextActions(presentationWithManifest.nextActions, genericNextActions, networkNextActions, streamNextActions);
|
|
228
|
+
presentationWithManifest.nextActions = readConfirmation ? buildReadConfirmationNextActions(readConfirmation, true) : mergeNextActions(presentationWithManifest.nextActions, genericNextActions, networkNextActions, streamNextActions);
|
|
200
229
|
presentationWithManifest.pageChangeSummary = presentationWithManifest.pageChangeSummary ?? buildPageChangeSummary({
|
|
201
230
|
artifacts: presentationWithManifest.artifacts,
|
|
202
231
|
commandInfo: presentationCommandInfo,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { isRecord } from "../parsing.js";
|
|
2
|
+
const RECORDING_QUALITY_WARNING = "Capture quality warning: repaint-driven capture, held/repeated or static frames, and late/final-state-only frames cannot establish UI smoothness. Output FPS is not captured-frame rate; inspect the recording and capture window before judging motion.";
|
|
3
|
+
function number(value) {
|
|
4
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
|
|
5
|
+
}
|
|
6
|
+
function text(value) {
|
|
7
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
8
|
+
}
|
|
9
|
+
export function getRecordingReceipt(value, outcome) {
|
|
10
|
+
if (!isRecord(value) || typeof value.path !== "string" || !value.path)
|
|
11
|
+
return undefined;
|
|
12
|
+
const capture = isRecord(value.capture) ? value.capture : {};
|
|
13
|
+
const output = isRecord(value.output) ? value.output : {};
|
|
14
|
+
const file = isRecord(value.file) ? value.file : {};
|
|
15
|
+
return {
|
|
16
|
+
warning: RECORDING_QUALITY_WARNING,
|
|
17
|
+
recordingId: text(value.recordingId), path: value.path,
|
|
18
|
+
success: typeof value.success === "boolean" ? value.success : value.success === null ? null : outcome ?? null,
|
|
19
|
+
error: text(value.error), frames: number(value.frames), capturedFrames: number(value.capturedFrames), fps: number(value.fps),
|
|
20
|
+
capture: {
|
|
21
|
+
startedAt: text(capture.startedAt), endedAt: text(capture.endedAt), durationMs: number(capture.durationMs),
|
|
22
|
+
firstFrameAt: text(capture.firstFrameAt), lastFrameAt: text(capture.lastFrameAt), firstFrameAfterMs: number(capture.firstFrameAfterMs), lastFrameAfterMs: number(capture.lastFrameAfterMs),
|
|
23
|
+
averageFps: number(capture.averageFps), maxFrameGapMs: number(capture.maxFrameGapMs), timestampSource: text(capture.timestampSource),
|
|
24
|
+
},
|
|
25
|
+
output: {
|
|
26
|
+
frames: number(output.frames ?? value.frames), fps: number(output.fps ?? value.fps), encodedFrames: number(output.encodedFrames),
|
|
27
|
+
durationMs: number(output.durationMs), durationSource: text(output.durationSource), heldFrames: number(output.heldFrames), droppedFrames: number(output.droppedFrames), skippedFrames: number(output.skippedFrames),
|
|
28
|
+
encoderSucceeded: typeof output.encoderSucceeded === "boolean" ? output.encoderSucceeded : null,
|
|
29
|
+
},
|
|
30
|
+
file: { exists: typeof file.exists === "boolean" ? file.exists : null, sizeBytes: number(file.sizeBytes) },
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function formatRecordingReceipt(receipt) {
|
|
34
|
+
const metric = (value, unit = "") => value === null ? "unknown" : `${value}${unit}`;
|
|
35
|
+
return [
|
|
36
|
+
`Recording ID: ${metric(receipt.recordingId)}`,
|
|
37
|
+
`Native recording outcome: ${receipt.success === true ? "succeeded" : receipt.success === false ? "failed" : "pending/unknown"}${receipt.error ? ` — ${receipt.error}` : ""}`,
|
|
38
|
+
`Capture started: ${metric(receipt.capture.startedAt)}; ended: ${metric(receipt.capture.endedAt)}`,
|
|
39
|
+
`Wall-clock capture duration: ${metric(receipt.capture.durationMs, " ms")}`,
|
|
40
|
+
`Captured frames (received, not pixel-unique): ${metric(receipt.capturedFrames)}`,
|
|
41
|
+
`Captured-frame rate: ${metric(receipt.capture.averageFps, " fps")}`,
|
|
42
|
+
`First frame: ${metric(receipt.capture.firstFrameAt)}; after start: ${metric(receipt.capture.firstFrameAfterMs, " ms")}`,
|
|
43
|
+
`Last frame: ${metric(receipt.capture.lastFrameAt)}; after start: ${metric(receipt.capture.lastFrameAfterMs, " ms")}`,
|
|
44
|
+
`Maximum frame gap: ${metric(receipt.capture.maxFrameGapMs, " ms")}; timestamp source: ${metric(receipt.capture.timestampSource)}`,
|
|
45
|
+
`Written frames: ${metric(receipt.output.frames)}; Encoded frames: ${metric(receipt.output.encodedFrames)}`,
|
|
46
|
+
`Held frames: ${metric(receipt.output.heldFrames)}; dropped frames: ${metric(receipt.output.droppedFrames)}; skipped frames: ${metric(receipt.output.skippedFrames)}`,
|
|
47
|
+
`Nominal/output FPS: ${metric(receipt.output.fps)}; output duration (encoded frames/fps): ${metric(receipt.output.durationMs, " ms")}`,
|
|
48
|
+
receipt.warning,
|
|
49
|
+
].join("\n");
|
|
50
|
+
}
|
|
@@ -20,7 +20,7 @@ const DEFAULT_IMPLICIT_SESSION_IDLE_TIMEOUT_MS = 15 * 60 * 1000;
|
|
|
20
20
|
const DEFAULT_IMPLICIT_SESSION_CLOSE_TIMEOUT_MS = 5_000;
|
|
21
21
|
const INSPECTION_FLAGS = new Set(["--help", "-h", "--version", "-V"]);
|
|
22
22
|
const SENSITIVE_VALUE_FLAGS = new Set(["--body", "--headers", "--password", "--proxy"]);
|
|
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;
|
|
23
|
+
const SENSITIVE_QUERY_PARAM_PATTERN = /^(?:access(?:_|-)?token|api(?:_|-)?key|auth|authorization|authorization(?:_|-)?session(?:_|-)?id|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
24
|
const AUTH_STATE_QUERY_PARAM_PATTERN = /^(?:nonce|state)$/i;
|
|
25
25
|
const AUTH_URL_CONTEXT_PATTERN = /(?:^|[./_-])(?:auth|authorize|callback|login|oauth2?|oidc|saml|sso)(?:[./?#_-]|$)/i;
|
|
26
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;
|
|
@@ -50,6 +50,7 @@ function redactUrlToken(token) {
|
|
|
50
50
|
catch {
|
|
51
51
|
return token;
|
|
52
52
|
}
|
|
53
|
+
const originalHref = parsed.href;
|
|
53
54
|
if (parsed.username.length > 0)
|
|
54
55
|
parsed.username = "[REDACTED]";
|
|
55
56
|
if (parsed.password.length > 0)
|
|
@@ -74,7 +75,7 @@ function redactUrlToken(token) {
|
|
|
74
75
|
if (hashMutated)
|
|
75
76
|
parsed.hash = `#${hashParams.toString()}`;
|
|
76
77
|
}
|
|
77
|
-
return parsed.
|
|
78
|
+
return parsed.href === originalHref ? token : parsed.href;
|
|
78
79
|
}
|
|
79
80
|
function redactLooseUrlParameterText(text) {
|
|
80
81
|
return text.replace(/(?<![^\s"'`<>\])}])[^\s"'`<>\])}]*[?#&][^\s"'`<>\])}]*/g, (token) => {
|
|
@@ -160,6 +161,52 @@ function findBalancedJsonEnd(text, startIndex) {
|
|
|
160
161
|
}
|
|
161
162
|
return undefined;
|
|
162
163
|
}
|
|
164
|
+
function redactSerializedJson(text) {
|
|
165
|
+
// Validate grammar only; rebuilding parsed values loses duplicates and numeric spelling.
|
|
166
|
+
try {
|
|
167
|
+
JSON.parse(text);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
let output = "";
|
|
173
|
+
let cursor = 0;
|
|
174
|
+
const strings = /"(?:\\.|[^"\\])*"/g;
|
|
175
|
+
let match;
|
|
176
|
+
while ((match = strings.exec(text)) !== null) {
|
|
177
|
+
const end = strings.lastIndex;
|
|
178
|
+
const value = JSON.parse(match[0]);
|
|
179
|
+
const redacted = redactSensitiveText(value);
|
|
180
|
+
if (redacted !== value) {
|
|
181
|
+
output += text.slice(cursor, match.index) + JSON.stringify(redacted);
|
|
182
|
+
cursor = end;
|
|
183
|
+
}
|
|
184
|
+
const separator = /^\s*:\s*/.exec(text.slice(end));
|
|
185
|
+
if (!separator || !isSensitiveFieldName(value))
|
|
186
|
+
continue;
|
|
187
|
+
const valueStart = end + separator[0].length;
|
|
188
|
+
let valueEnd = findBalancedJsonEnd(text, valueStart);
|
|
189
|
+
if (valueEnd !== undefined) {
|
|
190
|
+
valueEnd += 1;
|
|
191
|
+
}
|
|
192
|
+
else if (text[valueStart] === '"') {
|
|
193
|
+
strings.lastIndex = valueStart;
|
|
194
|
+
const fieldValue = strings.exec(text);
|
|
195
|
+
valueEnd = strings.lastIndex;
|
|
196
|
+
if (JSON.parse(fieldValue[0]) === "[REDACTED]")
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
valueEnd = valueStart;
|
|
201
|
+
while (valueEnd < text.length && !/[\s,\]}]/.test(text[valueEnd]))
|
|
202
|
+
valueEnd += 1;
|
|
203
|
+
}
|
|
204
|
+
output += text.slice(cursor, valueStart) + '"[REDACTED]"';
|
|
205
|
+
cursor = valueEnd;
|
|
206
|
+
strings.lastIndex = valueEnd;
|
|
207
|
+
}
|
|
208
|
+
return output + text.slice(cursor);
|
|
209
|
+
}
|
|
163
210
|
function redactEmbeddedStructuredText(text) {
|
|
164
211
|
let output = "";
|
|
165
212
|
let cursor = 0;
|
|
@@ -177,15 +224,7 @@ function redactEmbeddedStructuredText(text) {
|
|
|
177
224
|
continue;
|
|
178
225
|
}
|
|
179
226
|
const candidate = text.slice(cursor, endIndex + 1);
|
|
180
|
-
|
|
181
|
-
const parsed = JSON.parse(candidate);
|
|
182
|
-
const redacted = typeof parsed === "string" ? redactSensitiveText(parsed) : JSON.stringify(redactSensitiveValue(parsed));
|
|
183
|
-
const original = typeof parsed === "string" ? parsed : JSON.stringify(parsed);
|
|
184
|
-
output += redacted === original ? candidate : redacted;
|
|
185
|
-
}
|
|
186
|
-
catch {
|
|
187
|
-
output += candidate;
|
|
188
|
-
}
|
|
227
|
+
output += redactSerializedJson(candidate) ?? candidate;
|
|
189
228
|
cursor = endIndex + 1;
|
|
190
229
|
}
|
|
191
230
|
return output;
|
|
@@ -198,21 +237,22 @@ function redactStandaloneBasicCredential(text) {
|
|
|
198
237
|
});
|
|
199
238
|
}
|
|
200
239
|
function credentialTrailingPunctuation(credential) {
|
|
201
|
-
return credential.match(
|
|
202
|
-
}
|
|
203
|
-
function isBearerHelpPlaceholder(label, credential, trailing) {
|
|
204
|
-
return label.toLowerCase() === "authorization bearer" && credential.toLowerCase() === "token" && trailing === ")";
|
|
240
|
+
return credential.match(/[,.:;!?]+$/)?.[0] ?? "";
|
|
205
241
|
}
|
|
206
242
|
function formatRedactedCredential(label, credential, trailing = "") {
|
|
207
243
|
return `${label} [REDACTED]${credentialTrailingPunctuation(credential)}${trailing}`;
|
|
208
244
|
}
|
|
209
245
|
function redactBearerCredentials(text) {
|
|
210
246
|
return text
|
|
211
|
-
.replace(
|
|
247
|
+
.replace(/((?:\b([A-Za-z][A-Za-z0-9_-]*)\s*[:=]\s*|(?:^|\s)(?:-H\s*|--header(?:\s+|=)))["']?Bearer)\s+([^\s"',)\[\]]+)([),.]?)/gi, (match, label, field, credential, trailing) => {
|
|
248
|
+
if (field && !isSensitiveFieldName(field))
|
|
249
|
+
return match;
|
|
212
250
|
return formatRedactedCredential(label, credential, trailing);
|
|
213
251
|
})
|
|
214
|
-
.replace(/\b(
|
|
215
|
-
|
|
252
|
+
.replace(/\b(Bearer)\s+([^\s"',)\[\]]+)([),.]?)/gi, (match, label, credential, trailing) => {
|
|
253
|
+
// Without a credential field/header, require a bearer-token shape, not prose, HTML or a URL.
|
|
254
|
+
const token = credential.slice(0, credential.length - credentialTrailingPunctuation(credential).length);
|
|
255
|
+
if (!/^[A-Za-z0-9._~+/-]+=*$/.test(token) || !/[0-9._~+/=-]/.test(token))
|
|
216
256
|
return match;
|
|
217
257
|
return formatRedactedCredential(label, credential, trailing);
|
|
218
258
|
});
|
|
@@ -236,7 +276,13 @@ function redactEnvSecretAssignments(text) {
|
|
|
236
276
|
});
|
|
237
277
|
}
|
|
238
278
|
export function redactSensitiveText(text) {
|
|
239
|
-
|
|
279
|
+
// Redact JSON string literals before text heuristics can consume their escapes.
|
|
280
|
+
// Non-JSON stays whole so assignments and headers retain their credential context.
|
|
281
|
+
const serialized = redactSerializedJson(text);
|
|
282
|
+
if (serialized !== undefined)
|
|
283
|
+
return serialized;
|
|
284
|
+
const embeddedRedactedText = redactEmbeddedStructuredText(text);
|
|
285
|
+
return redactEmbeddedStructuredText(redactEnvSecretAssignments(redactStandaloneBasicCredential(redactBearerCredentials(redactLooseUrlParameterText(redactLooseUrlUserinfo(redactLooseUrlMatches(embeddedRedactedText))))
|
|
240
286
|
.replace(/\b(Authorization\s*:\s*Basic)\s+[^\s",]+/gi, "$1 [REDACTED]")
|
|
241
287
|
.replace(/\b(Cookie|Set-Cookie)\s*:\s*[^\n\r"]+/gi, "$1: [REDACTED]"))));
|
|
242
288
|
}
|
|
@@ -744,6 +790,8 @@ export function getDefaultHeadlessCompatUserAgent(platform = process.platform) {
|
|
|
744
790
|
return DEFAULT_HEADLESS_COMPAT_USER_AGENT_BY_PLATFORM[platform] ?? FALLBACK_HEADLESS_COMPAT_USER_AGENT;
|
|
745
791
|
}
|
|
746
792
|
export function canUseHeadlessCompatibilityUserAgent(args, env = getAgentBrowserProcessEnvironment()) {
|
|
793
|
+
if (env.AGENT_BROWSER_SESSION !== undefined)
|
|
794
|
+
return false;
|
|
747
795
|
if (hasFlagToken(args, "--user-agent") || hasFlagToken(args, "--args"))
|
|
748
796
|
return false;
|
|
749
797
|
if (hasFlagToken(args, "--cdp") || hasFlagToken(args, "--provider") || hasFlagToken(args, "-p"))
|
|
@@ -798,6 +846,9 @@ export function getStartupScopedFlags(args) {
|
|
|
798
846
|
.filter((flag) => hasLaunchScopedFlagToken(args, flag));
|
|
799
847
|
}
|
|
800
848
|
export function buildExecutionPlan(args, options) {
|
|
849
|
+
const nativeSession = getAgentBrowserProcessEnvironment().AGENT_BROWSER_SESSION;
|
|
850
|
+
if (nativeSession !== undefined && !isPlainTextInspectionArgs(args) && extractExplicitSessionName(args) === undefined)
|
|
851
|
+
args = ["--session", nativeSession, ...args];
|
|
801
852
|
const invalidValueFlag = getInvalidValueFlagDetails(args);
|
|
802
853
|
const explicitNamespacePresent = scanUpstreamGlobalFlagOccurrences(args, "--namespace").length > 0;
|
|
803
854
|
const explicitNamespace = extractExplicitNamespace(args);
|
|
@@ -806,7 +857,7 @@ export function buildExecutionPlan(args, options) {
|
|
|
806
857
|
const plainTextInspection = isPlainTextInspectionArgs(args);
|
|
807
858
|
const argvDescriptor = parseArgvDescriptor(args);
|
|
808
859
|
const commandInfo = argvDescriptor.commandInfo;
|
|
809
|
-
const commandNeedsManagedSession = !plainTextInspection && needsManagedSession(argvDescriptor);
|
|
860
|
+
const commandNeedsManagedSession = !plainTextInspection && !options.browserIndependentReadConfirmation && needsManagedSession(argvDescriptor, options.stdin);
|
|
810
861
|
const effectiveArgs = plainTextInspection ? [...args] : args.includes("--json") ? [] : ["--json"];
|
|
811
862
|
let namespace = explicitNamespacePresent ? explicitNamespace ?? "" : undefined;
|
|
812
863
|
if (plainTextInspection) {
|
|
@@ -902,6 +953,7 @@ export function buildExecutionPlan(args, options) {
|
|
|
902
953
|
namespace = resolveAgentBrowserNamespace(args, getAgentBrowserProcessEnvironment().AGENT_BROWSER_NAMESPACE);
|
|
903
954
|
}
|
|
904
955
|
const targetsActiveManagedSession = options.managedSessionActive
|
|
956
|
+
&& commandNeedsManagedSession
|
|
905
957
|
&& sessionName
|
|
906
958
|
&& getAgentBrowserSessionIdentityKey(sessionName, namespace) === getAgentBrowserSessionIdentityKey(options.managedSessionName, options.managedSessionNamespace);
|
|
907
959
|
if (targetsActiveManagedSession && startupScopedFlags.length > 0 && !isCloseCommand(commandInfo.command) && !validationError) {
|