@mercuryo-ai/agentbrowse 0.2.57 → 0.2.61
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/README.md +76 -57
- package/dist/browser-session-state.d.ts +39 -0
- package/dist/browser-session-state.d.ts.map +1 -1
- package/dist/browser-session-state.js +63 -1
- package/dist/command-name.js +1 -1
- package/dist/commands/act.d.ts.map +1 -1
- package/dist/commands/act.js +540 -528
- package/dist/commands/action-executor-helpers.d.ts.map +1 -1
- package/dist/commands/action-executor-helpers.js +10 -8
- package/dist/commands/attach.d.ts.map +1 -1
- package/dist/commands/attach.js +5 -10
- package/dist/commands/browser-connection-failure.d.ts +9 -0
- package/dist/commands/browser-connection-failure.d.ts.map +1 -0
- package/dist/commands/browser-connection-failure.js +15 -0
- package/dist/commands/browser-status.d.ts.map +1 -1
- package/dist/commands/browser-status.js +26 -30
- package/dist/commands/click-activation-policy.d.ts.map +1 -1
- package/dist/commands/click-activation-policy.js +6 -2
- package/dist/commands/close.d.ts.map +1 -1
- package/dist/commands/close.js +5 -0
- package/dist/commands/extract.d.ts.map +1 -1
- package/dist/commands/extract.js +147 -144
- package/dist/commands/launch.d.ts +0 -1
- package/dist/commands/launch.d.ts.map +1 -1
- package/dist/commands/launch.js +13 -16
- package/dist/commands/navigate.d.ts.map +1 -1
- package/dist/commands/navigate.js +79 -73
- package/dist/commands/observe-inventory.d.ts +6 -1
- package/dist/commands/observe-inventory.d.ts.map +1 -1
- package/dist/commands/observe-inventory.js +331 -8
- package/dist/commands/observe-persistence.d.ts.map +1 -1
- package/dist/commands/observe-persistence.js +2 -0
- package/dist/commands/observe-projection.d.ts +3 -2
- package/dist/commands/observe-projection.d.ts.map +1 -1
- package/dist/commands/observe-projection.js +1 -0
- package/dist/commands/observe-protected.d.ts +3 -1
- package/dist/commands/observe-protected.d.ts.map +1 -1
- package/dist/commands/observe-protected.js +23 -1
- package/dist/commands/observe-semantics.d.ts.map +1 -1
- package/dist/commands/observe-semantics.js +70 -0
- package/dist/commands/observe.d.ts +1 -0
- package/dist/commands/observe.d.ts.map +1 -1
- package/dist/commands/observe.js +260 -270
- package/dist/commands/screenshot.d.ts.map +1 -1
- package/dist/commands/screenshot.js +50 -64
- package/dist/control-semantics.d.ts.map +1 -1
- package/dist/control-semantics.js +5 -0
- package/dist/date-value-normalization.d.ts +16 -0
- package/dist/date-value-normalization.d.ts.map +1 -0
- package/dist/date-value-normalization.js +117 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -24
- package/dist/library.d.ts +5 -1
- package/dist/library.d.ts.map +1 -1
- package/dist/library.js +4 -1
- package/dist/protected-fill.d.ts +3 -2
- package/dist/protected-fill.d.ts.map +1 -1
- package/dist/protected-fill.js +46 -7
- package/dist/runtime-protected-state.d.ts.map +1 -1
- package/dist/runtime-protected-state.js +8 -1
- package/dist/runtime-state.d.ts +11 -0
- package/dist/runtime-state.d.ts.map +1 -1
- package/dist/secrets/form-matcher.d.ts +1 -2
- package/dist/secrets/form-matcher.d.ts.map +1 -1
- package/dist/secrets/form-matcher.js +125 -119
- package/dist/secrets/matching-helpers.d.ts +13 -0
- package/dist/secrets/matching-helpers.d.ts.map +1 -0
- package/dist/secrets/matching-helpers.js +147 -0
- package/dist/secrets/observed-field-resolution.d.ts +43 -0
- package/dist/secrets/observed-field-resolution.d.ts.map +1 -0
- package/dist/secrets/observed-field-resolution.js +223 -0
- package/dist/secrets/protected-field-semantics.d.ts.map +1 -1
- package/dist/secrets/protected-field-semantics.js +3 -2
- package/dist/secrets/protected-fill.d.ts +3 -1
- package/dist/secrets/protected-fill.d.ts.map +1 -1
- package/dist/secrets/protected-fill.js +31 -0
- package/dist/secrets/protected-value-adapters.d.ts.map +1 -1
- package/dist/secrets/protected-value-adapters.js +14 -22
- package/dist/secrets/types.d.ts +3 -0
- package/dist/secrets/types.d.ts.map +1 -1
- package/dist/sticky-owner-host-entry.d.ts +2 -0
- package/dist/sticky-owner-host-entry.d.ts.map +1 -0
- package/dist/sticky-owner-host-entry.js +97 -0
- package/dist/sticky-owner.d.ts +15 -0
- package/dist/sticky-owner.d.ts.map +1 -0
- package/dist/sticky-owner.js +431 -0
- package/docs/README.md +15 -2
- package/docs/api-reference.md +13 -3
- package/docs/assistive-runtime.md +63 -7
- package/docs/configuration.md +48 -8
- package/docs/getting-started.md +42 -9
- package/docs/integration-checklist.md +8 -7
- package/docs/protected-fill.md +40 -7
- package/docs/testing.md +4 -3
- package/docs/troubleshooting.md +126 -36
- package/examples/README.md +9 -2
- package/package.json +8 -3
- package/dist/protected-fill-browser.d.ts +0 -22
- package/dist/protected-fill-browser.d.ts.map +0 -1
- package/dist/protected-fill-browser.js +0 -52
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { canonicalFieldKeyOf, directFieldSignalValuesOf, fieldKeyEvidenceTerms, normalizeText, selectorRootOf, signalValuesOf, } from './matching-helpers.js';
|
|
2
|
+
function tokensOf(value) {
|
|
3
|
+
return value.split(/[^a-z0-9]+/).filter((token) => token.length > 0);
|
|
4
|
+
}
|
|
5
|
+
function evidenceMatchScore(term, signals) {
|
|
6
|
+
const termTokens = tokensOf(term);
|
|
7
|
+
let best = 0;
|
|
8
|
+
for (const signal of signals) {
|
|
9
|
+
if (signal === term) {
|
|
10
|
+
return 6;
|
|
11
|
+
}
|
|
12
|
+
if (signal.includes(term) || term.includes(signal)) {
|
|
13
|
+
best = Math.max(best, 4);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const signalTokens = new Set(tokensOf(signal));
|
|
17
|
+
const sharedTokenCount = termTokens.filter((token) => signalTokens.has(token)).length;
|
|
18
|
+
if (sharedTokenCount >= 2) {
|
|
19
|
+
best = Math.max(best, 3);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (sharedTokenCount === 1 &&
|
|
23
|
+
termTokens.some((token) => token.length >= 4 && signalTokens.has(token))) {
|
|
24
|
+
best = Math.max(best, 2);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return best;
|
|
28
|
+
}
|
|
29
|
+
function candidateEvidenceTerms(candidate) {
|
|
30
|
+
const terms = new Set();
|
|
31
|
+
const push = (value) => {
|
|
32
|
+
const normalized = normalizeText(value);
|
|
33
|
+
if (normalized) {
|
|
34
|
+
terms.add(normalized);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
for (const term of fieldKeyEvidenceTerms(candidate.fieldKey)) {
|
|
38
|
+
push(term);
|
|
39
|
+
}
|
|
40
|
+
push(candidate.label);
|
|
41
|
+
for (const tag of candidate.semanticTags ?? []) {
|
|
42
|
+
push(tag);
|
|
43
|
+
}
|
|
44
|
+
return [...terms];
|
|
45
|
+
}
|
|
46
|
+
function targetValueTypeOf(target) {
|
|
47
|
+
const inputType = normalizeText(target.inputType);
|
|
48
|
+
const autocomplete = normalizeText(target.autocomplete);
|
|
49
|
+
const signals = directFieldSignalValuesOf(target);
|
|
50
|
+
if (inputType === 'email' || autocomplete.includes('email')) {
|
|
51
|
+
return 'email';
|
|
52
|
+
}
|
|
53
|
+
if (inputType === 'url') {
|
|
54
|
+
return 'url';
|
|
55
|
+
}
|
|
56
|
+
if (inputType === 'number') {
|
|
57
|
+
return 'number';
|
|
58
|
+
}
|
|
59
|
+
if (inputType === 'date' ||
|
|
60
|
+
autocomplete === 'bday' ||
|
|
61
|
+
signals.some((signal) => /\b(date of birth|birth date|dob|birthday)\b/.test(signal))) {
|
|
62
|
+
return 'date';
|
|
63
|
+
}
|
|
64
|
+
return 'text';
|
|
65
|
+
}
|
|
66
|
+
function isEmailValue(value) {
|
|
67
|
+
return typeof value === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim());
|
|
68
|
+
}
|
|
69
|
+
function isDateValue(value) {
|
|
70
|
+
return typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value.trim());
|
|
71
|
+
}
|
|
72
|
+
function isNumberValue(value) {
|
|
73
|
+
if (typeof value === 'number') {
|
|
74
|
+
return Number.isFinite(value);
|
|
75
|
+
}
|
|
76
|
+
return typeof value === 'string' && /^-?\d+(?:\.\d+)?$/.test(value.trim());
|
|
77
|
+
}
|
|
78
|
+
function isUrlValue(value) {
|
|
79
|
+
return typeof value === 'string' && /^https?:\/\//.test(value.trim());
|
|
80
|
+
}
|
|
81
|
+
function isShapeCompatible(target, candidate) {
|
|
82
|
+
if (candidate.type === 'secret') {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
const targetValueType = targetValueTypeOf(target);
|
|
86
|
+
switch (targetValueType) {
|
|
87
|
+
case 'email':
|
|
88
|
+
return (isEmailValue(candidate.value) &&
|
|
89
|
+
(!candidate.type || candidate.type === 'email' || candidate.type === 'text'));
|
|
90
|
+
case 'date':
|
|
91
|
+
return (isDateValue(candidate.value) &&
|
|
92
|
+
(!candidate.type || candidate.type === 'date' || candidate.type === 'text'));
|
|
93
|
+
case 'number':
|
|
94
|
+
return (isNumberValue(candidate.value) &&
|
|
95
|
+
(!candidate.type || candidate.type === 'number' || candidate.type === 'text'));
|
|
96
|
+
case 'url':
|
|
97
|
+
return (isUrlValue(candidate.value) &&
|
|
98
|
+
(!candidate.type || candidate.type === 'url' || candidate.type === 'text'));
|
|
99
|
+
case 'text':
|
|
100
|
+
return ((typeof candidate.value === 'string' || typeof candidate.value === 'number') &&
|
|
101
|
+
(!candidate.type ||
|
|
102
|
+
candidate.type === 'text' ||
|
|
103
|
+
candidate.type === 'email' ||
|
|
104
|
+
candidate.type === 'number' ||
|
|
105
|
+
candidate.type === 'date' ||
|
|
106
|
+
candidate.type === 'url'));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function isScopeApplicable(candidate, host) {
|
|
110
|
+
if (!candidate.applicability || candidate.applicability.target === 'global') {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return Boolean(host && normalizeText(candidate.applicability.value) === normalizeText(host));
|
|
114
|
+
}
|
|
115
|
+
function contextCompatibilityScore(target, candidate) {
|
|
116
|
+
let score = 0;
|
|
117
|
+
const targetSelectorRoot = normalizeText(selectorRootOf(target) ?? undefined);
|
|
118
|
+
if (targetSelectorRoot && normalizeText(candidate.selectorRoot) === targetSelectorRoot) {
|
|
119
|
+
score += 2;
|
|
120
|
+
}
|
|
121
|
+
const targetPageFormSelector = normalizeText(target.pageFormSelector?.trim());
|
|
122
|
+
if (targetPageFormSelector &&
|
|
123
|
+
normalizeText(candidate.pageFormSelector?.trim()) === targetPageFormSelector) {
|
|
124
|
+
score += 2;
|
|
125
|
+
}
|
|
126
|
+
return score;
|
|
127
|
+
}
|
|
128
|
+
function candidatePriorityScore(candidate, host) {
|
|
129
|
+
let score = 0;
|
|
130
|
+
if (candidate.applicability?.target === 'host' &&
|
|
131
|
+
normalizeText(candidate.applicability.value) === normalizeText(host)) {
|
|
132
|
+
score += 3;
|
|
133
|
+
}
|
|
134
|
+
if (candidate.source === 'session_open_value') {
|
|
135
|
+
score += 2;
|
|
136
|
+
}
|
|
137
|
+
return score;
|
|
138
|
+
}
|
|
139
|
+
function evidenceScore(target, candidate) {
|
|
140
|
+
const directSignals = directFieldSignalValuesOf(target);
|
|
141
|
+
const broadSignals = signalValuesOf(target);
|
|
142
|
+
const terms = candidateEvidenceTerms(candidate);
|
|
143
|
+
let bestTermScore = 0;
|
|
144
|
+
for (const term of terms) {
|
|
145
|
+
bestTermScore = Math.max(bestTermScore, evidenceMatchScore(term, directSignals));
|
|
146
|
+
bestTermScore = Math.max(bestTermScore, evidenceMatchScore(term, broadSignals) - 1);
|
|
147
|
+
}
|
|
148
|
+
return bestTermScore + contextCompatibilityScore(target, candidate) + 1;
|
|
149
|
+
}
|
|
150
|
+
function rankCandidates(target, candidates, options = {}) {
|
|
151
|
+
return candidates
|
|
152
|
+
.map((candidate) => ({
|
|
153
|
+
candidate,
|
|
154
|
+
canonicalFieldKey: canonicalFieldKeyOf(candidate.fieldKey) || candidate.fieldKey,
|
|
155
|
+
score: evidenceScore(target, candidate) + candidatePriorityScore(candidate, options.host),
|
|
156
|
+
}))
|
|
157
|
+
.sort((left, right) => {
|
|
158
|
+
if (right.score !== left.score) {
|
|
159
|
+
return right.score - left.score;
|
|
160
|
+
}
|
|
161
|
+
return left.candidate.candidateRef.localeCompare(right.candidate.candidateRef);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
export function resolveObservedField(target, candidates, options = {}) {
|
|
165
|
+
if (options.protectedTargetRefs?.has(target.ref)) {
|
|
166
|
+
return {
|
|
167
|
+
status: 'no_match',
|
|
168
|
+
targetRef: target.ref,
|
|
169
|
+
reason: 'protected_target',
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (candidates.length === 0) {
|
|
173
|
+
return {
|
|
174
|
+
status: 'no_match',
|
|
175
|
+
targetRef: target.ref,
|
|
176
|
+
reason: 'no_candidate',
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const scopeEligible = candidates.filter((candidate) => isScopeApplicable(candidate, options.host));
|
|
180
|
+
if (scopeEligible.length === 0) {
|
|
181
|
+
return {
|
|
182
|
+
status: 'no_match',
|
|
183
|
+
targetRef: target.ref,
|
|
184
|
+
reason: 'scope_ineligible',
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const shapeEligible = scopeEligible.filter((candidate) => isShapeCompatible(target, candidate));
|
|
188
|
+
if (shapeEligible.length === 0) {
|
|
189
|
+
return {
|
|
190
|
+
status: 'no_match',
|
|
191
|
+
targetRef: target.ref,
|
|
192
|
+
reason: 'incompatible_shape',
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const minimumScore = options.minimumScore ?? 5;
|
|
196
|
+
const clearWinnerDelta = options.clearWinnerDelta ?? 2;
|
|
197
|
+
const ranked = rankCandidates(target, shapeEligible, { host: options.host });
|
|
198
|
+
const best = ranked[0];
|
|
199
|
+
if (!best || best.score < minimumScore) {
|
|
200
|
+
return {
|
|
201
|
+
status: 'no_match',
|
|
202
|
+
targetRef: target.ref,
|
|
203
|
+
reason: 'low_confidence',
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
const ambiguousCandidates = ranked.filter((candidate) => best.score - candidate.score < clearWinnerDelta);
|
|
207
|
+
if (ambiguousCandidates.length > 1) {
|
|
208
|
+
return {
|
|
209
|
+
status: 'ambiguous',
|
|
210
|
+
targetRef: target.ref,
|
|
211
|
+
candidates: ambiguousCandidates.map((candidate) => candidate.candidate.candidateRef),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
status: 'matched',
|
|
216
|
+
targetRef: target.ref,
|
|
217
|
+
fieldKey: best.canonicalFieldKey,
|
|
218
|
+
value: best.candidate.value,
|
|
219
|
+
source: best.candidate.source,
|
|
220
|
+
confidence: best.score >= 7 ? 'high' : 'medium',
|
|
221
|
+
candidateRef: best.candidate.candidateRef,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protected-field-semantics.d.ts","sourceRoot":"","sources":["../../src/secrets/protected-field-semantics.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEpG,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,SAAS,EAAE,yBAAyB,CAAC;CACtC;AAiED,wBAAgB,oCAAoC,CAClD,MAAM,EAAE,gBAAgB,GACvB,yBAAyB,EAAE,
|
|
1
|
+
{"version":3,"file":"protected-field-semantics.d.ts","sourceRoot":"","sources":["../../src/secrets/protected-field-semantics.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEpG,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,SAAS,EAAE,yBAAyB,CAAC;CACtC;AAiED,wBAAgB,oCAAoC,CAClD,MAAM,EAAE,gBAAgB,GACvB,yBAAyB,EAAE,CAyJ7B"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { inferComparableValueTypeFromFacts } from '../control-semantics.js';
|
|
2
|
-
const LOGIN_USERNAME_SIGNAL_RE = /\b(email|e-mail|username|user name|login|account(?: email|
|
|
2
|
+
const LOGIN_USERNAME_SIGNAL_RE = /\b(email|e-mail|username|user name|login|account(?:[ _-]?(?:email|name|id|login|username))|member(?:[ _-]?(?:email|name|id)))\b/i;
|
|
3
3
|
const LOGIN_PASSWORD_SIGNAL_RE = /\b(password|passcode|pass word)\b/i;
|
|
4
4
|
const PAYMENT_CARD_NAME_SIGNAL_RE = /\b(cardholder|card holder|name on card|cardholder name|full name)\b/i;
|
|
5
5
|
const PAYMENT_CARD_PAN_SIGNAL_RE = /\b(card number|card no|cc-number|cc number)\b/i;
|
|
@@ -65,7 +65,8 @@ export function inferProtectedFieldMeaningFromTarget(target) {
|
|
|
65
65
|
if (autocomplete.includes('username') ||
|
|
66
66
|
autocomplete.includes('email') ||
|
|
67
67
|
inputType === 'email' ||
|
|
68
|
-
/\b(user(name)?|login|email
|
|
68
|
+
/\b(user(name)?|login|email)\b/.test(inputName) ||
|
|
69
|
+
/\b(account|member)(?:_?(?:email|name|id|login|username))\b/.test(inputName) ||
|
|
69
70
|
signals.some((signal) => LOGIN_USERNAME_SIGNAL_RE.test(signal))) {
|
|
70
71
|
hints.push({ kind: 'login', fieldKey: 'username', valueHint: 'direct' });
|
|
71
72
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Page } from 'playwright-core';
|
|
2
2
|
import type { BrowserSessionState } from '../browser-session-state.js';
|
|
3
|
+
import { type DateValueNormalizationErrorReason } from '../date-value-normalization.js';
|
|
3
4
|
import { type TargetDescriptor } from '../runtime-state.js';
|
|
4
5
|
import type { PersistedFillableForm, StoredSecretFieldPolicies, StoredSecretFieldKey } from './types.js';
|
|
5
6
|
type ProtectedFillAction = 'fill' | 'select' | 'type';
|
|
@@ -11,6 +12,7 @@ export interface ProtectedFieldError extends ProtectedFilledField {
|
|
|
11
12
|
reason: 'client_validation_rejected' | 'value_not_applied';
|
|
12
13
|
validationTextRedacted?: true;
|
|
13
14
|
}
|
|
15
|
+
type ProtectedUnexpectedErrorReason = 'missing_protected_value' | 'unsupported_protected_field_group' | 'deterministic_only_resolution_failed' | 'assisted_value_resolution_failed' | 'action_failed' | DateValueNormalizationErrorReason;
|
|
14
16
|
type ProtectedBindingStaleReason = 'target_missing' | 'target_not_live' | 'page_signature_mismatch' | 'dom_signature_mismatch' | 'locator_resolution_failed' | 'target_blocked';
|
|
15
17
|
export type ProtectedFillExecutionResult = {
|
|
16
18
|
kind: 'success';
|
|
@@ -27,7 +29,7 @@ export type ProtectedFillExecutionResult = {
|
|
|
27
29
|
fieldErrors: ProtectedFieldError[];
|
|
28
30
|
} | {
|
|
29
31
|
kind: 'unexpected_error';
|
|
30
|
-
reason:
|
|
32
|
+
reason: ProtectedUnexpectedErrorReason;
|
|
31
33
|
};
|
|
32
34
|
declare function actionForTarget(target: TargetDescriptor): ProtectedFillAction;
|
|
33
35
|
declare function formatCardExpiry(month: string, year: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protected-fill.d.ts","sourceRoot":"","sources":["../../src/secrets/protected-fill.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAErD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAyB,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAiBnF,OAAO,KAAK,EAEV,qBAAqB,EACrB,yBAAyB,EACzB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAGpB,KAAK,mBAAmB,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEtD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAoB,SAAQ,oBAAoB;IAC/D,MAAM,EAAE,4BAA4B,GAAG,mBAAmB,CAAC;IAC3D,sBAAsB,CAAC,EAAE,IAAI,CAAC;CAC/B;AAED,KAAK,2BAA2B,GAC5B,gBAAgB,GAChB,iBAAiB,GACjB,yBAAyB,GACzB,wBAAwB,GACxB,2BAA2B,GAC3B,gBAAgB,CAAC;AAarB,MAAM,MAAM,4BAA4B,GACpC;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,YAAY,EAAE,oBAAoB,EAAE,CAAC;CACtC,GACD;IACE,IAAI,EAAE,eAAe,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,oBAAoB,EAAE,CAAC;IAClC,MAAM,EAAE,2BAA2B,CAAC;IACpC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,GACD;IACE,IAAI,EAAE,mBAAmB,CAAC;IAC1B,YAAY,EAAE,oBAAoB,EAAE,CAAC;IACrC,WAAW,EAAE,mBAAmB,EAAE,CAAC;CACpC,GACD;IACE,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,
|
|
1
|
+
{"version":3,"file":"protected-fill.d.ts","sourceRoot":"","sources":["../../src/secrets/protected-fill.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAErD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAEL,KAAK,iCAAiC,EAEvC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAyB,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAiBnF,OAAO,KAAK,EAEV,qBAAqB,EACrB,yBAAyB,EACzB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAGpB,KAAK,mBAAmB,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEtD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAoB,SAAQ,oBAAoB;IAC/D,MAAM,EAAE,4BAA4B,GAAG,mBAAmB,CAAC;IAC3D,sBAAsB,CAAC,EAAE,IAAI,CAAC;CAC/B;AAED,KAAK,8BAA8B,GAC/B,yBAAyB,GACzB,mCAAmC,GACnC,sCAAsC,GACtC,kCAAkC,GAClC,eAAe,GACf,iCAAiC,CAAC;AAEtC,KAAK,2BAA2B,GAC5B,gBAAgB,GAChB,iBAAiB,GACjB,yBAAyB,GACzB,wBAAwB,GACxB,2BAA2B,GAC3B,gBAAgB,CAAC;AAarB,MAAM,MAAM,4BAA4B,GACpC;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,YAAY,EAAE,oBAAoB,EAAE,CAAC;CACtC,GACD;IACE,IAAI,EAAE,eAAe,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,oBAAoB,EAAE,CAAC;IAClC,MAAM,EAAE,2BAA2B,CAAC;IACpC,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,GACD;IACE,IAAI,EAAE,mBAAmB,CAAC;IAC1B,YAAY,EAAE,oBAAoB,EAAE,CAAC;IACrC,WAAW,EAAE,mBAAmB,EAAE,CAAC;CACpC,GACD;IACE,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,EAAE,8BAA8B,CAAC;CACxC,CAAC;AAEN,iBAAS,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,mBAAmB,CActE;AAED,iBAAS,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAK7D;AAwUD,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,OAAO,EAAE,mBAAmB,CAAC;IAC7B,IAAI,EAAE,IAAI,CAAC;IACX,YAAY,EAAE,qBAAqB,CAAC;IACpC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,aAAa,CAAC,EAAE,yBAAyB,CAAC;CAC3C,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAgOxC;AAED,eAAO,MAAM,mBAAmB;;;qCAIjB,aAAa,CAAC,oBAAoB,CAAC,mBAC7B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,WAC9B,gBAAgB;CAuB5B,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AgentbrowseAssistiveRuntimeMissingError } from '../assistive-runtime.js';
|
|
2
|
+
import { isDateValueNormalizationErrorReason, normalizeStructuredDateValue, } from '../date-value-normalization.js';
|
|
2
3
|
import { getSurface, getTarget } from '../runtime-state.js';
|
|
3
4
|
import { createAcceptanceProbe, waitForAcceptanceProbe } from '../commands/action-acceptance.js';
|
|
4
5
|
import { applyActionWithFallbacks } from '../commands/action-executor.js';
|
|
@@ -44,6 +45,18 @@ function resolveBindingValue(fields, target, protectedValues, fieldPolicies, ass
|
|
|
44
45
|
if (typeof deterministicValue === 'string' && deterministicValue.length > 0) {
|
|
45
46
|
return deterministicValue;
|
|
46
47
|
}
|
|
48
|
+
if (field.fieldKey === 'date_of_birth') {
|
|
49
|
+
const rawDateValue = protectedValues.date_of_birth;
|
|
50
|
+
if (typeof rawDateValue === 'string' && rawDateValue.trim().length > 0) {
|
|
51
|
+
const normalizedDate = normalizeStructuredDateValue(rawDateValue);
|
|
52
|
+
if (normalizedDate.kind === 'error') {
|
|
53
|
+
throw new Error(normalizedDate.reason);
|
|
54
|
+
}
|
|
55
|
+
if (normalizedDate.kind === 'not_date_like') {
|
|
56
|
+
throw new Error('invalid_date_value');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
47
60
|
if ((field.valueHint ?? 'direct') !== 'direct') {
|
|
48
61
|
throw new Error('deterministic_only_resolution_failed');
|
|
49
62
|
}
|
|
@@ -253,6 +266,10 @@ function staleReasonFromError(error) {
|
|
|
253
266
|
}
|
|
254
267
|
return null;
|
|
255
268
|
}
|
|
269
|
+
function unexpectedReasonFromError(error) {
|
|
270
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
271
|
+
return isDateValueNormalizationErrorReason(message) ? message : null;
|
|
272
|
+
}
|
|
256
273
|
export async function executeProtectedFill(params) {
|
|
257
274
|
const preparedBindings = await prepareBindings(params.session, params.page, params.fillableForm);
|
|
258
275
|
if (!Array.isArray(preparedBindings)) {
|
|
@@ -305,6 +322,13 @@ export async function executeProtectedFill(params) {
|
|
|
305
322
|
actionValue = resolveBindingValue(binding.fields, binding.target, params.protectedValues, params.fieldPolicies, assistedValues);
|
|
306
323
|
}
|
|
307
324
|
catch (error) {
|
|
325
|
+
const unexpectedReason = unexpectedReasonFromError(error);
|
|
326
|
+
if (unexpectedReason) {
|
|
327
|
+
return {
|
|
328
|
+
kind: 'unexpected_error',
|
|
329
|
+
reason: unexpectedReason,
|
|
330
|
+
};
|
|
331
|
+
}
|
|
308
332
|
const message = error instanceof Error ? error.message : String(error);
|
|
309
333
|
if (message === 'missing_protected_value') {
|
|
310
334
|
return {
|
|
@@ -383,6 +407,13 @@ export async function executeProtectedFill(params) {
|
|
|
383
407
|
filledFields.push(...flattenFilledFields(binding.fieldKeys, binding.targetRef));
|
|
384
408
|
}
|
|
385
409
|
catch (error) {
|
|
410
|
+
const unexpectedReason = unexpectedReasonFromError(error);
|
|
411
|
+
if (unexpectedReason) {
|
|
412
|
+
return {
|
|
413
|
+
kind: 'unexpected_error',
|
|
414
|
+
reason: unexpectedReason,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
386
417
|
if (acceptanceProbe) {
|
|
387
418
|
acceptanceResult =
|
|
388
419
|
acceptanceResult ?? (await waitForAcceptanceProbe(acceptanceProbe).catch(() => null));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protected-value-adapters.d.ts","sourceRoot":"","sources":["../../src/secrets/protected-value-adapters.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"protected-value-adapters.d.ts","sourceRoot":"","sources":["../../src/secrets/protected-value-adapters.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,wBAAwB,EAAwB,MAAM,YAAY,CAAC;AA+EjF,wBAAgB,yCAAyC,CACvD,OAAO,EAAE,IAAI,CAAC,wBAAwB,EAAE,UAAU,GAAG,WAAW,CAAC,EACjE,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACvC,MAAM,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CAAC,GACpE,MAAM,GAAG,IAAI,CAqEf"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeStructuredDateValue } from '../date-value-normalization.js';
|
|
1
2
|
function normalizeWhitespace(value) {
|
|
2
3
|
return value.replace(/\s+/g, ' ').trim();
|
|
3
4
|
}
|
|
@@ -11,18 +12,9 @@ function directStoredValue(protectedValues, fieldKey) {
|
|
|
11
12
|
const value = protectedValues[fieldKey];
|
|
12
13
|
return typeof value === 'string' && value.trim().length > 0 ? value : null;
|
|
13
14
|
}
|
|
14
|
-
function
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
const year = match[1];
|
|
20
|
-
const month = match[2];
|
|
21
|
-
const day = match[3];
|
|
22
|
-
if (!year || !month || !day) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
return { year, month, day };
|
|
15
|
+
function normalizeDateValue(value) {
|
|
16
|
+
const normalized = normalizeStructuredDateValue(value);
|
|
17
|
+
return normalized.kind === 'normalized' ? normalized : null;
|
|
26
18
|
}
|
|
27
19
|
function monthName(month) {
|
|
28
20
|
const names = [
|
|
@@ -65,29 +57,29 @@ export function resolveDeterministicProtectedBindingValue(binding, protectedValu
|
|
|
65
57
|
if (!dateOfBirth) {
|
|
66
58
|
return null;
|
|
67
59
|
}
|
|
60
|
+
const normalizedDate = normalizeDateValue(dateOfBirth);
|
|
61
|
+
if (!normalizedDate) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
68
64
|
const valueHint = binding.valueHint ?? 'direct';
|
|
69
65
|
if (valueHint === 'direct') {
|
|
70
|
-
return
|
|
71
|
-
}
|
|
72
|
-
const parsed = parseIsoDate(dateOfBirth);
|
|
73
|
-
if (!parsed) {
|
|
74
|
-
return null;
|
|
66
|
+
return normalizedDate.iso;
|
|
75
67
|
}
|
|
76
68
|
if (valueHint === 'date_of_birth.day') {
|
|
77
|
-
return String(Number(
|
|
69
|
+
return String(Number(normalizedDate.day));
|
|
78
70
|
}
|
|
79
71
|
if (valueHint === 'date_of_birth.year') {
|
|
80
|
-
return
|
|
72
|
+
return normalizedDate.year;
|
|
81
73
|
}
|
|
82
74
|
if (valueHint === 'date_of_birth.month') {
|
|
83
75
|
const style = monthProjectionStyle(target);
|
|
84
76
|
if (style === 'name') {
|
|
85
|
-
return monthName(
|
|
77
|
+
return monthName(normalizedDate.month);
|
|
86
78
|
}
|
|
87
79
|
if (style === 'short') {
|
|
88
|
-
return monthShortName(
|
|
80
|
+
return monthShortName(normalizedDate.month);
|
|
89
81
|
}
|
|
90
|
-
return
|
|
82
|
+
return normalizedDate.month;
|
|
91
83
|
}
|
|
92
84
|
return null;
|
|
93
85
|
}
|
package/dist/secrets/types.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export type LoginFieldKey = (typeof LOGIN_FIELD_KEYS)[number];
|
|
|
11
11
|
export type IdentityFieldKey = (typeof IDENTITY_FIELD_KEYS)[number];
|
|
12
12
|
export type PaymentCardFieldKey = (typeof PAYMENT_CARD_FIELD_KEYS)[number];
|
|
13
13
|
export type StoredSecretFieldKey = LoginFieldKey | IdentityFieldKey | PaymentCardFieldKey;
|
|
14
|
+
export type LoginStepKind = 'full' | 'identifier' | 'password';
|
|
14
15
|
export type StoredSecretScope = 'site' | 'global';
|
|
15
16
|
export type ProtectedFieldPolicy = 'deterministic_only' | 'llm_assisted';
|
|
16
17
|
export declare const PROTECTED_BINDING_VALUE_HINTS: readonly ["direct", "full_name.given", "full_name.family", "date_of_birth.day", "date_of_birth.month", "date_of_birth.year"];
|
|
@@ -60,7 +61,9 @@ export interface PersistedFillableForm {
|
|
|
60
61
|
fillRef: string;
|
|
61
62
|
pageRef: string;
|
|
62
63
|
scopeRef?: string;
|
|
64
|
+
pageFormSelector?: string;
|
|
63
65
|
purpose: string;
|
|
66
|
+
loginStep?: LoginStepKind;
|
|
64
67
|
presence?: FillableFormPresence;
|
|
65
68
|
scopeEpoch?: number;
|
|
66
69
|
fields: FillableFormFieldBinding[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/secrets/types.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,mCAAoC,CAAC;AAClE,eAAO,MAAM,mBAAmB,2HAQtB,CAAC;AACX,eAAO,MAAM,uBAAuB,gEAM1B,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,UAAU,GAAG,cAAc,CAAC;AAErE,eAAO,MAAM,gCAAgC;;;;CAInC,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC9D,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AACpE,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC3E,MAAM,MAAM,oBAAoB,GAAG,aAAa,GAAG,gBAAgB,GAAG,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/secrets/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sticky-owner-host-entry.d.ts","sourceRoot":"","sources":["../src/sticky-owner-host-entry.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { existsSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { connectPlaywright, disconnectPlaywright } from './playwright-runtime.js';
|
|
3
|
+
function readArgValue(argv, name) {
|
|
4
|
+
const index = argv.indexOf(name);
|
|
5
|
+
const value = index >= 0 ? argv[index + 1] : undefined;
|
|
6
|
+
if (!value || value.startsWith('--')) {
|
|
7
|
+
throw new Error(`Missing ${name} argument.`);
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
function parseArgs(argv = process.argv) {
|
|
12
|
+
const ttlRaw = readArgValue(argv, '--ttl-ms');
|
|
13
|
+
const ttlMs = Number(ttlRaw);
|
|
14
|
+
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
15
|
+
throw new Error('Invalid --ttl-ms argument.');
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
manifestPath: readArgValue(argv, '--manifest'),
|
|
19
|
+
cdpUrl: readArgValue(argv, '--cdp-url'),
|
|
20
|
+
hostId: readArgValue(argv, '--host-id'),
|
|
21
|
+
browserSessionId: readArgValue(argv, '--browser-session-id'),
|
|
22
|
+
touchPath: readArgValue(argv, '--touch-path'),
|
|
23
|
+
ttlMs,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function touchLeaseFile(path) {
|
|
27
|
+
writeFileSync(path, new Date().toISOString());
|
|
28
|
+
}
|
|
29
|
+
function readLeaseAgeMs(path) {
|
|
30
|
+
if (!existsSync(path)) {
|
|
31
|
+
return Number.POSITIVE_INFINITY;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
return Math.max(0, Date.now() - statSync(path).mtimeMs);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return Number.POSITIVE_INFINITY;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function main() {
|
|
41
|
+
const args = parseArgs();
|
|
42
|
+
const browser = await connectPlaywright(args.cdpUrl);
|
|
43
|
+
const binding = await browser.bind(args.hostId, {
|
|
44
|
+
metadata: {
|
|
45
|
+
hostId: args.hostId,
|
|
46
|
+
browserSessionId: args.browserSessionId,
|
|
47
|
+
pid: process.pid,
|
|
48
|
+
startedAt: new Date().toISOString(),
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
touchLeaseFile(args.touchPath);
|
|
52
|
+
writeFileSync(args.manifestPath, JSON.stringify({
|
|
53
|
+
endpoint: binding.endpoint,
|
|
54
|
+
pid: process.pid,
|
|
55
|
+
startedAt: new Date().toISOString(),
|
|
56
|
+
}, null, 2));
|
|
57
|
+
let shuttingDown = false;
|
|
58
|
+
let ttlInterval = null;
|
|
59
|
+
const shutdown = async () => {
|
|
60
|
+
if (shuttingDown) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
shuttingDown = true;
|
|
64
|
+
if (ttlInterval) {
|
|
65
|
+
clearInterval(ttlInterval);
|
|
66
|
+
ttlInterval = null;
|
|
67
|
+
}
|
|
68
|
+
await browser.unbind().catch(() => undefined);
|
|
69
|
+
await disconnectPlaywright(browser).catch(() => undefined);
|
|
70
|
+
rmSync(args.touchPath, { force: true });
|
|
71
|
+
process.exit(0);
|
|
72
|
+
};
|
|
73
|
+
const reapExpiredOwner = () => {
|
|
74
|
+
if (readLeaseAgeMs(args.touchPath) >= args.ttlMs) {
|
|
75
|
+
void shutdown();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
browser.on('disconnected', () => {
|
|
79
|
+
void shutdown();
|
|
80
|
+
});
|
|
81
|
+
process.on('SIGINT', () => {
|
|
82
|
+
void shutdown();
|
|
83
|
+
});
|
|
84
|
+
process.on('SIGTERM', () => {
|
|
85
|
+
void shutdown();
|
|
86
|
+
});
|
|
87
|
+
const ttlCheckMs = Math.max(5_000, Math.min(Math.floor(args.ttlMs / 6), 60_000));
|
|
88
|
+
ttlInterval = setInterval(reapExpiredOwner, ttlCheckMs);
|
|
89
|
+
ttlInterval.unref?.();
|
|
90
|
+
reapExpiredOwner();
|
|
91
|
+
await new Promise(() => { });
|
|
92
|
+
}
|
|
93
|
+
void main().catch((error) => {
|
|
94
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
95
|
+
console.error(message);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Browser } from 'playwright-core';
|
|
2
|
+
import { type BrowserSessionState } from './browser-session-state.js';
|
|
3
|
+
export type RestoreBrowserSessionOwnerResult = {
|
|
4
|
+
success: true;
|
|
5
|
+
session: BrowserSessionState;
|
|
6
|
+
restored: boolean;
|
|
7
|
+
} | {
|
|
8
|
+
success: false;
|
|
9
|
+
reason: 'sticky_owner_unrecoverable';
|
|
10
|
+
};
|
|
11
|
+
export declare function initializeBrowserSessionOwner(session: BrowserSessionState): Promise<BrowserSessionState>;
|
|
12
|
+
export declare function terminateBrowserSessionOwner(session: BrowserSessionState | null): Promise<void>;
|
|
13
|
+
export declare function restoreBrowserSessionOwner(session: BrowserSessionState): Promise<RestoreBrowserSessionOwnerResult>;
|
|
14
|
+
export declare function withStickyOwnerBrowser<TResult>(session: BrowserSessionState, operation: (browser: Browser) => Promise<TResult>): Promise<TResult>;
|
|
15
|
+
//# sourceMappingURL=sticky-owner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sticky-owner.d.ts","sourceRoot":"","sources":["../src/sticky-owner.ts"],"names":[],"mappings":"AAMA,OAAO,EAAY,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAIL,KAAK,mBAAmB,EAGzB,MAAM,4BAA4B,CAAC;AAWpC,MAAM,MAAM,gCAAgC,GACxC;IACE,OAAO,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,mBAAmB,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;CACnB,GACD;IACE,OAAO,EAAE,KAAK,CAAC;IACf,MAAM,EAAE,4BAA4B,CAAC;CACtC,CAAC;AAyVN,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,mBAAmB,CAAC,CAU9B;AAED,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,mBAAmB,GAAG,IAAI,GAClC,OAAO,CAAC,IAAI,CAAC,CA2Bf;AAED,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,gCAAgC,CAAC,CAiG3C;AAED,wBAAsB,sBAAsB,CAAC,OAAO,EAClD,OAAO,EAAE,mBAAmB,EAC5B,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,GAChD,OAAO,CAAC,OAAO,CAAC,CA0BlB"}
|