@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.
- package/dist/bot/agent.d.ts +5 -1
- package/dist/bot/agent.d.ts.map +1 -1
- package/dist/bot/agent.js +281 -8
- package/dist/bot/agent.js.map +1 -1
- package/dist/bot/browser.d.ts +4 -0
- package/dist/bot/browser.d.ts.map +1 -1
- package/dist/bot/browser.js +360 -2
- package/dist/bot/browser.js.map +1 -1
- package/dist/bot/replay-skill.d.ts +16 -1
- package/dist/bot/replay-skill.d.ts.map +1 -1
- package/dist/bot/replay-skill.js +89 -12
- package/dist/bot/replay-skill.js.map +1 -1
- package/dist/install/cli.d.ts +1 -0
- package/dist/install/cli.d.ts.map +1 -1
- package/dist/install/cli.js +9 -0
- package/dist/install/cli.js.map +1 -1
- package/package.json +1 -1
|
@@ -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":"AA+CA,OAAO,KAAK,EACV,KAAK,EACL,SAAS,EAEV,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAS1E,OAAO,EAAE,KAAK,eAAe,EAAuC,MAAM,sBAAsB,CAAC;AAUjG,MAAM,WAAW,WAAW;IAC1B,oEAAoE;IACpE,KAAK,EAAE,KAAK,CAAC;IACb,+DAA+D;IAC/D,OAAO,EAAE,iBAAiB,CAAC;IAC3B;;;;OAIG;IACH,IAAI,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IACrE;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAClC;;;;;;;;;;OAUG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACtE;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,eAAe,CAAC;IACzC;;;;;;;;;;;OAWG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,YAAY,EAAE,SAAS,CAAC;IACxB,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,SAAS,EAAE,SAAS,kBAAkB,EAAE,CAAC;IACzC,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,KAAK,EAAE,KAAK,CAAC;CACd;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,aAAa,GAAG,OAAO,GAAG,aAAa,CAAA;CAAE,GAMhF;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;CAC1D,GACD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,SAAS,CAAA;CAAE,GACnF;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC5E;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"replay-skill.d.ts","sourceRoot":"","sources":["../../src/bot/replay-skill.ts"],"names":[],"mappings":"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"}
|
package/dist/bot/replay-skill.js
CHANGED
|
@@ -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
|
-
|
|
545
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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}`);
|