@mercuryo-ai/agentbrowse 0.2.61 → 0.2.63
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 +33 -1
- package/README.md +102 -9
- package/dist/browser-session-state.d.ts +2 -11
- package/dist/browser-session-state.d.ts.map +1 -1
- package/dist/browser-session-state.js +0 -4
- package/dist/commands/act.d.ts.map +1 -1
- package/dist/commands/act.js +14 -5
- package/dist/commands/attach.d.ts +1 -3
- package/dist/commands/attach.d.ts.map +1 -1
- package/dist/commands/attach.js +0 -2
- package/dist/commands/browser-status.d.ts +0 -2
- package/dist/commands/browser-status.d.ts.map +1 -1
- package/dist/commands/browser-status.js +1 -7
- package/dist/commands/interaction-kernel.d.ts +1 -1
- package/dist/commands/interaction-kernel.d.ts.map +1 -1
- package/dist/commands/interaction-kernel.js +1 -1
- package/dist/commands/launch.d.ts +0 -1
- package/dist/commands/launch.d.ts.map +1 -1
- package/dist/commands/launch.js +0 -4
- package/dist/commands/observe-accessibility.d.ts.map +1 -1
- package/dist/commands/observe-accessibility.js +36 -2
- package/dist/commands/observe-inventory.d.ts +49 -7
- package/dist/commands/observe-inventory.d.ts.map +1 -1
- package/dist/commands/observe-inventory.js +807 -96
- package/dist/commands/observe-persistence.d.ts.map +1 -1
- package/dist/commands/observe-persistence.js +49 -6
- package/dist/commands/observe-projection.d.ts +6 -2
- package/dist/commands/observe-projection.d.ts.map +1 -1
- package/dist/commands/observe-projection.js +251 -27
- package/dist/commands/observe-semantics.d.ts +1 -0
- package/dist/commands/observe-semantics.d.ts.map +1 -1
- package/dist/commands/observe-semantics.js +541 -135
- package/dist/commands/observe-signals.d.ts +4 -4
- package/dist/commands/observe-signals.d.ts.map +1 -1
- package/dist/commands/observe-signals.js +2 -2
- package/dist/commands/observe-surfaces.d.ts +2 -1
- package/dist/commands/observe-surfaces.d.ts.map +1 -1
- package/dist/commands/observe-surfaces.js +143 -45
- package/dist/commands/observe.d.ts +5 -1
- package/dist/commands/observe.d.ts.map +1 -1
- package/dist/commands/observe.js +15 -11
- package/dist/commands/semantic-observe.d.ts.map +1 -1
- package/dist/commands/semantic-observe.js +43 -0
- package/dist/library.d.ts +2 -1
- package/dist/library.d.ts.map +1 -1
- package/dist/library.js +2 -1
- package/dist/match-resolve-fill.d.ts +196 -0
- package/dist/match-resolve-fill.d.ts.map +1 -0
- package/dist/match-resolve-fill.js +700 -0
- package/dist/match-resolve-fill.test-support.d.ts +34 -0
- package/dist/match-resolve-fill.test-support.d.ts.map +1 -0
- package/dist/match-resolve-fill.test-support.js +81 -0
- package/dist/runtime-protected-state.d.ts.map +1 -1
- package/dist/runtime-protected-state.js +12 -0
- package/dist/runtime-state.d.ts +6 -0
- package/dist/runtime-state.d.ts.map +1 -1
- package/dist/runtime-state.js +6 -0
- package/dist/secrets/form-matcher.d.ts.map +1 -1
- package/dist/secrets/form-matcher.js +76 -27
- package/dist/secrets/protected-exact-value-redaction.d.ts.map +1 -1
- package/dist/secrets/protected-exact-value-redaction.js +6 -0
- package/dist/secrets/protected-fill.js +3 -3
- package/dist/session.d.ts +3 -3
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +2 -2
- package/dist/solver/browser-launcher.d.ts.map +1 -1
- package/dist/solver/browser-launcher.js +2 -1
- package/dist/testing.d.ts +1 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +1 -0
- package/docs/README.md +28 -11
- package/docs/api-reference.md +311 -19
- package/docs/assistive-runtime.md +41 -16
- package/docs/getting-started.md +45 -1
- package/docs/integration-checklist.md +32 -3
- package/docs/match-resolve-fill.md +699 -0
- package/docs/protected-fill.md +373 -91
- package/docs/testing.md +147 -15
- package/docs/troubleshooting.md +5 -0
- package/examples/README.md +7 -0
- package/examples/match-resolve-fill.ts +107 -0
- package/package.json +4 -2
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
import { actBrowser } from './commands/act.js';
|
|
2
|
+
import { resolveObservedField, } from './secrets/observed-field-resolution.js';
|
|
3
|
+
const INTERNAL_VALUE_ACCESSOR = Symbol('agentbrowse.internalValueAccessor');
|
|
4
|
+
function hasResolveCapability(resolver) {
|
|
5
|
+
return (Boolean(resolver) &&
|
|
6
|
+
typeof resolver.resolve === 'function');
|
|
7
|
+
}
|
|
8
|
+
function hasGroupFillCapability(resolver) {
|
|
9
|
+
return (Boolean(resolver) &&
|
|
10
|
+
typeof resolver.fill === 'function');
|
|
11
|
+
}
|
|
12
|
+
function isMatchStore(value) {
|
|
13
|
+
return Boolean(value) && typeof value === 'object' && 'entries' in value && 'read' in value;
|
|
14
|
+
}
|
|
15
|
+
function isGroupMatchStore(value) {
|
|
16
|
+
return (Boolean(value) && typeof value === 'object' && 'entries' in value && 'readArtifact' in value);
|
|
17
|
+
}
|
|
18
|
+
function isGroupSubject(subject) {
|
|
19
|
+
return (subject !== null &&
|
|
20
|
+
typeof subject === 'object' &&
|
|
21
|
+
'fillRef' in subject &&
|
|
22
|
+
'fields' in subject &&
|
|
23
|
+
Array.isArray(subject.fields));
|
|
24
|
+
}
|
|
25
|
+
function createAccessor(resourcesByRef) {
|
|
26
|
+
return {
|
|
27
|
+
read(resourceRef) {
|
|
28
|
+
return resourcesByRef.get(resourceRef);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function createValueRef(candidateRef, seed) {
|
|
33
|
+
return `value:${candidateRef}:${seed}:${Math.abs(candidateRef.length)}`;
|
|
34
|
+
}
|
|
35
|
+
function createArtifactRef(candidateRef, seed) {
|
|
36
|
+
return `artifact:${candidateRef}:${seed}:${Math.abs(candidateRef.length)}`;
|
|
37
|
+
}
|
|
38
|
+
function createResolvedValueRef(candidateRef) {
|
|
39
|
+
return `value:${candidateRef}:resolved`;
|
|
40
|
+
}
|
|
41
|
+
function createResolvedArtifactRef(candidateRef) {
|
|
42
|
+
return `artifact:${candidateRef}:resolved`;
|
|
43
|
+
}
|
|
44
|
+
function inferValueType(fieldKey, value) {
|
|
45
|
+
if (typeof value === 'number') {
|
|
46
|
+
return 'number';
|
|
47
|
+
}
|
|
48
|
+
if (typeof value !== 'string') {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim())) {
|
|
52
|
+
return 'email';
|
|
53
|
+
}
|
|
54
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(value.trim())) {
|
|
55
|
+
return 'date';
|
|
56
|
+
}
|
|
57
|
+
if (/^https?:\/\//.test(value.trim())) {
|
|
58
|
+
return 'url';
|
|
59
|
+
}
|
|
60
|
+
const normalizedFieldKey = fieldKey.trim().toLowerCase();
|
|
61
|
+
if (normalizedFieldKey.includes('password') || normalizedFieldKey.includes('pan')) {
|
|
62
|
+
return 'secret';
|
|
63
|
+
}
|
|
64
|
+
if (normalizedFieldKey.includes('email')) {
|
|
65
|
+
return 'email';
|
|
66
|
+
}
|
|
67
|
+
if (normalizedFieldKey.includes('date') ||
|
|
68
|
+
normalizedFieldKey.includes('birth') ||
|
|
69
|
+
normalizedFieldKey.includes('dob')) {
|
|
70
|
+
return 'date';
|
|
71
|
+
}
|
|
72
|
+
return 'text';
|
|
73
|
+
}
|
|
74
|
+
function placeholderValueFor(type) {
|
|
75
|
+
switch (type) {
|
|
76
|
+
case 'email':
|
|
77
|
+
return 'agentbrowse@example.com';
|
|
78
|
+
case 'date':
|
|
79
|
+
return '2000-01-01';
|
|
80
|
+
case 'number':
|
|
81
|
+
return 1;
|
|
82
|
+
case 'url':
|
|
83
|
+
return 'https://agentbrowse.example';
|
|
84
|
+
default:
|
|
85
|
+
return 'agentbrowse-placeholder';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function matchCandidateType(type) {
|
|
89
|
+
return type === 'secret' ? 'text' : type;
|
|
90
|
+
}
|
|
91
|
+
function normalizeSource(source) {
|
|
92
|
+
const readyValues = new Map();
|
|
93
|
+
const candidateRefByValueRef = new Map();
|
|
94
|
+
const storeSource = isMatchStore(source) ? source : null;
|
|
95
|
+
const entries = isMatchStore(source)
|
|
96
|
+
? [...source.entries()]
|
|
97
|
+
: Array.isArray(source)
|
|
98
|
+
? source.filter((entry) => 'fieldKey' in entry)
|
|
99
|
+
: Object.entries(source).map(([fieldKey, value]) => ({
|
|
100
|
+
fieldKey,
|
|
101
|
+
value,
|
|
102
|
+
}));
|
|
103
|
+
const candidates = entries.map((entry, index) => {
|
|
104
|
+
const candidateRef = entry.candidateRef ?? `candidate:${entry.fieldKey}:${index}`;
|
|
105
|
+
const resolvedType = entry.type ?? inferValueType(entry.fieldKey, entry.value);
|
|
106
|
+
const valueRef = entry.value !== undefined || (storeSource !== null && !entry.resolve)
|
|
107
|
+
? createValueRef(candidateRef, index)
|
|
108
|
+
: undefined;
|
|
109
|
+
if (valueRef && entry.value !== undefined) {
|
|
110
|
+
readyValues.set(valueRef, {
|
|
111
|
+
kind: 'value',
|
|
112
|
+
value: entry.value,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (valueRef) {
|
|
116
|
+
candidateRefByValueRef.set(valueRef, candidateRef);
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
candidateRef,
|
|
120
|
+
fieldKey: entry.fieldKey,
|
|
121
|
+
...(resolvedType ? { type: resolvedType } : {}),
|
|
122
|
+
...(entry.label ? { label: entry.label } : {}),
|
|
123
|
+
...(entry.semanticTags ? { semanticTags: [...entry.semanticTags] } : {}),
|
|
124
|
+
...(entry.applicability ? { applicability: { ...entry.applicability } } : {}),
|
|
125
|
+
...(valueRef ? { valueRef } : {}),
|
|
126
|
+
...(entry.resolve ? { resolve: { ...entry.resolve } } : {}),
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
const storeAccessor = storeSource
|
|
130
|
+
? {
|
|
131
|
+
read(candidateRef) {
|
|
132
|
+
return storeSource.read(candidateRef);
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
: null;
|
|
136
|
+
return {
|
|
137
|
+
candidates,
|
|
138
|
+
accessor: {
|
|
139
|
+
read(valueRef) {
|
|
140
|
+
const inlineValue = readyValues.get(valueRef);
|
|
141
|
+
if (inlineValue !== undefined) {
|
|
142
|
+
return inlineValue;
|
|
143
|
+
}
|
|
144
|
+
if (!storeAccessor) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
const candidateRef = candidateRefByValueRef.get(valueRef);
|
|
148
|
+
if (!candidateRef) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
const storedValue = storeAccessor.read(candidateRef);
|
|
152
|
+
return storedValue === undefined
|
|
153
|
+
? undefined
|
|
154
|
+
: {
|
|
155
|
+
kind: 'value',
|
|
156
|
+
value: storedValue,
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function normalizeGroupSource(source) {
|
|
163
|
+
const readyArtifacts = new Map();
|
|
164
|
+
const candidateRefByArtifactRef = new Map();
|
|
165
|
+
const storeSource = isGroupMatchStore(source) ? source : null;
|
|
166
|
+
const entries = isGroupMatchStore(source)
|
|
167
|
+
? source.entries()
|
|
168
|
+
: Array.isArray(source)
|
|
169
|
+
? source.filter((entry) => 'fieldKeys' in entry)
|
|
170
|
+
: [];
|
|
171
|
+
const candidates = entries.map((entry, index) => {
|
|
172
|
+
const candidateRef = entry.candidateRef ?? `group-candidate:${index}`;
|
|
173
|
+
const artifactRef = entry.artifact !== undefined || (storeSource !== null && !entry.resolve)
|
|
174
|
+
? createArtifactRef(candidateRef, index)
|
|
175
|
+
: undefined;
|
|
176
|
+
if (artifactRef && entry.artifact !== undefined) {
|
|
177
|
+
readyArtifacts.set(artifactRef, {
|
|
178
|
+
kind: 'artifact',
|
|
179
|
+
artifact: entry.artifact,
|
|
180
|
+
...(entry.itemRef ? { itemRef: entry.itemRef } : {}),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if (artifactRef) {
|
|
184
|
+
candidateRefByArtifactRef.set(artifactRef, candidateRef);
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
candidateRef,
|
|
188
|
+
...(entry.itemRef ? { itemRef: entry.itemRef } : {}),
|
|
189
|
+
...(entry.label ? { label: entry.label } : {}),
|
|
190
|
+
fieldKeys: [...entry.fieldKeys],
|
|
191
|
+
...(entry.confidence ? { confidence: entry.confidence } : {}),
|
|
192
|
+
...(entry.applicability ? { applicability: { ...entry.applicability } } : {}),
|
|
193
|
+
...(artifactRef ? { artifactRef } : {}),
|
|
194
|
+
...(entry.resolve ? { resolve: { ...entry.resolve } } : {}),
|
|
195
|
+
sortIndex: index,
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
return {
|
|
199
|
+
candidates,
|
|
200
|
+
accessor: {
|
|
201
|
+
read(artifactRef) {
|
|
202
|
+
const inlineArtifact = readyArtifacts.get(artifactRef);
|
|
203
|
+
if (inlineArtifact !== undefined) {
|
|
204
|
+
return inlineArtifact;
|
|
205
|
+
}
|
|
206
|
+
if (!storeSource) {
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
const candidateRef = candidateRefByArtifactRef.get(artifactRef);
|
|
210
|
+
if (!candidateRef) {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
const storedArtifact = storeSource.readArtifact(candidateRef);
|
|
214
|
+
return storedArtifact === undefined
|
|
215
|
+
? undefined
|
|
216
|
+
: {
|
|
217
|
+
kind: 'artifact',
|
|
218
|
+
artifact: storedArtifact,
|
|
219
|
+
};
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function toObservedFieldCandidates(candidates, accessor) {
|
|
225
|
+
return candidates.map((candidate) => {
|
|
226
|
+
const matchingType = matchCandidateType(candidate.type);
|
|
227
|
+
const resource = candidate.valueRef !== undefined ? accessor.read(candidate.valueRef) : undefined;
|
|
228
|
+
const value = resource?.kind === 'value' ? resource.value : placeholderValueFor(matchingType);
|
|
229
|
+
return {
|
|
230
|
+
candidateRef: candidate.candidateRef,
|
|
231
|
+
fieldKey: candidate.fieldKey,
|
|
232
|
+
value,
|
|
233
|
+
source: 'profile_facts',
|
|
234
|
+
...(matchingType ? { type: matchingType } : {}),
|
|
235
|
+
...(candidate.label ? { label: candidate.label } : {}),
|
|
236
|
+
...(candidate.semanticTags ? { semanticTags: [...candidate.semanticTags] } : {}),
|
|
237
|
+
...(candidate.applicability ? { applicability: { ...candidate.applicability } } : {}),
|
|
238
|
+
};
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
function normalizeTarget(target) {
|
|
242
|
+
const availability = typeof target.availability === 'string'
|
|
243
|
+
? { state: target.availability }
|
|
244
|
+
: (target.availability ?? { state: 'available' });
|
|
245
|
+
return {
|
|
246
|
+
ref: target.ref,
|
|
247
|
+
pageRef: target.pageRef ?? 'p0',
|
|
248
|
+
kind: target.kind ?? 'input',
|
|
249
|
+
label: target.label,
|
|
250
|
+
displayLabel: target.displayLabel,
|
|
251
|
+
placeholder: target.placeholder,
|
|
252
|
+
inputName: target.inputName,
|
|
253
|
+
inputType: target.inputType,
|
|
254
|
+
autocomplete: target.autocomplete,
|
|
255
|
+
selection: target.selection,
|
|
256
|
+
capability: target.capability ?? 'actionable',
|
|
257
|
+
lifecycle: target.lifecycle ?? 'live',
|
|
258
|
+
availability,
|
|
259
|
+
allowedActions: target.allowedActions ?? ['fill', 'type', 'select'],
|
|
260
|
+
locatorCandidates: [],
|
|
261
|
+
createdAt: 0,
|
|
262
|
+
...(target.context && typeof target.context === 'object' ? { context: target.context } : {}),
|
|
263
|
+
...(target.structure ? { structure: target.structure } : {}),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function attachAccessor(result, accessor) {
|
|
267
|
+
Object.defineProperty(result, INTERNAL_VALUE_ACCESSOR, {
|
|
268
|
+
value: accessor,
|
|
269
|
+
enumerable: false,
|
|
270
|
+
configurable: false,
|
|
271
|
+
writable: false,
|
|
272
|
+
});
|
|
273
|
+
return result;
|
|
274
|
+
}
|
|
275
|
+
function missingValueFailure(targetRef) {
|
|
276
|
+
return {
|
|
277
|
+
success: false,
|
|
278
|
+
failureSurface: 'contract',
|
|
279
|
+
error: 'match_value_unavailable',
|
|
280
|
+
outcomeType: 'blocked',
|
|
281
|
+
message: 'The matched value is no longer available for deterministic fill.',
|
|
282
|
+
reason: 'AgentBrowse could not dereference the internal value ref for the selected match.',
|
|
283
|
+
targetRef,
|
|
284
|
+
action: 'fill',
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function missingArtifactFailure(fillRef) {
|
|
288
|
+
return {
|
|
289
|
+
success: false,
|
|
290
|
+
failureSurface: 'contract',
|
|
291
|
+
error: 'match_artifact_unavailable',
|
|
292
|
+
outcomeType: 'blocked',
|
|
293
|
+
message: 'The matched artifact is no longer available for deterministic protected fill.',
|
|
294
|
+
reason: 'AgentBrowse could not dereference the internal artifact ref for the selected match.',
|
|
295
|
+
fillRef,
|
|
296
|
+
action: 'fill',
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function groupedResolverRequiredFailure(fillRef) {
|
|
300
|
+
return {
|
|
301
|
+
success: false,
|
|
302
|
+
failureSurface: 'contract',
|
|
303
|
+
error: 'match_resolver_required',
|
|
304
|
+
outcomeType: 'unsupported',
|
|
305
|
+
message: 'This grouped match needs a resolver that can apply the resolved artifact before AgentBrowse can fill the form.',
|
|
306
|
+
reason: 'The selected match resolves to a grouped protected artifact, not a direct field value.',
|
|
307
|
+
fillRef,
|
|
308
|
+
action: 'fill',
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function applicabilityMatches(applicability, host) {
|
|
312
|
+
if (!applicability || applicability.target === 'global') {
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
if (!host || !applicability.value) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
return applicability.value.trim().toLowerCase() === host.trim().toLowerCase();
|
|
319
|
+
}
|
|
320
|
+
function confidenceRank(confidence) {
|
|
321
|
+
if (confidence === 'high') {
|
|
322
|
+
return 2;
|
|
323
|
+
}
|
|
324
|
+
if (confidence === 'medium') {
|
|
325
|
+
return 1;
|
|
326
|
+
}
|
|
327
|
+
return 0;
|
|
328
|
+
}
|
|
329
|
+
function compareGroupCandidates(left, right) {
|
|
330
|
+
const overlapDelta = right.overlapCount - left.overlapCount;
|
|
331
|
+
if (overlapDelta !== 0) {
|
|
332
|
+
return overlapDelta;
|
|
333
|
+
}
|
|
334
|
+
const confidenceDelta = confidenceRank(right.confidence) - confidenceRank(left.confidence);
|
|
335
|
+
if (confidenceDelta !== 0) {
|
|
336
|
+
return confidenceDelta;
|
|
337
|
+
}
|
|
338
|
+
const labelDelta = (left.label ?? '').localeCompare(right.label ?? '');
|
|
339
|
+
if (labelDelta !== 0) {
|
|
340
|
+
return labelDelta;
|
|
341
|
+
}
|
|
342
|
+
const sortIndexDelta = left.sortIndex - right.sortIndex;
|
|
343
|
+
if (sortIndexDelta !== 0) {
|
|
344
|
+
return sortIndexDelta;
|
|
345
|
+
}
|
|
346
|
+
return left.candidateRef.localeCompare(right.candidateRef);
|
|
347
|
+
}
|
|
348
|
+
function matchGroup(subject, options) {
|
|
349
|
+
const { candidates, accessor } = normalizeGroupSource(options.from);
|
|
350
|
+
if (candidates.length === 0) {
|
|
351
|
+
return {
|
|
352
|
+
kind: 'no_match_group',
|
|
353
|
+
fillRef: subject.fillRef,
|
|
354
|
+
reason: 'no_candidate',
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
const scopeEligible = candidates.filter((candidate) => applicabilityMatches(candidate.applicability, options.host));
|
|
358
|
+
if (scopeEligible.length === 0) {
|
|
359
|
+
return {
|
|
360
|
+
kind: 'no_match_group',
|
|
361
|
+
fillRef: subject.fillRef,
|
|
362
|
+
reason: 'scope_ineligible',
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
const fieldKeys = subject.fields.map((field) => field.fieldKey);
|
|
366
|
+
const fieldKeySet = new Set(fieldKeys);
|
|
367
|
+
const ranked = scopeEligible
|
|
368
|
+
.map((candidate) => ({
|
|
369
|
+
...candidate,
|
|
370
|
+
overlapCount: candidate.fieldKeys.filter((fieldKey) => fieldKeySet.has(fieldKey)).length,
|
|
371
|
+
}))
|
|
372
|
+
.filter((candidate) => candidate.overlapCount > 0)
|
|
373
|
+
.sort(compareGroupCandidates);
|
|
374
|
+
const selected = ranked[0];
|
|
375
|
+
if (!selected) {
|
|
376
|
+
return {
|
|
377
|
+
kind: 'no_match_group',
|
|
378
|
+
fillRef: subject.fillRef,
|
|
379
|
+
reason: 'incompatible_shape',
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
const confidence = selected.overlapCount === fieldKeySet.size ? 'high' : 'medium';
|
|
383
|
+
const ambiguousCandidates = ranked.filter((candidate) => candidate.overlapCount === selected.overlapCount &&
|
|
384
|
+
confidenceRank(candidate.confidence) === confidenceRank(selected.confidence));
|
|
385
|
+
if (ambiguousCandidates.length > 1) {
|
|
386
|
+
return {
|
|
387
|
+
kind: 'ambiguous_group',
|
|
388
|
+
fillRef: subject.fillRef,
|
|
389
|
+
candidates: ambiguousCandidates.map((candidate) => candidate.candidateRef),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
if (selected.resolve) {
|
|
393
|
+
return {
|
|
394
|
+
kind: 'needs_resolution_group',
|
|
395
|
+
fillRef: subject.fillRef,
|
|
396
|
+
purpose: subject.purpose,
|
|
397
|
+
candidateRef: selected.candidateRef,
|
|
398
|
+
...(selected.itemRef ? { itemRef: selected.itemRef } : {}),
|
|
399
|
+
fieldKeys,
|
|
400
|
+
confidence,
|
|
401
|
+
plan: {
|
|
402
|
+
fillRef: subject.fillRef,
|
|
403
|
+
pageRef: subject.pageRef,
|
|
404
|
+
...(subject.scopeRef ? { scopeRef: subject.scopeRef } : {}),
|
|
405
|
+
purpose: subject.purpose,
|
|
406
|
+
candidateRef: selected.candidateRef,
|
|
407
|
+
...(selected.itemRef ? { itemRef: selected.itemRef } : {}),
|
|
408
|
+
fieldKeys,
|
|
409
|
+
resolve: { ...selected.resolve },
|
|
410
|
+
},
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
if (!selected.artifactRef) {
|
|
414
|
+
return {
|
|
415
|
+
kind: 'no_match_group',
|
|
416
|
+
fillRef: subject.fillRef,
|
|
417
|
+
reason: 'no_candidate',
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
return attachAccessor({
|
|
421
|
+
kind: 'ready_group',
|
|
422
|
+
fillRef: subject.fillRef,
|
|
423
|
+
purpose: subject.purpose,
|
|
424
|
+
candidateRef: selected.candidateRef,
|
|
425
|
+
...(selected.itemRef ? { itemRef: selected.itemRef } : {}),
|
|
426
|
+
fieldKeys,
|
|
427
|
+
artifactRef: selected.artifactRef,
|
|
428
|
+
confidence,
|
|
429
|
+
}, accessor);
|
|
430
|
+
}
|
|
431
|
+
export async function match(subject, options) {
|
|
432
|
+
if (isGroupSubject(subject)) {
|
|
433
|
+
return matchGroup(subject, options);
|
|
434
|
+
}
|
|
435
|
+
const normalizedTarget = normalizeTarget(subject);
|
|
436
|
+
const { candidates, accessor } = normalizeSource(options.from);
|
|
437
|
+
const resolution = resolveObservedField(normalizedTarget, toObservedFieldCandidates(candidates, accessor), {
|
|
438
|
+
host: options.host,
|
|
439
|
+
protectedTargetRefs: options.protectedTargetRefs,
|
|
440
|
+
});
|
|
441
|
+
if (resolution.status === 'ambiguous') {
|
|
442
|
+
return {
|
|
443
|
+
kind: 'ambiguous',
|
|
444
|
+
targetRef: resolution.targetRef,
|
|
445
|
+
candidates: resolution.candidates,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
if (resolution.status === 'no_match') {
|
|
449
|
+
return {
|
|
450
|
+
kind: 'no_match',
|
|
451
|
+
targetRef: resolution.targetRef,
|
|
452
|
+
reason: resolution.reason,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
const selected = candidates.find((candidate) => candidate.candidateRef === resolution.candidateRef);
|
|
456
|
+
if (!selected) {
|
|
457
|
+
return {
|
|
458
|
+
kind: 'no_match',
|
|
459
|
+
targetRef: resolution.targetRef,
|
|
460
|
+
reason: 'no_candidate',
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
const confidence = resolution.confidence;
|
|
464
|
+
if (selected.resolve) {
|
|
465
|
+
return {
|
|
466
|
+
kind: 'needs_resolution',
|
|
467
|
+
targetRef: resolution.targetRef,
|
|
468
|
+
fieldKey: resolution.fieldKey,
|
|
469
|
+
candidateRef: selected.candidateRef,
|
|
470
|
+
confidence,
|
|
471
|
+
plan: {
|
|
472
|
+
targetRef: resolution.targetRef,
|
|
473
|
+
candidateRef: selected.candidateRef,
|
|
474
|
+
fieldKey: resolution.fieldKey,
|
|
475
|
+
...(selected.type ? { type: selected.type } : {}),
|
|
476
|
+
resolve: { ...selected.resolve },
|
|
477
|
+
},
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
if (!selected.valueRef) {
|
|
481
|
+
return {
|
|
482
|
+
kind: 'no_match',
|
|
483
|
+
targetRef: resolution.targetRef,
|
|
484
|
+
reason: 'no_candidate',
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
return attachAccessor({
|
|
488
|
+
kind: 'ready',
|
|
489
|
+
targetRef: resolution.targetRef,
|
|
490
|
+
fieldKey: resolution.fieldKey,
|
|
491
|
+
candidateRef: selected.candidateRef,
|
|
492
|
+
valueRef: selected.valueRef,
|
|
493
|
+
confidence,
|
|
494
|
+
}, accessor);
|
|
495
|
+
}
|
|
496
|
+
function resolveSingleResolvedMatch(plan, resolved) {
|
|
497
|
+
if (plan.kind === 'needs_resolution') {
|
|
498
|
+
if (resolved.kind !== 'value') {
|
|
499
|
+
throw new Error('AgentBrowse resolver returned an artifact for a field resolution plan.');
|
|
500
|
+
}
|
|
501
|
+
const valueRef = createResolvedValueRef(plan.candidateRef);
|
|
502
|
+
const accessor = createAccessor(new Map([[valueRef, resolved]]));
|
|
503
|
+
return attachAccessor({
|
|
504
|
+
kind: 'ready',
|
|
505
|
+
targetRef: plan.targetRef,
|
|
506
|
+
fieldKey: plan.fieldKey,
|
|
507
|
+
candidateRef: plan.candidateRef,
|
|
508
|
+
valueRef,
|
|
509
|
+
confidence: plan.confidence,
|
|
510
|
+
}, accessor);
|
|
511
|
+
}
|
|
512
|
+
if (resolved.kind !== 'artifact') {
|
|
513
|
+
throw new Error('AgentBrowse resolver returned a field value for a grouped resolution plan.');
|
|
514
|
+
}
|
|
515
|
+
const artifactRef = createResolvedArtifactRef(plan.candidateRef);
|
|
516
|
+
const accessor = createAccessor(new Map([[artifactRef, resolved]]));
|
|
517
|
+
return attachAccessor({
|
|
518
|
+
kind: 'ready_group',
|
|
519
|
+
fillRef: plan.fillRef,
|
|
520
|
+
purpose: plan.purpose,
|
|
521
|
+
candidateRef: plan.candidateRef,
|
|
522
|
+
...(plan.itemRef ? { itemRef: plan.itemRef } : {}),
|
|
523
|
+
fieldKeys: [...plan.fieldKeys],
|
|
524
|
+
artifactRef,
|
|
525
|
+
confidence: plan.confidence,
|
|
526
|
+
...(resolved.requestId ? { requestId: resolved.requestId } : {}),
|
|
527
|
+
...(resolved.resolutionPath ? { resolutionPath: resolved.resolutionPath } : {}),
|
|
528
|
+
...(resolved.claimedAt ? { claimedAt: resolved.claimedAt } : {}),
|
|
529
|
+
}, accessor);
|
|
530
|
+
}
|
|
531
|
+
export async function resolve(plan, options) {
|
|
532
|
+
if (!Array.isArray(plan)) {
|
|
533
|
+
const singlePlan = plan;
|
|
534
|
+
if (singlePlan.kind !== 'needs_resolution' && singlePlan.kind !== 'needs_resolution_group') {
|
|
535
|
+
return singlePlan;
|
|
536
|
+
}
|
|
537
|
+
if (!options.with.resolve) {
|
|
538
|
+
throw new Error('AgentBrowse resolve(...) was called with a plan that needs resolution, but the adapter does not implement `resolve`.');
|
|
539
|
+
}
|
|
540
|
+
const resolved = await options.with.resolve(singlePlan.plan);
|
|
541
|
+
return resolveSingleResolvedMatch(singlePlan, resolved);
|
|
542
|
+
}
|
|
543
|
+
const unresolvedEntries = plan
|
|
544
|
+
.map((entry, index) => ({ entry, index }))
|
|
545
|
+
.filter((item) => item.entry.kind === 'needs_resolution' || item.entry.kind === 'needs_resolution_group');
|
|
546
|
+
if (unresolvedEntries.length === 0) {
|
|
547
|
+
return [...plan];
|
|
548
|
+
}
|
|
549
|
+
const unresolvedPlans = unresolvedEntries.map(({ entry }) => entry.plan);
|
|
550
|
+
const singleResolve = options.with.resolve;
|
|
551
|
+
if (unresolvedPlans.length > 0 && !options.with.resolveBatch && !singleResolve) {
|
|
552
|
+
throw new Error('AgentBrowse resolve(...) received plans that need resolution, but the adapter does not implement `resolve` or `resolveBatch`.');
|
|
553
|
+
}
|
|
554
|
+
const resolvedResources = unresolvedPlans.length > 1 && options.with.resolveBatch
|
|
555
|
+
? await options.with.resolveBatch(unresolvedPlans)
|
|
556
|
+
: await Promise.all(unresolvedPlans.map((entry) => singleResolve(entry)));
|
|
557
|
+
if (resolvedResources.length !== unresolvedEntries.length) {
|
|
558
|
+
throw new Error('AgentBrowse resolver returned an unexpected batch result length.');
|
|
559
|
+
}
|
|
560
|
+
const next = [...plan];
|
|
561
|
+
unresolvedEntries.forEach(({ entry, index }, resolvedIndex) => {
|
|
562
|
+
next[index] = resolveSingleResolvedMatch(entry, resolvedResources[resolvedIndex]);
|
|
563
|
+
});
|
|
564
|
+
return next;
|
|
565
|
+
}
|
|
566
|
+
export async function fill(session, subject, plan, options = {}) {
|
|
567
|
+
if (isGroupSubject(subject)) {
|
|
568
|
+
if (plan.kind === 'no_match_group') {
|
|
569
|
+
return {
|
|
570
|
+
success: false,
|
|
571
|
+
failureSurface: 'contract',
|
|
572
|
+
error: 'match_no_match',
|
|
573
|
+
outcomeType: 'blocked',
|
|
574
|
+
message: 'AgentBrowse could not find a deterministic grouped plan for this form.',
|
|
575
|
+
reason: plan.reason,
|
|
576
|
+
fillRef: subject.fillRef,
|
|
577
|
+
action: 'fill',
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
if (plan.kind === 'ambiguous_group') {
|
|
581
|
+
return {
|
|
582
|
+
success: false,
|
|
583
|
+
failureSurface: 'contract',
|
|
584
|
+
error: 'match_ambiguous',
|
|
585
|
+
outcomeType: 'blocked',
|
|
586
|
+
message: 'AgentBrowse found several competing grouped candidates for this form.',
|
|
587
|
+
reason: `Competing candidate refs: ${plan.candidates.join(', ')}`,
|
|
588
|
+
fillRef: subject.fillRef,
|
|
589
|
+
action: 'fill',
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
if (plan.kind === 'needs_resolution_group') {
|
|
593
|
+
if (!hasResolveCapability(options.resolver)) {
|
|
594
|
+
return {
|
|
595
|
+
success: false,
|
|
596
|
+
failureSurface: 'contract',
|
|
597
|
+
error: 'match_resolver_required',
|
|
598
|
+
outcomeType: 'unsupported',
|
|
599
|
+
message: 'This grouped match needs an explicit resolver before AgentBrowse can fill the form.',
|
|
600
|
+
reason: 'The selected grouped candidate does not carry a ready artifact ref.',
|
|
601
|
+
fillRef: subject.fillRef,
|
|
602
|
+
action: 'fill',
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
const [resolved] = (await resolve([plan], {
|
|
606
|
+
with: options.resolver,
|
|
607
|
+
}));
|
|
608
|
+
return fill(session, subject, resolved, options);
|
|
609
|
+
}
|
|
610
|
+
if (plan.kind === 'ready_group') {
|
|
611
|
+
if (!hasGroupFillCapability(options.resolver)) {
|
|
612
|
+
return groupedResolverRequiredFailure(subject.fillRef);
|
|
613
|
+
}
|
|
614
|
+
const accessor = plan[INTERNAL_VALUE_ACCESSOR];
|
|
615
|
+
const resolvedArtifact = accessor?.read(plan.artifactRef);
|
|
616
|
+
if (resolvedArtifact?.kind !== 'artifact') {
|
|
617
|
+
return missingArtifactFailure(subject.fillRef);
|
|
618
|
+
}
|
|
619
|
+
return options.resolver.fill(session, subject, {
|
|
620
|
+
candidateRef: plan.candidateRef,
|
|
621
|
+
itemRef: plan.itemRef ?? resolvedArtifact.itemRef,
|
|
622
|
+
fieldKeys: [...plan.fieldKeys],
|
|
623
|
+
artifact: resolvedArtifact.artifact,
|
|
624
|
+
requestId: plan.requestId ?? resolvedArtifact.requestId,
|
|
625
|
+
resolutionPath: plan.resolutionPath ?? resolvedArtifact.resolutionPath,
|
|
626
|
+
claimedAt: plan.claimedAt ?? resolvedArtifact.claimedAt,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
return {
|
|
630
|
+
success: false,
|
|
631
|
+
failureSurface: 'contract',
|
|
632
|
+
error: 'match_no_match',
|
|
633
|
+
outcomeType: 'blocked',
|
|
634
|
+
message: 'The provided match result belongs to an individual field, not a grouped form.',
|
|
635
|
+
reason: 'AgentBrowse received a field-level match result for a grouped protected fill subject.',
|
|
636
|
+
fillRef: subject.fillRef,
|
|
637
|
+
action: 'fill',
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
if (plan.kind === 'no_match') {
|
|
641
|
+
return {
|
|
642
|
+
success: false,
|
|
643
|
+
failureSurface: 'contract',
|
|
644
|
+
error: 'match_no_match',
|
|
645
|
+
outcomeType: 'blocked',
|
|
646
|
+
message: 'AgentBrowse could not find a deterministic value for this target.',
|
|
647
|
+
reason: plan.reason,
|
|
648
|
+
targetRef: subject.ref,
|
|
649
|
+
action: 'fill',
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
if (plan.kind === 'no_match_group' ||
|
|
653
|
+
plan.kind === 'ambiguous_group' ||
|
|
654
|
+
plan.kind === 'needs_resolution_group' ||
|
|
655
|
+
plan.kind === 'ready_group') {
|
|
656
|
+
return {
|
|
657
|
+
success: false,
|
|
658
|
+
failureSurface: 'contract',
|
|
659
|
+
error: 'match_no_match',
|
|
660
|
+
outcomeType: 'blocked',
|
|
661
|
+
message: 'The provided match result belongs to a grouped form, not an individual field target.',
|
|
662
|
+
reason: 'AgentBrowse received a grouped match result for a single-target fill subject.',
|
|
663
|
+
targetRef: subject.ref,
|
|
664
|
+
action: 'fill',
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
if (plan.kind === 'ambiguous') {
|
|
668
|
+
return {
|
|
669
|
+
success: false,
|
|
670
|
+
failureSurface: 'contract',
|
|
671
|
+
error: 'match_ambiguous',
|
|
672
|
+
outcomeType: 'blocked',
|
|
673
|
+
message: 'AgentBrowse found several competing values for this target.',
|
|
674
|
+
reason: `Competing candidate refs: ${plan.candidates.join(', ')}`,
|
|
675
|
+
targetRef: subject.ref,
|
|
676
|
+
action: 'fill',
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
if (plan.kind === 'needs_resolution') {
|
|
680
|
+
if (!hasResolveCapability(options.resolver)) {
|
|
681
|
+
return {
|
|
682
|
+
success: false,
|
|
683
|
+
failureSurface: 'contract',
|
|
684
|
+
error: 'match_resolver_required',
|
|
685
|
+
outcomeType: 'unsupported',
|
|
686
|
+
message: 'This match needs an explicit resolver before AgentBrowse can fill the target.',
|
|
687
|
+
reason: 'The selected candidate does not carry a ready value ref.',
|
|
688
|
+
targetRef: subject.ref,
|
|
689
|
+
action: 'fill',
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
return fill(session, subject, await resolve(plan, { with: options.resolver }), options);
|
|
693
|
+
}
|
|
694
|
+
const accessor = plan[INTERNAL_VALUE_ACCESSOR];
|
|
695
|
+
const resolvedValue = accessor?.read(plan.valueRef);
|
|
696
|
+
if (resolvedValue?.kind !== 'value') {
|
|
697
|
+
return missingValueFailure(subject.ref);
|
|
698
|
+
}
|
|
699
|
+
return actBrowser(session, subject.ref, 'fill', String(resolvedValue.value));
|
|
700
|
+
}
|