@trusty-squire/mcp 0.9.17 → 0.9.18-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.
@@ -57,6 +57,20 @@ export interface ReplayInput {
57
57
  fetchEmailCode?: (input: {
58
58
  alias: string;
59
59
  }) => Promise<string | null>;
60
+ /**
61
+ * Drives an interactive provider sign-in when the OAuth walk lands on a
62
+ * login/identifier page instead of a chooser/consent. Replay otherwise bails
63
+ * `needs_login` here — but the full discover bot would just type the
64
+ * password, and a freshly-created robot account lands on the identifier page
65
+ * the first time a given relying party requests OAuth even with a live
66
+ * session. The verifier wires this to the robot's credentials
67
+ * (verify-passwords.json) + `browser.loginGoogleInline`; the live-user
68
+ * router omits it (no stored end-user password to drive). Resolves true when
69
+ * the sign-in progressed (walk continues), false to fall through to the
70
+ * existing needs_login bail. Same caller-injected-transport separation as
71
+ * `fetchEmailCode`.
72
+ */
73
+ driveOAuthLogin?: (provider: OAuthProviderId) => Promise<boolean>;
60
74
  /**
61
75
  * Chrome profile whose OAuth-session marker should be trusted for
62
76
  * replay-time provider checks. Fresh-identity verifier replays pass the
@@ -123,6 +137,7 @@ export type ReplayOutcome = {
123
137
  kind: "needs_login";
124
138
  provider: "google" | "github";
125
139
  stepIndex: number;
140
+ afterOAuth: boolean;
126
141
  } | {
127
142
  kind: "skill_demoted";
128
143
  reason: string;
@@ -154,5 +169,5 @@ export declare function normalizeKindeReplayNavigateUrl(url: string): string;
154
169
  export declare function settledOnProductPage(currentUrl: string, expectedHost: string): boolean;
155
170
  export declare function pathHasOpaqueResourceId(path: string): boolean;
156
171
  export declare function inferProviderFromUrl(url: string): "google" | "github" | null;
157
- export declare function walkOAuthConsent(browser: BrowserController, providerId: OAuthProviderId): Promise<"ok" | "needs_login">;
172
+ export declare function walkOAuthConsent(browser: BrowserController, providerId: OAuthProviderId, driveOAuthLogin?: (provider: OAuthProviderId) => Promise<boolean>): Promise<"ok" | "needs_login">;
158
173
  //# sourceMappingURL=replay-skill.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"replay-skill.d.ts","sourceRoot":"","sources":["../../src/bot/replay-skill.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"replay-skill.d.ts","sourceRoot":"","sources":["../../src/bot/replay-skill.ts"],"names":[],"mappings":"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"}
@@ -248,7 +248,7 @@ export async function replaySkill(input) {
248
248
  validation = await preValidateStep(step, browser, templateValues);
249
249
  }
250
250
  else if (recovered.kind === "needs_login") {
251
- return { kind: "needs_login", provider: recovered.provider, stepIndex: i };
251
+ return { kind: "needs_login", provider: recovered.provider, stepIndex: i, afterOAuth: authedViaOAuth };
252
252
  }
253
253
  }
254
254
  let stepToExecute = step;
@@ -273,7 +273,7 @@ export async function replaySkill(input) {
273
273
  stepToExecute = fallbackResult.substitute;
274
274
  }
275
275
  else if (fallbackResult.kind === "needs_login") {
276
- return { kind: "needs_login", provider: fallbackResult.provider, stepIndex: i };
276
+ return { kind: "needs_login", provider: fallbackResult.provider, stepIndex: i, afterOAuth: authedViaOAuth };
277
277
  }
278
278
  else if (step.kind === "fill" &&
279
279
  isSkippableAbsentFill(step, validation.reason, i, skill.steps)) {
@@ -342,9 +342,9 @@ export async function replaySkill(input) {
342
342
  // the router can decide whether to retry or fall through to the
343
343
  // universal bot.
344
344
  try {
345
- const execOutcome = await executeStep(stepToExecute, browser, templateValues, skill, input.fetchEmailCode, input.profileDir, input.preferredOAuthProvider, skill.steps[i + 1]);
345
+ const execOutcome = await executeStep(stepToExecute, browser, templateValues, skill, input.fetchEmailCode, input.profileDir, input.preferredOAuthProvider, skill.steps[i + 1], input.driveOAuthLogin);
346
346
  if (execOutcome.kind === "needs_login") {
347
- return { kind: "needs_login", provider: execOutcome.provider, stepIndex: i };
347
+ return { kind: "needs_login", provider: execOutcome.provider, stepIndex: i, afterOAuth: authedViaOAuth };
348
348
  }
349
349
  // OAuth click succeeded (needs_login already returned above) → we're in
350
350
  // an authenticated returning-user session for the rest of the replay.
@@ -541,12 +541,32 @@ async function preValidateStep(step, browser, templateValues) {
541
541
  return { ok: true };
542
542
  }
543
543
  case "click_oauth_button": {
544
- const inventory = await browser.extractInteractiveElements();
545
- const matches = preferNonConsentClickMatches(inventory.filter((el) => matchesClickHint(el, step.text_match)), step.text_match);
544
+ let inventory = await browser.extractInteractiveElements();
545
+ let matches = preferNonConsentClickMatches(inventory.filter((el) => matchesClickHint(el, step.text_match)), step.text_match);
546
+ // Hardening (MEASURED 2026-06-24, the verifier sweep): the synthesizer
547
+ // hardcodes step.text_match to "Google"/"GitHub", but the OAuth button
548
+ // often (a) renders only after the SPA hydrates, or (b) is an icon /
549
+ // "Continue with Google" affordance the literal-word match misses — and a
550
+ // step-1 OAuth miss kills the WHOLE replay (the dominant verifier-hold
551
+ // mode). So when text_match finds nothing: re-read after a hydration
552
+ // settle, then fall back to the bot's provider-based finder
553
+ // (findOAuthButton matches by provider keyword in text/aria/href + OAuth
554
+ // scoring — the SAME logic discover used to find this button to begin
555
+ // with). The button identity is the provider, not a literal string.
546
556
  if (matches.length === 0) {
557
+ await browser.wait(2);
558
+ await browser.waitForInteractiveDom().catch(() => undefined);
559
+ inventory = await browser.extractInteractiveElements().catch(() => inventory);
560
+ matches = preferNonConsentClickMatches(inventory.filter((el) => matchesClickHint(el, step.text_match)), step.text_match);
561
+ }
562
+ if (matches.length === 0) {
563
+ const byProvider = findOAuthButton(inventory, step.provider);
564
+ if (byProvider !== null) {
565
+ return { ok: true, match: byProvider };
566
+ }
547
567
  return {
548
568
  ok: false,
549
- reason: `No element matches text_match=${JSON.stringify(step.text_match)} for ${step.provider} OAuth button.`,
569
+ reason: `No ${step.provider} OAuth button found (text_match=${JSON.stringify(step.text_match)} + provider scan).`,
550
570
  };
551
571
  }
552
572
  // Multiple matches — the disambiguator (C3) picks by role first,
@@ -1255,7 +1275,7 @@ async function attemptSimpleProjectOnboarding(browser, templateValues) {
1255
1275
  await browser.waitForInteractiveDom().catch(() => undefined);
1256
1276
  return true;
1257
1277
  }
1258
- async function executeStep(step, browser, templateValues, skill, fetchEmailCode, profileDir, preferredOAuthProvider, nextStep) {
1278
+ async function executeStep(step, browser, templateValues, skill, fetchEmailCode, profileDir, preferredOAuthProvider, nextStep, driveOAuthLogin) {
1259
1279
  switch (step.kind) {
1260
1280
  case "navigate": {
1261
1281
  // Rebase a captured per-account subdomain onto the live session's
@@ -1264,7 +1284,19 @@ async function executeStep(step, browser, templateValues, skill, fetchEmailCode,
1264
1284
  // gets rewritten to the current one. No-op for same-host / cross-product
1265
1285
  // / first-navigate (about:blank) cases.
1266
1286
  const targetUrl = normalizeKindeReplayNavigateUrl(rebaseSubdomain(step.url, browser.currentUrl()));
1267
- await browser.goto(targetUrl);
1287
+ try {
1288
+ await browser.goto(targetUrl);
1289
+ }
1290
+ catch (err) {
1291
+ // A goto can crash transiently ("Target page, context or browser has
1292
+ // been closed") under heavy concurrency or a redirect race. Retry once
1293
+ // before failing — a genuinely-dead context throws again and surfaces a
1294
+ // clean reason instead of a raw Playwright stack. (MEASURED 2026-06-24,
1295
+ // verifier sweep: step-2 goto crashes under 2-wide concurrency.)
1296
+ await browser.wait(1);
1297
+ await browser.goto(targetUrl);
1298
+ void err;
1299
+ }
1268
1300
  // Settle for SPA-style apps that fire route handlers post-
1269
1301
  // DOMContentLoaded. A fixed 2s under-waits heavy authenticated
1270
1302
  // dashboards (pusher's App Keys, imagekit's onboarding step rendered
@@ -1346,17 +1378,39 @@ async function executeStep(step, browser, templateValues, skill, fetchEmailCode,
1346
1378
  // chooser + basic consent out of band so a one-account capture replays for
1347
1379
  // an N-account user. Fast no-op when the round-trip already auto-completed
1348
1380
  // (single account → first state is not_provider / popup-closed → "ok").
1349
- const walk = await walkOAuthConsent(browser, step.provider);
1381
+ const walk = await walkOAuthConsent(browser, step.provider, driveOAuthLogin);
1350
1382
  if (walk === "needs_login") {
1351
1383
  return { kind: "needs_login", provider: step.provider };
1352
1384
  }
1385
+ // Restore the product page. Google Identity Services (meilisearch et al.)
1386
+ // runs OAuth in a POPUP that closes once consent posts the credential back
1387
+ // to the opener; without switching `this.page` off the now-closed popup,
1388
+ // the next replay step (a navigate/extract) runs against a dead page and
1389
+ // dies "Target page, context or browser has been closed". The OAuth
1390
+ // RECOVERY path already settles; the primary click_oauth_button path did
1391
+ // not — so popup-OAuth skills broke on the very next step the moment they
1392
+ // finally cleared OAuth. No-op for the same-tab redirect transport.
1393
+ await browser.settleAfterOAuth().catch(() => undefined);
1353
1394
  return { kind: "clicked" };
1354
1395
  }
1355
1396
  case "click": {
1356
1397
  let inventory = await browser.extractInteractiveElements();
1357
1398
  inventory = await maybeRefreshInventoryForHydratedClick(step, browser, inventory);
1358
- if (isLikelySubmitClick(step)) {
1399
+ // Fill the preconditions of a disabled submit BEFORE clicking it. The
1400
+ // text-gated isLikelySubmitClick only catches "create account / sign up"
1401
+ // wording; a post-OAuth ONBOARDING SURVEY (meilisearch: a required
1402
+ // role/use-case dropdown gating a "Continue") has a neutral button label,
1403
+ // so detect the disabled-submit state directly. fillRequiredComboboxes is
1404
+ // exactly what the discover bot runs here — the replay just never did, so
1405
+ // every survey-gated skill died "target is disabled after 15s" the moment
1406
+ // it cleared OAuth. Cheap DOM check; only fills when something is actually
1407
+ // unselected.
1408
+ if (isLikelySubmitClick(step) || (await browser.hasDisabledSubmit().catch(() => false))) {
1359
1409
  await autofillCommonIdentityFieldsBeforeSubmit(browser, templateValues);
1410
+ const picked = await browser.fillRequiredComboboxes().catch(() => []);
1411
+ if (picked.length > 0) {
1412
+ console.error(`[replay] filled ${picked.length} required combobox(es) before submit: ${picked.join(", ")}`);
1413
+ }
1360
1414
  inventory = await browser.extractInteractiveElements().catch(() => inventory);
1361
1415
  }
1362
1416
  await checkRequiredAgreementBoxesBeforeSubmitClick(browser, step);
@@ -3773,9 +3827,25 @@ async function clickConsentAffordance(browser) {
3773
3827
  // operator already established via `mcp login` — it does NOT log in.
3774
3828
  // Aborts to needs_login on a real challenge (2FA / verify-it's-you) or a
3775
3829
  // sensitive (non-basic) scope grant — both genuinely need a human.
3776
- export async function walkOAuthConsent(browser, providerId) {
3830
+ export async function walkOAuthConsent(browser, providerId, driveOAuthLogin) {
3777
3831
  const provider = OAUTH_PROVIDERS[providerId];
3778
3832
  const MAX_NAV = 6;
3833
+ // Bound the inline-login drive to ONE attempt — a credential that doesn't
3834
+ // clear the identifier/challenge after a full type-through is a real wall
3835
+ // (wrong password, 2SV the verifier can't satisfy), and re-driving would just
3836
+ // burn MAX_NAV iterations re-typing into the same dead form.
3837
+ let loginDriven = false;
3838
+ // When the provider supports an inline login drive, the identifier page is
3839
+ // recoverable, not terminal: try the credential type-through before bailing.
3840
+ const tryDriveLogin = async () => {
3841
+ if (driveOAuthLogin === undefined || loginDriven)
3842
+ return false;
3843
+ loginDriven = true;
3844
+ console.error(`[replay-oauth] ${providerId} login page — driving inline sign-in`);
3845
+ const ok = await driveOAuthLogin(providerId).catch(() => false);
3846
+ console.error(`[replay-oauth] inline sign-in ${ok ? "progressed" : "did not clear the login page"}`);
3847
+ return ok;
3848
+ };
3779
3849
  for (let i = 0; i < MAX_NAV; i++) {
3780
3850
  if (browser.oauthPageClosed())
3781
3851
  return "ok"; // popup closed → back on service
@@ -3807,6 +3877,8 @@ export async function walkOAuthConsent(browser, providerId) {
3807
3877
  continue;
3808
3878
  }
3809
3879
  if (providerId === "google" && /\/signin\/identifier\b/i.test(url)) {
3880
+ if (await tryDriveLogin())
3881
+ continue;
3810
3882
  console.error(`[replay-oauth] google identifier page — needs_login`);
3811
3883
  return "needs_login";
3812
3884
  }
@@ -3815,6 +3887,11 @@ export async function walkOAuthConsent(browser, providerId) {
3815
3887
  if (state === "not_provider")
3816
3888
  return "ok"; // flow left the provider
3817
3889
  if (state === "challenge" || state === "needs_login") {
3890
+ // A password/identifier screen is recoverable when we hold the
3891
+ // credential — drive the sign-in once before treating it as terminal.
3892
+ // (A genuine 2SV/verify-it's-you challenge won't clear and falls through.)
3893
+ if (await tryDriveLogin())
3894
+ continue;
3818
3895
  if (process.env.REPLAY_DEBUG) {
3819
3896
  try {
3820
3897
  writeFileSync(`/tmp/replay-oauth-${providerId}-${state}.txt`, `url=${url}\n\n${body}`);