pi-agent-browser-native 0.2.77 → 0.5.0
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 +154 -0
- package/README.md +69 -35
- package/dist/extensions/agent-browser/index.js +911 -121
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +35 -8
- package/dist/extensions/agent-browser/lib/argv-grammar.js +48 -7
- package/dist/extensions/agent-browser/lib/batch-lifecycle.js +71 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +1 -6
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +35 -8
- package/dist/extensions/agent-browser/lib/config-policy.js +0 -5
- package/dist/extensions/agent-browser/lib/config.js +0 -6
- package/dist/extensions/agent-browser/lib/electron/cdp.js +6 -7
- package/dist/extensions/agent-browser/lib/electron/cleanup.js +0 -7
- package/dist/extensions/agent-browser/lib/electron/discovery.js +0 -7
- package/dist/extensions/agent-browser/lib/electron/launch.js +0 -7
- package/dist/extensions/agent-browser/lib/input-modes/electron.js +0 -5
- package/dist/extensions/agent-browser/lib/input-modes/job.js +61 -9
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +2 -7
- package/dist/extensions/agent-browser/lib/input-modes/params.js +23 -29
- package/dist/extensions/agent-browser/lib/input-modes/script.js +462 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +51 -17
- package/dist/extensions/agent-browser/lib/input-modes/shared.js +0 -5
- package/dist/extensions/agent-browser/lib/input-modes/types.js +0 -5
- package/dist/extensions/agent-browser/lib/json-schema.js +0 -5
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +12 -5
- package/dist/extensions/agent-browser/lib/managed-session-capabilities.js +0 -2
- package/dist/extensions/agent-browser/lib/managed-session-policy-lock.js +3 -4
- package/dist/extensions/agent-browser/lib/managed-session-restore.js +55 -48
- package/dist/extensions/agent-browser/lib/managed-session-snapshots.js +2 -9
- package/dist/extensions/agent-browser/lib/managed-session-state-policy.js +48 -36
- package/dist/extensions/agent-browser/lib/managed-session-storage.js +50 -29
- package/dist/extensions/agent-browser/lib/navigation-policy.js +0 -5
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +26 -5
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +110 -30
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +27 -26
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +58 -33
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +2 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/managed-session-daemon-policy.js +22 -14
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +1 -1
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +148 -34
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +225 -61
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +74 -30
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +47 -26
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +40 -19
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +42 -1
- package/dist/extensions/agent-browser/lib/orchestration/script-mode.js +299 -0
- package/dist/extensions/agent-browser/lib/parsing.js +0 -7
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +36 -11
- package/dist/extensions/agent-browser/lib/playbook.js +20 -24
- package/dist/extensions/agent-browser/lib/process-environment.js +14 -0
- package/dist/extensions/agent-browser/lib/process-identity.js +4 -9
- package/dist/extensions/agent-browser/lib/process.js +131 -48
- package/dist/extensions/agent-browser/lib/prompt-policy.js +171 -27
- package/dist/extensions/agent-browser/lib/recording-reservations.js +183 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +63 -13
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +68 -11
- package/dist/extensions/agent-browser/lib/results/categories.js +7 -9
- package/dist/extensions/agent-browser/lib/results/confirmation.js +0 -7
- package/dist/extensions/agent-browser/lib/results/contracts.js +0 -7
- package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +0 -7
- package/dist/extensions/agent-browser/lib/results/envelope.js +0 -7
- package/dist/extensions/agent-browser/lib/results/network.js +0 -7
- package/dist/extensions/agent-browser/lib/results/next-actions.js +19 -35
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +85 -44
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +66 -11
- package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +18 -5
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +7 -6
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +2 -1
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/managed-list-filter.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +26 -16
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +58 -13
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +1 -15
- package/dist/extensions/agent-browser/lib/results/presentation/skills.js +0 -5
- package/dist/extensions/agent-browser/lib/results/presentation.js +6 -8
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +2 -7
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +0 -7
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +51 -15
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +13 -14
- package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +0 -7
- package/dist/extensions/agent-browser/lib/results/snapshot.js +0 -7
- package/dist/extensions/agent-browser/lib/results/text.js +0 -7
- package/dist/extensions/agent-browser/lib/runtime.js +119 -52
- package/dist/extensions/agent-browser/lib/session-page-state.js +62 -17
- package/dist/extensions/agent-browser/lib/string-enum-schema.js +0 -5
- package/dist/extensions/agent-browser/lib/temp.js +0 -7
- package/dist/extensions/agent-browser/lib/upstream-version.js +14 -0
- package/dist/extensions/agent-browser/lib/web-search.js +0 -5
- package/dist/extensions/agent-browser/script-worker.js +169 -0
- package/dist/scripts/agent-browser-target.mjs +3 -0
- package/docs/ARCHITECTURE.md +50 -31
- package/docs/COMMAND_REFERENCE.md +97 -41
- package/docs/ELECTRON.md +3 -19
- package/docs/RELEASE.md +8 -20
- package/docs/REQUIREMENTS.md +8 -6
- package/docs/SUPPORT_MATRIX.md +32 -25
- package/docs/TOOL_CONTRACT.md +107 -64
- package/docs/platform-smoke.md +1 -1
- package/package.json +7 -6
- package/platform-smoke.config.mjs +2 -2
- package/scripts/agent-browser-capability-baseline.mjs +24 -6
- package/scripts/agent-browser-target.mjs +3 -0
- package/scripts/build.mjs +41 -0
- package/scripts/doctor.mjs +8 -7
- package/scripts/platform-smoke/browser-dogfood-windows.ps1 +9 -3
- package/scripts/platform-smoke/targets.mjs +12 -6
- package/dist/extensions/agent-browser/lib/electron/text.js +0 -13
- package/dist/extensions/agent-browser/lib/input-modes.js +0 -10
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +0 -5
- package/dist/extensions/agent-browser/lib/results/artifact-state.js +0 -13
- package/dist/extensions/agent-browser/lib/results.js +0 -14
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { extractUpstreamCommandTokens } from "./argv-descriptor.js";
|
|
2
|
+
import { getAgentBrowserSessionIdentityKey, isAgentBrowserSessionIdentityKeyInNamespace } from "./argv-grammar.js";
|
|
3
|
+
import { batchHasSuccessfulCloseAll, getSuccessfulBatchCloseLifecycle } from "./batch-lifecycle.js";
|
|
4
|
+
import { isCloseAllCommand, isCloseCommand } from "./command-taxonomy.js";
|
|
5
|
+
import { isRecord } from "./parsing.js";
|
|
6
|
+
import { isPendingRecordingArtifact, isPendingRecordingCommand, isSessionArtifactManifest } from "./results/artifact-manifest.js";
|
|
7
|
+
export const RECORDING_RESERVATION_ENTRY_TYPE = "agent-browser-recording-reservation";
|
|
8
|
+
function getReservationKey(reservation) {
|
|
9
|
+
return getAgentBrowserSessionIdentityKey(reservation.sessionName, reservation.namespace);
|
|
10
|
+
}
|
|
11
|
+
function getArtifactReservation(artifact) {
|
|
12
|
+
if (!artifact.session || artifact.command !== "record" || artifact.kind !== "video")
|
|
13
|
+
return undefined;
|
|
14
|
+
return {
|
|
15
|
+
absolutePath: artifact.absolutePath,
|
|
16
|
+
cwd: artifact.cwd ?? process.cwd(),
|
|
17
|
+
namespace: artifact.namespace,
|
|
18
|
+
path: artifact.path,
|
|
19
|
+
sessionName: artifact.session,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function applyRecordingArtifactsToReservations(reservations, artifacts) {
|
|
23
|
+
const pendingBySession = new Map();
|
|
24
|
+
const terminalBySession = new Map();
|
|
25
|
+
for (const artifact of artifacts) {
|
|
26
|
+
const reservation = getArtifactReservation(artifact);
|
|
27
|
+
if (!reservation)
|
|
28
|
+
continue;
|
|
29
|
+
const key = getReservationKey(reservation);
|
|
30
|
+
if (isPendingRecordingArtifact(artifact))
|
|
31
|
+
pendingBySession.set(key, reservation);
|
|
32
|
+
else
|
|
33
|
+
terminalBySession.set(key, reservation);
|
|
34
|
+
}
|
|
35
|
+
const transitions = [];
|
|
36
|
+
for (const key of terminalBySession.keys()) {
|
|
37
|
+
if (pendingBySession.has(key))
|
|
38
|
+
continue;
|
|
39
|
+
const existing = reservations.get(key);
|
|
40
|
+
if (!existing)
|
|
41
|
+
continue;
|
|
42
|
+
reservations.delete(key);
|
|
43
|
+
transitions.push({ reservation: existing, state: "closed" });
|
|
44
|
+
}
|
|
45
|
+
for (const [key, pending] of pendingBySession) {
|
|
46
|
+
const existing = reservations.get(key);
|
|
47
|
+
reservations.set(key, pending);
|
|
48
|
+
if (!existing || existing.absolutePath !== pending.absolutePath || existing.cwd !== pending.cwd) {
|
|
49
|
+
transitions.push({ reservation: pending, state: "active" });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return transitions;
|
|
53
|
+
}
|
|
54
|
+
export function retireRecordingReservation(reservations, sessionName, namespace) {
|
|
55
|
+
const key = getAgentBrowserSessionIdentityKey(sessionName, namespace);
|
|
56
|
+
const reservation = reservations.get(key);
|
|
57
|
+
if (reservation)
|
|
58
|
+
reservations.delete(key);
|
|
59
|
+
return reservation;
|
|
60
|
+
}
|
|
61
|
+
export function appendRecordingReservationTransition(pi, transition) {
|
|
62
|
+
const { reservation, state } = transition;
|
|
63
|
+
pi.appendEntry(RECORDING_RESERVATION_ENTRY_TYPE, {
|
|
64
|
+
absolutePath: state === "active" ? reservation.absolutePath : undefined,
|
|
65
|
+
cwd: state === "active" ? reservation.cwd : undefined,
|
|
66
|
+
namespace: reservation.namespace,
|
|
67
|
+
path: state === "active" ? reservation.path : undefined,
|
|
68
|
+
sessionName: reservation.sessionName,
|
|
69
|
+
state,
|
|
70
|
+
version: 1,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function parseReservationTransition(data) {
|
|
74
|
+
if (!isRecord(data) || data.version !== 1 || (data.state !== "active" && data.state !== "closed"))
|
|
75
|
+
return undefined;
|
|
76
|
+
if (typeof data.sessionName !== "string" || data.sessionName.length === 0)
|
|
77
|
+
return undefined;
|
|
78
|
+
if (data.namespace !== undefined && typeof data.namespace !== "string")
|
|
79
|
+
return undefined;
|
|
80
|
+
if (data.state === "closed") {
|
|
81
|
+
return {
|
|
82
|
+
reservation: { absolutePath: "", cwd: "", namespace: data.namespace, path: "", sessionName: data.sessionName },
|
|
83
|
+
state: "closed",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (typeof data.absolutePath !== "string" || typeof data.cwd !== "string" || typeof data.path !== "string")
|
|
87
|
+
return undefined;
|
|
88
|
+
return {
|
|
89
|
+
reservation: {
|
|
90
|
+
absolutePath: data.absolutePath,
|
|
91
|
+
cwd: data.cwd,
|
|
92
|
+
namespace: data.namespace,
|
|
93
|
+
path: data.path,
|
|
94
|
+
sessionName: data.sessionName,
|
|
95
|
+
},
|
|
96
|
+
state: "active",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export function restoreRecordingReservationStateFromBranch(branch) {
|
|
100
|
+
const reservations = new Map();
|
|
101
|
+
const terminal = new Map();
|
|
102
|
+
for (const entry of branch) {
|
|
103
|
+
if (!isRecord(entry))
|
|
104
|
+
continue;
|
|
105
|
+
if (entry.type === "custom" && entry.customType === RECORDING_RESERVATION_ENTRY_TYPE) {
|
|
106
|
+
const transition = parseReservationTransition(entry.data);
|
|
107
|
+
if (!transition)
|
|
108
|
+
continue;
|
|
109
|
+
const key = getReservationKey(transition.reservation);
|
|
110
|
+
if (transition.state === "active") {
|
|
111
|
+
reservations.set(key, transition.reservation);
|
|
112
|
+
terminal.delete(key);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
reservations.delete(key);
|
|
116
|
+
terminal.set(key, transition.reservation);
|
|
117
|
+
}
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (entry.type !== "message")
|
|
121
|
+
continue;
|
|
122
|
+
const message = isRecord(entry.message) ? entry.message : undefined;
|
|
123
|
+
const details = message?.toolName === "agent_browser" && isRecord(message.details) ? message.details : undefined;
|
|
124
|
+
if (!details)
|
|
125
|
+
continue;
|
|
126
|
+
if (isSessionArtifactManifest(details.artifactManifest)) {
|
|
127
|
+
const artifacts = [...details.artifactManifest.entries]
|
|
128
|
+
.sort((left, right) => left.createdAtMs - right.createdAtMs
|
|
129
|
+
|| Number(isPendingRecordingCommand(left.command, left.subcommand, left.kind)) - Number(isPendingRecordingCommand(right.command, right.subcommand, right.kind))
|
|
130
|
+
|| left.path.localeCompare(right.path))
|
|
131
|
+
.filter((manifestEntry) => manifestEntry.command === "record" && manifestEntry.kind === "video" && manifestEntry.session)
|
|
132
|
+
.map((manifestEntry) => ({
|
|
133
|
+
absolutePath: manifestEntry.absolutePath ?? manifestEntry.path,
|
|
134
|
+
command: manifestEntry.command,
|
|
135
|
+
cwd: manifestEntry.cwd,
|
|
136
|
+
kind: "video",
|
|
137
|
+
namespace: manifestEntry.namespace,
|
|
138
|
+
path: manifestEntry.path,
|
|
139
|
+
session: manifestEntry.session,
|
|
140
|
+
status: isPendingRecordingCommand(manifestEntry.command, manifestEntry.subcommand, "video") ? "pending" : "saved",
|
|
141
|
+
subcommand: manifestEntry.subcommand,
|
|
142
|
+
}));
|
|
143
|
+
for (const artifact of artifacts) {
|
|
144
|
+
const reservation = getArtifactReservation(artifact);
|
|
145
|
+
if (!reservation)
|
|
146
|
+
continue;
|
|
147
|
+
const key = getReservationKey(reservation);
|
|
148
|
+
applyRecordingArtifactsToReservations(reservations, [artifact]);
|
|
149
|
+
if (isPendingRecordingArtifact(artifact))
|
|
150
|
+
terminal.delete(key);
|
|
151
|
+
else
|
|
152
|
+
terminal.set(key, reservation);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const messageSucceeded = typeof message?.isError === "boolean"
|
|
156
|
+
? !message.isError
|
|
157
|
+
: typeof details.exitCode !== "number" || details.exitCode === 0;
|
|
158
|
+
const args = Array.isArray(details.args) && details.args.every((arg) => typeof arg === "string") ? details.args : [];
|
|
159
|
+
const namespace = typeof details.namespace === "string" ? details.namespace : undefined;
|
|
160
|
+
const closeAllApplied = details.closeAllApplied === true
|
|
161
|
+
|| (messageSucceeded && isCloseAllCommand(extractUpstreamCommandTokens(args)))
|
|
162
|
+
|| batchHasSuccessfulCloseAll(details.batchSteps);
|
|
163
|
+
if (closeAllApplied) {
|
|
164
|
+
for (const [key, reservation] of reservations) {
|
|
165
|
+
if (!isAgentBrowserSessionIdentityKeyInNamespace(key, namespace))
|
|
166
|
+
continue;
|
|
167
|
+
terminal.set(key, reservation);
|
|
168
|
+
reservations.delete(key);
|
|
169
|
+
}
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const directCloseSucceeded = messageSucceeded && isCloseCommand(typeof details.command === "string" ? details.command : undefined);
|
|
173
|
+
const batchRecordingClosed = getSuccessfulBatchCloseLifecycle(details.batchSteps)?.recordingClosedAfterBatch === true;
|
|
174
|
+
if ((directCloseSucceeded || batchRecordingClosed) && typeof details.sessionName === "string") {
|
|
175
|
+
const key = getAgentBrowserSessionIdentityKey(details.sessionName, namespace);
|
|
176
|
+
const reservation = reservations.get(key);
|
|
177
|
+
if (reservation)
|
|
178
|
+
terminal.set(key, reservation);
|
|
179
|
+
reservations.delete(key);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { active: reservations, terminal };
|
|
183
|
+
}
|
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Build generic nextAction recommendations from result categories, artifacts, Electron lifecycle state, and recovery context.
|
|
3
|
-
* Responsibilities: Preserve stable action ids/order while keeping recommendation policy out of generic shared helpers.
|
|
4
|
-
* Scope: Generic result-level recommendations only; feature-specific diagnostics append their own actions in the extension entrypoint.
|
|
5
|
-
* Usage: Called by presentation and extension result assembly.
|
|
6
|
-
* Invariants/Assumptions: Action ids are public machine-readable contracts; preserve first-observed order.
|
|
7
|
-
*/
|
|
8
1
|
import { isOpenNavigationCommand, isPageMutationCommand } from "../command-taxonomy.js";
|
|
9
|
-
import { isPendingRecordingArtifact } from "./artifact-
|
|
10
|
-
import { buildNextToolAction } from "./next-actions.js";
|
|
2
|
+
import { isPendingRecordingArtifact } from "./artifact-manifest.js";
|
|
3
|
+
import { applySessionToNextActions, buildNextToolAction } from "./next-actions.js";
|
|
11
4
|
import { AGENT_BROWSER_RECOVERY_NEXT_ACTION_IDS, buildRecoveryNextActions, } from "./recovery-actions.js";
|
|
12
5
|
function buildArtifactAction(path) {
|
|
13
6
|
return {
|
|
@@ -127,9 +120,8 @@ export function buildAgentBrowserNextActions(options) {
|
|
|
127
120
|
actions.push(buildArtifactAction(options.savedFilePath));
|
|
128
121
|
}
|
|
129
122
|
for (const artifact of artifacts) {
|
|
130
|
-
if (isPendingRecordingArtifact(artifact))
|
|
123
|
+
if (isPendingRecordingArtifact(artifact))
|
|
131
124
|
continue;
|
|
132
|
-
}
|
|
133
125
|
if (artifact.exists === false) {
|
|
134
126
|
if (artifact.kind === "download") {
|
|
135
127
|
actions.push(buildNextToolAction({
|
|
@@ -153,7 +145,7 @@ export function buildAgentBrowserNextActions(options) {
|
|
|
153
145
|
switch (options.failureCategory) {
|
|
154
146
|
case "artifact-missing":
|
|
155
147
|
for (const artifact of options.artifacts ?? []) {
|
|
156
|
-
if (isPendingRecordingArtifact(artifact) || artifact.exists !== false)
|
|
148
|
+
if (isPendingRecordingArtifact(artifact) || (artifact.exists !== false && artifact.status !== "stale"))
|
|
157
149
|
continue;
|
|
158
150
|
if (artifact.kind === "download") {
|
|
159
151
|
actions.push(buildNextToolAction({
|
|
@@ -203,6 +195,56 @@ export function buildAgentBrowserNextActions(options) {
|
|
|
203
195
|
}));
|
|
204
196
|
}
|
|
205
197
|
break;
|
|
198
|
+
case "timeout":
|
|
199
|
+
{
|
|
200
|
+
const textAssertion = options.command === "wait" && options.args?.includes("--text") === true;
|
|
201
|
+
const urlAssertion = options.command === "wait" && options.args?.includes("--url") === true;
|
|
202
|
+
actions.push(buildNextToolAction({
|
|
203
|
+
args: ["snapshot", "-i"],
|
|
204
|
+
id: textAssertion ? "inspect-after-text-assertion-failure" : "inspect-after-timeout",
|
|
205
|
+
reason: textAssertion
|
|
206
|
+
? "Inspect the current page after the text assertion failed before concluding the expected text is absent."
|
|
207
|
+
: options.command === "wait"
|
|
208
|
+
? "Inspect the current page after the wait condition timed out before retrying with a different selector or timeout."
|
|
209
|
+
: "Inspect the current page after the timed-out browser operation.",
|
|
210
|
+
safety: textAssertion
|
|
211
|
+
? "Read-only snapshot; use current refs or visible text from this page before retrying the assertion."
|
|
212
|
+
: "Read-only snapshot; do not assume the timed-out interaction completed.",
|
|
213
|
+
}));
|
|
214
|
+
if (urlAssertion) {
|
|
215
|
+
actions.push(buildNextToolAction({
|
|
216
|
+
args: ["open", "about:blank"],
|
|
217
|
+
id: "fresh-session-after-url-wait-timeout",
|
|
218
|
+
reason: "If a preceding click or form submit reported success but the page never navigated, upstream click dispatch may have silently missed (observed with agent-browser 0.34 after many spaced commands); replace about:blank with the target URL and replay the flow as one batch in a fresh session instead of retrying the wait.",
|
|
219
|
+
safety: "Abandons the current browser session; capture page evidence with the inspect action first, and only abandon when the wait target is not simply wrong or slow.",
|
|
220
|
+
sessionMode: "fresh",
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
break;
|
|
225
|
+
case "upstream-error":
|
|
226
|
+
if (isOpenNavigationCommand(options.command)) {
|
|
227
|
+
actions.push(buildNextToolAction({
|
|
228
|
+
args: ["get", "url"],
|
|
229
|
+
id: "inspect-page-after-navigation-error",
|
|
230
|
+
reason: "Check which page, if any, remains active after the navigation or network error.",
|
|
231
|
+
safety: "Read-only URL inspection; verify connectivity and the target URL before retrying navigation.",
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
case "tab-gone":
|
|
236
|
+
actions.push(buildNextToolAction({
|
|
237
|
+
args: ["tab", "list"],
|
|
238
|
+
id: AGENT_BROWSER_RECOVERY_NEXT_ACTION_IDS.tabGoneListTabs,
|
|
239
|
+
reason: "The pinned bound tab is gone; inspect remaining tabs before acting on a neighbor.",
|
|
240
|
+
safety: "Read-only. Prefer a listed tab id, label, or CDP targetId, or open a new tab to rebind.",
|
|
241
|
+
}), buildNextToolAction({
|
|
242
|
+
args: ["tab", "new"],
|
|
243
|
+
id: AGENT_BROWSER_RECOVERY_NEXT_ACTION_IDS.tabGoneNewTab,
|
|
244
|
+
reason: "Bind a fresh tab after tab_gone instead of continuing on another session's page.",
|
|
245
|
+
safety: "Opens a new tab in this session and rebinds the pin; pass a URL if you know the intended page.",
|
|
246
|
+
}));
|
|
247
|
+
break;
|
|
206
248
|
case "tab-drift":
|
|
207
249
|
if (options.recovery?.kind === "about-blank" || options.recovery?.kind === "tab-drift") {
|
|
208
250
|
break;
|
|
@@ -216,5 +258,13 @@ export function buildAgentBrowserNextActions(options) {
|
|
|
216
258
|
break;
|
|
217
259
|
}
|
|
218
260
|
}
|
|
219
|
-
|
|
261
|
+
if ((options.artifacts ?? []).some(isPendingRecordingArtifact)) {
|
|
262
|
+
actions.push(buildNextToolAction({
|
|
263
|
+
args: ["record", "stop"],
|
|
264
|
+
id: "stop-pending-recording",
|
|
265
|
+
reason: "Stop the active recording so the requested video can be finalized and verified on disk.",
|
|
266
|
+
safety: "The file remains pending until record stop succeeds; verify details.artifactVerification afterward.",
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
return applySessionToNextActions(actions.length > 0 ? actions : undefined, options.sessionName);
|
|
220
270
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
* Purpose: Own persistent session artifact manifest merge, retention, and validation logic.
|
|
3
|
-
* Responsibilities: Parse manifest bounds, recognize manifest entries, merge new artifact rows, and format retention summaries.
|
|
4
|
-
* Scope: Manifest accounting only; artifact detection and presentation live in presentation modules.
|
|
5
|
-
* Usage: Imported by presentation and snapshot artifact persistence paths.
|
|
6
|
-
* Invariants/Assumptions: Explicit-path artifacts are host-owned while persistent-session spill files are bounded by the manifest cap.
|
|
7
|
-
*/
|
|
1
|
+
import { getAgentBrowserSessionIdentityKey } from "../argv-grammar.js";
|
|
8
2
|
import { isRecord } from "../parsing.js";
|
|
3
|
+
export function isPendingRecordingCommand(command, subcommand, kind) {
|
|
4
|
+
return command === "record" && (subcommand === "start" || subcommand === "restart") && kind === "video";
|
|
5
|
+
}
|
|
6
|
+
export function isPendingRecordingArtifact(artifact) {
|
|
7
|
+
return isPendingRecordingCommand(artifact.command, artifact.subcommand, artifact.kind);
|
|
8
|
+
}
|
|
9
9
|
export const SESSION_ARTIFACT_MANIFEST_VERSION = 1;
|
|
10
10
|
export const SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES_ENV = "PI_AGENT_BROWSER_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES";
|
|
11
11
|
export const DEFAULT_SESSION_ARTIFACT_MANIFEST_MAX_ENTRIES = 100;
|
|
@@ -33,6 +33,16 @@ function isManifestEntry(value) {
|
|
|
33
33
|
return false;
|
|
34
34
|
if (typeof value.kind !== "string" || value.kind.trim().length === 0)
|
|
35
35
|
return false;
|
|
36
|
+
for (const key of ["absolutePath", "command", "cwd", "extension", "mediaType", "namespace", "requestedPath", "session", "subcommand"]) {
|
|
37
|
+
if (value[key] !== undefined && typeof value[key] !== "string")
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
if (value.evictedAtMs !== undefined && (typeof value.evictedAtMs !== "number" || !Number.isFinite(value.evictedAtMs)))
|
|
41
|
+
return false;
|
|
42
|
+
if (value.exists !== undefined && typeof value.exists !== "boolean")
|
|
43
|
+
return false;
|
|
44
|
+
if (value.sizeBytes !== undefined && (typeof value.sizeBytes !== "number" || !Number.isFinite(value.sizeBytes) || value.sizeBytes < 0))
|
|
45
|
+
return false;
|
|
36
46
|
return true;
|
|
37
47
|
}
|
|
38
48
|
export function isSessionArtifactManifest(value) {
|
|
@@ -74,7 +84,34 @@ export function formatSessionArtifactRetentionSummary(manifest) {
|
|
|
74
84
|
return `Session artifacts: ${parts.join(", ")} (${manifest.entries.length}/${manifest.maxEntries} recent).`;
|
|
75
85
|
}
|
|
76
86
|
export function getSessionArtifactManifestEntryKey(entry) {
|
|
77
|
-
|
|
87
|
+
const pathKey = entry.storageScope === "explicit-path" && entry.absolutePath ? `${entry.storageScope}:${entry.absolutePath}` : `${entry.storageScope}:${entry.path}`;
|
|
88
|
+
const recordingSessionKey = entry.command === "record" && entry.kind === "video" && entry.session
|
|
89
|
+
? getAgentBrowserSessionIdentityKey(entry.session, entry.namespace)
|
|
90
|
+
: undefined;
|
|
91
|
+
return recordingSessionKey ? `${pathKey}\0${recordingSessionKey}` : pathKey;
|
|
92
|
+
}
|
|
93
|
+
export function retirePendingRecordingManifestEntries(manifest, sessionName, namespace, nowMs = Date.now()) {
|
|
94
|
+
let changed = false;
|
|
95
|
+
const sessionKey = sessionName ? getAgentBrowserSessionIdentityKey(sessionName, namespace) : undefined;
|
|
96
|
+
const entries = manifest.entries.map((entry) => {
|
|
97
|
+
if (!sessionKey
|
|
98
|
+
|| !entry.session
|
|
99
|
+
|| getAgentBrowserSessionIdentityKey(entry.session, entry.namespace) !== sessionKey
|
|
100
|
+
|| entry.kind !== "video"
|
|
101
|
+
|| !isPendingRecordingCommand(entry.command, entry.subcommand, entry.kind))
|
|
102
|
+
return entry;
|
|
103
|
+
changed = true;
|
|
104
|
+
return { ...entry, retentionState: "missing", subcommand: "close-abandoned" };
|
|
105
|
+
});
|
|
106
|
+
if (!changed)
|
|
107
|
+
return manifest;
|
|
108
|
+
return {
|
|
109
|
+
...manifest,
|
|
110
|
+
entries,
|
|
111
|
+
evictedCount: entries.filter((entry) => entry.retentionState === "evicted").length,
|
|
112
|
+
liveCount: entries.filter((entry) => entry.retentionState === "live").length,
|
|
113
|
+
updatedAtMs: nowMs,
|
|
114
|
+
};
|
|
78
115
|
}
|
|
79
116
|
export function mergeSessionArtifactManifest(options) {
|
|
80
117
|
const nowMs = options.nowMs ?? Date.now();
|
|
@@ -83,13 +120,31 @@ export function mergeSessionArtifactManifest(options) {
|
|
|
83
120
|
for (const entry of options.base?.entries ?? []) {
|
|
84
121
|
byPath.set(getSessionArtifactManifestEntryKey(entry), entry);
|
|
85
122
|
}
|
|
86
|
-
|
|
123
|
+
const orderedEntries = (options.entries ?? [])
|
|
124
|
+
.map((entry, index) => ({ entry, index }))
|
|
125
|
+
.sort((left, right) => left.entry.createdAtMs - right.entry.createdAtMs || left.index - right.index)
|
|
126
|
+
.map(({ entry }) => entry);
|
|
127
|
+
for (const entry of orderedEntries) {
|
|
87
128
|
const key = getSessionArtifactManifestEntryKey(entry);
|
|
129
|
+
if (entry.command === "record" && entry.kind === "video") {
|
|
130
|
+
const entrySessionKey = entry.session ? getAgentBrowserSessionIdentityKey(entry.session, entry.namespace) : undefined;
|
|
131
|
+
for (const [candidateKey, candidate] of byPath) {
|
|
132
|
+
const sameRecordingSession = entrySessionKey === undefined
|
|
133
|
+
? candidate.session === undefined
|
|
134
|
+
: candidate.session !== undefined && getAgentBrowserSessionIdentityKey(candidate.session, candidate.namespace) === entrySessionKey;
|
|
135
|
+
if (candidateKey !== key
|
|
136
|
+
&& sameRecordingSession
|
|
137
|
+
&& candidate.kind === "video"
|
|
138
|
+
&& isPendingRecordingCommand(candidate.command, candidate.subcommand, candidate.kind)) {
|
|
139
|
+
byPath.delete(candidateKey);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
88
143
|
const existing = byPath.get(key);
|
|
89
144
|
byPath.set(key, {
|
|
90
145
|
...existing,
|
|
91
146
|
...entry,
|
|
92
|
-
createdAtMs: existing?.createdAtMs ?? entry.createdAtMs,
|
|
147
|
+
createdAtMs: entry.command === "record" && entry.kind === "video" ? entry.createdAtMs : existing?.createdAtMs ?? entry.createdAtMs,
|
|
93
148
|
evictedAtMs: entry.retentionState === "evicted" ? (entry.evictedAtMs ?? nowMs) : entry.evictedAtMs,
|
|
94
149
|
});
|
|
95
150
|
}
|
|
@@ -99,7 +154,9 @@ export function mergeSessionArtifactManifest(options) {
|
|
|
99
154
|
.sort((left, right) => {
|
|
100
155
|
const leftTime = left.evictedAtMs ?? left.createdAtMs;
|
|
101
156
|
const rightTime = right.evictedAtMs ?? right.createdAtMs;
|
|
102
|
-
return rightTime - leftTime
|
|
157
|
+
return rightTime - leftTime
|
|
158
|
+
|| Number(isPendingRecordingCommand(right.command, right.subcommand, right.kind)) - Number(isPendingRecordingCommand(left.command, left.subcommand, left.kind))
|
|
159
|
+
|| left.path.localeCompare(right.path);
|
|
103
160
|
})
|
|
104
161
|
.slice(0, maxEntries);
|
|
105
162
|
return {
|
|
@@ -1,17 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
* Purpose: Classify successful and failed agent-browser outcomes into stable result categories.
|
|
3
|
-
* Responsibilities: Map artifacts, inspection calls, errors, refs, selectors, downloads, drift, and timeouts to small enums.
|
|
4
|
-
* Scope: Category policy only; next-action recommendations and presentation formatting live elsewhere.
|
|
5
|
-
* Usage: Called by presentation and extension result assembly before details are exposed to Pi.
|
|
6
|
-
* Invariants/Assumptions: Category strings are public machine-readable contracts covered by tests and docs.
|
|
7
|
-
*/
|
|
8
|
-
import { isPendingRecordingArtifact } from "./artifact-state.js";
|
|
1
|
+
import { isPendingRecordingArtifact } from "./artifact-manifest.js";
|
|
9
2
|
function hasUnverifiedFileArtifact(artifacts) {
|
|
10
3
|
return (artifacts ?? []).some((artifact) => !isPendingRecordingArtifact(artifact) && artifact.exists !== true);
|
|
11
4
|
}
|
|
12
5
|
export function classifyAgentBrowserSuccessCategory(options) {
|
|
13
6
|
if (options.inspection)
|
|
14
7
|
return "inspection";
|
|
8
|
+
if ((options.artifacts ?? []).some(isPendingRecordingArtifact))
|
|
9
|
+
return "artifact-pending";
|
|
15
10
|
if ((options.artifacts ?? []).length > 0)
|
|
16
11
|
return hasUnverifiedFileArtifact(options.artifacts) ? "artifact-unverified" : "artifact-saved";
|
|
17
12
|
if (options.savedFile)
|
|
@@ -26,6 +21,9 @@ export function classifyAgentBrowserFailureCategory(options) {
|
|
|
26
21
|
// missed control named "Confirmation required" still gets selector recovery.
|
|
27
22
|
if (options.confirmationRequired)
|
|
28
23
|
return "confirmation-required";
|
|
24
|
+
// Canonical upstream prefix first: lastUrl can contain aborted / policy-blocked / about:blank.
|
|
25
|
+
if (/\btab_gone:/i.test(text))
|
|
26
|
+
return "tab-gone";
|
|
29
27
|
// Upstream 0.32.4+ locator misses keep detail and may echo getByRole/getByText or Names seen lists.
|
|
30
28
|
// Evaluate before text-derived timeout/confirmation so accessible-name substrings cannot suppress recovery.
|
|
31
29
|
const isUpstreamLocatorMiss = /\bNo element found:\s*(?:getBy[A-Za-z]+|role=|text=|label=|placeholder=|alt=|title=|testid=)/i.test(text) ||
|
|
@@ -55,7 +53,7 @@ export function classifyAgentBrowserFailureCategory(options) {
|
|
|
55
53
|
return "policy-blocked";
|
|
56
54
|
if (/cleanup failed|cleanup.*partial|partial cleanup|remaining resources/i.test(text))
|
|
57
55
|
return "cleanup-failed";
|
|
58
|
-
if (options.validationError)
|
|
56
|
+
if (options.validationError || /Agent-browser Unix socket path would be|Agent-browser socket storage .* is unusable/i.test(text))
|
|
59
57
|
return "validation-error";
|
|
60
58
|
if (options.tabDrift || /could not re-select the intended tab|about:blank|selected tab looks wrong|tab drift|tab.*wrong/i.test(text))
|
|
61
59
|
return "tab-drift";
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Detect upstream guarded-action confirmation-needed result shapes without creating wrapper-owned confirmation state.
|
|
3
|
-
* Responsibilities: Recognize confirmation-required markers, extract the pending upstream confirmation id, and optionally surface a short upstream action label.
|
|
4
|
-
* Scope: Pure result-shape detection shared by presentation and error derivation; command execution, approval state, and redaction stay in their existing modules.
|
|
5
|
-
* Usage: Imported by result presentation to render recovery commands and by envelope error handling to avoid hiding actionable confirmation payloads behind generic failure text.
|
|
6
|
-
* Invariants/Assumptions: Detection must be conservative: a confirmation marker and a non-empty upstream id are both required before a result is treated as actionable.
|
|
7
|
-
*/
|
|
8
1
|
import { isRecord } from "../parsing.js";
|
|
9
2
|
const CONFIRMATION_REQUIRED_FIELD_NAMES = [
|
|
10
3
|
"confirmation_required",
|
|
@@ -1,8 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Define stable result-rendering data contracts shared across focused result modules.
|
|
3
|
-
* Responsibilities: Keep upstream envelope, presentation, artifact, category, and network shapes in one type-only surface.
|
|
4
|
-
* Scope: Types only; runtime classifiers, manifests, network rules, and text helpers live in neighboring modules.
|
|
5
|
-
* Usage: Imported with `import type` by result modules and re-exported by the public results facade.
|
|
6
|
-
* Invariants/Assumptions: This file has no runtime policy so adding fields cannot hide behavior in a catch-all module.
|
|
7
|
-
*/
|
|
8
1
|
export {};
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Detect whether upstream ref metadata or snapshot text proves an element is editable.
|
|
3
|
-
* Responsibilities: Parse structured ref flags and snapshot-line contenteditable/editable markers into a conservative boolean/unknown signal.
|
|
4
|
-
* Scope: Editable evidence only; callers decide how to rank controls or build recovery actions from the signal.
|
|
5
|
-
* Usage: Imported by snapshot compaction and selector/fill recovery diagnostics.
|
|
6
|
-
* Invariants/Assumptions: Explicit false evidence wins over positive hints; unknown remains undefined.
|
|
7
|
-
*/
|
|
8
1
|
const EDITABLE_REF_EVIDENCE_KEYS = ["editable", "contentEditable", "contenteditable", "isContentEditable"];
|
|
9
2
|
const EDITABLE_FALSE_TEXT_PATTERN = /\b(?:contenteditable|editable)\s*=\s*["']?(?:false|0)["']?/i;
|
|
10
3
|
const EDITABLE_ASSIGNMENT_TEXT_PATTERN = /\b(contenteditable|editable)\s*=\s*("[^"]*"|'[^']*'|[^\s,\]]+)/gi;
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Parse upstream agent-browser output and turn failure envelopes into actionable error text.
|
|
3
|
-
* Responsibilities: Read inline or spilled stdout, parse observed JSON envelope shapes, normalize batch arrays, and extract the most useful error text from nested upstream failures.
|
|
4
|
-
* Scope: Envelope parsing and error derivation only; content rendering and snapshot compaction live in separate modules.
|
|
5
|
-
* Usage: Imported by the public `lib/results.ts` facade and by tests through that facade.
|
|
6
|
-
* Invariants/Assumptions: Upstream `agent-browser --json` responses follow the observed `{ success, data, error }` envelope shape or the array shape returned by `batch --json`.
|
|
7
|
-
*/
|
|
8
1
|
import { readFile } from "node:fs/promises";
|
|
9
2
|
import { isRecord } from "../parsing.js";
|
|
10
3
|
import { detectConfirmationRequired } from "./confirmation.js";
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Classify failed network requests into actionable vs benign diagnostics.
|
|
3
|
-
* Responsibilities: Recognize failed request rows, de-prioritize browser icon misses, and summarize failure counts.
|
|
4
|
-
* Scope: Network diagnostic classification only.
|
|
5
|
-
* Usage: QA preset analysis and presentation network summaries.
|
|
6
|
-
* Invariants/Assumptions: Browser favicon/apple-touch icon misses are warnings; API/document/script failures are actionable.
|
|
7
|
-
*/
|
|
8
1
|
import { isRecord } from "../parsing.js";
|
|
9
2
|
export function getStringRecordField(value, key) {
|
|
10
3
|
const field = value[key];
|
|
@@ -1,22 +1,15 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Own machine-readable agent_browser next-action contracts and merge policy.
|
|
3
|
-
* Responsibilities: Define the stable nextAction shape, build basic argv follow-ups, and provide deterministic action-list collection helpers.
|
|
4
|
-
* Scope: Result follow-up action mechanics only; command-specific recovery and artifact policies live in neighboring modules.
|
|
5
|
-
* Usage: Imported by result presentation helpers and the extension entrypoint when attaching details.nextActions.
|
|
6
|
-
* Invariants/Assumptions: Action ids are stable machine-readable contracts; dedupe preserves first occurrence order.
|
|
7
|
-
*/
|
|
8
1
|
export function withOptionalNamespaceArgs(namespace, args) {
|
|
9
|
-
return namespace && args[0] !== "--namespace" ? ["--namespace", namespace, ...args] : args;
|
|
2
|
+
return namespace !== undefined && args[0] !== "--namespace" ? ["--namespace", namespace, ...args] : args;
|
|
10
3
|
}
|
|
11
4
|
export function withOptionalSessionArgs(sessionName, args) {
|
|
12
|
-
if (!sessionName || args[0] === "--session")
|
|
5
|
+
if (!sessionName || args[0] === "--session" || (args[0] === "--namespace" && args[2] === "--session"))
|
|
13
6
|
return args;
|
|
14
|
-
if (args[0] === "--namespace" && args
|
|
7
|
+
if (args[0] === "--namespace" && args.length >= 2)
|
|
15
8
|
return [args[0], args[1], "--session", sessionName, ...args.slice(2)];
|
|
16
9
|
return ["--session", sessionName, ...args];
|
|
17
10
|
}
|
|
18
11
|
export function applyNamespaceToNextActions(actions, namespace) {
|
|
19
|
-
if (
|
|
12
|
+
if (namespace === undefined || !actions)
|
|
20
13
|
return actions;
|
|
21
14
|
return actions.map((action) => {
|
|
22
15
|
const args = action.params?.args;
|
|
@@ -26,6 +19,18 @@ export function applyNamespaceToNextActions(actions, namespace) {
|
|
|
26
19
|
return networkSourceLookup ? { ...action, params: { ...action.params, networkSourceLookup: { ...networkSourceLookup, namespace } } } : action;
|
|
27
20
|
});
|
|
28
21
|
}
|
|
22
|
+
export function applySessionToNextActions(actions, sessionName) {
|
|
23
|
+
if (!sessionName || !actions)
|
|
24
|
+
return actions;
|
|
25
|
+
return actions.map((action) => {
|
|
26
|
+
// Fresh-session actions deliberately target a new session; the planner ignores sessionMode when an
|
|
27
|
+
// explicit --session is present, so prefixing one here would silently downgrade them to reuse.
|
|
28
|
+
if (action.params?.sessionMode === "fresh")
|
|
29
|
+
return action;
|
|
30
|
+
const args = action.params?.args;
|
|
31
|
+
return args ? { ...action, params: { ...action.params, args: withOptionalSessionArgs(sessionName, args) } } : action;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
29
34
|
export function buildNextToolAction(options) {
|
|
30
35
|
return {
|
|
31
36
|
id: options.id,
|
|
@@ -55,7 +60,9 @@ export function isStandaloneSnapshotNextAction(action) {
|
|
|
55
60
|
const args = action.params?.args;
|
|
56
61
|
if (!args || action.params?.stdin)
|
|
57
62
|
return false;
|
|
58
|
-
|
|
63
|
+
let commandIndex = args[0] === "--namespace" ? 2 : 0;
|
|
64
|
+
if (args[commandIndex] === "--session")
|
|
65
|
+
commandIndex += 2;
|
|
59
66
|
return args[commandIndex] === "snapshot";
|
|
60
67
|
}
|
|
61
68
|
export function alignPageChangeSummaryNextActionIds(summary, nextActions) {
|
|
@@ -65,26 +72,3 @@ export function alignPageChangeSummaryNextActionIds(summary, nextActions) {
|
|
|
65
72
|
const alignedIds = summary.nextActionIds.filter((id) => nextActionIds.has(id));
|
|
66
73
|
return alignedIds.length > 0 ? { ...summary, nextActionIds: alignedIds } : { ...summary, nextActionIds: undefined };
|
|
67
74
|
}
|
|
68
|
-
export class AgentBrowserNextActionCollector {
|
|
69
|
-
actions;
|
|
70
|
-
constructor(initialActions = undefined) {
|
|
71
|
-
this.actions = initialActions ? [...initialActions] : [];
|
|
72
|
-
}
|
|
73
|
-
append(actions) {
|
|
74
|
-
if (!actions || actions.length === 0)
|
|
75
|
-
return;
|
|
76
|
-
this.actions.push(...actions);
|
|
77
|
-
}
|
|
78
|
-
appendUnique(actions) {
|
|
79
|
-
appendUniqueAgentBrowserNextActions(this.actions, actions);
|
|
80
|
-
}
|
|
81
|
-
replace(actions) {
|
|
82
|
-
this.actions = actions ? [...actions] : [];
|
|
83
|
-
}
|
|
84
|
-
removeWhere(predicate) {
|
|
85
|
-
this.actions = this.actions.filter((action) => !predicate(action));
|
|
86
|
-
}
|
|
87
|
-
toArray() {
|
|
88
|
-
return this.actions.length > 0 ? [...this.actions] : undefined;
|
|
89
|
-
}
|
|
90
|
-
}
|