pi-agent-browser-native 0.2.48 → 0.2.49
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 +17 -0
- package/README.md +16 -6
- package/dist/extensions/agent-browser/index.js +785 -0
- package/dist/extensions/agent-browser/lib/argv-descriptor.js +71 -0
- package/dist/extensions/agent-browser/lib/argv-grammar.js +121 -0
- package/dist/extensions/agent-browser/lib/bash-guard.js +190 -0
- package/dist/extensions/agent-browser/lib/command-policy.js +85 -0
- package/dist/extensions/agent-browser/lib/command-taxonomy.js +302 -0
- package/dist/extensions/agent-browser/lib/config-policy.js +686 -0
- package/dist/extensions/agent-browser/lib/config.js +122 -0
- package/dist/extensions/agent-browser/lib/electron/cdp.js +51 -0
- package/dist/extensions/agent-browser/lib/electron/cleanup.js +212 -0
- package/dist/extensions/agent-browser/lib/electron/discovery.js +633 -0
- package/dist/extensions/agent-browser/lib/electron/launch.js +351 -0
- package/{extensions/agent-browser/lib/electron/text.ts → dist/extensions/agent-browser/lib/electron/text.js} +5 -5
- package/dist/extensions/agent-browser/lib/executable-path.js +20 -0
- package/dist/extensions/agent-browser/lib/fs-utils.js +18 -0
- package/dist/extensions/agent-browser/lib/input-modes/electron.js +165 -0
- package/dist/extensions/agent-browser/lib/input-modes/job.js +519 -0
- package/dist/extensions/agent-browser/lib/input-modes/lookups.js +440 -0
- package/dist/extensions/agent-browser/lib/input-modes/params.js +164 -0
- package/dist/extensions/agent-browser/lib/input-modes/semantic-action.js +119 -0
- package/dist/extensions/agent-browser/lib/input-modes/shared.js +42 -0
- package/dist/extensions/agent-browser/lib/input-modes/types.js +21 -0
- package/dist/extensions/agent-browser/lib/input-modes.js +10 -0
- package/dist/extensions/agent-browser/lib/json-schema.js +58 -0
- package/dist/extensions/agent-browser/lib/launch-scoped-flags.js +59 -0
- package/dist/extensions/agent-browser/lib/navigation-policy.js +83 -0
- package/dist/extensions/agent-browser/lib/orchestration/batch-stdin.js +62 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.js +39 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.js +276 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.js +909 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/final-result.js +443 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/index.js +47 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.js +141 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.js +108 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.js +112 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.js +158 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.js +54 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prepare.js +762 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/process-output.js +491 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.js +40 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.js +5 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/session-state.js +731 -0
- package/dist/extensions/agent-browser/lib/orchestration/browser-run/types.js +1 -0
- package/dist/extensions/agent-browser/lib/orchestration/electron-host/index.js +718 -0
- package/dist/extensions/agent-browser/lib/orchestration/input-plan.js +247 -0
- package/dist/extensions/agent-browser/lib/orchestration/output-file.js +68 -0
- package/{extensions/agent-browser/lib/parsing.ts → dist/extensions/agent-browser/lib/parsing.js} +12 -11
- package/dist/extensions/agent-browser/lib/pi-tool-rendering.js +241 -0
- package/dist/extensions/agent-browser/lib/playbook.js +121 -0
- package/dist/extensions/agent-browser/lib/process.js +448 -0
- package/dist/extensions/agent-browser/lib/prompt-policy.js +91 -0
- package/dist/extensions/agent-browser/lib/results/action-recommendations.js +220 -0
- package/dist/extensions/agent-browser/lib/results/artifact-manifest.js +111 -0
- package/{extensions/agent-browser/lib/results/artifact-state.ts → dist/extensions/agent-browser/lib/results/artifact-state.js} +4 -8
- package/dist/extensions/agent-browser/lib/results/categories.js +76 -0
- package/dist/extensions/agent-browser/lib/results/confirmation.js +63 -0
- package/dist/extensions/agent-browser/lib/results/contracts.js +8 -0
- package/dist/extensions/agent-browser/lib/results/editable-ref-evidence.js +74 -0
- package/dist/extensions/agent-browser/lib/results/envelope.js +166 -0
- package/dist/extensions/agent-browser/lib/results/network-routes.js +92 -0
- package/dist/extensions/agent-browser/lib/results/network.js +73 -0
- package/dist/extensions/agent-browser/lib/results/next-actions.js +72 -0
- package/dist/extensions/agent-browser/lib/results/presentation/artifacts.js +515 -0
- package/dist/extensions/agent-browser/lib/results/presentation/batch.js +397 -0
- package/dist/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.js +55 -0
- package/dist/extensions/agent-browser/lib/results/presentation/common.js +46 -0
- package/dist/extensions/agent-browser/lib/results/presentation/content.js +24 -0
- package/dist/extensions/agent-browser/lib/results/presentation/diagnostics.js +960 -0
- package/dist/extensions/agent-browser/lib/results/presentation/errors.js +205 -0
- package/dist/extensions/agent-browser/lib/results/presentation/large-output.js +134 -0
- package/dist/extensions/agent-browser/lib/results/presentation/navigation.js +159 -0
- package/dist/extensions/agent-browser/lib/results/presentation/registry.js +216 -0
- package/dist/extensions/agent-browser/lib/results/presentation/semantic-action.js +104 -0
- package/dist/extensions/agent-browser/lib/results/presentation/skills.js +152 -0
- package/dist/extensions/agent-browser/lib/results/presentation.js +177 -0
- package/dist/extensions/agent-browser/lib/results/recovery-actions.js +107 -0
- package/dist/extensions/agent-browser/lib/results/recovery-next-actions.js +50 -0
- package/dist/extensions/agent-browser/lib/results/selector-recovery.js +225 -0
- package/{extensions/agent-browser/lib/results/shared.ts → dist/extensions/agent-browser/lib/results/shared.js} +0 -1
- package/dist/extensions/agent-browser/lib/results/snapshot-high-value-controls.js +208 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-refs.js +78 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-segments.js +331 -0
- package/dist/extensions/agent-browser/lib/results/snapshot-spill.js +40 -0
- package/dist/extensions/agent-browser/lib/results/snapshot.js +264 -0
- package/dist/extensions/agent-browser/lib/results/text.js +40 -0
- package/{extensions/agent-browser/lib/results.ts → dist/extensions/agent-browser/lib/results.js} +2 -32
- package/dist/extensions/agent-browser/lib/runtime.js +816 -0
- package/dist/extensions/agent-browser/lib/session-page-state.js +411 -0
- package/dist/extensions/agent-browser/lib/string-enum-schema.js +13 -0
- package/dist/extensions/agent-browser/lib/temp.js +498 -0
- package/dist/extensions/agent-browser/lib/web-search.js +562 -0
- package/docs/RELEASE.md +22 -11
- package/docs/SUPPORT_MATRIX.md +4 -3
- package/package.json +9 -5
- package/scripts/config.mjs +8 -2
- package/scripts/doctor.mjs +8 -7
- package/extensions/agent-browser/index.ts +0 -961
- package/extensions/agent-browser/lib/argv-descriptor.ts +0 -90
- package/extensions/agent-browser/lib/argv-grammar.ts +0 -128
- package/extensions/agent-browser/lib/bash-guard.ts +0 -205
- package/extensions/agent-browser/lib/command-policy.ts +0 -71
- package/extensions/agent-browser/lib/command-taxonomy.ts +0 -336
- package/extensions/agent-browser/lib/config-policy.js +0 -690
- package/extensions/agent-browser/lib/config.ts +0 -211
- package/extensions/agent-browser/lib/electron/cdp.ts +0 -69
- package/extensions/agent-browser/lib/electron/cleanup.ts +0 -235
- package/extensions/agent-browser/lib/electron/discovery.ts +0 -710
- package/extensions/agent-browser/lib/electron/launch.ts +0 -499
- package/extensions/agent-browser/lib/executable-path.ts +0 -19
- package/extensions/agent-browser/lib/fs-utils.ts +0 -18
- package/extensions/agent-browser/lib/input-modes/electron.ts +0 -170
- package/extensions/agent-browser/lib/input-modes/job.ts +0 -527
- package/extensions/agent-browser/lib/input-modes/lookups.ts +0 -447
- package/extensions/agent-browser/lib/input-modes/params.ts +0 -205
- package/extensions/agent-browser/lib/input-modes/semantic-action.ts +0 -127
- package/extensions/agent-browser/lib/input-modes/shared.ts +0 -46
- package/extensions/agent-browser/lib/input-modes/types.ts +0 -225
- package/extensions/agent-browser/lib/input-modes.ts +0 -45
- package/extensions/agent-browser/lib/json-schema.ts +0 -73
- package/extensions/agent-browser/lib/launch-scoped-flags.ts +0 -67
- package/extensions/agent-browser/lib/navigation-policy.ts +0 -95
- package/extensions/agent-browser/lib/orchestration/batch-stdin.ts +0 -65
- package/extensions/agent-browser/lib/orchestration/browser-run/artifact-paths.ts +0 -44
- package/extensions/agent-browser/lib/orchestration/browser-run/click-dispatch.ts +0 -280
- package/extensions/agent-browser/lib/orchestration/browser-run/diagnostics.ts +0 -914
- package/extensions/agent-browser/lib/orchestration/browser-run/final-result.ts +0 -521
- package/extensions/agent-browser/lib/orchestration/browser-run/index.ts +0 -53
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/direct-anchor-download.ts +0 -158
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/network-page-filter.ts +0 -116
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/scroll-shims.ts +0 -147
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/snapshot-filter.ts +0 -183
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare/wait-timeouts.ts +0 -58
- package/extensions/agent-browser/lib/orchestration/browser-run/prepare.ts +0 -847
- package/extensions/agent-browser/lib/orchestration/browser-run/process-output.ts +0 -559
- package/extensions/agent-browser/lib/orchestration/browser-run/prompt-guards.ts +0 -47
- package/extensions/agent-browser/lib/orchestration/browser-run/session-artifacts.ts +0 -8
- package/extensions/agent-browser/lib/orchestration/browser-run/session-state.ts +0 -868
- package/extensions/agent-browser/lib/orchestration/browser-run/types.ts +0 -565
- package/extensions/agent-browser/lib/orchestration/electron-host/index.ts +0 -855
- package/extensions/agent-browser/lib/orchestration/input-plan.ts +0 -375
- package/extensions/agent-browser/lib/orchestration/output-file.ts +0 -86
- package/extensions/agent-browser/lib/pi-tool-rendering.ts +0 -267
- package/extensions/agent-browser/lib/playbook.ts +0 -142
- package/extensions/agent-browser/lib/process.ts +0 -516
- package/extensions/agent-browser/lib/prompt-policy.ts +0 -105
- package/extensions/agent-browser/lib/results/action-recommendations.ts +0 -264
- package/extensions/agent-browser/lib/results/artifact-manifest.ts +0 -111
- package/extensions/agent-browser/lib/results/categories.ts +0 -106
- package/extensions/agent-browser/lib/results/confirmation.ts +0 -76
- package/extensions/agent-browser/lib/results/contracts.ts +0 -241
- package/extensions/agent-browser/lib/results/editable-ref-evidence.ts +0 -72
- package/extensions/agent-browser/lib/results/envelope.ts +0 -195
- package/extensions/agent-browser/lib/results/network-routes.ts +0 -83
- package/extensions/agent-browser/lib/results/network.ts +0 -78
- package/extensions/agent-browser/lib/results/next-actions.ts +0 -117
- package/extensions/agent-browser/lib/results/presentation/artifacts.ts +0 -588
- package/extensions/agent-browser/lib/results/presentation/batch.ts +0 -450
- package/extensions/agent-browser/lib/results/presentation/browser-profile-recovery.ts +0 -67
- package/extensions/agent-browser/lib/results/presentation/common.ts +0 -53
- package/extensions/agent-browser/lib/results/presentation/content.ts +0 -36
- package/extensions/agent-browser/lib/results/presentation/diagnostics.ts +0 -923
- package/extensions/agent-browser/lib/results/presentation/errors.ts +0 -227
- package/extensions/agent-browser/lib/results/presentation/large-output.ts +0 -182
- package/extensions/agent-browser/lib/results/presentation/navigation.ts +0 -184
- package/extensions/agent-browser/lib/results/presentation/registry.ts +0 -242
- package/extensions/agent-browser/lib/results/presentation/semantic-action.ts +0 -131
- package/extensions/agent-browser/lib/results/presentation/skills.ts +0 -143
- package/extensions/agent-browser/lib/results/presentation.ts +0 -257
- package/extensions/agent-browser/lib/results/recovery-actions.ts +0 -139
- package/extensions/agent-browser/lib/results/recovery-next-actions.ts +0 -71
- package/extensions/agent-browser/lib/results/selector-recovery.ts +0 -320
- package/extensions/agent-browser/lib/results/snapshot-high-value-controls.ts +0 -273
- package/extensions/agent-browser/lib/results/snapshot-refs.ts +0 -100
- package/extensions/agent-browser/lib/results/snapshot-segments.ts +0 -366
- package/extensions/agent-browser/lib/results/snapshot-spill.ts +0 -63
- package/extensions/agent-browser/lib/results/snapshot.ts +0 -329
- package/extensions/agent-browser/lib/results/text.ts +0 -40
- package/extensions/agent-browser/lib/runtime.ts +0 -988
- package/extensions/agent-browser/lib/session-page-state.ts +0 -512
- package/extensions/agent-browser/lib/string-enum-schema.ts +0 -20
- package/extensions/agent-browser/lib/temp.ts +0 -577
- package/extensions/agent-browser/lib/web-search.ts +0 -728
- /package/{extensions/agent-browser/lib/orchestration/browser-run.ts → dist/extensions/agent-browser/lib/orchestration/browser-run.js} +0 -0
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Own pure selector-miss recovery diagnostics for visible refs and rich editable inputs.
|
|
3
|
-
* Responsibilities: Parse find/semantic action targets, match current snapshot refs, build public diagnostics, text, and safe nextActions.
|
|
4
|
-
* Scope: Selector recovery policy only; subprocess snapshot probing and result orchestration stay in the extension entrypoint.
|
|
5
|
-
* Usage: The extension entrypoint supplies command tokens plus snapshot data after a selector-not-found failure.
|
|
6
|
-
* Invariants/Assumptions: Public fill recovery must never echo or auto-submit the user-provided fill text; guarded semanticAction fill pre-resolution may execute only one exact current editable ref.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { isRecord } from "../parsing.js";
|
|
10
|
-
import { extractRefSnapshotFromData, type SessionRefSnapshot } from "../session-page-state.js";
|
|
11
|
-
import { getEditableRefEvidence } from "./editable-ref-evidence.js";
|
|
12
|
-
import { type AgentBrowserNextAction, withOptionalSessionArgs } from "./next-actions.js";
|
|
13
|
-
import {
|
|
14
|
-
getAgentBrowserRichInputRecoveryNextActionId,
|
|
15
|
-
getAgentBrowserRichInputRecoveryNextActionIds,
|
|
16
|
-
} from "./recovery-actions.js";
|
|
17
|
-
import {
|
|
18
|
-
getSnapshotLineTextByRef,
|
|
19
|
-
getSnapshotRefRecord,
|
|
20
|
-
getSnapshotRefRole,
|
|
21
|
-
} from "./snapshot-refs.js";
|
|
22
|
-
import { compareRefIds } from "./text.js";
|
|
23
|
-
|
|
24
|
-
export type SelectorRecoveryActionName = "check" | "click" | "fill" | "select" | "uncheck";
|
|
25
|
-
|
|
26
|
-
export interface SelectorRecoveryCompiledAction {
|
|
27
|
-
action: SelectorRecoveryActionName;
|
|
28
|
-
args: string[];
|
|
29
|
-
locator?: string;
|
|
30
|
-
selector?: string;
|
|
31
|
-
values?: string[];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface VisibleRefFallbackCandidate {
|
|
35
|
-
action: SelectorRecoveryActionName;
|
|
36
|
-
args?: string[];
|
|
37
|
-
editableEvidence?: boolean;
|
|
38
|
-
name: string;
|
|
39
|
-
reason: string;
|
|
40
|
-
ref: string;
|
|
41
|
-
role: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface VisibleRefFallbackDiagnostic {
|
|
45
|
-
candidates: VisibleRefFallbackCandidate[];
|
|
46
|
-
snapshot: SessionRefSnapshot;
|
|
47
|
-
summary: string;
|
|
48
|
-
target: {
|
|
49
|
-
action: SelectorRecoveryActionName;
|
|
50
|
-
roles: string[];
|
|
51
|
-
targetName: string;
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface PublicVisibleRefFallbackCandidate {
|
|
56
|
-
action: SelectorRecoveryActionName;
|
|
57
|
-
args?: string[];
|
|
58
|
-
name: string;
|
|
59
|
-
reason: string;
|
|
60
|
-
ref: string;
|
|
61
|
-
role: string;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface PublicVisibleRefFallbackDiagnostic {
|
|
65
|
-
candidates: PublicVisibleRefFallbackCandidate[];
|
|
66
|
-
snapshot: SessionRefSnapshot;
|
|
67
|
-
summary: string;
|
|
68
|
-
target: VisibleRefFallbackDiagnostic["target"];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export interface VisibleRefFallbackTarget {
|
|
72
|
-
action: SelectorRecoveryActionName;
|
|
73
|
-
roles: string[];
|
|
74
|
-
text?: string;
|
|
75
|
-
targetName: string;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export interface RichInputRecoveryCandidate {
|
|
79
|
-
clickArgs: string[];
|
|
80
|
-
focusArgs: string[];
|
|
81
|
-
name: string;
|
|
82
|
-
reason: string;
|
|
83
|
-
ref: string;
|
|
84
|
-
role: string;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export interface RichInputRecoveryDiagnostic {
|
|
88
|
-
candidates: RichInputRecoveryCandidate[];
|
|
89
|
-
inputMethodHint: string;
|
|
90
|
-
nextActionIds: string[];
|
|
91
|
-
summary: string;
|
|
92
|
-
target: {
|
|
93
|
-
roles: string[];
|
|
94
|
-
targetName: string;
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const SELECTOR_RECOVERY_ACTION_NAMES = new Set<SelectorRecoveryActionName>(["check", "click", "fill", "select", "uncheck"]);
|
|
99
|
-
const VISIBLE_REF_FALLBACK_CANDIDATE_LIMIT = 3;
|
|
100
|
-
const EDITABLE_CONTROL_ROLES = new Set(["combobox", "searchbox", "textbox"]);
|
|
101
|
-
const RICH_INPUT_RECOVERY_EDITABLE_ROLES = new Set(["searchbox", "textbox"]);
|
|
102
|
-
const RICH_INPUT_RECOVERY_HINT = "After the editable ref is focused, use keyboard inserttext or keyboard type with the intended text in a separate call, and do not press Enter or otherwise submit unless the user flow explicitly calls for it.";
|
|
103
|
-
|
|
104
|
-
function isSelectorRecoveryActionName(action: string): action is SelectorRecoveryActionName {
|
|
105
|
-
return SELECTOR_RECOVERY_ACTION_NAMES.has(action as SelectorRecoveryActionName);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function getFindNameFlagValue(args: string[], startIndex: number): string | undefined {
|
|
109
|
-
const nameFlagIndex = args.indexOf("--name", startIndex);
|
|
110
|
-
const name = nameFlagIndex >= 0 ? args[nameFlagIndex + 1] : undefined;
|
|
111
|
-
return name && !name.startsWith("-") ? name : undefined;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function getFindVisibleRefFallbackTarget(args: string[], options: { allowLeadingDashFillText?: boolean } = {}): VisibleRefFallbackTarget | undefined {
|
|
115
|
-
const findIndex = args[0] === "--session" ? 2 : 0;
|
|
116
|
-
if (args[findIndex] !== "find") return undefined;
|
|
117
|
-
const locator = args[findIndex + 1];
|
|
118
|
-
const value = args[findIndex + 2];
|
|
119
|
-
const action = args[findIndex + 3];
|
|
120
|
-
if (!locator || !value || !isSelectorRecoveryActionName(action) || action === "select") return undefined;
|
|
121
|
-
const text = action === "fill" ? args[findIndex + 4] : undefined;
|
|
122
|
-
if (action === "fill" && (!text || (!options.allowLeadingDashFillText && text.startsWith("-")))) return undefined;
|
|
123
|
-
if (locator === "role") {
|
|
124
|
-
const targetName = getFindNameFlagValue(args, findIndex + 4);
|
|
125
|
-
return targetName ? { action, roles: [value], targetName, text } : undefined;
|
|
126
|
-
}
|
|
127
|
-
if (locator === "text" && action === "click") {
|
|
128
|
-
return { action, roles: ["button", "link"], targetName: value };
|
|
129
|
-
}
|
|
130
|
-
if (locator === "text" && action === "fill") {
|
|
131
|
-
return { action, roles: ["searchbox", "textbox"], targetName: value, text };
|
|
132
|
-
}
|
|
133
|
-
if (locator === "label" && action === "fill") {
|
|
134
|
-
return { action, roles: ["textbox"], targetName: value, text };
|
|
135
|
-
}
|
|
136
|
-
if (locator === "placeholder" && action === "fill") {
|
|
137
|
-
return { action, roles: ["searchbox", "textbox"], targetName: value, text };
|
|
138
|
-
}
|
|
139
|
-
return undefined;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export function getVisibleRefFallbackTarget(options: {
|
|
143
|
-
commandTokens: string[];
|
|
144
|
-
compiledSemanticAction?: SelectorRecoveryCompiledAction;
|
|
145
|
-
}): VisibleRefFallbackTarget | undefined {
|
|
146
|
-
return getFindVisibleRefFallbackTarget(options.commandTokens, { allowLeadingDashFillText: true }) ?? (options.compiledSemanticAction ? getFindVisibleRefFallbackTarget(options.compiledSemanticAction.args, { allowLeadingDashFillText: true }) : undefined);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function getVisibleRefFallbackCandidates(target: VisibleRefFallbackTarget, snapshotData: unknown): VisibleRefFallbackCandidate[] {
|
|
150
|
-
const refs = getSnapshotRefRecord(snapshotData);
|
|
151
|
-
if (!refs) return [];
|
|
152
|
-
const snapshotLineByRef = getSnapshotLineTextByRef(snapshotData);
|
|
153
|
-
const roleOrder = target.roles.map((role) => role.toLowerCase());
|
|
154
|
-
const targetName = normalizeSemanticActionAccessibleName(target.targetName);
|
|
155
|
-
const candidates = Object.entries(refs).flatMap(([ref, entry]): VisibleRefFallbackCandidate[] => {
|
|
156
|
-
if (!/^e\d+$/.test(ref) || !isRecord(entry)) return [];
|
|
157
|
-
const snapshotLine = snapshotLineByRef.get(ref);
|
|
158
|
-
const editableEvidence = getEditableRefEvidence({ ref: entry, text: snapshotLine });
|
|
159
|
-
const role = getSnapshotRefRole(entry, editableEvidence);
|
|
160
|
-
const name = typeof entry.name === "string" ? entry.name : undefined;
|
|
161
|
-
if (!role || !name || !roleOrder.includes(role.toLowerCase()) || normalizeSemanticActionAccessibleName(name) !== targetName) return [];
|
|
162
|
-
if (target.action === "fill" && editableEvidence === false && EDITABLE_CONTROL_ROLES.has(role.toLowerCase())) return [];
|
|
163
|
-
const directRefArgs = target.action === "fill" ? undefined : [target.action, `@${ref}`];
|
|
164
|
-
return [{
|
|
165
|
-
action: target.action,
|
|
166
|
-
...(directRefArgs ? { args: directRefArgs } : {}),
|
|
167
|
-
name,
|
|
168
|
-
reason: `Current snapshot shows ${role} ${JSON.stringify(name)} at @${ref}, matching the failed ${target.action} locator exactly.`,
|
|
169
|
-
ref: `@${ref}`,
|
|
170
|
-
role,
|
|
171
|
-
...(editableEvidence !== undefined ? { editableEvidence } : {}),
|
|
172
|
-
}];
|
|
173
|
-
});
|
|
174
|
-
candidates.sort((left, right) => roleOrder.indexOf(left.role.toLowerCase()) - roleOrder.indexOf(right.role.toLowerCase()) || compareRefIds(left.ref.slice(1), right.ref.slice(1)));
|
|
175
|
-
return candidates.slice(0, VISIBLE_REF_FALLBACK_CANDIDATE_LIMIT);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export function buildVisibleRefFallbackDiagnosticFromSnapshot(options: {
|
|
179
|
-
snapshotData: unknown;
|
|
180
|
-
target: VisibleRefFallbackTarget;
|
|
181
|
-
}): VisibleRefFallbackDiagnostic | undefined {
|
|
182
|
-
const snapshot = extractRefSnapshotFromData(options.snapshotData);
|
|
183
|
-
if (!snapshot) return undefined;
|
|
184
|
-
const candidates = getVisibleRefFallbackCandidates(options.target, options.snapshotData);
|
|
185
|
-
if (candidates.length === 0) return undefined;
|
|
186
|
-
return {
|
|
187
|
-
candidates,
|
|
188
|
-
snapshot,
|
|
189
|
-
summary: candidates.length === 1
|
|
190
|
-
? `Current snapshot has one exact visible ref match for ${options.target.action} ${JSON.stringify(options.target.targetName)}.`
|
|
191
|
-
: `Current snapshot has ${candidates.length} exact visible ref matches for ${options.target.action} ${JSON.stringify(options.target.targetName)}; choose only if the intended control is unambiguous.`,
|
|
192
|
-
target: { action: options.target.action, roles: options.target.roles, targetName: options.target.targetName },
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
export interface VisibleRefActionResolution {
|
|
197
|
-
args: string[];
|
|
198
|
-
snapshot: SessionRefSnapshot;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
export function resolveVisibleRefActionFromSnapshot(options: {
|
|
202
|
-
allowFill?: boolean;
|
|
203
|
-
compiledAction: SelectorRecoveryCompiledAction;
|
|
204
|
-
snapshotData: unknown;
|
|
205
|
-
}): VisibleRefActionResolution | undefined {
|
|
206
|
-
const target = getFindVisibleRefFallbackTarget(options.compiledAction.args, { allowLeadingDashFillText: true });
|
|
207
|
-
if (!target || target.action === "select") return undefined;
|
|
208
|
-
const snapshot = extractRefSnapshotFromData(options.snapshotData);
|
|
209
|
-
if (!snapshot) return undefined;
|
|
210
|
-
const candidates = getVisibleRefFallbackCandidates(target, options.snapshotData);
|
|
211
|
-
if (target.action === "fill") {
|
|
212
|
-
if (!options.allowFill || candidates.length !== 1 || target.text === undefined) return undefined;
|
|
213
|
-
const [candidate] = candidates;
|
|
214
|
-
if (!candidate || candidate.editableEvidence === false || !EDITABLE_CONTROL_ROLES.has(candidate.role.toLowerCase())) return undefined;
|
|
215
|
-
return { args: ["fill", candidate.ref, target.text], snapshot };
|
|
216
|
-
}
|
|
217
|
-
const candidate = candidates.find((item) => item.args !== undefined);
|
|
218
|
-
if (!candidate?.args) return undefined;
|
|
219
|
-
return { args: candidate.args, snapshot };
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export function buildVisibleRefFallbackNextActions(options: { diagnostic: VisibleRefFallbackDiagnostic; sessionName?: string }): AgentBrowserNextAction[] {
|
|
223
|
-
const ambiguous = options.diagnostic.candidates.length > 1;
|
|
224
|
-
return options.diagnostic.candidates.flatMap((candidate, index) => candidate.args ? [{
|
|
225
|
-
id: ambiguous ? `try-current-visible-ref-${index + 1}` : "try-current-visible-ref",
|
|
226
|
-
params: { args: withOptionalSessionArgs(options.sessionName, candidate.args) },
|
|
227
|
-
reason: candidate.reason,
|
|
228
|
-
safety: ambiguous
|
|
229
|
-
? "Several current refs share the same exact role/name. Inspect the snapshot and use only the ref that clearly matches the intended target."
|
|
230
|
-
: "Use only while this current snapshot still represents the page; refresh refs first if the page changed.",
|
|
231
|
-
tool: "agent_browser" as const,
|
|
232
|
-
}] : []);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
export function formatVisibleRefFallbackText(diagnostic: VisibleRefFallbackDiagnostic | undefined): string | undefined {
|
|
236
|
-
if (!diagnostic) return undefined;
|
|
237
|
-
return [
|
|
238
|
-
"Current snapshot ref fallback:",
|
|
239
|
-
...diagnostic.candidates.map((candidate) => `- ${candidate.ref}${candidate.role ? ` ${candidate.role}` : ""} ${JSON.stringify(candidate.name)}: ${candidate.reason}`),
|
|
240
|
-
].join("\n");
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export function sanitizeVisibleRefFallbackDiagnostic(diagnostic: VisibleRefFallbackDiagnostic): PublicVisibleRefFallbackDiagnostic {
|
|
244
|
-
return {
|
|
245
|
-
candidates: diagnostic.candidates.map(({ editableEvidence: _editableEvidence, ...candidate }) => candidate),
|
|
246
|
-
snapshot: diagnostic.snapshot,
|
|
247
|
-
summary: diagnostic.summary,
|
|
248
|
-
target: diagnostic.target,
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function isRichInputRecoveryCandidate(candidate: VisibleRefFallbackCandidate): boolean {
|
|
253
|
-
return candidate.action === "fill" && candidate.editableEvidence !== false && RICH_INPUT_RECOVERY_EDITABLE_ROLES.has(candidate.role.toLowerCase());
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export function buildRichInputRecoveryDiagnostic(diagnostic: VisibleRefFallbackDiagnostic | undefined): RichInputRecoveryDiagnostic | undefined {
|
|
257
|
-
if (!diagnostic || diagnostic.target.action !== "fill") return undefined;
|
|
258
|
-
const candidates = diagnostic.candidates.filter(isRichInputRecoveryCandidate).map((candidate): RichInputRecoveryCandidate => ({
|
|
259
|
-
clickArgs: ["click", candidate.ref],
|
|
260
|
-
focusArgs: ["focus", candidate.ref],
|
|
261
|
-
name: candidate.name,
|
|
262
|
-
reason: `Current snapshot shows editable ${candidate.role} ${JSON.stringify(candidate.name)} at ${candidate.ref}; focus or click it before keyboard insertion instead of retrying fill with copied text.`,
|
|
263
|
-
ref: candidate.ref,
|
|
264
|
-
role: candidate.role,
|
|
265
|
-
}));
|
|
266
|
-
if (candidates.length === 0) return undefined;
|
|
267
|
-
return {
|
|
268
|
-
candidates,
|
|
269
|
-
inputMethodHint: RICH_INPUT_RECOVERY_HINT,
|
|
270
|
-
nextActionIds: getAgentBrowserRichInputRecoveryNextActionIds(candidates.length),
|
|
271
|
-
summary: candidates.length === 1
|
|
272
|
-
? "Fill locator missed, but the current snapshot has one exact editable ref candidate for safe keyboard-based recovery."
|
|
273
|
-
: `Fill locator missed, but the current snapshot has ${candidates.length} exact editable ref candidates; choose only if the intended input is unambiguous.`,
|
|
274
|
-
target: { roles: diagnostic.target.roles, targetName: diagnostic.target.targetName },
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
export function buildRichInputRecoveryNextActions(options: { diagnostic: RichInputRecoveryDiagnostic; sessionName?: string }): AgentBrowserNextAction[] {
|
|
279
|
-
const candidateCount = options.diagnostic.candidates.length;
|
|
280
|
-
const ambiguous = candidateCount > 1;
|
|
281
|
-
return options.diagnostic.candidates.flatMap((candidate, index): AgentBrowserNextAction[] => {
|
|
282
|
-
const focusId = getAgentBrowserRichInputRecoveryNextActionId("focus", index, candidateCount);
|
|
283
|
-
const clickId = getAgentBrowserRichInputRecoveryNextActionId("click", index, candidateCount);
|
|
284
|
-
const safety = ambiguous
|
|
285
|
-
? `Several editable refs share the same exact name. Inspect the current snapshot and use only the ${candidate.ref} ${candidate.role} if it is clearly the intended input. No fill text or submit key is included.`
|
|
286
|
-
: "Does not include fill text or submit the form. After focus/click succeeds, use keyboard inserttext or keyboard type with the intended text only if this is the right input.";
|
|
287
|
-
return [
|
|
288
|
-
{
|
|
289
|
-
id: focusId,
|
|
290
|
-
params: { args: withOptionalSessionArgs(options.sessionName, candidate.focusArgs) },
|
|
291
|
-
reason: candidate.reason,
|
|
292
|
-
safety,
|
|
293
|
-
tool: "agent_browser" as const,
|
|
294
|
-
},
|
|
295
|
-
{
|
|
296
|
-
id: clickId,
|
|
297
|
-
params: { args: withOptionalSessionArgs(options.sessionName, candidate.clickArgs) },
|
|
298
|
-
reason: `Click ${candidate.ref} to focus the editable ${candidate.role} before keyboard insertion when focus alone is insufficient.`,
|
|
299
|
-
safety: `${safety} A click may run normal focus/click handlers, but this action does not press Enter or auto-submit.`,
|
|
300
|
-
tool: "agent_browser" as const,
|
|
301
|
-
},
|
|
302
|
-
];
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
export function formatRichInputRecoveryText(diagnostic: RichInputRecoveryDiagnostic | undefined): string | undefined {
|
|
307
|
-
if (!diagnostic) return undefined;
|
|
308
|
-
return [
|
|
309
|
-
"Rich input recovery:",
|
|
310
|
-
...diagnostic.candidates.map((candidate, index) => {
|
|
311
|
-
const [focusId, clickId] = diagnostic.nextActionIds.slice(index * 2, index * 2 + 2);
|
|
312
|
-
return `- ${candidate.ref} ${candidate.role} ${JSON.stringify(candidate.name)}: use ${focusId} or ${clickId}; then use keyboard inserttext/type with the intended text.`;
|
|
313
|
-
}),
|
|
314
|
-
`- ${diagnostic.inputMethodHint}`,
|
|
315
|
-
].join("\n");
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
export function normalizeSemanticActionAccessibleName(name: string): string {
|
|
319
|
-
return name.replace(/\s+/g, " ").trim().toLowerCase();
|
|
320
|
-
}
|
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Select the omitted snapshot refs most likely to be actionable controls.
|
|
3
|
-
* Responsibilities: Classify editable, surface, primary-action, and role-based control refs with deterministic diversity and top-up rules.
|
|
4
|
-
* Scope: High-value control ranking only; snapshot parsing, noise filtering, and presentation text live in neighboring modules.
|
|
5
|
-
* Usage: Snapshot presentation passes already-visible, non-noise omitted refs and receives the bounded high-value subset to surface.
|
|
6
|
-
* Invariants/Assumptions: Ranking must preserve scarce control categories before filling dominant buckets so dense desktop hosts stay navigable.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { SnapshotRefEntry } from "./snapshot-refs.js";
|
|
10
|
-
import { compareRefIds } from "./text.js";
|
|
11
|
-
|
|
12
|
-
const SNAPSHOT_HIGH_VALUE_EDITABLE_REF_FILL_TARGET_LINES = 4;
|
|
13
|
-
const SNAPSHOT_HIGH_VALUE_SURFACE_REF_FILL_TARGET_LINES = 3;
|
|
14
|
-
const SNAPSHOT_HIGH_VALUE_PRIMARY_ACTION_REF_FILL_TARGET_LINES = 3;
|
|
15
|
-
|
|
16
|
-
const SNAPSHOT_HIGH_VALUE_CONTROL_ROLES = new Set([
|
|
17
|
-
"button",
|
|
18
|
-
"checkbox",
|
|
19
|
-
"combobox",
|
|
20
|
-
"link",
|
|
21
|
-
"menuitem",
|
|
22
|
-
"option",
|
|
23
|
-
"radio",
|
|
24
|
-
"searchbox",
|
|
25
|
-
"tab",
|
|
26
|
-
"textbox",
|
|
27
|
-
]);
|
|
28
|
-
|
|
29
|
-
const SNAPSHOT_HIGH_VALUE_CONTROL_ROLE_PRIORITY: Record<string, number> = {
|
|
30
|
-
searchbox: 0,
|
|
31
|
-
textbox: 1,
|
|
32
|
-
combobox: 2,
|
|
33
|
-
button: 3,
|
|
34
|
-
link: 4,
|
|
35
|
-
tab: 5,
|
|
36
|
-
checkbox: 6,
|
|
37
|
-
radio: 7,
|
|
38
|
-
option: 8,
|
|
39
|
-
menuitem: 9,
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const SNAPSHOT_SURFACE_CONTROL_NAME_PATTERNS = [
|
|
43
|
-
/\b(?:agents?|browser|canvas|chat|editor|panel|pane|preview|surface|tab|terminal|thread|view|window|workspace)\b/i,
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
const SNAPSHOT_PRIMARY_ACTION_BUTTON_NAME_PATTERNS = [
|
|
47
|
-
/^(?:add|apply|ask|attach|choose|confirm|connect|continue|create|deploy|done|download|go|insert|launch|log in|new|next|ok|open|publish|refresh|retry|run|save|search|select|send|sign in|sign up|start|submit|upload)$/i,
|
|
48
|
-
/^(?:add|apply|ask|confirm|connect|continue|create|launch|new|open|refresh|retry|run|save|search|send|start|submit)\b/i,
|
|
49
|
-
];
|
|
50
|
-
|
|
51
|
-
const SNAPSHOT_HIGH_VALUE_LINK_NAME_PATTERNS = [
|
|
52
|
-
/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/i,
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
function getHighValueControlRole(entry: SnapshotRefEntry): string {
|
|
56
|
-
return entry.isEditable === true && (entry.role === "unknown" || entry.role === "generic") ? "textbox" : entry.role;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function isEditableControlRef(entry: SnapshotRefEntry): boolean {
|
|
60
|
-
if (entry.isEditable === false) return false;
|
|
61
|
-
const role = getHighValueControlRole(entry);
|
|
62
|
-
return entry.isEditable === true || role === "searchbox" || role === "textbox" || role === "combobox";
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function isNamedSurfaceControlRef(entry: SnapshotRefEntry): boolean {
|
|
66
|
-
if (entry.name.length === 0) return false;
|
|
67
|
-
const role = getHighValueControlRole(entry);
|
|
68
|
-
if (role === "tab") return true;
|
|
69
|
-
if (role !== "button" && role !== "menuitem" && role !== "option") return false;
|
|
70
|
-
return SNAPSHOT_SURFACE_CONTROL_NAME_PATTERNS.some((pattern) => pattern.test(entry.name));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function isPrimaryActionButtonRef(entry: SnapshotRefEntry): boolean {
|
|
74
|
-
return (
|
|
75
|
-
getHighValueControlRole(entry) === "button" &&
|
|
76
|
-
entry.name.length > 0 &&
|
|
77
|
-
SNAPSHOT_PRIMARY_ACTION_BUTTON_NAME_PATTERNS.some((pattern) => pattern.test(entry.name))
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
type HighValueControlCategory = "editable" | "named-surface" | "primary-action" | "role";
|
|
82
|
-
|
|
83
|
-
interface HighValueControlCategoryRule {
|
|
84
|
-
bucketKey(entry: SnapshotRefEntry, role: string): string;
|
|
85
|
-
fillTarget?: number;
|
|
86
|
-
id: HighValueControlCategory;
|
|
87
|
-
matches(entry: SnapshotRefEntry, role: string): boolean;
|
|
88
|
-
priority: number;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
interface HighValueControlScore {
|
|
92
|
-
category: HighValueControlCategory;
|
|
93
|
-
categoryPriority: number;
|
|
94
|
-
diversityBucketKey: string;
|
|
95
|
-
lineIndex: number;
|
|
96
|
-
namePriority: 0 | 1;
|
|
97
|
-
refId: string;
|
|
98
|
-
role: string;
|
|
99
|
-
rolePriority: number;
|
|
100
|
-
roundRobinBucketKey: string;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
interface HighValueControlCandidate {
|
|
104
|
-
entry: SnapshotRefEntry;
|
|
105
|
-
score: HighValueControlScore;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const SNAPSHOT_HIGH_VALUE_CONTROL_CATEGORY_RULES: readonly HighValueControlCategoryRule[] = [
|
|
109
|
-
{
|
|
110
|
-
bucketKey: () => "editable",
|
|
111
|
-
fillTarget: SNAPSHOT_HIGH_VALUE_EDITABLE_REF_FILL_TARGET_LINES,
|
|
112
|
-
id: "editable",
|
|
113
|
-
matches: (entry) => isEditableControlRef(entry),
|
|
114
|
-
priority: 0,
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
bucketKey: () => "named-surface",
|
|
118
|
-
fillTarget: SNAPSHOT_HIGH_VALUE_SURFACE_REF_FILL_TARGET_LINES,
|
|
119
|
-
id: "named-surface",
|
|
120
|
-
matches: (entry) => isNamedSurfaceControlRef(entry),
|
|
121
|
-
priority: 1,
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
bucketKey: () => "primary-action",
|
|
125
|
-
fillTarget: SNAPSHOT_HIGH_VALUE_PRIMARY_ACTION_REF_FILL_TARGET_LINES,
|
|
126
|
-
id: "primary-action",
|
|
127
|
-
matches: (entry) => isPrimaryActionButtonRef(entry),
|
|
128
|
-
priority: 2,
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
bucketKey: (_entry, role) => role,
|
|
132
|
-
id: "role",
|
|
133
|
-
matches: () => true,
|
|
134
|
-
priority: 3,
|
|
135
|
-
},
|
|
136
|
-
] as const;
|
|
137
|
-
|
|
138
|
-
function isHighValueLinkRef(entry: SnapshotRefEntry): boolean {
|
|
139
|
-
return entry.name.length > 0 && SNAPSHOT_HIGH_VALUE_LINK_NAME_PATTERNS.some((pattern) => pattern.test(entry.name));
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export function isHighValueControlEntry(entry: SnapshotRefEntry): boolean {
|
|
143
|
-
const role = getHighValueControlRole(entry);
|
|
144
|
-
if (!SNAPSHOT_HIGH_VALUE_CONTROL_ROLES.has(role)) return false;
|
|
145
|
-
if (role === "link") return isHighValueLinkRef(entry);
|
|
146
|
-
if (entry.isEditable === false && (role === "searchbox" || role === "textbox" || role === "combobox")) return false;
|
|
147
|
-
return entry.name.length > 0 || isEditableControlRef(entry);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function getHighValueControlCategoryRule(entry: SnapshotRefEntry, role: string): HighValueControlCategoryRule | undefined {
|
|
151
|
-
return SNAPSHOT_HIGH_VALUE_CONTROL_CATEGORY_RULES.find((rule) => rule.matches(entry, role));
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function classifyHighValueControlRef(entry: SnapshotRefEntry): HighValueControlCandidate | undefined {
|
|
155
|
-
if (!isHighValueControlEntry(entry)) return undefined;
|
|
156
|
-
const role = getHighValueControlRole(entry);
|
|
157
|
-
const rule = getHighValueControlCategoryRule(entry, role);
|
|
158
|
-
if (!rule) return undefined;
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
entry,
|
|
162
|
-
score: {
|
|
163
|
-
category: rule.id,
|
|
164
|
-
categoryPriority: rule.priority,
|
|
165
|
-
diversityBucketKey: `${rule.priority}:${rule.bucketKey(entry, role)}`,
|
|
166
|
-
lineIndex: entry.lineIndex ?? Number.MAX_SAFE_INTEGER,
|
|
167
|
-
namePriority: entry.name.length > 0 ? 0 : 1,
|
|
168
|
-
refId: entry.id,
|
|
169
|
-
role,
|
|
170
|
-
rolePriority: SNAPSHOT_HIGH_VALUE_CONTROL_ROLE_PRIORITY[role] ?? 50,
|
|
171
|
-
roundRobinBucketKey: `${rule.priority}:${role}`,
|
|
172
|
-
},
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function compareHighValueControlCandidates(left: HighValueControlCandidate, right: HighValueControlCandidate): number {
|
|
177
|
-
return (
|
|
178
|
-
left.score.categoryPriority - right.score.categoryPriority ||
|
|
179
|
-
left.score.rolePriority - right.score.rolePriority ||
|
|
180
|
-
left.score.namePriority - right.score.namePriority ||
|
|
181
|
-
left.score.lineIndex - right.score.lineIndex ||
|
|
182
|
-
compareRefIds(left.score.refId, right.score.refId)
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function takeHighValueCandidate(
|
|
187
|
-
candidate: HighValueControlCandidate,
|
|
188
|
-
selected: HighValueControlCandidate[],
|
|
189
|
-
selectedIds: Set<string>,
|
|
190
|
-
): void {
|
|
191
|
-
selected.push(candidate);
|
|
192
|
-
selectedIds.add(candidate.entry.id);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function takeFirstPerDiversityBucket(
|
|
196
|
-
candidates: HighValueControlCandidate[],
|
|
197
|
-
selected: HighValueControlCandidate[],
|
|
198
|
-
selectedIds: Set<string>,
|
|
199
|
-
limit: number,
|
|
200
|
-
): void {
|
|
201
|
-
const seenBuckets = new Set<string>();
|
|
202
|
-
for (const candidate of candidates) {
|
|
203
|
-
if (selected.length >= limit) break;
|
|
204
|
-
if (seenBuckets.has(candidate.score.diversityBucketKey)) continue;
|
|
205
|
-
seenBuckets.add(candidate.score.diversityBucketKey);
|
|
206
|
-
takeHighValueCandidate(candidate, selected, selectedIds);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function topUpHighValueCategory(
|
|
211
|
-
candidates: HighValueControlCandidate[],
|
|
212
|
-
selected: HighValueControlCandidate[],
|
|
213
|
-
selectedIds: Set<string>,
|
|
214
|
-
category: HighValueControlCategory,
|
|
215
|
-
target: number,
|
|
216
|
-
limit: number,
|
|
217
|
-
): void {
|
|
218
|
-
let count = selected.filter((candidate) => candidate.score.category === category).length;
|
|
219
|
-
for (const candidate of candidates) {
|
|
220
|
-
if (selected.length >= limit || count >= target) break;
|
|
221
|
-
if (selectedIds.has(candidate.entry.id) || candidate.score.category !== category) continue;
|
|
222
|
-
takeHighValueCandidate(candidate, selected, selectedIds);
|
|
223
|
-
count += 1;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function buildRemainingHighValueBuckets(
|
|
228
|
-
candidates: HighValueControlCandidate[],
|
|
229
|
-
selectedIds: Set<string>,
|
|
230
|
-
): HighValueControlCandidate[][] {
|
|
231
|
-
const buckets = new Map<string, HighValueControlCandidate[]>();
|
|
232
|
-
for (const candidate of candidates) {
|
|
233
|
-
if (selectedIds.has(candidate.entry.id)) continue;
|
|
234
|
-
const bucket = buckets.get(candidate.score.roundRobinBucketKey);
|
|
235
|
-
if (bucket) bucket.push(candidate);
|
|
236
|
-
else buckets.set(candidate.score.roundRobinBucketKey, [candidate]);
|
|
237
|
-
}
|
|
238
|
-
return [...buckets.values()].sort((left, right) => compareHighValueControlCandidates(left[0], right[0]));
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function roundRobinHighValueBuckets(
|
|
242
|
-
buckets: HighValueControlCandidate[][],
|
|
243
|
-
selected: HighValueControlCandidate[],
|
|
244
|
-
selectedIds: Set<string>,
|
|
245
|
-
limit: number,
|
|
246
|
-
): void {
|
|
247
|
-
let bucketIndex = 0;
|
|
248
|
-
while (selected.length < limit && buckets.some((bucket) => bucket.length > 0)) {
|
|
249
|
-
const bucket = buckets[bucketIndex % buckets.length];
|
|
250
|
-
const candidate = bucket.shift();
|
|
251
|
-
if (candidate) takeHighValueCandidate(candidate, selected, selectedIds);
|
|
252
|
-
bucketIndex += 1;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export function selectHighValueControlEntries(entries: SnapshotRefEntry[], limit: number): SnapshotRefEntry[] {
|
|
257
|
-
const candidates = entries
|
|
258
|
-
.map(classifyHighValueControlRef)
|
|
259
|
-
.filter((candidate): candidate is HighValueControlCandidate => Boolean(candidate))
|
|
260
|
-
.sort(compareHighValueControlCandidates);
|
|
261
|
-
const selected: HighValueControlCandidate[] = [];
|
|
262
|
-
const selectedIds = new Set<string>();
|
|
263
|
-
|
|
264
|
-
takeFirstPerDiversityBucket(candidates, selected, selectedIds, limit);
|
|
265
|
-
|
|
266
|
-
for (const rule of SNAPSHOT_HIGH_VALUE_CONTROL_CATEGORY_RULES) {
|
|
267
|
-
if (rule.fillTarget === undefined) continue;
|
|
268
|
-
topUpHighValueCategory(candidates, selected, selectedIds, rule.id, rule.fillTarget, limit);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
roundRobinHighValueBuckets(buildRemainingHighValueBuckets(candidates, selectedIds), selected, selectedIds, limit);
|
|
272
|
-
return selected.map((candidate) => candidate.entry);
|
|
273
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Purpose: Own canonical parsing and enrichment of refs from agent-browser snapshot payloads.
|
|
3
|
-
* Responsibilities: Read structured refs, correlate them with raw snapshot lines, and infer editable/ref role evidence once for consumers.
|
|
4
|
-
* Scope: Snapshot ref metadata only; section preview, high-value ranking, and presentation assembly live in neighboring modules.
|
|
5
|
-
* Usage: Imported by snapshot presentation and recovery diagnostics that need consistent ref/name/role/editable evidence.
|
|
6
|
-
* Invariants/Assumptions: Snapshot text parsing is best-effort and must tolerate upstream formatting changes by preserving structured ref data when line parsing fails.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { isRecord } from "../parsing.js";
|
|
10
|
-
import { getEditableRefEvidence } from "./editable-ref-evidence.js";
|
|
11
|
-
import { compareRefIds, normalizeWhitespace } from "./text.js";
|
|
12
|
-
|
|
13
|
-
export interface SnapshotRefEntry {
|
|
14
|
-
id: string;
|
|
15
|
-
isEditable?: boolean;
|
|
16
|
-
lineIndex?: number;
|
|
17
|
-
name: string;
|
|
18
|
-
refData?: Record<string, unknown>;
|
|
19
|
-
role: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface SnapshotLineRefInfo {
|
|
23
|
-
index: number;
|
|
24
|
-
name: string;
|
|
25
|
-
raw: string;
|
|
26
|
-
ref?: string;
|
|
27
|
-
role: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function getSnapshotRefRecord(data: unknown): Record<string, unknown> | undefined {
|
|
31
|
-
return isRecord(data) && isRecord(data.refs) ? data.refs : undefined;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function getSnapshotLineTextByRef(data: unknown): Map<string, string> {
|
|
35
|
-
const snapshot = isRecord(data) && typeof data.snapshot === "string" ? data.snapshot : "";
|
|
36
|
-
const lineByRef = new Map<string, string>();
|
|
37
|
-
for (const line of snapshot.split("\n")) {
|
|
38
|
-
const ref = line.match(/\bref=([^,\]\s]+)/)?.[1];
|
|
39
|
-
if (!ref || lineByRef.has(ref)) continue;
|
|
40
|
-
lineByRef.set(ref, line);
|
|
41
|
-
}
|
|
42
|
-
return lineByRef;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function getSnapshotRefEntries(data: Record<string, unknown>): SnapshotRefEntry[] {
|
|
46
|
-
const refs = getSnapshotRefRecord(data);
|
|
47
|
-
if (!refs) return [];
|
|
48
|
-
|
|
49
|
-
return Object.entries(refs)
|
|
50
|
-
.map(([id, value]) => {
|
|
51
|
-
if (!isRecord(value)) {
|
|
52
|
-
return { id, name: "", role: "unknown" } satisfies SnapshotRefEntry;
|
|
53
|
-
}
|
|
54
|
-
const name = typeof value.name === "string" ? normalizeWhitespace(value.name) : "";
|
|
55
|
-
const role = typeof value.role === "string" && value.role.length > 0 ? value.role : "unknown";
|
|
56
|
-
const isEditable = getEditableRefEvidence({ ref: value });
|
|
57
|
-
return { id, isEditable, name, refData: value, role } satisfies SnapshotRefEntry;
|
|
58
|
-
})
|
|
59
|
-
.sort((a, b) => compareRefIds(a.id, b.id));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function isEditableSnapshotLine(line: SnapshotLineRefInfo): boolean | undefined {
|
|
63
|
-
const editableEvidence = getEditableRefEvidence({ text: line.raw });
|
|
64
|
-
if (editableEvidence !== undefined) return editableEvidence;
|
|
65
|
-
return line.role === "searchbox" || line.role === "textbox" || line.role === "combobox" ? true : undefined;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function getSnapshotRefRole(entry: { role?: unknown }, editableEvidence: boolean | undefined): string {
|
|
69
|
-
const rawRole = typeof entry.role === "string" && entry.role.length > 0 ? entry.role : "unknown";
|
|
70
|
-
const normalizedRole = rawRole.toLowerCase();
|
|
71
|
-
if ((normalizedRole === "generic" || normalizedRole === "unknown") && editableEvidence === true) {
|
|
72
|
-
return "textbox";
|
|
73
|
-
}
|
|
74
|
-
return rawRole;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function enrichSnapshotRefEntries(refEntries: SnapshotRefEntry[], snapshotLines: SnapshotLineRefInfo[]): SnapshotRefEntry[] {
|
|
78
|
-
const lineByRef = new Map<string, SnapshotLineRefInfo>();
|
|
79
|
-
for (const line of snapshotLines) {
|
|
80
|
-
if (!line.ref || lineByRef.has(line.ref)) continue;
|
|
81
|
-
lineByRef.set(line.ref, line);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return refEntries.map((entry) => {
|
|
85
|
-
const line = lineByRef.get(entry.id);
|
|
86
|
-
const lineRole = line && line.role !== "unknown" ? line.role : undefined;
|
|
87
|
-
const editableEvidence = getEditableRefEvidence({ ref: entry.refData, text: line?.raw });
|
|
88
|
-
const hasEditableRole = line ? isEditableSnapshotLine(line) === true && !["unknown", "generic"].includes(line.role) : false;
|
|
89
|
-
const isEditable = editableEvidence === true || (editableEvidence !== false && hasEditableRole);
|
|
90
|
-
const roleFromRefOrLine = entry.role !== "unknown" && entry.role !== "generic" ? entry.role : lineRole ?? entry.role;
|
|
91
|
-
const role = getSnapshotRefRole({ role: roleFromRefOrLine }, isEditable);
|
|
92
|
-
return {
|
|
93
|
-
...entry,
|
|
94
|
-
isEditable,
|
|
95
|
-
lineIndex: line?.index,
|
|
96
|
-
name: entry.name.length > 0 ? entry.name : (line?.name ?? ""),
|
|
97
|
-
role,
|
|
98
|
-
} satisfies SnapshotRefEntry;
|
|
99
|
-
});
|
|
100
|
-
}
|