@trusty-squire/mcp 0.9.16 → 0.9.17-rc.2
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/dist/bot/affordance-probe.d.ts +15 -0
- package/dist/bot/affordance-probe.d.ts.map +1 -0
- package/dist/bot/affordance-probe.js +63 -0
- package/dist/bot/affordance-probe.js.map +1 -0
- package/dist/bot/agent.d.ts +31 -1
- package/dist/bot/agent.d.ts.map +1 -1
- package/dist/bot/agent.js +1826 -129
- package/dist/bot/agent.js.map +1 -1
- package/dist/bot/browser.d.ts +9 -0
- package/dist/bot/browser.d.ts.map +1 -1
- package/dist/bot/browser.js +281 -8
- package/dist/bot/browser.js.map +1 -1
- package/dist/bot/extraction.d.ts +18 -0
- package/dist/bot/extraction.d.ts.map +1 -0
- package/dist/bot/extraction.js +55 -0
- package/dist/bot/extraction.js.map +1 -0
- package/dist/bot/form-fill.d.ts +144 -0
- package/dist/bot/form-fill.d.ts.map +1 -0
- package/dist/bot/form-fill.js +320 -0
- package/dist/bot/form-fill.js.map +1 -0
- package/dist/bot/google-login.d.ts.map +1 -1
- package/dist/bot/google-login.js +6 -2
- package/dist/bot/google-login.js.map +1 -1
- package/dist/bot/llm-client.d.ts +12 -0
- package/dist/bot/llm-client.d.ts.map +1 -1
- package/dist/bot/llm-client.js +99 -0
- package/dist/bot/llm-client.js.map +1 -1
- package/dist/bot/nav-search.d.ts +80 -0
- package/dist/bot/nav-search.d.ts.map +1 -0
- package/dist/bot/nav-search.js +409 -0
- package/dist/bot/nav-search.js.map +1 -0
- package/dist/bot/oauth-flow.d.ts +48 -0
- package/dist/bot/oauth-flow.d.ts.map +1 -0
- package/dist/bot/oauth-flow.js +111 -0
- package/dist/bot/oauth-flow.js.map +1 -0
- package/dist/bot/onboarding-capture.d.ts +4 -0
- package/dist/bot/onboarding-capture.d.ts.map +1 -1
- package/dist/bot/onboarding-capture.js +5 -0
- package/dist/bot/onboarding-capture.js.map +1 -1
- package/dist/bot/redact.d.ts +1 -0
- package/dist/bot/redact.d.ts.map +1 -1
- package/dist/bot/redact.js +46 -0
- package/dist/bot/redact.js.map +1 -1
- package/dist/skill-registry-client.d.ts +4 -0
- package/dist/skill-registry-client.d.ts.map +1 -1
- package/dist/skill-registry-client.js +4 -0
- package/dist/skill-registry-client.js.map +1 -1
- package/dist/tools/provision-any.d.ts +21 -0
- package/dist/tools/provision-any.d.ts.map +1 -1
- package/dist/tools/provision-any.js +16 -7
- package/dist/tools/provision-any.js.map +1 -1
- package/dist/tools/signup-telemetry.d.ts +6 -0
- package/dist/tools/signup-telemetry.d.ts.map +1 -1
- package/dist/tools/signup-telemetry.js +4 -0
- package/dist/tools/signup-telemetry.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
// nav-search.ts — goal-directed search over a dashboard's affordances for the
|
|
2
|
+
// POST-signup "find/create the API key" phase. This is strangler-fig slice 1 of
|
|
3
|
+
// de-monolithing agent.ts: the enumeration, ranking, and goal classification are
|
|
4
|
+
// PURE functions (inventory / url / text in, decision out — no browser, no LLM),
|
|
5
|
+
// so they're unit-testable in isolation. The browser-driving search loop (click →
|
|
6
|
+
// overlay → verify → extract) is a thin orchestrator over these, added in a later
|
|
7
|
+
// task. Design + reviewed decisions: docs/DESIGN-post-signup-nav-search.md.
|
|
8
|
+
//
|
|
9
|
+
// Why this exists: the current greedy postVerifyLoop guesses URLs (404s) and
|
|
10
|
+
// flails to a 600s timeout because it has no goal definition and no map. Every
|
|
11
|
+
// dashboard DOES expose a finite set of navigable affordances — links AND buttons,
|
|
12
|
+
// some behind expandable menus. Rank them deterministically toward "API keys",
|
|
13
|
+
// click the real ones, stop on a concrete goal. The LLM is a bounded tiebreaker,
|
|
14
|
+
// not the loop driver (reviewed decision A1).
|
|
15
|
+
import { findCreateKeyAffordance, detectExistingAccountNoExtract } from "./agent.js";
|
|
16
|
+
// Resolve a candidate's href to an absolute URL worth navigating to, or null
|
|
17
|
+
// when it isn't a real destination (empty, "#", javascript:, mailto:, or a bare
|
|
18
|
+
// fragment). Relative paths resolve against the current page URL. Pure.
|
|
19
|
+
export function resolveNavHref(href, currentUrl) {
|
|
20
|
+
if (href === null)
|
|
21
|
+
return null;
|
|
22
|
+
const h = href.trim();
|
|
23
|
+
if (h.length === 0 || h === "#" || h.startsWith("#"))
|
|
24
|
+
return null;
|
|
25
|
+
if (/^(?:javascript:|mailto:|tel:)/i.test(h))
|
|
26
|
+
return null;
|
|
27
|
+
try {
|
|
28
|
+
const abs = new URL(h, currentUrl);
|
|
29
|
+
if (abs.protocol !== "http:" && abs.protocol !== "https:")
|
|
30
|
+
return null;
|
|
31
|
+
// A link that points back to exactly where we are is not progress.
|
|
32
|
+
if (abs.href === currentUrl)
|
|
33
|
+
return null;
|
|
34
|
+
return abs.href;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// True if `href` resolves to a DIFFERENT registrable domain than the page we're
|
|
41
|
+
// on — i.e. it would navigate off the authenticated app (e.g. console.neon.tech
|
|
42
|
+
// → neon.com/docs). The key hunt must stay inside the app; a marketing/docs link
|
|
43
|
+
// is a dead end that also burns the step budget and drops the session. Buttons
|
|
44
|
+
// and same-site/relative links are NOT off-site. Registrable domain = last two
|
|
45
|
+
// labels (good enough for these dev-tool hosts; no PSL dependency). Pure.
|
|
46
|
+
export function isOffSiteHref(currentUrl, href) {
|
|
47
|
+
const abs = resolveNavHref(href, currentUrl);
|
|
48
|
+
if (abs === null)
|
|
49
|
+
return false; // relative/self/non-navigable → never off-site
|
|
50
|
+
try {
|
|
51
|
+
const reg = (h) => h.split(".").slice(-2).join(".");
|
|
52
|
+
return reg(new URL(abs).hostname) !== reg(new URL(currentUrl).hostname);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return true; // unparseable target → treat as off-site (don't follow)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Union two inventory snapshots, deduped by selector (first wins). Used to
|
|
59
|
+
// survive expandLatentNav's destructive menu-toggling: an affordance present
|
|
60
|
+
// in EITHER the pre- or post-expand read is kept, so an already-open dropdown
|
|
61
|
+
// that the toggle later closes still contributes its items. Pure.
|
|
62
|
+
export function mergeInventories(a, b) {
|
|
63
|
+
const bySelector = new Map();
|
|
64
|
+
for (const el of [...a, ...b]) {
|
|
65
|
+
if (!bySelector.has(el.selector))
|
|
66
|
+
bySelector.set(el.selector, el);
|
|
67
|
+
}
|
|
68
|
+
return [...bySelector.values()];
|
|
69
|
+
}
|
|
70
|
+
// Destructive / irreversible actions nav-search must NEVER auto-click — neither
|
|
71
|
+
// by deterministic rank nor by LLM tiebreak. The search clicks affordances
|
|
72
|
+
// unattended chasing a key; a positional/portaled mis-click on one of these
|
|
73
|
+
// could delete the account or revoke a credential. Excluded at enumeration so
|
|
74
|
+
// no downstream path can ever reach them. (Observed: the LLM tiebreak once
|
|
75
|
+
// picked "Delete Account" on a settings page that had no keys.)
|
|
76
|
+
const DESTRUCTIVE_TEXT = /\b(?:delete|remove|deactivate|destroy|revoke|cancel\s+(?:account|subscription|plan)|close\s+account|wipe|reset\s+account|leave\s+(?:team|organization|org))\b/i;
|
|
77
|
+
// Pull the navigable affordances out of an inventory. Pure.
|
|
78
|
+
export function enumerateCandidates(inventory) {
|
|
79
|
+
const out = [];
|
|
80
|
+
for (const el of inventory) {
|
|
81
|
+
const clickable = el.tag === "a" || el.tag === "button" || el.role === "link" || el.role === "button";
|
|
82
|
+
if (!clickable)
|
|
83
|
+
continue;
|
|
84
|
+
if (el.visible === false)
|
|
85
|
+
continue;
|
|
86
|
+
const text = [el.visibleText, el.ariaLabel, el.title, el.labelText, el.iconLabel]
|
|
87
|
+
.filter((s) => s !== null && s !== undefined && s.length > 0)
|
|
88
|
+
.join(" ")
|
|
89
|
+
.trim();
|
|
90
|
+
if (DESTRUCTIVE_TEXT.test(text))
|
|
91
|
+
continue; // never auto-click a destructive control
|
|
92
|
+
const href = el.href ?? null;
|
|
93
|
+
if (text.length === 0 && (href === null || href.length === 0))
|
|
94
|
+
continue;
|
|
95
|
+
out.push({
|
|
96
|
+
selector: el.selector,
|
|
97
|
+
href,
|
|
98
|
+
text,
|
|
99
|
+
isAnchor: el.tag === "a",
|
|
100
|
+
inViewport: el.inViewport === true,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
// THE key-destination URL pattern — the single source of truth used by BOTH the
|
|
106
|
+
// ranker (href match) and the goal verifier (am I on a key surface?). They MUST
|
|
107
|
+
// agree: if the ranker navigates to a URL it thinks is a key destination, the
|
|
108
|
+
// goal verifier has to recognize the same URL, or the loop arrives and never
|
|
109
|
+
// extracts. The loose tokens (settings/account/keys/tokens/developers) are
|
|
110
|
+
// trusted because an href/url is a structured path, not free text.
|
|
111
|
+
export const KEYS_DESTINATION_URL = /\/(?:api[-_]?keys?|api[-_]?tokens?|access[-_]?tokens?|auth[-_]?tokens?|secret[-_]?keys?|personal[-_]?access[-_]?tokens?|developers?|settings|account|keys?|tokens?)(?:[/?#]|$)/i;
|
|
112
|
+
const KEYS_HREF = KEYS_DESTINATION_URL;
|
|
113
|
+
const KEYS_TEXT_STRONG = /\b(?:api|access|secret|auth|personal\s+access)\s*(?:keys?|tokens?)\b/i;
|
|
114
|
+
const KEYS_TEXT_WEAK = /\b(?:developers?|settings|account)\b/i;
|
|
115
|
+
const NEG_TEXT = /\b(?:billing|invoices?|docs|documentation|pricing|log\s*out|sign\s*out|keyboard\s+shortcuts)\b/i;
|
|
116
|
+
export function scoreCandidate(c) {
|
|
117
|
+
if (NEG_TEXT.test(c.text))
|
|
118
|
+
return 0;
|
|
119
|
+
// Base = keys relevance. Anchor/viewport are TIEBREAKERS that only apply on a
|
|
120
|
+
// relevant base — otherwise every visible link would score >0 and a plain
|
|
121
|
+
// "Dashboard" link would rank as a candidate.
|
|
122
|
+
let base = 0;
|
|
123
|
+
if (c.href !== null && KEYS_HREF.test(c.href))
|
|
124
|
+
base += 4; // a real navigable path
|
|
125
|
+
if (KEYS_TEXT_STRONG.test(c.text))
|
|
126
|
+
base += 3; // explicit "API key(s)/token(s)"
|
|
127
|
+
else if (KEYS_TEXT_WEAK.test(c.text))
|
|
128
|
+
base += 1; // settings/account/developers
|
|
129
|
+
if (base === 0)
|
|
130
|
+
return 0; // no keys relevance → not a candidate
|
|
131
|
+
let s = base;
|
|
132
|
+
if (c.isAnchor)
|
|
133
|
+
s += 1; // prefer real anchors over role=button
|
|
134
|
+
if (c.inViewport)
|
|
135
|
+
s += 1;
|
|
136
|
+
return s;
|
|
137
|
+
}
|
|
138
|
+
export function rankCandidates(candidates) {
|
|
139
|
+
const scored = candidates
|
|
140
|
+
.map((c) => ({ c, s: scoreCandidate(c) }))
|
|
141
|
+
.filter((x) => x.s > 0)
|
|
142
|
+
.sort((a, b) => b.s - a.s);
|
|
143
|
+
const ranked = scored.map((x) => x.c);
|
|
144
|
+
const needsTiebreak = ranked.length === 0 || (scored.length >= 2 && scored[0].s === scored[1].s);
|
|
145
|
+
return { ranked, needsTiebreak };
|
|
146
|
+
}
|
|
147
|
+
export function assessKeyGoal(input) {
|
|
148
|
+
// A create-key affordance on a key surface → mint a fresh key. This is checked
|
|
149
|
+
// BEFORE the existing-account-no-extract heuristic below, and deliberately so:
|
|
150
|
+
// an EMPTY keys table still renders its column headers ("Key name" / "Created" /
|
|
151
|
+
// "Last used"), which that heuristic mistakes for an existing masked listing
|
|
152
|
+
// (groq's /keys on a virgin account) — and then extraction finds nothing and the
|
|
153
|
+
// loop wanders off-surface. When a "Create API Key" button is right there,
|
|
154
|
+
// minting is unambiguously the move: it yields a fresh extractable key whether
|
|
155
|
+
// the account is virgin OR has masked existing keys. A stray "create" button
|
|
156
|
+
// elsewhere can't hijack the search — we gate on being on a key surface.
|
|
157
|
+
const create = findCreateKeyAffordance(input.inventory);
|
|
158
|
+
if (create !== null && KEYS_DESTINATION_URL.test(input.url)) {
|
|
159
|
+
return { kind: "create_gated", createSelector: create.selector };
|
|
160
|
+
}
|
|
161
|
+
// No create affordance, but an existing-key listing with no re-reveal (e.g.
|
|
162
|
+
// Neon) IS arrival — the caller extracts/handles whatever is shown.
|
|
163
|
+
if (detectExistingAccountNoExtract({
|
|
164
|
+
url: input.url,
|
|
165
|
+
pageText: input.pageText,
|
|
166
|
+
lastPlannerReason: "",
|
|
167
|
+
})) {
|
|
168
|
+
return { kind: "on_key_surface" };
|
|
169
|
+
}
|
|
170
|
+
// On a keys/settings surface but no create affordance yet (key may be masked /
|
|
171
|
+
// behind a reveal / already listed) → let the caller run the extractor.
|
|
172
|
+
if (KEYS_DESTINATION_URL.test(input.url)) {
|
|
173
|
+
return { kind: "on_key_surface" };
|
|
174
|
+
}
|
|
175
|
+
// A create affordance off a non-key URL is still worth trying as a last move,
|
|
176
|
+
// but it's not arrival — keep searching with it available to the caller.
|
|
177
|
+
return { kind: "not_yet" };
|
|
178
|
+
}
|
|
179
|
+
// ── overlay / wizard handling (T3) ───────────────────────────────────────────
|
|
180
|
+
// Onboarding wizards and modals are a RECURRING obstacle (imagekit role-select,
|
|
181
|
+
// unify "Hire Assistant", "name your project") — each currently re-improvised and
|
|
182
|
+
// trapped the bot. One mechanism: when blocked, DISMISS the overlay (skip/close)
|
|
183
|
+
// or, failing that, ADVANCE the wizard (Next/Continue). Pure detection here; the
|
|
184
|
+
// click (and an Escape-key fallback) is the loop's browser action.
|
|
185
|
+
// Generalizes the inline WIZARD_FORWARD (agent.ts:10413). A forward control that
|
|
186
|
+
// advances a multi-step onboarding wizard.
|
|
187
|
+
const WIZARD_ADVANCE = /^\s*(?:next|continue|submit|finish|done|get\s+started|let'?s\s+go|proceed)\s*$/i;
|
|
188
|
+
// A control that skips/closes an onboarding step or modal entirely — preferred
|
|
189
|
+
// over advancing, since skipping gets us to the dashboard fastest.
|
|
190
|
+
const OVERLAY_DISMISS = /\b(?:skip(?:\s+for\s+now)?|dismiss|not\s+now|maybe\s+later|no\s+thanks?|close|×|✕|✖)\b/i;
|
|
191
|
+
function isClickable(el) {
|
|
192
|
+
return ((el.tag === "button" || el.tag === "a" || el.role === "button" || el.role === "link") &&
|
|
193
|
+
el.visible !== false);
|
|
194
|
+
}
|
|
195
|
+
function elText(el) {
|
|
196
|
+
return [el.visibleText, el.ariaLabel, el.title, el.labelText, el.iconLabel]
|
|
197
|
+
.filter((s) => s !== null && s !== undefined && s.length > 0)
|
|
198
|
+
.join(" ")
|
|
199
|
+
.trim();
|
|
200
|
+
}
|
|
201
|
+
// Find a skip/close affordance for a blocking onboarding step or modal. Pure.
|
|
202
|
+
export function findOverlayDismiss(inventory) {
|
|
203
|
+
for (const el of inventory) {
|
|
204
|
+
if (!isClickable(el))
|
|
205
|
+
continue;
|
|
206
|
+
const t = elText(el);
|
|
207
|
+
if (t.length === 0)
|
|
208
|
+
continue;
|
|
209
|
+
// Long text that merely CONTAINS "close" (e.g. "Close your first deal") is
|
|
210
|
+
// not a dismiss control — require the control to be short/affordance-shaped.
|
|
211
|
+
if (t.length > 24)
|
|
212
|
+
continue;
|
|
213
|
+
if (OVERLAY_DISMISS.test(t))
|
|
214
|
+
return el;
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
// Find a Next/Continue-style control that advances a wizard. Pure. Matches on
|
|
219
|
+
// the WHOLE label (anchored) so a "Continue with Google" OAuth button or a
|
|
220
|
+
// "Submit feedback" doesn't trip it.
|
|
221
|
+
export function findWizardAdvance(inventory) {
|
|
222
|
+
for (const el of inventory) {
|
|
223
|
+
if (!isClickable(el))
|
|
224
|
+
continue;
|
|
225
|
+
if (WIZARD_ADVANCE.test(elText(el)))
|
|
226
|
+
return el;
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
// On an onboarding CHOICE step (a role/usage picker that gates progress — no
|
|
231
|
+
// nav, no skip, no Next: unify's "Just for me / For my team", imagekit's role
|
|
232
|
+
// select), "satisfy the minimal step": pick the LEAST-committal option so the
|
|
233
|
+
// flow advances toward the dashboard. Returns the selector, or null when this
|
|
234
|
+
// isn't a recognizable choice step (so the caller doesn't click random buttons).
|
|
235
|
+
const ONBOARDING_CONTEXT = /\b(?:how (?:do|will) you (?:plan to )?use|get started|welcome to|set\s*up your|tell us about|what brings you|choose your|select (?:your )?(?:a )?role|how do you describe)\b/i;
|
|
236
|
+
const MINIMAL_CHOICE = /\b(?:just for me|personal|individual|myself|solo|skip|i'?m an individual|for myself)\b/i;
|
|
237
|
+
export function planOnboardingChoice(input) {
|
|
238
|
+
const onboardingish = /\/(?:onboarding|welcome|setup|get-started)\b/i.test(input.url) ||
|
|
239
|
+
ONBOARDING_CONTEXT.test(input.pageText);
|
|
240
|
+
if (!onboardingish)
|
|
241
|
+
return null;
|
|
242
|
+
const choices = input.inventory.filter((el) => (el.tag === "button" || el.role === "button") &&
|
|
243
|
+
el.visible !== false &&
|
|
244
|
+
(el.visibleText ?? el.ariaLabel ?? "").trim().length > 0);
|
|
245
|
+
if (choices.length === 0)
|
|
246
|
+
return null;
|
|
247
|
+
const text = (el) => (el.visibleText ?? el.ariaLabel ?? "").trim();
|
|
248
|
+
const minimal = choices.find((el) => MINIMAL_CHOICE.test(text(el)));
|
|
249
|
+
return (minimal ?? choices[0]).selector;
|
|
250
|
+
}
|
|
251
|
+
export async function runNavSearch(browser, deps) {
|
|
252
|
+
const maxSteps = deps.maxSteps ?? 12;
|
|
253
|
+
const log = deps.log ?? (() => { });
|
|
254
|
+
const tried = new Set(); // affordance selectors already clicked
|
|
255
|
+
const overlayTried = new Set(); // overlay controls already actioned
|
|
256
|
+
const capture = async (action, inv, selector) => {
|
|
257
|
+
if (deps.captureRound === undefined)
|
|
258
|
+
return;
|
|
259
|
+
try {
|
|
260
|
+
await deps.captureRound({
|
|
261
|
+
url: browser.currentUrl(),
|
|
262
|
+
inventory: inv,
|
|
263
|
+
action,
|
|
264
|
+
...(selector !== undefined ? { selector } : {}),
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
// capture is best-effort — never fail the search
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
272
|
+
await browser.settle();
|
|
273
|
+
let inv = await browser.extractInventory();
|
|
274
|
+
// Per-step diagnostic so a failed run is debuggable from the step trail
|
|
275
|
+
// alone (URL + element count + goal verdict + text snippet) — mirrors the
|
|
276
|
+
// greedy loop's "Inventory diagnostic" line. Without it an exhaustion is
|
|
277
|
+
// a black box: we can't see what page each click landed on.
|
|
278
|
+
{
|
|
279
|
+
const durl = browser.currentUrl();
|
|
280
|
+
const dtext = (await browser.extractText().catch(() => "")).replace(/\s+/g, " ").trim();
|
|
281
|
+
const dgoal = assessKeyGoal({ url: durl, pageText: dtext, inventory: inv });
|
|
282
|
+
log(`nav-search: step ${step} url=${durl} elements=${inv.length} goal=${dgoal.kind} ` +
|
|
283
|
+
`text="${dtext.slice(0, 160)}"`);
|
|
284
|
+
}
|
|
285
|
+
// 1) Clear a blocking onboarding overlay / modal before anything else.
|
|
286
|
+
const overlay = planOverlayStep(inv);
|
|
287
|
+
if (overlay.kind !== "none" && !overlayTried.has(overlay.selector)) {
|
|
288
|
+
overlayTried.add(overlay.selector);
|
|
289
|
+
log(`nav-search: overlay ${overlay.kind} → ${overlay.selector}`);
|
|
290
|
+
await capture(overlay.kind === "dismiss" ? "overlay_dismiss" : "overlay_advance", inv, overlay.selector);
|
|
291
|
+
try {
|
|
292
|
+
await browser.clickSelector(overlay.selector);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
await browser.pressEscape().catch(() => { }); // modal with no in-DOM close
|
|
296
|
+
}
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
// 2) Goal check. Try extraction whenever we're plausibly on a key surface
|
|
300
|
+
// (on_key_surface OR create_gated — both imply we're at the keys page,
|
|
301
|
+
// and after a create-click the key may now be present). Extraction-first
|
|
302
|
+
// so a freshly-created key isn't missed because the create button persists.
|
|
303
|
+
const url = browser.currentUrl();
|
|
304
|
+
const text = await browser.extractText().catch(() => "");
|
|
305
|
+
const goal = assessKeyGoal({ url, pageText: text, inventory: inv });
|
|
306
|
+
if (goal.kind === "on_key_surface" || goal.kind === "create_gated") {
|
|
307
|
+
const creds = await deps.extractKey().catch(() => null);
|
|
308
|
+
if (creds !== null && Object.keys(creds).length > 0) {
|
|
309
|
+
log(`nav-search: key extracted on ${url}`);
|
|
310
|
+
await capture("extract", inv);
|
|
311
|
+
return { kind: "found", credentials: creds };
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
// 2b) No key yet, but a create affordance is available and untried → MINT.
|
|
315
|
+
// Prefer the full mint dep (it drives the two-step "name + confirm"
|
|
316
|
+
// modal, waits out the create POST's server round-trip, and reveals a
|
|
317
|
+
// masked-on-first-show value) — a bare click would leave the modal
|
|
318
|
+
// un-submitted and the loop would then dismiss it as an overlay. Fall
|
|
319
|
+
// back to a bare click only when no mint dep is wired (unit tests).
|
|
320
|
+
if (goal.kind === "create_gated" && !tried.has(goal.createSelector)) {
|
|
321
|
+
tried.add(goal.createSelector);
|
|
322
|
+
log(`nav-search: create-key subgoal → ${goal.createSelector}`);
|
|
323
|
+
await capture("create_key", inv, goal.createSelector);
|
|
324
|
+
if (deps.mintKey !== undefined) {
|
|
325
|
+
const minted = await deps.mintKey().catch(() => null);
|
|
326
|
+
if (minted !== null && Object.keys(minted).length > 0) {
|
|
327
|
+
log(`nav-search: minted key on ${url}`);
|
|
328
|
+
await capture("extract", inv);
|
|
329
|
+
return { kind: "found", credentials: minted };
|
|
330
|
+
}
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
await browser.clickSelector(goal.createSelector).catch(() => { });
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
// 3) Move: expand latent nav, then rank candidates from the UNION of the
|
|
337
|
+
// pre-expand and post-expand inventories. expandLatentNav toggles
|
|
338
|
+
// aria-haspopup menus blindly — if a menu (e.g. the user-avatar dropdown
|
|
339
|
+
// holding Account/Settings/Billing) was ALREADY open, the toggle closes
|
|
340
|
+
// it and a post-expand-only read loses exactly the keys-bearing nav. The
|
|
341
|
+
// union keeps an already-open menu's items AND adds a newly-opened one's.
|
|
342
|
+
const invBefore = inv;
|
|
343
|
+
await browser.expandLatentNav().catch(() => { });
|
|
344
|
+
const invAfter = await browser.extractInventory();
|
|
345
|
+
const mergedInv = mergeInventories(invBefore, invAfter);
|
|
346
|
+
inv = mergedInv;
|
|
347
|
+
// Drop already-tried AND off-site anchors: the key always lives inside the
|
|
348
|
+
// authenticated app, never on its marketing/docs domain.
|
|
349
|
+
const candidates = enumerateCandidates(mergedInv).filter((c) => !tried.has(c.selector) && !isOffSiteHref(url, c.href));
|
|
350
|
+
const rank = rankCandidates(candidates);
|
|
351
|
+
let pick = rank.ranked[0]?.selector ?? null;
|
|
352
|
+
if (pick === null && rank.needsTiebreak && deps.tiebreak !== undefined && candidates.length > 0) {
|
|
353
|
+
pick = await deps.tiebreak(candidates).catch(() => null);
|
|
354
|
+
if (pick !== null && tried.has(pick))
|
|
355
|
+
pick = null;
|
|
356
|
+
}
|
|
357
|
+
if (pick === null) {
|
|
358
|
+
// No rankable nav — but a gating onboarding CHOICE step (role/usage
|
|
359
|
+
// picker) blocks the dashboard. Satisfy the minimal step to advance.
|
|
360
|
+
const choice = planOnboardingChoice({ url, pageText: text, inventory: inv });
|
|
361
|
+
if (choice !== null && !tried.has(choice)) {
|
|
362
|
+
tried.add(choice);
|
|
363
|
+
log(`nav-search: onboarding choice → ${choice}`);
|
|
364
|
+
await capture("overlay_advance", inv, choice);
|
|
365
|
+
await browser.clickSelector(choice).catch(() => { });
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
// Dump what WAS on the page (text + href + selector) so an exhaustion is
|
|
369
|
+
// diagnosable: did the keys nav exist but score 0, or was it genuinely
|
|
370
|
+
// absent (behind a menu/icon we didn't expand)?
|
|
371
|
+
const seen = enumerateCandidates(inv).map((c) => `[${c.isAnchor ? "a" : "btn"}] "${c.text.slice(0, 40)}" href=${c.href ?? "-"} sel=${c.selector}`);
|
|
372
|
+
log(`nav-search: candidates exhausted — no self-serve key surface reachable. ` +
|
|
373
|
+
`page had ${seen.length} clickable(s): ${seen.join(" | ")}`);
|
|
374
|
+
return { kind: "no_self_serve_key" };
|
|
375
|
+
}
|
|
376
|
+
tried.add(pick);
|
|
377
|
+
const picked = candidates.find((c) => c.selector === pick);
|
|
378
|
+
const pickText = picked?.text ?? "";
|
|
379
|
+
// Prefer direct href-navigation for anchor picks: a click on a portaled/
|
|
380
|
+
// animated dropdown item (avatar menu, Radix popover) is fragile and often
|
|
381
|
+
// no-ops. The href is the known destination — go there.
|
|
382
|
+
const hrefTarget = picked?.isAnchor === true ? resolveNavHref(picked.href, url) : null;
|
|
383
|
+
await capture("navigate", inv, pick);
|
|
384
|
+
if (hrefTarget !== null) {
|
|
385
|
+
log(`nav-search: → "${pickText.slice(0, 40)}" via href ${hrefTarget}`);
|
|
386
|
+
await browser.navigate(hrefTarget).catch(() => { });
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
log(`nav-search: → "${pickText.slice(0, 40)}" ${pick}`);
|
|
390
|
+
await browser.clickSelector(pick).catch(() => { });
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
log(`nav-search: step cap (${maxSteps}) reached without a key`);
|
|
394
|
+
return { kind: "no_self_serve_key" };
|
|
395
|
+
}
|
|
396
|
+
// Plan one step to get past a blocking overlay/wizard: prefer dismissing
|
|
397
|
+
// (skip/close — exits the wizard) over advancing (only when there's no skip).
|
|
398
|
+
// Pure; the caller clicks the selector (with an Escape-key fallback for modals
|
|
399
|
+
// that expose no in-DOM close control).
|
|
400
|
+
export function planOverlayStep(inventory) {
|
|
401
|
+
const dismiss = findOverlayDismiss(inventory);
|
|
402
|
+
if (dismiss !== null)
|
|
403
|
+
return { kind: "dismiss", selector: dismiss.selector };
|
|
404
|
+
const advance = findWizardAdvance(inventory);
|
|
405
|
+
if (advance !== null)
|
|
406
|
+
return { kind: "advance", selector: advance.selector };
|
|
407
|
+
return { kind: "none" };
|
|
408
|
+
}
|
|
409
|
+
//# sourceMappingURL=nav-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nav-search.js","sourceRoot":"","sources":["../../src/bot/nav-search.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { OAuthProviderId } from "./oauth-providers.js";
|
|
2
|
+
export type ProviderAuthState = "consent" | "needs_login" | "challenge" | "not_provider";
|
|
3
|
+
export declare function isAccountChooserUrl(url: string): boolean;
|
|
4
|
+
export interface OAuthState {
|
|
5
|
+
providerId: OAuthProviderId;
|
|
6
|
+
consentAlreadyApproved: boolean;
|
|
7
|
+
omniauthPostTried: boolean;
|
|
8
|
+
allowBlindOAuthConsent: boolean;
|
|
9
|
+
allowExtraOAuthScopes: readonly string[];
|
|
10
|
+
}
|
|
11
|
+
export interface OAuthObservation {
|
|
12
|
+
isChooser: boolean;
|
|
13
|
+
authState: ProviderAuthState;
|
|
14
|
+
hasLoginForm: boolean;
|
|
15
|
+
omniAuthPassthru: boolean;
|
|
16
|
+
scopes: readonly string[] | null;
|
|
17
|
+
dangerPhrases: readonly string[];
|
|
18
|
+
domBasicFromDom: boolean;
|
|
19
|
+
domBasicGis: boolean;
|
|
20
|
+
}
|
|
21
|
+
export type AdvanceMode = "approve" | "blind" | "soft";
|
|
22
|
+
export type OAuthAction = {
|
|
23
|
+
kind: "click_account_card";
|
|
24
|
+
} | {
|
|
25
|
+
kind: "advance_consent";
|
|
26
|
+
mode: AdvanceMode;
|
|
27
|
+
onAdvanceFail: "abort" | "bounded_wait" | "wait_nav";
|
|
28
|
+
} | {
|
|
29
|
+
kind: "recover_omniauth_post";
|
|
30
|
+
} | {
|
|
31
|
+
kind: "settle_left_provider";
|
|
32
|
+
} | {
|
|
33
|
+
kind: "handle_challenge";
|
|
34
|
+
} | {
|
|
35
|
+
kind: "abort";
|
|
36
|
+
reason: "needs_login" | "oauth_consent_needs_review";
|
|
37
|
+
clearProviderLoggedIn: boolean;
|
|
38
|
+
unauthorizedScopes?: readonly string[];
|
|
39
|
+
};
|
|
40
|
+
export interface OAuthStep {
|
|
41
|
+
action: OAuthAction;
|
|
42
|
+
nextState: OAuthState;
|
|
43
|
+
}
|
|
44
|
+
export interface OAuthDeps {
|
|
45
|
+
scopesAreBasic: (scopes: readonly string[]) => boolean;
|
|
46
|
+
}
|
|
47
|
+
export declare function decideOAuthStep(state: OAuthState, obs: OAuthObservation, deps: OAuthDeps): OAuthStep;
|
|
48
|
+
//# sourceMappingURL=oauth-flow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-flow.d.ts","sourceRoot":"","sources":["../../src/bot/oauth-flow.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAK5D,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,GAAG,cAAc,CAAC;AAQzF,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAExD;AAID,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,eAAe,CAAC;IAC5B,sBAAsB,EAAE,OAAO,CAAC;IAChC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,sBAAsB,EAAE,OAAO,CAAC;IAChC,qBAAqB,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAGD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,iBAAiB,CAAC;IAC7B,YAAY,EAAE,OAAO,CAAC;IAEtB,gBAAgB,EAAE,OAAO,CAAC;IAE1B,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IACjC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;AACvD,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,oBAAoB,CAAA;CAAE,GAK9B;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,aAAa,EAAE,OAAO,GAAG,cAAc,GAAG,UAAU,CAAA;CAAE,GACpG;IAAE,IAAI,EAAE,uBAAuB,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,sBAAsB,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,GAC5B;IACE,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,aAAa,GAAG,4BAA4B,CAAC;IAGrD,qBAAqB,EAAE,OAAO,CAAC;IAE/B,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC,CAAC;AAEN,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,UAAU,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IAGxB,cAAc,EAAE,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAC;CACxD;AAKD,wBAAgB,eAAe,CAC7B,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,SAAS,GACd,SAAS,CA6EX"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// oauth-flow.ts — the PURE decision REDUCER for the OAuth/consent phase, carved
|
|
2
|
+
// out of runOAuthFlow's ~1450-line state machine (strangler slice 2 — see
|
|
3
|
+
// docs/DESIGN-oauth-consent-engine.md). Eng-reviewed (Claude + Codex): an
|
|
4
|
+
// action-only function was too thin for a stateful machine, so this models the
|
|
5
|
+
// whole decision as a reducer:
|
|
6
|
+
//
|
|
7
|
+
// decideOAuthStep(state, observation, deps) → { action, nextState }
|
|
8
|
+
//
|
|
9
|
+
// Browser-free and exhaustively unit-tested so the security-critical decisions
|
|
10
|
+
// (never type into a login form; never blind-approve a non-basic scope) and the
|
|
11
|
+
// intricate consent ORDERING are verifiable in isolation.
|
|
12
|
+
//
|
|
13
|
+
// BOUNDARY: the reducer DECIDES; the executor (agent.ts) does the I/O and owns
|
|
14
|
+
// the advance-success bookkeeping. Specifically the executor, not the reducer:
|
|
15
|
+
// • gathers the `observation` (reads the browser: url, scopes, DOM checks),
|
|
16
|
+
// • performs the consent advance (`advanceOAuthConsent`) for advance_consent
|
|
17
|
+
// actions and handles its !advanced micro-logic per `onAdvanceFail`,
|
|
18
|
+
// • flips `consentAlreadyApproved` → true on a SUCCESSFUL advance (the live
|
|
19
|
+
// code only sets it after the advance lands),
|
|
20
|
+
// • owns the hydrate-retry budget (`consentAdvanceWaits`) — pure I/O timing,
|
|
21
|
+
// not a decision,
|
|
22
|
+
// • runs the challenge mechanics, OmniAuth POST, notifications.
|
|
23
|
+
// The reducer never touches a browser and only mutates `omniauthPostTried`
|
|
24
|
+
// (a POST is "tried" regardless of outcome — a decision-relevant fact).
|
|
25
|
+
// The Google "Choose an account" chooser. Its "…to continue to <app>" copy
|
|
26
|
+
// matches the consent classifier, but it is an account PICKER, not a scope
|
|
27
|
+
// grant — it must be clicked through BEFORE the consent decision runs. The live
|
|
28
|
+
// loop gates this on Google (eng-review #8); the executor passes `isChooser`
|
|
29
|
+
// already provider-gated, but the helper is exported for that computation.
|
|
30
|
+
const ACCOUNT_CHOOSER_URL = /\/(?:accountchooser|chooseaccount|oauthchooseaccount)/i;
|
|
31
|
+
export function isAccountChooserUrl(url) {
|
|
32
|
+
return ACCOUNT_CHOOSER_URL.test(url);
|
|
33
|
+
}
|
|
34
|
+
// Decide the next OAuth step. PURE. Mirrors the live loop's branch ORDER exactly
|
|
35
|
+
// (the consent ordering is security- and correctness-critical — see the inline
|
|
36
|
+
// step numbers against agent.ts).
|
|
37
|
+
export function decideOAuthStep(state, obs, deps) {
|
|
38
|
+
const keep = (action) => ({ action, nextState: state });
|
|
39
|
+
// The account chooser is handled FIRST, before the auth-state branch.
|
|
40
|
+
if (obs.isChooser)
|
|
41
|
+
return keep({ kind: "click_account_card" });
|
|
42
|
+
switch (obs.authState) {
|
|
43
|
+
case "not_provider":
|
|
44
|
+
// OmniAuth GET-passthru → re-POST once; else the flow left the provider.
|
|
45
|
+
if (obs.omniAuthPassthru && !state.omniauthPostTried) {
|
|
46
|
+
return {
|
|
47
|
+
action: { kind: "recover_omniauth_post" },
|
|
48
|
+
nextState: { ...state, omniauthPostTried: true },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return keep({ kind: "settle_left_provider" });
|
|
52
|
+
case "challenge":
|
|
53
|
+
return keep({ kind: "handle_challenge" });
|
|
54
|
+
case "needs_login":
|
|
55
|
+
return keep({ kind: "abort", reason: "needs_login", clearProviderLoggedIn: true });
|
|
56
|
+
case "consent": {
|
|
57
|
+
// INVARIANT #1: a live login form on a "consent"-classified page → never
|
|
58
|
+
// type; abort needs_login.
|
|
59
|
+
if (obs.hasLoginForm) {
|
|
60
|
+
return keep({ kind: "abort", reason: "needs_login", clearProviderLoggedIn: true });
|
|
61
|
+
}
|
|
62
|
+
// ── readable scopes (scope gate, invariant #2) ──
|
|
63
|
+
if (obs.scopes !== null) {
|
|
64
|
+
const nonBasic = obs.scopes.filter((s) => !deps.scopesAreBasic([s]));
|
|
65
|
+
const unauthorized = nonBasic.filter((s) => !state.allowExtraOAuthScopes.includes(s));
|
|
66
|
+
if (unauthorized.length > 0) {
|
|
67
|
+
return keep({
|
|
68
|
+
kind: "abort",
|
|
69
|
+
reason: "oauth_consent_needs_review",
|
|
70
|
+
clearProviderLoggedIn: false,
|
|
71
|
+
unauthorizedScopes: unauthorized,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// basic, or every non-basic scope pre-approved → approve.
|
|
75
|
+
return keep({ kind: "advance_consent", mode: "approve", onAdvanceFail: "abort" });
|
|
76
|
+
}
|
|
77
|
+
// ── scopes unreadable (opaque part= URL) — ORDER MIRRORS agent.ts ──
|
|
78
|
+
// 1. danger scope-grant verb phrases in the DOM → abort (it IS a grant).
|
|
79
|
+
if (obs.dangerPhrases.length > 0) {
|
|
80
|
+
return keep({
|
|
81
|
+
kind: "abort",
|
|
82
|
+
reason: "oauth_consent_needs_review",
|
|
83
|
+
clearProviderLoggedIn: false,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// 2. basic-only DOM recover (only on the FIRST consent page) [agent.ts:7648].
|
|
87
|
+
if (state.providerId === "google" && !state.consentAlreadyApproved && obs.domBasicFromDom) {
|
|
88
|
+
return keep({ kind: "advance_consent", mode: "approve", onAdvanceFail: "abort" });
|
|
89
|
+
}
|
|
90
|
+
// 3. F16: post-grant page after an earlier approval → soft-advance [7679].
|
|
91
|
+
if (state.consentAlreadyApproved) {
|
|
92
|
+
return keep({ kind: "advance_consent", mode: "soft", onAdvanceFail: "wait_nav" });
|
|
93
|
+
}
|
|
94
|
+
// 4. blind-consent (user delegated) → advance with bounded hydrate retry [7705].
|
|
95
|
+
if (state.allowBlindOAuthConsent) {
|
|
96
|
+
return keep({ kind: "advance_consent", mode: "blind", onAdvanceFail: "bounded_wait" });
|
|
97
|
+
}
|
|
98
|
+
// 5. GIS basic-identity recover (second DOM check) [7742].
|
|
99
|
+
if (obs.domBasicGis) {
|
|
100
|
+
return keep({ kind: "advance_consent", mode: "approve", onAdvanceFail: "abort" });
|
|
101
|
+
}
|
|
102
|
+
// 6. conservative abort — unreadable scopes, no recovery signal [7753].
|
|
103
|
+
return keep({
|
|
104
|
+
kind: "abort",
|
|
105
|
+
reason: "oauth_consent_needs_review",
|
|
106
|
+
clearProviderLoggedIn: false,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=oauth-flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-flow.js","sourceRoot":"","sources":["../../src/bot/oauth-flow.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,6 +16,8 @@ export interface OnboardingRoundCapture {
|
|
|
16
16
|
};
|
|
17
17
|
inventory: readonly InteractiveElement[];
|
|
18
18
|
observed: PostVerifyStep;
|
|
19
|
+
resolved_model?: string;
|
|
20
|
+
resolved_provider?: string;
|
|
19
21
|
}
|
|
20
22
|
export interface OnboardingCaseFile {
|
|
21
23
|
capture_format_version: typeof CAPTURE_FORMAT_VERSION;
|
|
@@ -25,6 +27,8 @@ export interface OnboardingCaseFile {
|
|
|
25
27
|
state: OnboardingRoundCapture["state"];
|
|
26
28
|
inventory: readonly InteractiveElement[];
|
|
27
29
|
observed: PostVerifyStep;
|
|
30
|
+
resolved_model?: string;
|
|
31
|
+
resolved_provider?: string;
|
|
28
32
|
expect: null;
|
|
29
33
|
prev_hash: string | null;
|
|
30
34
|
content_hash: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"onboarding-capture.d.ts","sourceRoot":"","sources":["../../src/bot/onboarding-capture.ts"],"names":[],"mappings":"AA0CA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAwB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAK7E,eAAO,MAAM,sBAAsB,EAAG,CAAU,CAAC;AAUjD,wBAAgB,YAAY,IAAI,MAAM,GAAG,SAAS,CAEjD;AAWD,wBAAgB,iBAAiB,IAAI,IAAI,CAIxC;AAkBD,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IACxE,SAAS,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAGzC,QAAQ,EAAE,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"onboarding-capture.d.ts","sourceRoot":"","sources":["../../src/bot/onboarding-capture.ts"],"names":[],"mappings":"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"}
|