pi-agent-browser-native 0.9.1 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +35 -8
- package/dist/extensions/agent-browser/index.js +39 -16
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +6 -3
- package/dist/extensions/agent-browser/lib/browser-transcript.js +2 -1
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +4 -0
- package/dist/extensions/agent-browser/lib/native-output.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +2 -8
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +12 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +62 -10
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +18 -12
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +33 -24
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +111 -53
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/screenshot-observation.js +6 -2
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +32 -20
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +37 -32
- package/dist/extensions/agent-browser/lib/orchestration/native-session-defaults.js +30 -11
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +3 -1
- package/dist/extensions/agent-browser/lib/page-target-validation.js +10 -3
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +2 -1
- package/dist/extensions/agent-browser/lib/playbook.js +7 -6
- package/dist/extensions/agent-browser/lib/process-environment.js +12 -3
- package/dist/extensions/agent-browser/lib/process.js +6 -6
- package/dist/extensions/agent-browser/lib/read-confirmation.js +56 -16
- package/dist/extensions/agent-browser/lib/results/confirmation.js +3 -0
- package/dist/extensions/agent-browser/lib/results/envelope.js +5 -1
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +10 -1
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +1 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +34 -1
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +5 -1
- package/dist/extensions/agent-browser/lib/results/presentation.js +15 -2
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +5 -5
- package/dist/extensions/agent-browser/lib/runtime.js +5 -1
- package/dist/extensions/agent-browser/lib/session-page-state.js +35 -16
- package/docs/ARCHITECTURE.md +10 -9
- package/docs/COMMAND_REFERENCE.md +37 -13
- package/docs/RELEASE.md +6 -5
- package/docs/REQUIREMENTS.md +3 -1
- package/docs/SESSION_STORAGE.md +1 -1
- package/docs/SUPPORT_MATRIX.md +12 -3
- package/docs/TOOL_CONTRACT.md +29 -20
- package/package.json +4 -4
|
@@ -2,21 +2,40 @@ import { extractUpstreamCommandTokens } from "./argv-descriptor.js";
|
|
|
2
2
|
import { extractExplicitSessionName, getAgentBrowserSessionIdentityKey, resolveAgentBrowserNamespace, scanUpstreamGlobalFlagOccurrences } from "./argv-grammar.js";
|
|
3
3
|
import { getExplicitReadUrl } from "./command-policy.js";
|
|
4
4
|
import { isCloseCommand } from "./command-taxonomy.js";
|
|
5
|
+
import { getExplicitNavigationTarget } from "./page-target-validation.js";
|
|
5
6
|
import { isRecord } from "./parsing.js";
|
|
7
|
+
import { getUpstreamEffectiveBatchSteps } from "./orchestration/batch-stdin.js";
|
|
6
8
|
export function parseReadConfirmation(value) {
|
|
7
|
-
if (!isRecord(value) || value.source !== "native-explicit-url-read" || (value.state !== "pending" && value.state !== "cleared"))
|
|
9
|
+
if (!isRecord(value) || (value.source !== "native-explicit-url-read" && value.source !== "native-guarded-action") || (value.state !== "pending" && value.state !== "cleared"))
|
|
8
10
|
return undefined;
|
|
9
11
|
if (typeof value.id !== "string" || !value.id || typeof value.sessionName !== "string" || !value.sessionName || (value.namespace !== undefined && typeof value.namespace !== "string"))
|
|
10
12
|
return undefined;
|
|
11
|
-
|
|
13
|
+
if (value.source === "native-guarded-action" && (typeof value.command !== "string" || !value.command || typeof value.action !== "string" || !value.action))
|
|
14
|
+
return undefined;
|
|
15
|
+
return { ...(value.source === "native-explicit-url-read" && isRecord(value.capabilities) && value.capabilities.readRequiresConfirmation === true ? { capabilities: { readRequiresConfirmation: true } } : {}), id: value.id, sessionName: value.sessionName, namespace: value.namespace, source: value.source, state: value.state,
|
|
16
|
+
...(value.source === "native-guarded-action" ? { command: typeof value.command === "string" ? value.command : undefined, action: typeof value.action === "string" ? value.action : undefined } : {}),
|
|
17
|
+
...(value.source === "native-guarded-action" && value.state === "cleared" && value.command === "snapshot" && value.action === "snapshot" && value.refSnapshotFresh === true ? { refSnapshotFresh: true } : {}) };
|
|
18
|
+
}
|
|
19
|
+
export function isBrowserIndependentConfirmation(value) {
|
|
20
|
+
return value?.source === "native-explicit-url-read" && value.capabilities?.readRequiresConfirmation === true;
|
|
21
|
+
}
|
|
22
|
+
export function suppressConfirmationPageHelpers(value) {
|
|
23
|
+
return value?.state === "pending" && (value.source === "native-guarded-action" || isBrowserIndependentConfirmation(value));
|
|
12
24
|
}
|
|
13
|
-
export function
|
|
14
|
-
|
|
25
|
+
export function isSuccessfulNativeConfirmedClose(commandTokens, data) {
|
|
26
|
+
return commandTokens[0] === "confirm" && commandTokens.length === 2 && isRecord(data) && data.confirmed === true
|
|
27
|
+
&& data.action === "close" && isRecord(data.result) && data.result.success === true
|
|
28
|
+
&& isRecord(data.result.data) && data.result.data.closed === true;
|
|
29
|
+
}
|
|
30
|
+
export function findReadConfirmation(args, confirmations, namespace, stdin) {
|
|
31
|
+
const command = extractUpstreamCommandTokens(args);
|
|
32
|
+
const tokens = command[0] === "batch" ? getUpstreamEffectiveBatchSteps(command, stdin)[0] ?? [] : command;
|
|
15
33
|
if (tokens.length !== 2 || !["confirm", "deny"].includes(tokens[0]))
|
|
16
34
|
return undefined;
|
|
17
35
|
const sessionName = extractExplicitSessionName(args);
|
|
18
36
|
const effectiveNamespace = resolveAgentBrowserNamespace(args, namespace);
|
|
19
37
|
const matches = [...confirmations].filter(value => value.state === "pending" && value.id === tokens[1]
|
|
38
|
+
&& (command[0] !== "batch" || value.source === "native-guarded-action")
|
|
20
39
|
&& (sessionName === undefined || getAgentBrowserSessionIdentityKey(sessionName, value.namespace) === getAgentBrowserSessionIdentityKey(value.sessionName, value.namespace))
|
|
21
40
|
&& (effectiveNamespace === undefined || getAgentBrowserSessionIdentityKey(value.sessionName, effectiveNamespace) === getAgentBrowserSessionIdentityKey(value.sessionName, value.namespace)));
|
|
22
41
|
return matches.length === 1 ? matches[0] : undefined;
|
|
@@ -28,32 +47,53 @@ export function scopeReadConfirmationArgs(args, confirmation) {
|
|
|
28
47
|
...args,
|
|
29
48
|
];
|
|
30
49
|
}
|
|
50
|
+
export function getNativeTabContinuationGuidance(commandTokens, data) {
|
|
51
|
+
if (commandTokens[0] !== "tab" || commandTokens[1] !== "new" || !isRecord(data)
|
|
52
|
+
|| data.confirmation_required !== true || typeof data.confirmation_id !== "string" || !data.confirmation_id || data.action !== "tab_new"
|
|
53
|
+
|| !Object.keys(data).every(key => ["confirmation_required", "confirmation_id", "action", "capabilities", "after_confirmation", "guidance"].includes(key))
|
|
54
|
+
|| !Array.isArray(data.after_confirmation) || data.after_confirmation.length !== 2 || data.after_confirmation[0] !== "open"
|
|
55
|
+
|| typeof data.after_confirmation[1] !== "string" || !data.after_confirmation[1]
|
|
56
|
+
|| data.after_confirmation[1] !== getExplicitNavigationTarget(commandTokens)
|
|
57
|
+
|| typeof data.guidance !== "string" || !data.guidance.trim())
|
|
58
|
+
return undefined;
|
|
59
|
+
// Transport prose only: approval creates the tab; the continuation is never stored or executed.
|
|
60
|
+
return data.guidance;
|
|
61
|
+
}
|
|
31
62
|
export function nextReadConfirmation(options) {
|
|
32
63
|
const { commandTokens: tokens, current } = options;
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
64
|
+
const settles = current?.state === "pending" && tokens.length === 2 && ["confirm", "deny"].includes(tokens[0]) && tokens[1] === current.id;
|
|
65
|
+
const confirmed = settles && tokens[0] === "confirm" && isRecord(options.data) && options.data.confirmed === true
|
|
66
|
+
&& options.data.action === (current.source === "native-explicit-url-read" ? "read" : current.action) && isRecord(options.data.result);
|
|
67
|
+
const control = confirmed && isRecord(options.data) && isRecord(options.data.result) ? options.data.result.data : options.data;
|
|
68
|
+
// Only native control fields and a validated tab transport continuation. Page output is never provenance.
|
|
69
|
+
if (options.succeeded && isRecord(control) && !Array.isArray(control) && control.confirmation_required === true
|
|
70
|
+
&& typeof control.confirmation_id === "string" && control.confirmation_id && typeof control.action === "string" && control.action
|
|
71
|
+
&& (Object.keys(control).every(key => ["confirmation_required", "confirmation_id", "action", "capabilities"].includes(key))
|
|
72
|
+
|| getNativeTabContinuationGuidance(tokens, control) !== undefined)) {
|
|
73
|
+
const explicitRead = control.action === "read" && (typeof getExplicitReadUrl(tokens) === "string" || confirmed && current.source === "native-explicit-url-read");
|
|
74
|
+
if (explicitRead)
|
|
75
|
+
return { ...(isRecord(control.capabilities) && control.capabilities.readRequiresConfirmation === true ? { capabilities: { readRequiresConfirmation: true } } : {}), id: control.confirmation_id, namespace: options.namespace, sessionName: options.sessionName, source: "native-explicit-url-read", state: "pending" };
|
|
76
|
+
if (tokens[0] && !["confirm", "deny"].includes(tokens[0]) || confirmed && current.source === "native-guarded-action") {
|
|
77
|
+
return { id: control.confirmation_id, namespace: options.namespace, sessionName: options.sessionName, source: "native-guarded-action", command: confirmed ? current.command : tokens[0], action: control.action, state: "pending" };
|
|
40
78
|
}
|
|
41
79
|
if (current?.state === "pending")
|
|
42
80
|
return { ...current, state: "cleared" };
|
|
43
81
|
}
|
|
44
|
-
return options.succeeded && current?.state === "pending" && (
|
|
82
|
+
return options.succeeded && current?.state === "pending" && (settles || isCloseCommand(tokens[0])) ? { ...current, state: "cleared" } : undefined;
|
|
45
83
|
}
|
|
46
84
|
export function buildReadConfirmationNextActions(confirmation, pendingResponse) {
|
|
47
85
|
if (confirmation.state === "cleared")
|
|
48
86
|
return [];
|
|
49
87
|
const prefix = ["--namespace", confirmation.namespace ?? "", "--session", confirmation.sessionName];
|
|
50
88
|
if (!pendingResponse)
|
|
51
|
-
return [{ id: "inspect-read-confirmation-session", tool: "agent_browser", params: { args: [...prefix, "session", "info"] }, reason: "Inspect the exact native session after the
|
|
89
|
+
return [{ id: "inspect-read-confirmation-session", tool: "agent_browser", params: { args: [...prefix, "session", "info"] }, reason: "Inspect the exact native session after the confirmation failed; rerun the original command if its ID expired.", safety: "Read-only status, without browser launch or tab changes. Do not substitute a different pending confirmation ID." }];
|
|
52
90
|
return ["confirm", "deny"].map(command => ({
|
|
53
91
|
id: command === "confirm" ? "approve-confirmation" : "deny-confirmation", tool: "agent_browser", params: { args: [...prefix, command, confirmation.id] },
|
|
54
|
-
reason: `${command === "confirm" ? "Approve" : "Deny"} the native confirmation for this explicit URL read.`,
|
|
55
|
-
safety: confirmation
|
|
92
|
+
reason: `${command === "confirm" ? "Approve" : "Deny"} the native confirmation for ${confirmation.source === "native-explicit-url-read" ? "this explicit URL read" : `${confirmation.command} (${confirmation.action})`}.`,
|
|
93
|
+
safety: isBrowserIndependentConfirmation(confirmation)
|
|
56
94
|
? "Review the requested read first. The native capability proves ID matching; no DOM confirmation is implied."
|
|
57
|
-
:
|
|
95
|
+
: confirmation.source === "native-guarded-action"
|
|
96
|
+
? "Review the original guarded action. Exact wrapper observation preserves its session and avoids overwriting helpers; native ID validation is not implied."
|
|
97
|
+
: "Native ID matching/browser independence is unproven. The exact native session is preserved, but this confirmation retains normal page checks.",
|
|
58
98
|
}));
|
|
59
99
|
}
|
|
@@ -34,6 +34,9 @@ function getNestedConfirmationRecord(data) {
|
|
|
34
34
|
return undefined;
|
|
35
35
|
}
|
|
36
36
|
export function detectConfirmationRequired(data) {
|
|
37
|
+
// Native confirm can acknowledge one action while its original compound command remains pending.
|
|
38
|
+
if (isRecord(data) && data.confirmed === true && isRecord(data.result) && data.result.success === true)
|
|
39
|
+
data = data.result.data;
|
|
37
40
|
if (!isRecord(data)) {
|
|
38
41
|
return undefined;
|
|
39
42
|
}
|
|
@@ -47,6 +47,10 @@ export async function parseAgentBrowserEnvelope(options) {
|
|
|
47
47
|
catch (error) {
|
|
48
48
|
return { parseError: error instanceof Error ? error.message : String(error) };
|
|
49
49
|
}
|
|
50
|
+
// ponytail: native text has no machine receipts; use JSON mode for structured evidence.
|
|
51
|
+
// JSON-looking page text must never become an envelope, confirmation, or batch row.
|
|
52
|
+
if (typeof options !== "string" && options.textOutput)
|
|
53
|
+
return { envelope: { success: true, data: stdout } };
|
|
50
54
|
const trimmed = stdout.trim();
|
|
51
55
|
const plainText = typeof options !== "string" && options.plainText === true;
|
|
52
56
|
if (trimmed.length === 0 && !plainText) {
|
|
@@ -150,7 +154,7 @@ export function getAgentBrowserErrorText(options) {
|
|
|
150
154
|
if (spawnError)
|
|
151
155
|
return spawnError.message;
|
|
152
156
|
if (parseError)
|
|
153
|
-
return parseError;
|
|
157
|
+
return exitCode !== 0 && stderr.trim() ? stderr.trim() : parseError;
|
|
154
158
|
if (envelope?.success === false) {
|
|
155
159
|
const explicitErrorText = extractEnvelopeErrorText(envelope.error);
|
|
156
160
|
if ((hasStructuredBatchStepFailure(envelope.data) || detectConfirmationRequired(envelope.data)) && explicitErrorText === undefined) {
|
|
@@ -6,6 +6,7 @@ import { getExplicitArtifactDestination } from "../../orchestration/browser-run/
|
|
|
6
6
|
import { isRecord, parsePositiveInteger } from "../../parsing.js";
|
|
7
7
|
import { formatSessionArtifactRetentionSummary, getSessionArtifactManifestEntryKey, isPendingRecordingArtifact, isPendingRecordingCommand, mergeSessionArtifactManifest, } from "../artifact-manifest.js";
|
|
8
8
|
import { classifyAgentBrowserSuccessCategory } from "../categories.js";
|
|
9
|
+
import { LIGHTPANDA_IMAGE_REASON } from "./common.js";
|
|
9
10
|
import { formatRecordingReceipt, getRecordingReceipt } from "../recording.js";
|
|
10
11
|
const PNG_HEADER = Buffer.from("89504e470d0a1a0a0000000d49484452", "hex");
|
|
11
12
|
const INLINE_IMAGE_MAX_BYTES_ENV = "PI_AGENT_BROWSER_INLINE_IMAGE_MAX_BYTES";
|
|
@@ -220,6 +221,9 @@ async function buildPreviousRestartRecordingArtifact(options) {
|
|
|
220
221
|
const recording = getRecordingReceipt(options.data.previousRecording);
|
|
221
222
|
return recording ? buildFileArtifactMetadata({ ...options, commandInfo: { command: "record", subcommand: "restart-previous" }, path: recording.path, recording }) : undefined;
|
|
222
223
|
}
|
|
224
|
+
// A rejected or pending restart has not acknowledged replacement of the active take.
|
|
225
|
+
if (!isRecord(options.data) || options.data.restarted !== true)
|
|
226
|
+
return undefined;
|
|
223
227
|
const sessionKey = options.sessionName ? getAgentBrowserSessionIdentityKey(options.sessionName, options.namespace) : undefined;
|
|
224
228
|
const previousRecording = options.artifactManifest?.entries.find((entry) => (entry.command === "record" &&
|
|
225
229
|
(entry.subcommand === "start" || entry.subcommand === "restart") &&
|
|
@@ -587,11 +591,16 @@ export async function attachInlineImage(presentation, imagePath, modelVisible =
|
|
|
587
591
|
return presentation;
|
|
588
592
|
const dimensions = getImageDimensions(header.toString("base64"), mimeType);
|
|
589
593
|
presentation.imagePath = imagePath;
|
|
594
|
+
const lifecycle = isRecord(presentation.data) && isRecord(presentation.data.lifecycle) ? presentation.data.lifecycle : undefined;
|
|
595
|
+
const textRendered = isRecord(lifecycle?.effectiveLaunch) && lifecycle.effectiveLaunch.engine === "lightpanda";
|
|
590
596
|
presentation.imageObservations = [{ path: imagePath, mimeType,
|
|
597
|
+
...(textRendered ? { rendering: "text" } : {}),
|
|
591
598
|
pixels: dimensions ? { width: dimensions.widthPx, height: dimensions.heightPx } : undefined,
|
|
592
|
-
capture: "unknown", geometry: { status: "unknown", reason: dimensions ? "Capture geometry was not observed." : "Image dimensions could not be read from the bounded header." } }];
|
|
599
|
+
capture: "unknown", geometry: { status: "unknown", reason: textRendered ? LIGHTPANDA_IMAGE_REASON : dimensions ? "Capture geometry was not observed." : "Image dimensions could not be read from the bounded header." } }];
|
|
593
600
|
if (!modelVisible)
|
|
594
601
|
return presentation;
|
|
602
|
+
if (textRendered)
|
|
603
|
+
appendPresentationNotice(presentation, LIGHTPANDA_IMAGE_REASON);
|
|
595
604
|
const inlineImageMaxBytes = getInlineImageMaxBytes();
|
|
596
605
|
if (fileStats.size > inlineImageMaxBytes) {
|
|
597
606
|
appendPresentationNotice(presentation, `Image attachment skipped: ${formatByteCount(fileStats.size)} exceeds the inline limit of ${formatByteCount(inlineImageMaxBytes)}.`);
|
|
@@ -2,6 +2,7 @@ import { isRecord } from "../../parsing.js";
|
|
|
2
2
|
import { redactSensitiveText, redactSensitiveValue } from "../../runtime.js";
|
|
3
3
|
import { stringifyUnknown, truncateText } from "../text.js";
|
|
4
4
|
const UNTITLED_PAGE_SUMMARY = "(untitled page)";
|
|
5
|
+
export const LIGHTPANDA_IMAGE_REASON = "Lightpanda screenshot is a text-rendered page representation, not a graphical capture; CSS/mouse coordinate mapping is unknown.";
|
|
5
6
|
export function stringifyModelFacing(value) {
|
|
6
7
|
return stringifyUnknown(redactSensitiveValue(value));
|
|
7
8
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isRecord } from "../../parsing.js";
|
|
2
|
+
import { getUpstreamEffectiveBatchSteps } from "../../orchestration/batch-stdin.js";
|
|
2
3
|
import { redactSensitiveText, redactSensitiveValue } from "../../runtime.js";
|
|
3
4
|
import { classifyNetworkRequestFailure, isApiLikeNetworkRequest, isNetworkArtifactNoiseRequest, summarizeNetworkFailures } from "../network.js";
|
|
4
5
|
import { withOptionalSessionArgs } from "../next-actions.js";
|
|
@@ -866,6 +867,8 @@ function redactStorageData(value) {
|
|
|
866
867
|
if (!isRecord(value))
|
|
867
868
|
return redactSensitiveValue(value);
|
|
868
869
|
const entries = Object.fromEntries(Object.entries(value).map(([key, entryValue]) => {
|
|
870
|
+
if (key === "data" && isRecord(entryValue))
|
|
871
|
+
return [key, Object.fromEntries(Object.entries(entryValue).map(([storageKey, storageValue]) => [storageKey, shouldRevealStorageValue(storageKey, storageValue) ? redactSensitiveValue(storageValue) : "[REDACTED]"]))];
|
|
869
872
|
if ((key === "entries" || key === "items") && Array.isArray(entryValue))
|
|
870
873
|
return [key, entryValue.map((item) => isRecord(item) ? redactStorageEntryValue(item) : redactSensitiveValue(item))];
|
|
871
874
|
if (key === "value") {
|
|
@@ -885,6 +888,9 @@ function redactStorageData(value) {
|
|
|
885
888
|
}
|
|
886
889
|
function formatStorageText(data) {
|
|
887
890
|
const type = getStringField(data, "type") ?? getStringField(data, "storage") ?? "storage";
|
|
891
|
+
if (isRecord(data.data)) {
|
|
892
|
+
return Object.entries(data.data).map(([key, value]) => `${redactModelFacingText(key)}: ${formatStorageValue(key, value)}`).join("\n") || `${type}: no entries.`;
|
|
893
|
+
}
|
|
888
894
|
const entries = getArrayField(data, "entries") ?? getArrayField(data, "items");
|
|
889
895
|
if (entries) {
|
|
890
896
|
if (entries.length === 0)
|
|
@@ -974,7 +980,34 @@ function redactStatefulValues(value, sensitiveKeys) {
|
|
|
974
980
|
sensitiveKeys.has(key.toLowerCase()) ? "[REDACTED]" : redactStatefulValues(entryValue, sensitiveKeys),
|
|
975
981
|
]));
|
|
976
982
|
}
|
|
977
|
-
|
|
983
|
+
function redactNativeContextText(commandInfo, text, stdin) {
|
|
984
|
+
if (commandInfo.command === "batch") {
|
|
985
|
+
const steps = getUpstreamEffectiveBatchSteps(commandInfo.commandTokens ?? ["batch"], stdin);
|
|
986
|
+
if (steps.some(step => step[0] === "cookies"))
|
|
987
|
+
text = redactNativeContextText({ command: "cookies" }, text);
|
|
988
|
+
if (steps.some(step => step[0] === "storage"))
|
|
989
|
+
text = redactNativeContextText({ command: "storage" }, text);
|
|
990
|
+
return text;
|
|
991
|
+
}
|
|
992
|
+
if (commandInfo.command === "cookies")
|
|
993
|
+
return text.replace(/^([^=\r\n]*)=([^\r\n]*)/gm, "$1=[REDACTED]");
|
|
994
|
+
if (commandInfo.command !== "storage")
|
|
995
|
+
return text;
|
|
996
|
+
const tokens = commandInfo.commandTokens;
|
|
997
|
+
const operation = tokens?.[2];
|
|
998
|
+
const key = operation === "get" ? tokens?.[3] : operation === "set" || operation === "clear" ? undefined : operation;
|
|
999
|
+
if (key !== undefined && text.startsWith(`${key}: `)) {
|
|
1000
|
+
const suffix = text.endsWith("\n") ? "\n" : "";
|
|
1001
|
+
const value = text.slice(key.length + 2, suffix ? -1 : undefined);
|
|
1002
|
+
return `${key}: ${formatStorageValue(key, value)}${suffix}`;
|
|
1003
|
+
}
|
|
1004
|
+
// ponytail: native all-entry/raw-batch text does not escape multiline values.
|
|
1005
|
+
// Format heuristics cannot separate lookalike entries; use structured JSON for sensitive storage.
|
|
1006
|
+
return text.replace(/^([^\r\n]*?): ([^\r\n]*)/gm, (_line, key, value) => `${key}: ${formatStorageValue(key, value)}`);
|
|
1007
|
+
}
|
|
1008
|
+
export function redactPresentationData(commandInfo, data, stdin) {
|
|
1009
|
+
if (typeof data === "string")
|
|
1010
|
+
return redactSensitiveText(redactNativeContextText(commandInfo, data, stdin));
|
|
978
1011
|
if (commandInfo.command === "cookies")
|
|
979
1012
|
return redactStatefulValues(data, new Set(["value"]));
|
|
980
1013
|
if (commandInfo.command === "storage")
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isRecord } from "../../parsing.js";
|
|
2
|
+
import { getNativeTabContinuationGuidance } from "../../read-confirmation.js";
|
|
2
3
|
import { detectConfirmationRequired } from "../confirmation.js";
|
|
3
4
|
import { formatRawSnapshotText, formatSnapshotSummary } from "../snapshot.js";
|
|
4
5
|
import { getPageSummary, omitUpstreamLifecycle, redactModelFacingText, stringifyModelFacing } from "./common.js";
|
|
@@ -225,7 +226,10 @@ export function formatPresentationSummary(commandInfo, data, compiledSemanticAct
|
|
|
225
226
|
export function formatPresentationContentText(commandInfo, data, compiledSemanticAction) {
|
|
226
227
|
const confirmationRequired = detectConfirmationRequired(data);
|
|
227
228
|
if (confirmationRequired)
|
|
228
|
-
return
|
|
229
|
+
return [
|
|
230
|
+
formatConfirmationRequiredText(confirmationRequired),
|
|
231
|
+
getNativeTabContinuationGuidance(commandInfo.commandTokens ?? [], data),
|
|
232
|
+
].filter(Boolean).join("\n\n");
|
|
229
233
|
const presenterText = commandInfo.command ? COMMAND_PRESENTERS[commandInfo.command]?.text?.(commandInfo, data) : undefined;
|
|
230
234
|
if (presenterText)
|
|
231
235
|
return presenterText;
|
|
@@ -53,6 +53,19 @@ export async function buildToolPresentation(options) {
|
|
|
53
53
|
const { args, artifactManifest, artifactRequest, commandInfo, compiledSemanticAction, cwd, envelope, errorText, namespace, networkRouteDiagnostics, networkRoutes, persistentArtifactStore, sessionName, } = options;
|
|
54
54
|
const commandInfoWithTokens = commandInfo.commandTokens || !args ? commandInfo : { ...commandInfo, commandTokens: extractUpstreamCommandTokens(args) };
|
|
55
55
|
const presentationCommandInfo = resolvePresentationCommandInfo(commandInfoWithTokens, compiledSemanticAction);
|
|
56
|
+
if (options.textOutput) {
|
|
57
|
+
const text = String(redactPresentationData(commandInfoWithTokens, typeof envelope?.data === "string" ? envelope.data : "", options.stdin));
|
|
58
|
+
const failure = errorText ? buildErrorPresentation({ args, commandInfo, errorText, sessionName }) : undefined;
|
|
59
|
+
const presentation = {
|
|
60
|
+
...failure,
|
|
61
|
+
content: options.modelVisible === false ? [] : [{ type: "text", text: [errorText, text].filter(Boolean).join("\n\n") }],
|
|
62
|
+
data: text,
|
|
63
|
+
summary: failure?.summary ?? (text.split("\n", 1)[0] || "Native text command completed."),
|
|
64
|
+
};
|
|
65
|
+
return sanitizeModelFacingPresentation(options.modelVisible === false ? presentation : await compactLargePresentationOutput({
|
|
66
|
+
artifactManifest, commandInfo, data: presentation.data, persistentArtifactStore, presentation,
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
56
69
|
const recordingCommand = commandInfo.command === "record";
|
|
57
70
|
const recordingBatch = commandInfo.command === "batch" && isAgentBrowserBatchResultArray(envelope?.data)
|
|
58
71
|
&& envelope.data.some((row) => row.command?.[0] === "record");
|
|
@@ -66,6 +79,7 @@ export async function buildToolPresentation(options) {
|
|
|
66
79
|
data = { ...data, piCleanupOwnership: options.piCleanupOwnership ?? "unknown" };
|
|
67
80
|
}
|
|
68
81
|
const readConfirmation = nextReadConfirmation({ commandTokens: commandInfoWithTokens.commandTokens ?? [], data, namespace, sessionName: sessionName ?? "default", succeeded: envelope?.success !== false });
|
|
82
|
+
const confirmationRequired = detectConfirmationRequired(data);
|
|
69
83
|
const presentationData = commandInfo.command === "batch" && isAgentBrowserBatchResultArray(data)
|
|
70
84
|
? redactBatchSpillData(data)
|
|
71
85
|
: redactPresentationData(commandInfoWithTokens, data);
|
|
@@ -93,7 +107,7 @@ export async function buildToolPresentation(options) {
|
|
|
93
107
|
summary,
|
|
94
108
|
});
|
|
95
109
|
}
|
|
96
|
-
else if (options.modelVisible !== false && commandInfo.command === "snapshot" && isRecord(data)) {
|
|
110
|
+
else if (options.modelVisible !== false && commandInfo.command === "snapshot" && isRecord(data) && !confirmationRequired) {
|
|
97
111
|
presentation = await buildSnapshotPresentation(data, persistentArtifactStore, artifactManifest);
|
|
98
112
|
}
|
|
99
113
|
else {
|
|
@@ -160,7 +174,6 @@ export async function buildToolPresentation(options) {
|
|
|
160
174
|
secondaryPaths: presentationWithManifest.fullOutputPaths,
|
|
161
175
|
}));
|
|
162
176
|
presentationWithManifest.artifactVerification = buildArtifactVerificationSummary(artifacts, presentationWithManifest.artifactManifest, currentSpillPaths) ?? presentationWithManifest.artifactVerification;
|
|
163
|
-
const confirmationRequired = detectConfirmationRequired(data);
|
|
164
177
|
const missingArtifactFailureText = formatMissingArtifactFailureText(presentationWithManifest.artifacts);
|
|
165
178
|
if (!errorText && missingArtifactFailureText && hasMissingFileArtifact(presentationWithManifest.artifacts)) {
|
|
166
179
|
presentationWithManifest.resultCategory = "failure";
|
|
@@ -74,8 +74,8 @@ function getFindVisibleRefFallbackTarget(args, options = {}) {
|
|
|
74
74
|
export function getVisibleRefFallbackTarget(options) {
|
|
75
75
|
return getFindVisibleRefFallbackTarget(options.commandTokens, { allowLeadingDashFillText: true }) ?? (options.compiledSemanticAction ? getFindVisibleRefFallbackTarget(options.compiledSemanticAction.args, { allowLeadingDashFillText: true }) : undefined);
|
|
76
76
|
}
|
|
77
|
-
function getVisibleRefFallbackCandidates(target, snapshotData) {
|
|
78
|
-
const refs = getSnapshotRefRecord(snapshotData);
|
|
77
|
+
function getVisibleRefFallbackCandidates(target, snapshotData, refSnapshot) {
|
|
78
|
+
const refs = refSnapshot?.refs ?? getSnapshotRefRecord(snapshotData);
|
|
79
79
|
if (!refs)
|
|
80
80
|
return [];
|
|
81
81
|
const snapshotLineByRef = getSnapshotLineTextByRef(snapshotData);
|
|
@@ -85,7 +85,7 @@ function getVisibleRefFallbackCandidates(target, snapshotData) {
|
|
|
85
85
|
if (!/^e\d+$/.test(ref) || !isRecord(entry))
|
|
86
86
|
return [];
|
|
87
87
|
const snapshotLine = snapshotLineByRef.get(ref);
|
|
88
|
-
const editableEvidence = getEditableRefEvidence({ ref: entry, text: snapshotLine });
|
|
88
|
+
const editableEvidence = refSnapshot ? refSnapshot.refs?.[ref]?.isEditable : getEditableRefEvidence({ ref: entry, text: snapshotLine });
|
|
89
89
|
const role = getSnapshotRefRole(entry, editableEvidence);
|
|
90
90
|
const name = typeof entry.name === "string" ? entry.name : undefined;
|
|
91
91
|
if (!role || !name || !roleOrder.includes(role.toLowerCase()) || normalizeSemanticActionAccessibleName(name) !== targetName)
|
|
@@ -132,7 +132,7 @@ export function resolveVisibleRefActionFromSnapshot(options) {
|
|
|
132
132
|
const target = getFindVisibleRefFallbackTarget(options.compiledAction.args, { allowLeadingDashFillText: true });
|
|
133
133
|
if (!target)
|
|
134
134
|
return undefined;
|
|
135
|
-
const snapshot = extractRefSnapshotFromData(options.snapshotData);
|
|
135
|
+
const snapshot = options.refSnapshot ?? extractRefSnapshotFromData(options.snapshotData);
|
|
136
136
|
if (!snapshot)
|
|
137
137
|
return undefined;
|
|
138
138
|
const selectOptionValues = options.compiledAction.values && options.compiledAction.values.length > 0
|
|
@@ -141,7 +141,7 @@ export function resolveVisibleRefActionFromSnapshot(options) {
|
|
|
141
141
|
const effectiveTarget = target.action === "select" && selectOptionValues
|
|
142
142
|
? { ...target, optionValues: selectOptionValues }
|
|
143
143
|
: target;
|
|
144
|
-
const candidates = getVisibleRefFallbackCandidates(effectiveTarget, options.snapshotData);
|
|
144
|
+
const candidates = getVisibleRefFallbackCandidates(effectiveTarget, options.snapshotData, options.refSnapshot);
|
|
145
145
|
if (effectiveTarget.action === "fill") {
|
|
146
146
|
if (!options.allowFill || candidates.length !== 1 || effectiveTarget.text === undefined)
|
|
147
147
|
return undefined;
|
|
@@ -157,6 +157,10 @@ function createBalancedJsonEndFinder(text) {
|
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
159
|
function redactSerializedJson(text) {
|
|
160
|
+
if (text === "[REDACTED]")
|
|
161
|
+
return undefined;
|
|
162
|
+
if (!/^\s*[\[{"\d\-tfn]/.test(text))
|
|
163
|
+
return undefined;
|
|
160
164
|
// Validate grammar only; rebuilding parsed values loses duplicates and numeric spelling.
|
|
161
165
|
try {
|
|
162
166
|
JSON.parse(text);
|
|
@@ -878,7 +882,7 @@ export function buildExecutionPlan(args, options) {
|
|
|
878
882
|
const argvDescriptor = parseArgvDescriptor(args);
|
|
879
883
|
const commandInfo = argvDescriptor.commandInfo;
|
|
880
884
|
const commandNeedsManagedSession = !plainTextInspection && !options.browserIndependentReadConfirmation && needsManagedSession(argvDescriptor, options.stdin);
|
|
881
|
-
const effectiveArgs = plainTextInspection ? [...args] : args
|
|
885
|
+
const effectiveArgs = plainTextInspection ? [...args] : getBooleanFlagValue(args, "--json") !== undefined ? [] : ["--json"];
|
|
882
886
|
let namespace = explicitNamespacePresent ? explicitNamespace ?? "" : undefined;
|
|
883
887
|
if (plainTextInspection) {
|
|
884
888
|
return {
|
|
@@ -4,7 +4,8 @@ import { getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNa
|
|
|
4
4
|
import { getBrowserRecord, snapshotFromDefinition } from "./browser-transcript.js";
|
|
5
5
|
import { isCloseCommand, isOpenNavigationCommand, isReadOnlyDiagnosticSessionTargetCommand, isRecordPageTransitionCommand, isUnverifiedPageTransitionCommand, isWebMcpPageMutationCommand, isWindowOrDiffPageTransitionCommand } from "./command-taxonomy.js";
|
|
6
6
|
import { isRecord } from "./parsing.js";
|
|
7
|
-
import {
|
|
7
|
+
import { detectConfirmationRequired } from "./results/confirmation.js";
|
|
8
|
+
import { findReadConfirmation as findPendingReadConfirmation, isSuccessfulNativeConfirmedClose, parseReadConfirmation } from "./read-confirmation.js";
|
|
8
9
|
import { getEditableRefEvidence } from "./results/editable-ref-evidence.js";
|
|
9
10
|
import { enrichSnapshotRefEntries, getFullSnapshotData, getSnapshotRefEntries } from "./results/snapshot-refs.js";
|
|
10
11
|
import { parseSnapshotLines } from "./results/snapshot-segments.js";
|
|
@@ -81,6 +82,10 @@ function extractBatchResultCommand(item) {
|
|
|
81
82
|
}
|
|
82
83
|
export function extractSessionTabTargetFromCommandData(commandTokens, data) {
|
|
83
84
|
const [command, subcommand] = commandTokens;
|
|
85
|
+
if (command === "confirm" && isRecord(data) && data.confirmed === true && ["navigate", "url"].includes(String(data.action))
|
|
86
|
+
&& isRecord(data.result) && data.result.success === true && !detectConfirmationRequired(data)) {
|
|
87
|
+
return extractSessionTabTargetFromData(data.result.data);
|
|
88
|
+
}
|
|
84
89
|
if (command === "get" && subcommand === "url") {
|
|
85
90
|
return normalizeSessionTabTarget({ url: extractStringResultField(data, "url") ?? extractStringResultField(data, "result") });
|
|
86
91
|
}
|
|
@@ -93,7 +98,7 @@ export function extractSessionTabTargetFromBatchResults(data) {
|
|
|
93
98
|
let currentTarget;
|
|
94
99
|
let pendingTitle;
|
|
95
100
|
for (const item of data) {
|
|
96
|
-
if (!isRecord(item))
|
|
101
|
+
if (!isRecord(item) || detectConfirmationRequired(item.result))
|
|
97
102
|
continue;
|
|
98
103
|
// Only this row's native identity can describe the active tab after it ran.
|
|
99
104
|
if (currentTarget?.targetId)
|
|
@@ -108,7 +113,7 @@ export function extractSessionTabTargetFromBatchResults(data) {
|
|
|
108
113
|
if (item.success === false)
|
|
109
114
|
continue;
|
|
110
115
|
const result = item.result;
|
|
111
|
-
if (isCloseCommand(name)) {
|
|
116
|
+
if (isCloseCommand(name) || isSuccessfulNativeConfirmedClose(commandTokens, result)) {
|
|
112
117
|
currentTarget = undefined;
|
|
113
118
|
pendingTitle = undefined;
|
|
114
119
|
continue;
|
|
@@ -138,9 +143,7 @@ export function deriveSessionTabTarget(options) {
|
|
|
138
143
|
if (isCloseCommand(options.command)) {
|
|
139
144
|
return undefined;
|
|
140
145
|
}
|
|
141
|
-
const commandDataTarget =
|
|
142
|
-
? undefined
|
|
143
|
-
: extractSessionTabTargetFromData(options.data);
|
|
146
|
+
const commandDataTarget = extractSessionTabTargetFromCommandData([options.command, options.subcommand].filter((token) => token !== undefined), options.data);
|
|
144
147
|
const observedTarget = normalizeSessionTabTarget(options.navigationSummary)
|
|
145
148
|
?? extractSessionTabTargetFromBatchResults(options.data)
|
|
146
149
|
?? commandDataTarget;
|
|
@@ -163,6 +166,8 @@ function extractRefSnapshotRefs(data) {
|
|
|
163
166
|
return Object.keys(refs).length > 0 ? refs : undefined;
|
|
164
167
|
}
|
|
165
168
|
export function extractRefSnapshotFromData(value) {
|
|
169
|
+
if (detectConfirmationRequired(value))
|
|
170
|
+
return undefined;
|
|
166
171
|
const data = getFullSnapshotData(value);
|
|
167
172
|
if (!data)
|
|
168
173
|
return undefined;
|
|
@@ -214,7 +219,7 @@ export function extractLatestRefSnapshotStateFromBatchResults(data) {
|
|
|
214
219
|
continue;
|
|
215
220
|
const commandTokens = extractUpstreamCommandTokens(extractBatchResultCommand(item));
|
|
216
221
|
const [name] = commandTokens;
|
|
217
|
-
if (item.success !== false && isCloseCommand(name)) {
|
|
222
|
+
if (item.success !== false && !detectConfirmationRequired(item.result) && (isCloseCommand(name) || isSuccessfulNativeConfirmedClose(commandTokens, item.result))) {
|
|
218
223
|
latestState = undefined;
|
|
219
224
|
continue;
|
|
220
225
|
}
|
|
@@ -258,6 +263,7 @@ export function getSessionPageStateKey(sessionName, namespace) {
|
|
|
258
263
|
return sessionName ? getAgentBrowserSessionIdentityKey(sessionName, namespace) : undefined;
|
|
259
264
|
}
|
|
260
265
|
export class SessionPageState {
|
|
266
|
+
confirmActions = new Map();
|
|
261
267
|
readConfirmations = new Map();
|
|
262
268
|
refSnapshotInvalidations = new Map();
|
|
263
269
|
refSnapshots = new Map();
|
|
@@ -281,6 +287,7 @@ export class SessionPageState {
|
|
|
281
287
|
/** A command operates on its admitted state while the committed view is unavailable. */
|
|
282
288
|
fork() {
|
|
283
289
|
const state = new SessionPageState();
|
|
290
|
+
state.confirmActions = new Map(this.confirmActions);
|
|
284
291
|
state.readConfirmations = new Map(this.readConfirmations);
|
|
285
292
|
state.refSnapshotInvalidations = new Map(this.refSnapshotInvalidations);
|
|
286
293
|
state.refSnapshots = new Map(this.refSnapshots);
|
|
@@ -293,7 +300,7 @@ export class SessionPageState {
|
|
|
293
300
|
return state;
|
|
294
301
|
}
|
|
295
302
|
views() {
|
|
296
|
-
return new Map([...new Set([...this.tabTargets.keys(), ...this.tabTargetUnknownOrders.keys(), ...this.refSnapshots.keys(), ...this.refSnapshotInvalidations.keys()])].map(key => [key, this.get(key)]));
|
|
303
|
+
return new Map([...new Set([...this.confirmActions.keys(), ...this.tabTargets.keys(), ...this.tabTargetUnknownOrders.keys(), ...this.refSnapshots.keys(), ...this.refSnapshotInvalidations.keys()])].map(key => [key, this.get(key)]));
|
|
297
304
|
}
|
|
298
305
|
/** The same reducer commits live observations and replays selected journal envelopes. */
|
|
299
306
|
applyBrowserRecord({ event, snapshot }) {
|
|
@@ -301,12 +308,6 @@ export class SessionPageState {
|
|
|
301
308
|
const confirmation = parseReadConfirmation(event.state.readConfirmation);
|
|
302
309
|
for (const page of event.pages ?? []) {
|
|
303
310
|
const key = page.key;
|
|
304
|
-
if (event.phase === "begin") {
|
|
305
|
-
this.pending.set(key, { operationId: event.operationId, snapshot: this.refSnapshots.get(key) ?? this.pending.get(key)?.snapshot });
|
|
306
|
-
this.markTabTargetUnknown({ sessionName: key, update });
|
|
307
|
-
this.applyRefSnapshotInvalidation({ sessionName: key, update, invalidation: buildPageTransitionRefSnapshotInvalidation("This browser operation has no persisted finish. Inspect the current URL and take a fresh snapshot before using refs; changes may already have happened.") });
|
|
308
|
-
continue;
|
|
309
|
-
}
|
|
310
311
|
if (page.clear) {
|
|
311
312
|
this.clearSession(key);
|
|
312
313
|
continue;
|
|
@@ -314,6 +315,14 @@ export class SessionPageState {
|
|
|
314
315
|
const pending = this.pending.get(key);
|
|
315
316
|
if (event.phase === "finish" && pending && pending.operationId !== event.operationId)
|
|
316
317
|
continue;
|
|
318
|
+
if (page.confirmActions !== undefined)
|
|
319
|
+
this.setConfirmActions(key, page.confirmActions ?? undefined);
|
|
320
|
+
if (event.phase === "begin") {
|
|
321
|
+
this.pending.set(key, { operationId: event.operationId, snapshot: this.refSnapshots.get(key) ?? this.pending.get(key)?.snapshot });
|
|
322
|
+
this.markTabTargetUnknown({ sessionName: key, update });
|
|
323
|
+
this.applyRefSnapshotInvalidation({ sessionName: key, update, invalidation: buildPageTransitionRefSnapshotInvalidation("This browser operation has no persisted finish. Inspect the current URL and take a fresh snapshot before using refs; changes may already have happened.") });
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
317
326
|
if (page.unknown || page.refs.kind === "unknown") {
|
|
318
327
|
this.markTabTargetUnknown({ sessionName: key, update });
|
|
319
328
|
this.applyRefSnapshotInvalidation({ sessionName: key, update, invalidation: page.refs.kind === "unknown" && page.refs.invalidation
|
|
@@ -361,6 +370,7 @@ export class SessionPageState {
|
|
|
361
370
|
return this.updateOrder;
|
|
362
371
|
}
|
|
363
372
|
reset() {
|
|
373
|
+
this.confirmActions.clear();
|
|
364
374
|
this.readConfirmations.clear();
|
|
365
375
|
this.pending.clear();
|
|
366
376
|
this.nativeGenerations.clear();
|
|
@@ -375,6 +385,7 @@ export class SessionPageState {
|
|
|
375
385
|
if (!sessionName)
|
|
376
386
|
return {};
|
|
377
387
|
return {
|
|
388
|
+
...(this.confirmActions.has(sessionName) ? { confirmActions: this.confirmActions.get(sessionName) } : {}),
|
|
378
389
|
pinningReason: this.tabPinningReasons.get(sessionName),
|
|
379
390
|
...(this.tabTargets.get(sessionName)?.reopenPending !== undefined ? { tabReopenPending: this.tabTargets.get(sessionName)?.reopenPending } : {}),
|
|
380
391
|
refSnapshot: stripRefSnapshotOrder(this.refSnapshots.get(sessionName)),
|
|
@@ -383,8 +394,14 @@ export class SessionPageState {
|
|
|
383
394
|
tabTarget: this.tabTargets.get(sessionName)?.target,
|
|
384
395
|
};
|
|
385
396
|
}
|
|
386
|
-
findReadConfirmation(args, namespace) {
|
|
387
|
-
return findPendingReadConfirmation(args, [...this.readConfirmations.values()].map(entry => entry.value), namespace);
|
|
397
|
+
findReadConfirmation(args, namespace, stdin) {
|
|
398
|
+
return findPendingReadConfirmation(args, [...this.readConfirmations.values()].map(entry => entry.value), namespace, stdin);
|
|
399
|
+
}
|
|
400
|
+
setConfirmActions(sessionName, value) {
|
|
401
|
+
if (value !== undefined)
|
|
402
|
+
this.confirmActions.set(sessionName, value);
|
|
403
|
+
else
|
|
404
|
+
this.confirmActions.delete(sessionName);
|
|
388
405
|
}
|
|
389
406
|
getReadConfirmation(sessionKey) {
|
|
390
407
|
return this.readConfirmations.get(sessionKey)?.value;
|
|
@@ -455,6 +472,7 @@ export class SessionPageState {
|
|
|
455
472
|
return { ...this.get(options.sessionName), applied: true };
|
|
456
473
|
}
|
|
457
474
|
clearSession(sessionName) {
|
|
475
|
+
this.confirmActions.delete(sessionName);
|
|
458
476
|
this.pending.delete(sessionName);
|
|
459
477
|
this.nativeGenerations.delete(sessionName);
|
|
460
478
|
this.readConfirmations.delete(sessionName);
|
|
@@ -466,6 +484,7 @@ export class SessionPageState {
|
|
|
466
484
|
}
|
|
467
485
|
clearNamespace(namespace) {
|
|
468
486
|
const sessionKeys = new Set([
|
|
487
|
+
...this.confirmActions.keys(),
|
|
469
488
|
...this.readConfirmations.keys(),
|
|
470
489
|
...this.refSnapshotInvalidations.keys(),
|
|
471
490
|
...this.refSnapshots.keys(),
|