@trusty-squire/mcp 1.1.28 → 1.1.29-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/api-client.d.ts +6 -0
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js.map +1 -1
- package/dist/bot/act/act.d.ts.map +1 -1
- package/dist/bot/act/act.js +15 -11
- package/dist/bot/act/act.js.map +1 -1
- package/dist/bot/browser.d.ts +0 -29
- package/dist/bot/browser.d.ts.map +1 -1
- package/dist/bot/browser.js +2 -747
- package/dist/bot/browser.js.map +1 -1
- package/dist/bot/capture/capture.d.ts +7 -15
- package/dist/bot/capture/capture.d.ts.map +1 -1
- package/dist/bot/capture/capture.js +71 -253
- package/dist/bot/capture/capture.js.map +1 -1
- package/dist/bot/card-release-approval.d.ts.map +1 -1
- package/dist/bot/card-release-approval.js +11 -2
- package/dist/bot/card-release-approval.js.map +1 -1
- package/dist/bot/credential-capture.d.ts +10 -0
- package/dist/bot/credential-capture.d.ts.map +1 -1
- package/dist/bot/credential-capture.js +5 -0
- package/dist/bot/credential-capture.js.map +1 -1
- package/dist/bot/credential-shape.d.ts +0 -5
- package/dist/bot/credential-shape.d.ts.map +1 -1
- package/dist/bot/credential-shape.js +8 -132
- package/dist/bot/credential-shape.js.map +1 -1
- package/dist/bot/drive-feedback.d.ts +2 -8
- package/dist/bot/drive-feedback.d.ts.map +1 -1
- package/dist/bot/drive-feedback.js +7 -49
- package/dist/bot/drive-feedback.js.map +1 -1
- package/dist/bot/jev-client.d.ts +3 -0
- package/dist/bot/jev-client.d.ts.map +1 -1
- package/dist/bot/jev-client.js +14 -5
- package/dist/bot/jev-client.js.map +1 -1
- package/dist/bot/operate-drive.d.ts +13 -63
- package/dist/bot/operate-drive.d.ts.map +1 -1
- package/dist/bot/operate-drive.js +136 -592
- package/dist/bot/operate-drive.js.map +1 -1
- package/dist/bot/page-driver.js +1 -1
- package/dist/bot/page-driver.js.map +1 -1
- package/dist/bot/provision-session.d.ts +3 -1
- package/dist/bot/provision-session.d.ts.map +1 -1
- package/dist/bot/provision-session.js +1 -1
- package/dist/bot/provision-session.js.map +1 -1
- package/dist/bot/session/lifecycle.d.ts.map +1 -1
- package/dist/bot/session/lifecycle.js +14 -13
- package/dist/bot/session/lifecycle.js.map +1 -1
- package/dist/bot/skill-hint.d.ts.map +1 -1
- package/dist/bot/skill-hint.js +4 -3
- package/dist/bot/skill-hint.js.map +1 -1
- package/dist/install/broker-service.d.ts.map +1 -1
- package/dist/install/broker-service.js +13 -15
- package/dist/install/broker-service.js.map +1 -1
- package/dist/install/cli.d.ts.map +1 -1
- package/dist/install/cli.js +43 -32
- package/dist/install/cli.js.map +1 -1
- package/dist/install/connect-report.d.ts +7 -7
- package/dist/install/connect-report.d.ts.map +1 -1
- package/dist/install/connect-report.js +31 -25
- package/dist/install/connect-report.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +42 -3
- package/dist/server.js.map +1 -1
- package/dist/tools/fetch-credential.d.ts.map +1 -1
- package/dist/tools/fetch-credential.js +3 -1
- package/dist/tools/fetch-credential.js.map +1 -1
- package/dist/tools/provision-drive-schema.d.ts +0 -1
- package/dist/tools/provision-drive-schema.d.ts.map +1 -1
- package/dist/tools/provision-drive-schema.js +125 -238
- package/dist/tools/provision-drive-schema.js.map +1 -1
- package/dist/tools/provision-drive.d.ts +217 -73
- package/dist/tools/provision-drive.d.ts.map +1 -1
- package/dist/tools/provision-drive.js +133 -180
- package/dist/tools/provision-drive.js.map +1 -1
- package/dist/tools/store-credential.d.ts.map +1 -1
- package/dist/tools/store-credential.js +5 -1
- package/dist/tools/store-credential.js.map +1 -1
- package/dist/tools/use-credential.js +2 -2
- package/package.json +1 -1
- package/dist/bot/credential-text.d.ts +0 -4
- package/dist/bot/credential-text.d.ts.map +0 -1
- package/dist/bot/credential-text.js +0 -296
- package/dist/bot/credential-text.js.map +0 -1
- package/dist/bot/extraction.d.ts +0 -18
- package/dist/bot/extraction.d.ts.map +0 -1
- package/dist/bot/extraction.js +0 -54
- package/dist/bot/extraction.js.map +0 -1
|
@@ -9,20 +9,20 @@
|
|
|
9
9
|
// facts bag. A search or query field may receive a phrase Jev assigns from the
|
|
10
10
|
// goal's own words or the facts; nothing is composed.
|
|
11
11
|
//
|
|
12
|
-
// Confidence
|
|
13
|
-
// shape
|
|
12
|
+
// Confidence below DRIVE_CONFIDENCE_THRESHOLD hands control back to the
|
|
13
|
+
// operator. Answer-shape validation and purchase approval stay. Safety net is
|
|
14
14
|
// validate_choice, fingerprint-bound consume-once, and three consecutive
|
|
15
15
|
// non-wait actions with no page change (re-snapshot once before a strike).
|
|
16
16
|
import { appendFileSync } from "node:fs";
|
|
17
|
-
import { JevUnavailableError, JevRequestError, askJev, } from "./jev-client.js";
|
|
18
|
-
import { act, awaitVerification,
|
|
17
|
+
import { JevUnavailableError, JevRequestError, JevInvalidResponseError, askJev, } from "./jev-client.js";
|
|
18
|
+
import { act, awaitVerification, observe, startProvisionSession, TargetStaleError, } from "./provision-session.js";
|
|
19
19
|
import { resolveInboxSearch } from "./capture/verification.js";
|
|
20
20
|
import { liveProviderSessionsForSession, sessionForCall } from "./session/lifecycle.js";
|
|
21
21
|
import { compactV2EpochDoc, compactV2RefAllocator, invalidateCompactV2Snapshot, observedThreeDsChallenge, rememberCompactV2SourcePage, } from "./observe/observe.js";
|
|
22
22
|
import { COMPACT_V2_HANDLE_LENGTH, controlLabelV2, isCompactV2Handle, safePageSemanticsV2, safeStageV2, wireRoleToSafeRoleV2, } from "./compact-observation-v2.js";
|
|
23
23
|
import { lastSelectOptions, } from "./session/model.js";
|
|
24
24
|
import { captureFrameSnapshot, driveRowsFromSnapshot, frameDynamicsSignature, mergeSnapshots, snapshotSelectOptions, snapshotToObservation, } from "./drive-snapshot.js";
|
|
25
|
-
import {
|
|
25
|
+
import { evaluateBound } from "./drive-evaluate.js";
|
|
26
26
|
import { DriveRefBridge } from "./drive-ref-bridge.js";
|
|
27
27
|
import { dispatchDriveAct } from "./act/act.js";
|
|
28
28
|
import { frameOriginOf } from "./browser-use-capture.js";
|
|
@@ -31,10 +31,9 @@ import { rememberDriveIdentities, resolveIdentityScope } from "./act/identity.js
|
|
|
31
31
|
import { approvalItemWithNote, readPageCheckoutTexts, resolveDriveApprovalAmount, } from "./checkout-total.js";
|
|
32
32
|
import { attemptOperateCaptchaAutoSolve, captchaAutoSolveHasWork, injectPendingCaptchaToken, } from "./captcha-solve.js";
|
|
33
33
|
import { RES_POLL_INTERVAL_MS, RES_TIMEOUT_MS, hasVisibleCheckboxCaptchaWidget, solveVisibleCaptcha, } from "./captcha.js";
|
|
34
|
-
import { findCredentialTokens, isMaskedDisplay
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import { DRIVE_FIXED_GO_BACK, DRIVE_FIXED_NONE_OF_THESE, DRIVE_TRAIL_CAP, DRIVE_TRAIL_STORE_CAP, classifyDriveOutcome, composeDrivePageText, feedbackPagePath, goalDoneWhen, goalPhase, goalPhaseAndDoneWhen, isKeyGoal, stripVolatileQuery, truncateDriveTrailText, } from "./drive-feedback.js";
|
|
34
|
+
import { findCredentialTokens, isMaskedDisplay } from "./credential-shape.js";
|
|
35
|
+
import { extractCredentials, storableCredentials } from "./capture/capture.js";
|
|
36
|
+
import { DRIVE_FIXED_GO_BACK, DRIVE_FIXED_NONE_OF_THESE, DRIVE_TRAIL_CAP, DRIVE_TRAIL_STORE_CAP, classifyDriveOutcome, composeDrivePageText, feedbackPagePath, goalDoneWhen, goalPhaseAndDoneWhen, stripVolatileQuery, truncateDriveTrailText, } from "./drive-feedback.js";
|
|
38
37
|
export const DRIVE_CONFIDENCE_THRESHOLD = 0.6;
|
|
39
38
|
export const DRIVE_DEFAULT_MAX_STEPS = 60;
|
|
40
39
|
export const DRIVE_DEFAULT_MAX_SECONDS = 45;
|
|
@@ -132,8 +131,8 @@ export const DRIVE_RULES = [
|
|
|
132
131
|
"If a needed control is covered, act on whatever covers it first.",
|
|
133
132
|
"Prefer controls that match the current page phase implied by the URL and headings.",
|
|
134
133
|
"A disabled submit means a required field is still empty until every fillable is populated. Disable is not a gate.",
|
|
135
|
-
"
|
|
136
|
-
"The
|
|
134
|
+
"Use the goal's full text to decide what result is required; no goal type is inferred for you.",
|
|
135
|
+
"The done_when condition includes every stated requirement in the goal.",
|
|
137
136
|
"The trail records what each earlier action actually did. Do not repeat an action whose trail outcome is no_change, not_executed, or bounced_back.",
|
|
138
137
|
"Pick NONE_OF_THESE instead of a low-confidence click when nothing on this page advances the goal.",
|
|
139
138
|
"Pick GO_BACK when the trail shows this page was reached by mistake.",
|
|
@@ -405,17 +404,21 @@ export function redactSecretShapedValue(value, sessionId) {
|
|
|
405
404
|
}
|
|
406
405
|
return value;
|
|
407
406
|
}
|
|
408
|
-
|
|
409
|
-
|
|
407
|
+
/** Exact-value card mask only. This is all the model (Jev) input carries. */
|
|
408
|
+
function maskCardValues(session, value) {
|
|
409
|
+
return typeof session.browser.maskOperatorOutput === "function"
|
|
410
410
|
? session.browser.maskOperatorOutput(value)
|
|
411
411
|
: value;
|
|
412
|
-
|
|
412
|
+
}
|
|
413
|
+
/** Trace files can outlive the session audience, so screen their contents. */
|
|
414
|
+
function maskPersistedDriveTrace(session, value) {
|
|
415
|
+
return redactSecretShapedValue(maskCardValues(session, value), session.id);
|
|
413
416
|
}
|
|
414
417
|
function appendDriveTrace(session, entry) {
|
|
415
418
|
const path = process.env.DRIVE_TRACE_PATH;
|
|
416
419
|
if (path === undefined || path.length === 0)
|
|
417
420
|
return;
|
|
418
|
-
appendFileSync(path, `${JSON.stringify(
|
|
421
|
+
appendFileSync(path, `${JSON.stringify(maskPersistedDriveTrace(session, entry))}\n`);
|
|
419
422
|
}
|
|
420
423
|
async function nativeSelectSnapshot(session) {
|
|
421
424
|
const page = session.browser.page;
|
|
@@ -679,12 +682,12 @@ export function cycleReason(url, repeated = []) {
|
|
|
679
682
|
return `cycling through ${repeated.join(", ")}`;
|
|
680
683
|
return `cycling through ${pagePathKey(url)}`;
|
|
681
684
|
}
|
|
682
|
-
export function isPendingPageAction(row,
|
|
685
|
+
export function isPendingPageAction(row, _goal = "") {
|
|
683
686
|
if (isRevealOrCopyRow(row) || isCreateEntryRow(row))
|
|
684
687
|
return true;
|
|
685
688
|
if (isSubmitLikeRow(row) && !isProgressSubmitRow(row) && !isOauthChromeRow(row))
|
|
686
689
|
return true;
|
|
687
|
-
return
|
|
690
|
+
return false;
|
|
688
691
|
}
|
|
689
692
|
export function pageHasUntriedPendingAction(rows, drive, pageUrl, goal) {
|
|
690
693
|
const failed = new Set(drive.failedActionKeys ?? []);
|
|
@@ -773,39 +776,8 @@ export function inferPagePhase(url, headings = []) {
|
|
|
773
776
|
return "login";
|
|
774
777
|
return "unknown";
|
|
775
778
|
}
|
|
776
|
-
function goalWantsSignup(goal) {
|
|
777
|
-
if (goal === undefined || goal.length === 0)
|
|
778
|
-
return false;
|
|
779
|
-
return /sign\s*up|register|create\s+(?:an?\s+)?account/.test(goal.toLowerCase());
|
|
780
|
-
}
|
|
781
|
-
function goalWantsKey(goal) {
|
|
782
|
-
if (goal === undefined || goal.length === 0)
|
|
783
|
-
return false;
|
|
784
|
-
return /api\s*key|access\s*token|credential/.test(goal.toLowerCase());
|
|
785
|
-
}
|
|
786
|
-
function isCreateAccountRow(row) {
|
|
787
|
-
const label = readableLabel(row).toLowerCase();
|
|
788
|
-
return /create\s+(?:an?\s+)?account|sign\s*up|register/.test(label);
|
|
789
|
-
}
|
|
790
|
-
function isLoginRow(row) {
|
|
791
|
-
const label = readableLabel(row).toLowerCase();
|
|
792
|
-
return /log\s*in|sign\s*in/.test(label) && !isCreateAccountRow(row);
|
|
793
|
-
}
|
|
794
|
-
function isKeyNavRow(row) {
|
|
795
|
-
if (isListFilterRow(row))
|
|
796
|
-
return false;
|
|
797
|
-
const label = readableLabel(row).toLowerCase();
|
|
798
|
-
return /api\s*key|access\s*token|create\s+(?:a\s+)?(?:key|token)|credentials?|settings/.test(label);
|
|
799
|
-
}
|
|
800
|
-
function isNavChromeRow(row) {
|
|
801
|
-
if (isSubmitLikeRow(row) || isFillableRow(row) || isChoiceRow(row) || isConsentRow(row)) {
|
|
802
|
-
return false;
|
|
803
|
-
}
|
|
804
|
-
return row[1] === "l" || row[1] === "link" || row[1] === "tb" || row[1] === "tab";
|
|
805
|
-
}
|
|
806
779
|
export function candidateAimScore(candidate, input) {
|
|
807
780
|
const row = candidate.row;
|
|
808
|
-
const phase = input.phase ?? inferPagePhase(input.pageUrl ?? "", input.headings ?? []);
|
|
809
781
|
const filledIds = filledFormIds(input.rows, input.filledRefs ?? []);
|
|
810
782
|
const formId = rowFormId(row);
|
|
811
783
|
const occluder = rowOccluder(row);
|
|
@@ -828,76 +800,14 @@ export function candidateAimScore(candidate, input) {
|
|
|
828
800
|
if (failed.has(actionFailureKey(row, input.pageUrl ?? "")) || failed.has(row[0])) {
|
|
829
801
|
score -= 70;
|
|
830
802
|
}
|
|
831
|
-
if (phase === "signup") {
|
|
832
|
-
if (isSubmitLikeRow(row) || isFillableRow(row) || isChoiceRow(row))
|
|
833
|
-
score += 40;
|
|
834
|
-
if (isOauthChromeRow(row))
|
|
835
|
-
score -= 40;
|
|
836
|
-
if (isSearchRow(row) && rowListChoice(row) === undefined)
|
|
837
|
-
score -= 30;
|
|
838
|
-
if (isLoginRow(row))
|
|
839
|
-
score -= 20;
|
|
840
|
-
}
|
|
841
|
-
else if (phase === "login") {
|
|
842
|
-
if (goalWantsSignup(input.goal) && isCreateAccountRow(row))
|
|
843
|
-
score += 80;
|
|
844
|
-
else if (isLoginRow(row) || isSubmitLikeRow(row))
|
|
845
|
-
score += 20;
|
|
846
|
-
if (isOauthChromeRow(row) && goalWantsSignup(input.goal))
|
|
847
|
-
score -= 30;
|
|
848
|
-
}
|
|
849
|
-
else if (phase === "verify") {
|
|
850
|
-
if (/verify|confirm|email|inbox|gmail|mail/.test(readableLabel(row).toLowerCase()))
|
|
851
|
-
score += 50;
|
|
852
|
-
}
|
|
853
|
-
else if (phase === "onboarding") {
|
|
854
|
-
if (isChoiceRow(row) || rowListChoice(row) !== undefined)
|
|
855
|
-
score += 40;
|
|
856
|
-
if (isSearchRow(row) && rowListChoice(row) === undefined)
|
|
857
|
-
score -= 40;
|
|
858
|
-
}
|
|
859
|
-
else if (phase === "keys" || (phase === "unknown" && goalWantsKey(input.goal))) {
|
|
860
|
-
if (isKeyNavRow(row))
|
|
861
|
-
score += 80;
|
|
862
|
-
}
|
|
863
|
-
else if (phase === "checkout") {
|
|
864
|
-
if (isButtonLikeRow(row) || isPaymentRow(row) || isCvvRow(row))
|
|
865
|
-
score += 40;
|
|
866
|
-
}
|
|
867
|
-
if (isNavChromeRow(row) && (phase === "signup" || phase === "onboarding" || phase === "login")) {
|
|
868
|
-
score -= 10;
|
|
869
|
-
}
|
|
870
|
-
if (isAlreadyHereNav(row, input.pageUrl ?? ""))
|
|
871
|
-
score -= 80;
|
|
872
|
-
if (isAppRootOrLogoRow(row, input.pageUrl ?? "") ||
|
|
873
|
-
isSamePageAnchorRow(row, input.pageUrl ?? "")) {
|
|
874
|
-
score -= 80;
|
|
875
|
-
}
|
|
876
|
-
if (isCreateEntryRow(row) && listedItemRows(input.rows, input.pageUrl ?? "").length > 0) {
|
|
877
|
-
score -= 50;
|
|
878
|
-
}
|
|
879
|
-
if (listedItemRows(input.rows, input.pageUrl ?? "").some((entry) => entry[0] === row[0])) {
|
|
880
|
-
score += 70;
|
|
881
|
-
}
|
|
882
|
-
if (isRevealOrCopyRow(row) && goalWantsKey(input.goal))
|
|
883
|
-
score += 80;
|
|
884
|
-
if (isListFilterRow(row) && goalWantsKey(input.goal))
|
|
885
|
-
score -= 50;
|
|
886
|
-
if (goalWantsKey(input.goal) &&
|
|
887
|
-
isEligibleSectionNavRow(row, input.pageUrl ?? "") &&
|
|
888
|
-
!isAlreadyHereNav(row, input.pageUrl ?? "") &&
|
|
889
|
-
!rowMatchesGoalSeek(row, input.goal ?? "")) {
|
|
890
|
-
score += listedItemRows(input.rows, input.pageUrl ?? "").length > 0 ? -20 : 40;
|
|
891
|
-
}
|
|
892
803
|
return score;
|
|
893
804
|
}
|
|
894
805
|
export function rankDriveCandidates(candidates, input) {
|
|
895
|
-
const phase = inferPagePhase(input.pageUrl ?? "", input.headings ?? []);
|
|
896
806
|
return candidates
|
|
897
807
|
.map((candidate, index) => ({
|
|
898
808
|
candidate,
|
|
899
809
|
index,
|
|
900
|
-
score: candidateAimScore(candidate,
|
|
810
|
+
score: candidateAimScore(candidate, input),
|
|
901
811
|
}))
|
|
902
812
|
.sort((left, right) => right.score - left.score || left.index - right.index)
|
|
903
813
|
.map((entry) => entry.candidate);
|
|
@@ -927,16 +837,9 @@ const BUTTON_LIKE_ROLES = new Set(["b", "button"]);
|
|
|
927
837
|
export function isButtonLikeRow(row) {
|
|
928
838
|
return BUTTON_LIKE_ROLES.has(row[1]);
|
|
929
839
|
}
|
|
930
|
-
/**
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
* viewport) and the act path scrolls, so a checkout's buttons stay reachable.
|
|
934
|
-
* The page, not the label, is the test: a label allowlist would drop a
|
|
935
|
-
* localized "Payer maintenant". Off a checkout no offscreen button is offered,
|
|
936
|
-
* so a product page's own "Buy now" stays unclickable.
|
|
937
|
-
*/
|
|
938
|
-
export function offscreenRowStaysOffered(row, pageUrl) {
|
|
939
|
-
return isButtonLikeRow(row) && isCheckoutUrl(pageUrl);
|
|
840
|
+
/** The act path scrolls an offscreen target into view before acting. */
|
|
841
|
+
export function offscreenRowStaysOffered(_row, _pageUrl) {
|
|
842
|
+
return true;
|
|
940
843
|
}
|
|
941
844
|
/** Whether the drive already asked to pay since the card went in.
|
|
942
845
|
*
|
|
@@ -983,7 +886,7 @@ export function pageHasListedWork(rows, typeTextCount, selectCount) {
|
|
|
983
886
|
* call (a card-fill goal never submits at all).
|
|
984
887
|
*/
|
|
985
888
|
function isPaymentSubstituteRow(row) {
|
|
986
|
-
return isClickableRow(row) && !isFillableRow(row)
|
|
889
|
+
return isClickableRow(row) && !isFillableRow(row);
|
|
987
890
|
}
|
|
988
891
|
export function paymentSubmitControlMissing(input) {
|
|
989
892
|
if (!input.includePayment ||
|
|
@@ -1016,9 +919,6 @@ function urlPathname(url) {
|
|
|
1016
919
|
export function isCheckoutUrl(url) {
|
|
1017
920
|
return /(?:^|\/)(?:checkouts?|payment)(?:\/|$)/.test(urlPathname(url));
|
|
1018
921
|
}
|
|
1019
|
-
export function goalSeeksKey(goal) {
|
|
1020
|
-
return /api\s*key|access\s*token|credential/.test(goal.toLowerCase());
|
|
1021
|
-
}
|
|
1022
922
|
export function isListFilterRow(row) {
|
|
1023
923
|
const label = readableLabel(row).toLowerCase();
|
|
1024
924
|
const chooser = row[1] === "c" ||
|
|
@@ -1082,21 +982,6 @@ export function isOffProductNavRow(row, pageUrl) {
|
|
|
1082
982
|
return /\/(?:docs?|documentation|reference|help|blog|guides?)(?:\/|$)/.test(href);
|
|
1083
983
|
}
|
|
1084
984
|
}
|
|
1085
|
-
export function isGoalDestinationRow(row) {
|
|
1086
|
-
if (isListFilterRow(row) ||
|
|
1087
|
-
isContextPickerRow(row) ||
|
|
1088
|
-
isFillableRow(row) ||
|
|
1089
|
-
isConsentRow(row) ||
|
|
1090
|
-
isCodeSampleRow(row)) {
|
|
1091
|
-
return false;
|
|
1092
|
-
}
|
|
1093
|
-
return (row[1] === "l" ||
|
|
1094
|
-
row[1] === "link" ||
|
|
1095
|
-
row[1] === "b" ||
|
|
1096
|
-
row[1] === "button" ||
|
|
1097
|
-
row[1] === "tb" ||
|
|
1098
|
-
row[1] === "tab");
|
|
1099
|
-
}
|
|
1100
985
|
const SECTION_NAV_SKIP = /home|docs|documentation|logout|sign\s*out|help|pricing|blog|status|privacy|terms/;
|
|
1101
986
|
const SECTION_NAV_NOTICE = /more information|reactivat|provide more|complete your|verify your identity/;
|
|
1102
987
|
const PAGE_LEVEL_ANSWER = /more information|reactivat|provide more|account (?:is |has been )?(?:suspend|restrict|locked|disabled|on hold)|needs (?:more )?(?:information|attention)|complete (?:your )?(?:profile|verification|account)/i;
|
|
@@ -1276,33 +1161,17 @@ export function rowShowsSecretEvidence(row) {
|
|
|
1276
1161
|
export function pageShowsRevealedKey(rows, _pageText = "") {
|
|
1277
1162
|
return rows.some((row) => rowShowsSecretEvidence(row));
|
|
1278
1163
|
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
//
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
return {
|
|
1291
|
-
credentials: extracted.credentials,
|
|
1292
|
-
maskedRemaining: extracted.masked_remaining ?? [],
|
|
1293
|
-
};
|
|
1294
|
-
}
|
|
1295
|
-
catch {
|
|
1296
|
-
// The completion check must never crash the loop; an unavailable page is
|
|
1297
|
-
// simply "no credential yet", and the loop carries on.
|
|
1298
|
-
return { credentials: {}, maskedRemaining: [] };
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
/** "still masked: a, b" for a stuck reason, or undefined when nothing was. */
|
|
1302
|
-
export function maskedRemainingReason(evidence) {
|
|
1303
|
-
return evidence.maskedRemaining.length > 0
|
|
1304
|
-
? `still masked: ${evidence.maskedRemaining.join(", ")}`
|
|
1305
|
-
: undefined;
|
|
1164
|
+
function pageShowsCredentialValue(rows) {
|
|
1165
|
+
if (pageShowsRevealedKey(rows))
|
|
1166
|
+
return true;
|
|
1167
|
+
// A labeled value can be a key even when it has no recognizable prefix or
|
|
1168
|
+
// length. Shape is never the criterion for capturing or storing it.
|
|
1169
|
+
return rows.some((row) => {
|
|
1170
|
+
if (!/^(?:api[- ]?key|access token|token|secret)$/i.test(readableLabel(row)))
|
|
1171
|
+
return false;
|
|
1172
|
+
const value = /(?:^|\|)n=([^|]+)/.exec(row[2] ?? "")?.[1];
|
|
1173
|
+
return value !== undefined && value.length > 0 && !isMaskedDisplay(value);
|
|
1174
|
+
});
|
|
1306
1175
|
}
|
|
1307
1176
|
export function isKeyCreateRow(row) {
|
|
1308
1177
|
if (isFillableRow(row) || isConsentRow(row) || isOauthChromeRow(row))
|
|
@@ -1314,12 +1183,20 @@ export function isKeyCreateRow(row) {
|
|
|
1314
1183
|
const thing = /(?:^|[\s\-_])(?:api[\s\-_]*)?(?:key|token|secret|credential)s?(?:[\s\-_]|$)/.test(label);
|
|
1315
1184
|
return (create && thing) || (isCreateEntryRow(row) && thing);
|
|
1316
1185
|
}
|
|
1317
|
-
/**
|
|
1318
|
-
function isExistingCredentialMutationRow(row) {
|
|
1319
|
-
|
|
1186
|
+
/** Hand an existing credential mutation to the operator before dispatch. */
|
|
1187
|
+
export function isExistingCredentialMutationRow(row, pageUrl, rows = [], selectedOption = "", headings = []) {
|
|
1188
|
+
const label = `${readableLabel(row)} ${selectedOption}`;
|
|
1189
|
+
if (!/\b(?:regenerat\w*|rotat\w*|reset|revoke|delet\w*|remove)\b/i.test(label)) {
|
|
1190
|
+
return false;
|
|
1191
|
+
}
|
|
1192
|
+
const credentialNoun = /\b(?:(?:api[- ]?)?keys?|tokens?|secrets?|credentials?)\b/i;
|
|
1193
|
+
return credentialNoun.test(label) ||
|
|
1194
|
+
/(?:^|\/)(?:api[-_]?keys?|keys?|tokens?|credentials?)(?:\/|$)/.test(urlPathname(pageUrl)) ||
|
|
1195
|
+
headings.some((heading) => credentialNoun.test(heading)) ||
|
|
1196
|
+
rows.some((entry) => /^(?:h[1-6]|heading)$/.test(entry[1]) && credentialNoun.test(readableLabel(entry)));
|
|
1320
1197
|
}
|
|
1321
|
-
function isKeyCreateOpener(row
|
|
1322
|
-
return
|
|
1198
|
+
function isKeyCreateOpener(row) {
|
|
1199
|
+
return isKeyCreateRow(row) && rowFormId(row) === undefined;
|
|
1323
1200
|
}
|
|
1324
1201
|
/** Map the act layer's own refusal reason to the trail's short vocabulary:
|
|
1325
1202
|
* covered, stale, refused, dialog. */
|
|
@@ -1424,41 +1301,21 @@ export function attachRevealedSecretMarker(observation, rows) {
|
|
|
1424
1301
|
return { observation, rows: [...rows], attached: false };
|
|
1425
1302
|
}
|
|
1426
1303
|
const length = lengths.length > 0 ? Math.max(...lengths) : 16;
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
const facts = row[2] === undefined ? undefined : redact(row[2]);
|
|
1431
|
-
if (role === row[1] && facts === row[2])
|
|
1432
|
-
return row;
|
|
1433
|
-
return (facts === undefined ? [row[0], role] : [row[0], role, facts]);
|
|
1434
|
-
});
|
|
1304
|
+
// The marker tells the model a key is on the page; the page content itself
|
|
1305
|
+
// reaches it unaltered.
|
|
1306
|
+
const nextRows = [...rows];
|
|
1435
1307
|
if (!nextRows.some((row) => row[0] === REVEALED_SECRET_REF)) {
|
|
1436
1308
|
nextRows.push(revealedSecretMarkerRow(length, tokens.size));
|
|
1437
1309
|
}
|
|
1438
1310
|
const headings = [
|
|
1439
|
-
...(observation.semantic?.headings ?? [])
|
|
1311
|
+
...(observation.semantic?.headings ?? []),
|
|
1440
1312
|
revealedSecretMarkerRow(length, tokens.size)[2],
|
|
1441
1313
|
];
|
|
1442
1314
|
return {
|
|
1443
1315
|
observation: {
|
|
1444
1316
|
...observation,
|
|
1445
|
-
...(observation.dom === undefined ? {} : { dom: redact(observation.dom) }),
|
|
1446
1317
|
safe_table: nextRows,
|
|
1447
|
-
semantic: {
|
|
1448
|
-
...observation.semantic,
|
|
1449
|
-
...(observation.semantic?.title === undefined
|
|
1450
|
-
? {}
|
|
1451
|
-
: { title: redact(observation.semantic.title) }),
|
|
1452
|
-
headings,
|
|
1453
|
-
...(observation.semantic?.blockers === undefined
|
|
1454
|
-
? {}
|
|
1455
|
-
: {
|
|
1456
|
-
blockers: observation.semantic.blockers.map((blocker) => ({
|
|
1457
|
-
...blocker,
|
|
1458
|
-
text: redact(blocker.text),
|
|
1459
|
-
})),
|
|
1460
|
-
}),
|
|
1461
|
-
},
|
|
1318
|
+
semantic: { ...observation.semantic, headings },
|
|
1462
1319
|
},
|
|
1463
1320
|
rows: nextRows,
|
|
1464
1321
|
attached: true,
|
|
@@ -1479,58 +1336,9 @@ export function isEntityNameRow(row) {
|
|
|
1479
1336
|
export function isLogoutRow(row) {
|
|
1480
1337
|
return /log\s*out|sign\s*out|signout|logout/.test(readableLabel(row).toLowerCase());
|
|
1481
1338
|
}
|
|
1482
|
-
export function alreadySignedInReason() {
|
|
1483
|
-
return "already signed in as another account";
|
|
1484
|
-
}
|
|
1485
|
-
const EMAIL_IN_TEXT = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}/gi;
|
|
1486
|
-
export function emailsInText(text) {
|
|
1487
|
-
const seen = new Set();
|
|
1488
|
-
const out = [];
|
|
1489
|
-
for (const match of text.matchAll(EMAIL_IN_TEXT)) {
|
|
1490
|
-
const email = match[0].toLowerCase();
|
|
1491
|
-
if (seen.has(email))
|
|
1492
|
-
continue;
|
|
1493
|
-
seen.add(email);
|
|
1494
|
-
out.push(email);
|
|
1495
|
-
}
|
|
1496
|
-
return out;
|
|
1497
|
-
}
|
|
1498
|
-
/** Goal asks to use an already-signed-in third-party identity. */
|
|
1499
|
-
export function goalSeeksThirdPartySignin(goal) {
|
|
1500
|
-
if (goalExcludesOauth(goal))
|
|
1501
|
-
return false;
|
|
1502
|
-
return /continue with (?:google|github)|sign(?:\s*up|\s*in) with (?:google|github)|third[- ]party sign-?in|account already signed in/.test(goal.toLowerCase());
|
|
1503
|
-
}
|
|
1504
1339
|
export function shouldInspectSubmitResponse(action, row) {
|
|
1505
1340
|
return action.kind === "click" && row !== undefined && isSubmitLikeRow(row);
|
|
1506
1341
|
}
|
|
1507
|
-
/**
|
|
1508
|
-
* A check-your-email / verify page — visible copy and OTP fields only.
|
|
1509
|
-
* The word "verif" in a URL or link label is not this: product sections
|
|
1510
|
-
* about identity verifications are ordinary signed-in pages.
|
|
1511
|
-
*/
|
|
1512
|
-
export function pageLooksLikeEmailVerification(rows, _pageUrl = "", pageText = "") {
|
|
1513
|
-
if (rows.some((row) => isOtpRow(row) && isFillableRow(row)))
|
|
1514
|
-
return true;
|
|
1515
|
-
return emailVerificationCopy(`${pageText} ${headingCopy(rows)}`);
|
|
1516
|
-
}
|
|
1517
|
-
/** Page copy names an email the goal did not. Unknown or matching identity is not this. */
|
|
1518
|
-
export function pageShowsForeignIdentity(rows, pageText, goal) {
|
|
1519
|
-
const wanted = emailsInText(goal);
|
|
1520
|
-
if (wanted.length === 0)
|
|
1521
|
-
return false;
|
|
1522
|
-
const wantedSet = new Set(wanted);
|
|
1523
|
-
const pageEmails = emailsInText(`${pageText} ${rows.map((row) => readableLabel(row)).join(" ")}`);
|
|
1524
|
-
return pageEmails.some((email) => !wantedSet.has(email));
|
|
1525
|
-
}
|
|
1526
|
-
export function isPreexistingSessionPage(input) {
|
|
1527
|
-
if (goalSeeksThirdPartySignin(input.goal))
|
|
1528
|
-
return false;
|
|
1529
|
-
if (pageShowsForeignIdentity(input.rows, input.pageText, input.goal))
|
|
1530
|
-
return true;
|
|
1531
|
-
return (input.submittedThisDrive !== true &&
|
|
1532
|
-
pageLooksLikeEmailVerification(input.rows, input.pageUrl, input.pageText));
|
|
1533
|
-
}
|
|
1534
1342
|
export function invalidFieldReason(rows) {
|
|
1535
1343
|
const invalid = rows.find((row) => isInvalidRow(row));
|
|
1536
1344
|
if (invalid === undefined)
|
|
@@ -1642,55 +1450,7 @@ function shouldRecordVisit(row, pageUrl) {
|
|
|
1642
1450
|
const kind = row[1];
|
|
1643
1451
|
return kind === "l" || kind === "link" || kind === "tb" || kind === "tab";
|
|
1644
1452
|
}
|
|
1645
|
-
function
|
|
1646
|
-
return decision.kind === "act";
|
|
1647
|
-
}
|
|
1648
|
-
export function rowCarriesGoalNoun(row, goal) {
|
|
1649
|
-
if (!goalSeeksKey(goal) || isConsentRow(row) || isContextPickerRow(row))
|
|
1650
|
-
return false;
|
|
1651
|
-
const label = readableLabel(row).toLowerCase();
|
|
1652
|
-
return /api\s*key|access\s*token|create\s+(?:a\s+)?(?:key|token)|credentials?|\b(?:api[-_]?keys?|tokens?)\b/.test(label);
|
|
1653
|
-
}
|
|
1654
|
-
export function rowMatchesGoalSeek(row, goal) {
|
|
1655
|
-
if (rowCarriesGoalNoun(row, goal))
|
|
1656
|
-
return true;
|
|
1657
|
-
if (!goalSeeksKey(goal) || isConsentRow(row) || isContextPickerRow(row))
|
|
1658
|
-
return false;
|
|
1659
|
-
return /\bsettings\b/.test(readableLabel(row).toLowerCase());
|
|
1660
|
-
}
|
|
1661
|
-
export function pageIsPostAuthSetup(url) {
|
|
1662
|
-
return /(?:^|\/)(?:welcome|onboarding|getting[-_]?started|confirm|dashboard|console|home|teams|app)(?:\/|$)/.test(urlPathname(url));
|
|
1663
|
-
}
|
|
1664
|
-
export function pageLooksLikeKeyDestination(url) {
|
|
1665
|
-
return /(?:^|\/)(?:api[-_]?keys?|tokens?|credentials?|settings)(?:\/|$)/.test(urlPathname(url));
|
|
1666
|
-
}
|
|
1667
|
-
export function clickGoalSeekScore(row, goal, pageUrl) {
|
|
1668
|
-
if (!goalSeeksKey(goal))
|
|
1669
|
-
return 0;
|
|
1670
|
-
const onSetup = pageIsPostAuthSetup(pageUrl);
|
|
1671
|
-
const onKeys = pageLooksLikeKeyDestination(pageUrl);
|
|
1672
|
-
if (!onSetup && !onKeys)
|
|
1673
|
-
return 0;
|
|
1674
|
-
if (isContextPickerRow(row) ||
|
|
1675
|
-
isAlreadyHereNav(row, pageUrl) ||
|
|
1676
|
-
isOffProductNavRow(row, pageUrl)) {
|
|
1677
|
-
return 0;
|
|
1678
|
-
}
|
|
1679
|
-
if (isRevealOrCopyRow(row))
|
|
1680
|
-
return 3;
|
|
1681
|
-
if (isDeeperDestination(rowHref(row), pageUrl) || isListedEntityRow(row))
|
|
1682
|
-
return 2;
|
|
1683
|
-
if (rowMatchesGoalSeek(row, goal) && isGoalDestinationRow(row))
|
|
1684
|
-
return 2;
|
|
1685
|
-
if (rowMatchesGoalSeek(row, goal))
|
|
1686
|
-
return 1;
|
|
1687
|
-
if (onSetup && isSubmitLikeRow(row) && !isDisabledRow(row))
|
|
1688
|
-
return 1;
|
|
1689
|
-
return 0;
|
|
1690
|
-
}
|
|
1691
|
-
export function isCandidateRow(row, includePayment, pageUrl = "") {
|
|
1692
|
-
if (isOffscreenRow(row) && !offscreenRowStaysOffered(row, pageUrl))
|
|
1693
|
-
return false;
|
|
1453
|
+
export function isCandidateRow(row, includePayment, _pageUrl = "") {
|
|
1694
1454
|
if (isDisabledRow(row) && !isSubmitLikeRow(row))
|
|
1695
1455
|
return false;
|
|
1696
1456
|
if (!includePayment && (isPaymentRow(row) || isCvvRow(row)))
|
|
@@ -1851,34 +1611,13 @@ export function isOauthChromeRow(row) {
|
|
|
1851
1611
|
return /\b(?:microsoft|hasura|sso|oauth)\b/.test(label) ||
|
|
1852
1612
|
/\/broker\/[^/]+\/login\b/i.test(row[2] ?? "");
|
|
1853
1613
|
}
|
|
1854
|
-
/** Goal text that rules out third-party sign-in as the path to take. */
|
|
1855
|
-
export function goalExcludesOauth(goal) {
|
|
1856
|
-
const text = goal.toLowerCase();
|
|
1857
|
-
if (/\b(?:without|not|don't|dont|exclude|avoid|no)\b[\s\S]{0,48}\b(?:google|github|sso|oauth|third[- ]party)\b/.test(text)) {
|
|
1858
|
-
return true;
|
|
1859
|
-
}
|
|
1860
|
-
if (/\b(?:google|github|sso|oauth|third[- ]party)\b[\s\S]{0,48}\b(?:excluded|forbidden|disabled)\b/.test(text)) {
|
|
1861
|
-
return true;
|
|
1862
|
-
}
|
|
1863
|
-
return (/\b(?:email|password)\b/.test(text) &&
|
|
1864
|
-
/\b(?:only|instead|without|not|exclude|avoid)\b/.test(text));
|
|
1865
|
-
}
|
|
1866
|
-
/** Sign-up chrome is only third-party links — no email, password, or native submit. */
|
|
1867
|
-
export function pageOffersOnlyThirdPartySignup(rows) {
|
|
1868
|
-
if (!rows.some((row) => isOauthChromeRow(row)))
|
|
1869
|
-
return false;
|
|
1870
|
-
return formSurfaceRows(rows).length === 0;
|
|
1871
|
-
}
|
|
1872
|
-
export function noOtherSignupPathReason() {
|
|
1873
|
-
return "The page offers no other sign-up path besides third-party sign-in links.";
|
|
1874
|
-
}
|
|
1875
1614
|
export function isChoiceRow(row) {
|
|
1876
1615
|
const role = row[1];
|
|
1877
1616
|
return role === "c" || role === "checkbox" || role === "combobox" || isPickerRow(row);
|
|
1878
1617
|
}
|
|
1879
1618
|
export function formSurfaceRows(rows) {
|
|
1880
1619
|
return rows.filter((row) => {
|
|
1881
|
-
if (isOauthChromeRow(row)
|
|
1620
|
+
if (isOauthChromeRow(row))
|
|
1882
1621
|
return false;
|
|
1883
1622
|
return isFillableRow(row) || isSubmitLikeRow(row) || isChoiceRow(row);
|
|
1884
1623
|
});
|
|
@@ -2246,26 +1985,15 @@ export function applyReleasedCardFacts(facts, card) {
|
|
|
2246
1985
|
}
|
|
2247
1986
|
return next;
|
|
2248
1987
|
}
|
|
2249
|
-
export function ensureGeneratedFacts(
|
|
1988
|
+
export function ensureGeneratedFacts(_rows, facts, _context = {}) {
|
|
2250
1989
|
const next = { ...facts };
|
|
2251
1990
|
const first = next.first_name?.trim() ?? "";
|
|
2252
1991
|
const last = next.last_name?.trim() ?? "";
|
|
2253
1992
|
if (next.name === undefined && first.length > 0 && last.length > 0) {
|
|
2254
1993
|
next.name = `${first} ${last}`;
|
|
2255
1994
|
}
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
const hasLoginHeading = context.headings?.some((heading) => /\b(?:log|sign)\s*in\b/i.test(heading)) === true;
|
|
2259
|
-
const hasSignupAction = rows.some((row) => isButtonLikeRow(row) && isCreateAccountRow(row));
|
|
2260
|
-
const hasConfirmPassword = rows.some((row) => isFillableRow(row) && isPasswordRow(row) &&
|
|
2261
|
-
/(?:^|[_ ])(?:confirm|confirmation|repeat|reenter|verify)(?:[_ ]|$)/.test(`${normalizeKey(fieldNameForRow(row))} ${normalizeKey(readableLabel(row))}`));
|
|
2262
|
-
if (phase !== "login" && (phase === "signup" || (!hasLoginAction && !hasLoginHeading)) &&
|
|
2263
|
-
(phase === "signup" || hasSignupAction || hasConfirmPassword || goalWantsSignup(context.goal))) {
|
|
2264
|
-
if (next.password === undefined && rows.some((row) => isFillableRow(row) && !isActedRow(row) && !isPaymentRow(row) && !isCvvRow(row) &&
|
|
2265
|
-
isPasswordRow(row) && matchingFactKeys(next, row).length === 0)) {
|
|
2266
|
-
next.password = generatePassword();
|
|
2267
|
-
}
|
|
2268
|
-
}
|
|
1995
|
+
// Password creation belongs to operate_login's sealed signup path. A required
|
|
1996
|
+
// password without a fact reaches the drive's needs_value handoff.
|
|
2269
1997
|
return next;
|
|
2270
1998
|
}
|
|
2271
1999
|
export function slugifyCriteriaKey(seed) {
|
|
@@ -2538,19 +2266,16 @@ export function clickableCandidates(rows, includePayment, skippedRefs = [], page
|
|
|
2538
2266
|
description: actionDescription(candidate.row, rows, "CLICK"),
|
|
2539
2267
|
}));
|
|
2540
2268
|
}
|
|
2541
|
-
export function fillableCandidates(rows, facts, includePayment, filledRefs = [],
|
|
2269
|
+
export function fillableCandidates(rows, facts, includePayment, filledRefs = [], _pageUrl = "") {
|
|
2542
2270
|
const filled = new Set(filledRefs);
|
|
2543
2271
|
const used = new Set();
|
|
2544
2272
|
const candidates = [];
|
|
2545
|
-
const allowOffscreen = pageUrl.length === 0 || isCheckoutUrl(pageUrl);
|
|
2546
2273
|
for (const row of rows) {
|
|
2547
2274
|
if (!isFillableRow(row) || isDisabledRow(row) || isActedRow(row))
|
|
2548
2275
|
continue;
|
|
2549
2276
|
// An invalid field is not done even if we already typed it.
|
|
2550
2277
|
if (filled.has(row[0]) && !isInvalidRow(row))
|
|
2551
2278
|
continue;
|
|
2552
|
-
if (isOffscreenRow(row) && !allowOffscreen)
|
|
2553
|
-
continue;
|
|
2554
2279
|
if (!includePayment && (isPaymentRow(row) || isCvvRow(row)))
|
|
2555
2280
|
continue;
|
|
2556
2281
|
if (isPaymentRow(row) || isCvvRow(row))
|
|
@@ -2584,14 +2309,11 @@ export function typeableCandidates(rows, facts, includePayment, filledRefs = [],
|
|
|
2584
2309
|
const used = new Set(typed.map((candidate) => candidate.slug));
|
|
2585
2310
|
const extra = [];
|
|
2586
2311
|
const filled = new Set(filledRefs);
|
|
2587
|
-
const allowOffscreen = pageUrl.length === 0 || isCheckoutUrl(pageUrl);
|
|
2588
2312
|
for (const row of rows) {
|
|
2589
2313
|
if (!isFillableRow(row) || isSelectRow(row) || isDisabledRow(row) || isActedRow(row))
|
|
2590
2314
|
continue;
|
|
2591
2315
|
if (filled.has(row[0]) || seen.has(row[0]))
|
|
2592
2316
|
continue;
|
|
2593
|
-
if (isOffscreenRow(row) && !allowOffscreen)
|
|
2594
|
-
continue;
|
|
2595
2317
|
if (isPaymentRow(row) || isCvvRow(row))
|
|
2596
2318
|
continue;
|
|
2597
2319
|
// A checkout site-search box is not a fill. Offering it after the card
|
|
@@ -2616,11 +2338,10 @@ export function typeableCandidates(rows, facts, includePayment, filledRefs = [],
|
|
|
2616
2338
|
}
|
|
2617
2339
|
return [...typed, ...extra];
|
|
2618
2340
|
}
|
|
2619
|
-
export function selectCandidates(rows, facts, includePayment, filledRefs = [],
|
|
2341
|
+
export function selectCandidates(rows, facts, includePayment, filledRefs = [], _pageUrl = "") {
|
|
2620
2342
|
const filled = new Set(filledRefs);
|
|
2621
2343
|
const used = new Set();
|
|
2622
2344
|
const candidates = [];
|
|
2623
|
-
const allowOffscreen = pageUrl.length === 0 || isCheckoutUrl(pageUrl);
|
|
2624
2345
|
for (const row of rows) {
|
|
2625
2346
|
if (!isFillableRow(row) || !isSelectRow(row) || isDisabledRow(row) || isActedRow(row))
|
|
2626
2347
|
continue;
|
|
@@ -2628,8 +2349,6 @@ export function selectCandidates(rows, facts, includePayment, filledRefs = [], p
|
|
|
2628
2349
|
continue;
|
|
2629
2350
|
if (rowAlreadyShowsFact(row, facts, matchingFactKeys(facts, row)))
|
|
2630
2351
|
continue;
|
|
2631
|
-
if (isOffscreenRow(row) && !allowOffscreen)
|
|
2632
|
-
continue;
|
|
2633
2352
|
if (isPaymentRow(row) || isCvvRow(row))
|
|
2634
2353
|
continue;
|
|
2635
2354
|
const role = ROLE_LETTERS[row[1]] ?? row[1];
|
|
@@ -2659,7 +2378,7 @@ export function selectTargets(candidates, facts, pageOptions = new Map(), maskTe
|
|
|
2659
2378
|
if (texts.has(text))
|
|
2660
2379
|
return;
|
|
2661
2380
|
texts.add(text);
|
|
2662
|
-
const label =
|
|
2381
|
+
const label = maskText(text);
|
|
2663
2382
|
const optionSlug = uniqueCriteriaSlug(label, used);
|
|
2664
2383
|
used.add(optionSlug);
|
|
2665
2384
|
targets.push({
|
|
@@ -2818,7 +2537,7 @@ export function requiredFactTypeAction(rows, facts, filledRefs = [], pageUrl = "
|
|
|
2818
2537
|
}
|
|
2819
2538
|
return undefined;
|
|
2820
2539
|
}
|
|
2821
|
-
export function requiredFactComboboxAction(rows, facts, filledRefs = [],
|
|
2540
|
+
export function requiredFactComboboxAction(rows, facts, filledRefs = [], _pageUrl = "", _goal) {
|
|
2822
2541
|
const filled = new Set(filledRefs);
|
|
2823
2542
|
for (const row of rows) {
|
|
2824
2543
|
if (row[1] !== "combobox" || isDisabledRow(row) || isActedRow(row) || filled.has(row[0])) {
|
|
@@ -2844,14 +2563,11 @@ export function requiredFactComboboxAction(rows, facts, filledRefs = [], pageUrl
|
|
|
2844
2563
|
}
|
|
2845
2564
|
return undefined;
|
|
2846
2565
|
}
|
|
2847
|
-
export function requiredFillableMissingFact(rows, facts, filledRefs = [],
|
|
2566
|
+
export function requiredFillableMissingFact(rows, facts, filledRefs = [], _pageUrl = "", goal = "") {
|
|
2848
2567
|
const filled = new Set(filledRefs);
|
|
2849
|
-
const allowOffscreen = pageUrl.length === 0 || isCheckoutUrl(pageUrl);
|
|
2850
2568
|
for (const row of rows) {
|
|
2851
2569
|
if (!isFillableRow(row) || isDisabledRow(row) || isActedRow(row) || filled.has(row[0]))
|
|
2852
2570
|
continue;
|
|
2853
|
-
if (isOffscreenRow(row) && !allowOffscreen)
|
|
2854
|
-
continue;
|
|
2855
2571
|
if (isPaymentRow(row) || isCvvRow(row) || isOtpRow(row) || allowsGoalValueAssignment(row))
|
|
2856
2572
|
continue;
|
|
2857
2573
|
if (isFormValueField(row, rows, goal) &&
|
|
@@ -2886,10 +2602,8 @@ export function compactRowsText(url, stage, rows) {
|
|
|
2886
2602
|
return `${header}\n${JSON.stringify(rows)}`;
|
|
2887
2603
|
}
|
|
2888
2604
|
export function nextActionInstructions(goal) {
|
|
2889
|
-
const phase = goalPhase(goal);
|
|
2890
2605
|
const doneWhen = goalDoneWhen(goal);
|
|
2891
2606
|
return (`You are driving a browser to: ${goal}. ` +
|
|
2892
|
-
(phase === undefined ? "" : `Current phase: ${phase}. `) +
|
|
2893
2607
|
`The goal is complete only when ${doneWhen}. ` +
|
|
2894
2608
|
"Pick the single next operation that advances it. " +
|
|
2895
2609
|
"Pick DONE only when that done_when condition is on the page now. " +
|
|
@@ -3032,39 +2746,25 @@ export function elementState(candidate, extras = {}) {
|
|
|
3032
2746
|
export function buildJevState(goal, factKeys, history, url, title, candidates, pageText = "", feedback = {}) {
|
|
3033
2747
|
const recent = history.slice(-DRIVE_HISTORY_CAP);
|
|
3034
2748
|
const pageUrl = feedback.pageUrl ?? url;
|
|
3035
|
-
const layerOpen = pageOcclusionLayer(candidates.map((candidate) => candidate.row)) !== undefined;
|
|
3036
2749
|
const tried = new Set(feedback.triedHere ?? []);
|
|
3037
2750
|
const seenElementIds = new Set();
|
|
3038
2751
|
const elements = candidates
|
|
3039
|
-
.slice(0, DRIVE_MAX_CANDIDATES)
|
|
3040
|
-
// Rows outside the main region only matter when a dialog or overlay is
|
|
3041
|
-
// open. Hidden content is a distractor, not a lead.
|
|
3042
|
-
.filter((candidate) => {
|
|
3043
|
-
const region = driveElementLayer(candidate.row, pageUrl);
|
|
3044
|
-
return layerOpen || (region !== "dialog" && region !== "overlay");
|
|
3045
|
-
})
|
|
3046
2752
|
.map((candidate) => {
|
|
3047
2753
|
const leads = driveElementLeads(candidate.row, pageUrl);
|
|
3048
|
-
seenElementIds.add(candidate.
|
|
2754
|
+
seenElementIds.add(candidate.ref);
|
|
3049
2755
|
return elementState(candidate, {
|
|
3050
2756
|
tried: tried.has(stableControlKey(candidate.row, pageUrl)),
|
|
3051
2757
|
...(leads === undefined ? {} : { leads }),
|
|
3052
2758
|
layer: driveElementLayer(candidate.row, pageUrl),
|
|
3053
2759
|
});
|
|
3054
2760
|
});
|
|
3055
|
-
//
|
|
3056
|
-
//
|
|
3057
|
-
//
|
|
2761
|
+
// Include every observed row. A row absent from the action choices still
|
|
2762
|
+
// gives the decider page context, including disabled and previously tried
|
|
2763
|
+
// controls, documentation links, and fields below the fold.
|
|
3058
2764
|
for (const row of feedback.rows ?? []) {
|
|
3059
|
-
if (!tried.has(stableControlKey(row, pageUrl)))
|
|
3060
|
-
continue;
|
|
3061
2765
|
const region = driveElementLayer(row, pageUrl);
|
|
3062
|
-
if (!layerOpen && (region === "dialog" || region === "overlay"))
|
|
3063
|
-
continue;
|
|
3064
2766
|
if (seenElementIds.has(row[0]))
|
|
3065
2767
|
continue;
|
|
3066
|
-
if (elements.length >= DRIVE_MAX_CANDIDATES)
|
|
3067
|
-
break;
|
|
3068
2768
|
const leads = driveElementLeads(row, pageUrl);
|
|
3069
2769
|
seenElementIds.add(row[0]);
|
|
3070
2770
|
elements.push(elementState({
|
|
@@ -3074,7 +2774,7 @@ export function buildJevState(goal, factKeys, history, url, title, candidates, p
|
|
|
3074
2774
|
description: readableLabel(row),
|
|
3075
2775
|
row,
|
|
3076
2776
|
}, {
|
|
3077
|
-
tried:
|
|
2777
|
+
tried: tried.has(stableControlKey(row, pageUrl)),
|
|
3078
2778
|
...(leads === undefined ? {} : { leads }),
|
|
3079
2779
|
layer: region,
|
|
3080
2780
|
}));
|
|
@@ -3104,7 +2804,7 @@ export function operationCriteria(operations) {
|
|
|
3104
2804
|
const criteria = {};
|
|
3105
2805
|
for (const operation of operations) {
|
|
3106
2806
|
if (operation === "CLICK")
|
|
3107
|
-
criteria.CLICK = "click a
|
|
2807
|
+
criteria.CLICK = "click a control on the page";
|
|
3108
2808
|
else if (operation === "TYPE_TEXT")
|
|
3109
2809
|
criteria.TYPE_TEXT = "type a provided fact into a field";
|
|
3110
2810
|
else if (operation === "SELECT")
|
|
@@ -3155,43 +2855,17 @@ export function selectOptionsFromElements(elements) {
|
|
|
3155
2855
|
return out;
|
|
3156
2856
|
}
|
|
3157
2857
|
export function driveTargetSets(rows, facts, includePayment, filledRefs = [], pageUrl = "", pageOptions = new Map(), maskText = (text) => text, skippedClickRefs = [], aim = {}) {
|
|
3158
|
-
|
|
2858
|
+
// Bound each action family independently so a long form cannot consume the
|
|
2859
|
+
// entire candidate budget and make another action kind disappear.
|
|
2860
|
+
const candidateBudget = () => ({ n: DRIVE_MAX_CANDIDATES });
|
|
3159
2861
|
const skipped = new Set(skippedClickRefs);
|
|
3160
|
-
const offeredProviders = new Set(rows.map(oauthProviderForRow).filter((provider) => provider !== undefined));
|
|
3161
|
-
const liveProviders = new Set([...offeredProviders].filter((provider) => aim.liveProviders?.includes(provider)));
|
|
3162
|
-
const hideFilters = goalSeeksKey(aim.goal ?? "");
|
|
3163
|
-
const hasInAppWork = rows.some((row) => isGoalDestinationRow(row) &&
|
|
3164
|
-
!isOffProductNavRow(row, pageUrl) &&
|
|
3165
|
-
!isAppRootOrLogoRow(row, pageUrl) &&
|
|
3166
|
-
!isContextPickerRow(row));
|
|
3167
|
-
const visited = aim.visitedSectionKeys ?? [];
|
|
3168
|
-
const untriedEntries = hideFilters ? untriedListedItemRows(rows, visited, pageUrl) : [];
|
|
3169
2862
|
const layer = pageOcclusionLayer(rows);
|
|
3170
2863
|
const failed = new Set(aim.failedKeys ?? []);
|
|
3171
2864
|
const keepRow = (row) => {
|
|
3172
|
-
const provider = oauthProviderForRow(row);
|
|
3173
|
-
if (offeredProviders.size > 1 && liveProviders.size > 0 && isOauthChromeRow(row) &&
|
|
3174
|
-
(provider === undefined || !liveProviders.has(provider)))
|
|
3175
|
-
return false;
|
|
3176
|
-
if (isCodeSampleRow(row))
|
|
3177
|
-
return false;
|
|
3178
|
-
if (isKeyGoal(aim.goal ?? "") && isExistingCredentialMutationRow(row))
|
|
3179
|
-
return false;
|
|
3180
2865
|
if (failed.has(actionFailureKey(row, pageUrl)) || failed.has(row[0]))
|
|
3181
2866
|
return false;
|
|
3182
2867
|
if (layer !== undefined && !isLayerCandidateRow(row, rows, layer))
|
|
3183
2868
|
return false;
|
|
3184
|
-
if (hideFilters && isContextPickerRow(row))
|
|
3185
|
-
return false;
|
|
3186
|
-
if (hasInAppWork && isOffProductNavRow(row, pageUrl))
|
|
3187
|
-
return false;
|
|
3188
|
-
if (isAppRootOrLogoRow(row, pageUrl) || isSamePageAnchorRow(row, pageUrl))
|
|
3189
|
-
return false;
|
|
3190
|
-
if (visited.includes(sectionIdentity(row, pageUrl)))
|
|
3191
|
-
return false;
|
|
3192
|
-
if (hideFilters && untriedEntries.length > 0 && isSiblingSectionNavRow(row, pageUrl, rows)) {
|
|
3193
|
-
return false;
|
|
3194
|
-
}
|
|
3195
2869
|
return true;
|
|
3196
2870
|
};
|
|
3197
2871
|
const aimInput = {
|
|
@@ -3202,17 +2876,10 @@ export function driveTargetSets(rows, facts, includePayment, filledRefs = [], pa
|
|
|
3202
2876
|
failedKeys: aim.failedKeys ?? [],
|
|
3203
2877
|
...(aim.goal === undefined ? {} : { goal: aim.goal }),
|
|
3204
2878
|
};
|
|
3205
|
-
const typeText = takeCapped(rankDriveCandidates(typeableCandidates(rows, facts, includePayment, filledRefs, pageUrl, aim.goal).filter((candidate) => !skipped.has(candidate.ref) && keepRow(candidate.row)), aimInput),
|
|
3206
|
-
const select = takeCapped(rankDriveCandidates(selectTargets(selectCandidates(rows, facts, includePayment, filledRefs, pageUrl).filter((candidate) => !skipped.has(candidate.ref) && keepRow(candidate.row)), facts, pageOptions, maskText), aimInput),
|
|
3207
|
-
const click = takeCapped(rankDriveCandidates(clickableCandidates(rows, includePayment, skippedClickRefs, pageUrl, filledRefs).filter((candidate) => keepRow(candidate.row)), aimInput)
|
|
3208
|
-
|
|
3209
|
-
candidate,
|
|
3210
|
-
index,
|
|
3211
|
-
score: clickGoalSeekScore(candidate.row, aim.goal ?? "", pageUrl),
|
|
3212
|
-
}))
|
|
3213
|
-
.sort((left, right) => right.score - left.score || left.index - right.index)
|
|
3214
|
-
.map((entry) => entry.candidate), remaining);
|
|
3215
|
-
const scroll = takeCapped(rankDriveCandidates(scrollTargets(rows), aimInput), remaining);
|
|
2879
|
+
const typeText = takeCapped(rankDriveCandidates(typeableCandidates(rows, facts, includePayment, filledRefs, pageUrl, aim.goal).filter((candidate) => !skipped.has(candidate.ref) && keepRow(candidate.row)), aimInput), candidateBudget());
|
|
2880
|
+
const select = takeCapped(rankDriveCandidates(selectTargets(selectCandidates(rows, facts, includePayment, filledRefs, pageUrl).filter((candidate) => !skipped.has(candidate.ref) && keepRow(candidate.row)), facts, pageOptions, maskText), aimInput), candidateBudget());
|
|
2881
|
+
const click = takeCapped(rankDriveCandidates(clickableCandidates(rows, includePayment, skippedClickRefs, pageUrl, filledRefs).filter((candidate) => keepRow(candidate.row)), aimInput), candidateBudget());
|
|
2882
|
+
const scroll = takeCapped(rankDriveCandidates(scrollTargets(rows), aimInput), candidateBudget());
|
|
3216
2883
|
const operations = [];
|
|
3217
2884
|
if (click.length > 0)
|
|
3218
2885
|
operations.push("CLICK");
|
|
@@ -3222,31 +2889,12 @@ export function driveTargetSets(rows, facts, includePayment, filledRefs = [], pa
|
|
|
3222
2889
|
operations.push("SELECT");
|
|
3223
2890
|
if (scroll.length > 0)
|
|
3224
2891
|
operations.push("SCROLL");
|
|
3225
|
-
|
|
3226
|
-
// candidate is suppressed, withholding WAIT and BLOCKED too would leave DONE
|
|
3227
|
-
// as the only admissible answer and force a false "complete".
|
|
3228
|
-
const goalWork = (pageIsPostAuthSetup(pageUrl) || pageLooksLikeKeyDestination(pageUrl)) &&
|
|
3229
|
-
rows.some((row) => !isDisabledRow(row) &&
|
|
3230
|
-
!isOffscreenRow(row) &&
|
|
3231
|
-
rowMatchesGoalSeek(row, aim.goal ?? "") &&
|
|
3232
|
-
click.some((candidate) => candidate.ref === row[0]));
|
|
3233
|
-
const listedWork = operations.length > 0 && (pageHasListedWork(rows, typeText.length, select.length) || goalWork);
|
|
3234
|
-
const pageText = (aim.headings ?? []).join(" ");
|
|
3235
|
-
const inboxReady = !skipped.has("INBOX") &&
|
|
3236
|
-
aim.submittedThisDrive === true &&
|
|
3237
|
-
(pageSuggestsInboxWait(rows, pageUrl, pageText) ||
|
|
3238
|
-
(goalSeeksVerification(aim.goal ?? "") && !listedWork));
|
|
3239
|
-
if (inboxReady)
|
|
2892
|
+
if (!skipped.has("INBOX"))
|
|
3240
2893
|
operations.push("INBOX");
|
|
3241
|
-
|
|
3242
|
-
// model-chosen wait that left a page with rows unchanged. An empty snapshot
|
|
3243
|
-
// never records one, so a payment settling behind a blank processor screen
|
|
3244
|
-
// keeps its wait for as long as the budgets allow.
|
|
3245
|
-
if (!listedWork && !inboxReady && !skipped.has("WAIT"))
|
|
2894
|
+
if (!skipped.has("WAIT"))
|
|
3246
2895
|
operations.push("WAIT");
|
|
3247
2896
|
operations.push("DONE");
|
|
3248
|
-
|
|
3249
|
-
operations.push("BLOCKED");
|
|
2897
|
+
operations.push("BLOCKED");
|
|
3250
2898
|
return { operations, TYPE_TEXT: typeText, SELECT: select, CLICK: click, SCROLL: scroll };
|
|
3251
2899
|
}
|
|
3252
2900
|
function criteriaFromCandidates(candidates, operation) {
|
|
@@ -3270,7 +2918,6 @@ export function emailCodeCandidates(rows, filledRefs = []) {
|
|
|
3270
2918
|
.filter((row) => isFillableRow(row) &&
|
|
3271
2919
|
!isSelectRow(row) &&
|
|
3272
2920
|
!isDisabledRow(row) &&
|
|
3273
|
-
!isOffscreenRow(row) &&
|
|
3274
2921
|
!isActedRow(row) &&
|
|
3275
2922
|
!filled.has(row[0]) &&
|
|
3276
2923
|
rowValueMissing(row) &&
|
|
@@ -3471,10 +3118,7 @@ export function pageSuggestsInboxWait(rows, _pageUrl = "", pageText = "") {
|
|
|
3471
3118
|
const hay = `${pageText} ${headingCopy(rows)} ${rows.map((row) => readableLabel(row)).join(" ")}`.toLowerCase();
|
|
3472
3119
|
return emailVerificationCopy(hay) || /open gmail|#search\//.test(hay);
|
|
3473
3120
|
}
|
|
3474
|
-
export function
|
|
3475
|
-
return /verif(?:y|ication)|confirm(?:ation)?(?:\s+your)?\s+e-?mail|check your e-?mail/.test(goal.toLowerCase());
|
|
3476
|
-
}
|
|
3477
|
-
export function inboxSpecialPlan(rows, decisionKind, _clicked, remainingFillCount, pageUrl = "", pageText = "", goal = "", submittedThisDrive = false) {
|
|
3121
|
+
export function inboxSpecialPlan(rows, decisionKind, _clicked, remainingFillCount, pageUrl = "", pageText = "", _goal = "", submittedThisDrive = false) {
|
|
3478
3122
|
if (decisionKind !== "stuck" && decisionKind !== "wait")
|
|
3479
3123
|
return undefined;
|
|
3480
3124
|
const waitingForMail = pageSuggestsInboxWait(rows, pageUrl, pageText);
|
|
@@ -3500,12 +3144,8 @@ export function inboxSpecialPlan(rows, decisionKind, _clicked, remainingFillCoun
|
|
|
3500
3144
|
!isOtpRow(row) &&
|
|
3501
3145
|
!isOauthChromeRow(row) &&
|
|
3502
3146
|
!isOffscreenRow(row))) {
|
|
3503
|
-
if (submittedThisDrive && goalSeeksVerification(goal))
|
|
3504
|
-
return { kind: "link" };
|
|
3505
3147
|
return undefined;
|
|
3506
3148
|
}
|
|
3507
|
-
if (submittedThisDrive && goalSeeksVerification(goal))
|
|
3508
|
-
return { kind: "link" };
|
|
3509
3149
|
return undefined;
|
|
3510
3150
|
}
|
|
3511
3151
|
export function inboxVerificationDecision(verification, planKind) {
|
|
@@ -4005,7 +3645,7 @@ function publicDriveHandoff(session, handoff) {
|
|
|
4005
3645
|
}
|
|
4006
3646
|
return value;
|
|
4007
3647
|
};
|
|
4008
|
-
return
|
|
3648
|
+
return maskCardValues(session, visit(handoff));
|
|
4009
3649
|
}
|
|
4010
3650
|
function maskedRefsOf(drive) {
|
|
4011
3651
|
if (!Array.isArray(drive.maskedValueRefs))
|
|
@@ -4272,11 +3912,34 @@ async function actSafely(deps, sessionId, action) {
|
|
|
4272
3912
|
};
|
|
4273
3913
|
}
|
|
4274
3914
|
}
|
|
4275
|
-
|
|
3915
|
+
function resumeAllowsRow(row, rows, liveProviders) {
|
|
3916
|
+
const liveProviderOffered = rows.some((candidate) => {
|
|
3917
|
+
const provider = oauthProviderForRow(candidate);
|
|
3918
|
+
return provider !== undefined && liveProviders?.includes(provider) === true;
|
|
3919
|
+
});
|
|
3920
|
+
if (!liveProviderOffered || !isOauthChromeRow(row))
|
|
3921
|
+
return true;
|
|
3922
|
+
const provider = oauthProviderForRow(row);
|
|
3923
|
+
return provider !== undefined && liveProviders?.includes(provider) === true;
|
|
3924
|
+
}
|
|
3925
|
+
function resumeTargetSets(rows, facts, goal, includePayment, pageUrl, liveProviders) {
|
|
4276
3926
|
const sets = driveTargetSets(rows, facts, includePayment, [], pageUrl, new Map(), (text) => text, [], {
|
|
4277
3927
|
goal,
|
|
4278
3928
|
...(liveProviders === undefined ? {} : { liveProviders }),
|
|
4279
3929
|
});
|
|
3930
|
+
// Jev sees every row. The human handoff offers only providers already live
|
|
3931
|
+
// in this browser when one of those providers is present on the page.
|
|
3932
|
+
const click = sets.CLICK.filter((candidate) => resumeAllowsRow(candidate.row, rows, liveProviders));
|
|
3933
|
+
return {
|
|
3934
|
+
...sets,
|
|
3935
|
+
CLICK: click,
|
|
3936
|
+
operations: click.length === 0
|
|
3937
|
+
? sets.operations.filter((operation) => operation !== "CLICK")
|
|
3938
|
+
: sets.operations,
|
|
3939
|
+
};
|
|
3940
|
+
}
|
|
3941
|
+
export function resumeAnswerOptions(rows, facts, goal, includePayment, pageUrl, liveProviders) {
|
|
3942
|
+
const sets = resumeTargetSets(rows, facts, goal, includePayment, pageUrl, liveProviders);
|
|
4280
3943
|
// The question builder applies the decision-budget cap to these same sets.
|
|
4281
3944
|
// A handoff must offer exactly the target keys its resume validator accepts.
|
|
4282
3945
|
buildDriveQuestions(rows, facts, goal, includePayment, [], pageUrl, new Map(), sets);
|
|
@@ -4298,21 +3961,14 @@ export function resumeAction(answer, rows, facts, goal, cardRef, pageUrl, livePr
|
|
|
4298
3961
|
if (answer === "WAIT" || answer === "wait")
|
|
4299
3962
|
return { kind: "wait", confidence: 1 };
|
|
4300
3963
|
const includePayment = cardRef !== undefined;
|
|
4301
|
-
const sets =
|
|
4302
|
-
goal,
|
|
4303
|
-
...(liveProviders === undefined ? {} : { liveProviders }),
|
|
4304
|
-
});
|
|
3964
|
+
const sets = resumeTargetSets(rows, facts, goal, includePayment, pageUrl, liveProviders);
|
|
4305
3965
|
const questions = buildDriveQuestions(rows, facts, goal, includePayment, [], pageUrl, new Map(), sets);
|
|
4306
3966
|
const validKeys = Object.keys(resumeAnswerOptions(rows, facts, goal, includePayment, pageUrl, liveProviders));
|
|
4307
3967
|
const offered = [...sets.CLICK, ...sets.TYPE_TEXT, ...sets.SELECT].find((entry) => entry.slug === answer || entry.ref === answer);
|
|
4308
3968
|
const fallbackRow = findRow(rows, answer, pageUrl);
|
|
4309
|
-
const
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
return offeredProvider !== undefined && liveProviders?.includes(offeredProvider) === true;
|
|
4313
|
-
});
|
|
4314
|
-
const row = offered?.row ?? (fallbackRow !== undefined && isOauthChromeRow(fallbackRow) && liveProviderOffered &&
|
|
4315
|
-
(provider === undefined || !liveProviders?.includes(provider)) ? undefined : fallbackRow);
|
|
3969
|
+
const row = offered?.row ?? (fallbackRow !== undefined && resumeAllowsRow(fallbackRow, rows, liveProviders)
|
|
3970
|
+
? fallbackRow
|
|
3971
|
+
: undefined);
|
|
4316
3972
|
if (row === undefined) {
|
|
4317
3973
|
return {
|
|
4318
3974
|
kind: "invalid_answer",
|
|
@@ -4559,7 +4215,7 @@ async function driveLoop(input) {
|
|
|
4559
4215
|
let liveProviders;
|
|
4560
4216
|
const detectOfferedProviderSessions = async () => {
|
|
4561
4217
|
if (liveProviders === undefined &&
|
|
4562
|
-
|
|
4218
|
+
rows.filter(isOauthChromeRow).length > 1) {
|
|
4563
4219
|
liveProviders = await liveProviderSessionsForSession(sessionId);
|
|
4564
4220
|
}
|
|
4565
4221
|
};
|
|
@@ -4779,39 +4435,25 @@ async function driveLoop(input) {
|
|
|
4779
4435
|
if (completeSnap !== "ok")
|
|
4780
4436
|
return completeSnap;
|
|
4781
4437
|
const fresh = driveProgressFingerprint(observation, rows, drive, session);
|
|
4782
|
-
|
|
4783
|
-
// binding from the preceding page must not veto a readable credential.
|
|
4784
|
-
if (!isKeyGoal(drive.goal) &&
|
|
4785
|
-
drive.boundFingerprint !== null &&
|
|
4438
|
+
if (drive.boundFingerprint !== null &&
|
|
4786
4439
|
fresh !== drive.boundFingerprint) {
|
|
4787
4440
|
drive.consumedActionKey = null;
|
|
4788
4441
|
automaticDecisionRefused = true;
|
|
4789
4442
|
return "continue";
|
|
4790
4443
|
}
|
|
4791
|
-
//
|
|
4792
|
-
//
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
action: "DONE",
|
|
4799
|
-
step: drive.trajectory.length,
|
|
4800
|
-
executed: false,
|
|
4801
|
-
notExecutedReason: "refused",
|
|
4802
|
-
beforeFingerprint: drive.boundFingerprint ?? fresh,
|
|
4803
|
-
afterFingerprint: fresh,
|
|
4804
|
-
});
|
|
4805
|
-
const masked = maskedRemainingReason(keyEvidence);
|
|
4806
|
-
drive.history.push(masked === undefined
|
|
4807
|
-
? "DONE refused: extraction found no secret-shaped credential"
|
|
4808
|
-
: `DONE refused: ${masked}`);
|
|
4444
|
+
// Try the known Copy source on every DONE, even if the value has no
|
|
4445
|
+
// recognizable shape or label in the observation. Page evidence only
|
|
4446
|
+
// decides whether a failed extraction must hand back for explicit capture.
|
|
4447
|
+
const extracted = await extractCredentials(sessionId).catch(() => null);
|
|
4448
|
+
if (storableCredentials(extracted?.credentials ?? {}) === null &&
|
|
4449
|
+
pageShowsCredentialValue(rows)) {
|
|
4450
|
+
drive.history.push("DONE refused: a visible credential has no captured source");
|
|
4809
4451
|
drive.consumedActionKey = null;
|
|
4810
4452
|
const stallKey = pageProgressKey(observation.url, rows, drive.filledRefs, observation.semantic?.headings ?? []);
|
|
4811
4453
|
drive.stallKeys ??= [];
|
|
4812
4454
|
if (drive.stallKeys.includes(stallKey)) {
|
|
4813
4455
|
return finish("stuck", {
|
|
4814
|
-
reason:
|
|
4456
|
+
reason: "no key with a known source: read the page with operate_observe and point capture at the key",
|
|
4815
4457
|
});
|
|
4816
4458
|
}
|
|
4817
4459
|
drive.stallKeys.push(stallKey);
|
|
@@ -5082,6 +4724,13 @@ async function driveLoop(input) {
|
|
|
5082
4724
|
automaticDecisionRefused = true;
|
|
5083
4725
|
return "continue";
|
|
5084
4726
|
}
|
|
4727
|
+
if ((decision.action.kind === "click" || decision.action.kind === "select") &&
|
|
4728
|
+
liveRow !== undefined &&
|
|
4729
|
+
isExistingCredentialMutationRow(liveRow, observation.url, rows, decision.action.kind === "select" ? decision.action.text : "", observation.semantic?.headings ?? [])) {
|
|
4730
|
+
return finish("stuck", {
|
|
4731
|
+
reason: `operator decision required before ${readableLabel(liveRow)} on ${observation.url}; no action was dispatched`,
|
|
4732
|
+
});
|
|
4733
|
+
}
|
|
5085
4734
|
if ((decision.action.kind === "click" || decision.action.kind === "oauth_login") &&
|
|
5086
4735
|
liveRow !== undefined &&
|
|
5087
4736
|
isSubmitLikeRow(liveRow) &&
|
|
@@ -5533,7 +5182,7 @@ async function driveLoop(input) {
|
|
|
5533
5182
|
// progress to fill, not a failed response to a submitted form.
|
|
5534
5183
|
if (clicked !== undefined &&
|
|
5535
5184
|
isSubmitLikeRow(clicked) &&
|
|
5536
|
-
!isKeyCreateOpener(clicked
|
|
5185
|
+
!isKeyCreateOpener(clicked)) {
|
|
5537
5186
|
captchaAfterSubmit = true;
|
|
5538
5187
|
drive.submitBeforeText = textBeforeClick;
|
|
5539
5188
|
drive.submitExcludeLabels = excludeBeforeClick;
|
|
@@ -5573,7 +5222,7 @@ async function driveLoop(input) {
|
|
|
5573
5222
|
}
|
|
5574
5223
|
}
|
|
5575
5224
|
if (shouldInspectSubmitResponse(decision.action, clickedBefore) &&
|
|
5576
|
-
!isKeyCreateOpener(clickedBefore
|
|
5225
|
+
!isKeyCreateOpener(clickedBefore)) {
|
|
5577
5226
|
const navigated = pagePathKey(observation.url) !== pagePathKey(urlBeforeClick);
|
|
5578
5227
|
const fieldError = invalidFieldReason(rows);
|
|
5579
5228
|
if (fieldError !== undefined) {
|
|
@@ -5662,7 +5311,7 @@ async function driveLoop(input) {
|
|
|
5662
5311
|
binding: decisionTargetBinding(clicked, urlBeforeClick),
|
|
5663
5312
|
url: urlBeforeClick,
|
|
5664
5313
|
};
|
|
5665
|
-
if (isSubmitLikeRow(clicked) && !isKeyCreateOpener(clicked
|
|
5314
|
+
if (isSubmitLikeRow(clicked) && !isKeyCreateOpener(clicked)) {
|
|
5666
5315
|
drive.submittedThisDrive = true;
|
|
5667
5316
|
}
|
|
5668
5317
|
}
|
|
@@ -5772,13 +5421,15 @@ async function driveLoop(input) {
|
|
|
5772
5421
|
}
|
|
5773
5422
|
const ask = async (state, questions) => {
|
|
5774
5423
|
try {
|
|
5775
|
-
const jev = await dependencies.askJev(api,
|
|
5424
|
+
const jev = await dependencies.askJev(api, maskCardValues(session, state), maskCardValues(session, questions), context?.signal);
|
|
5776
5425
|
drive.jevCalls += 1;
|
|
5777
5426
|
return jev;
|
|
5778
5427
|
}
|
|
5779
5428
|
catch (error) {
|
|
5780
|
-
if (error instanceof JevUnavailableError ||
|
|
5781
|
-
|
|
5429
|
+
if (error instanceof JevUnavailableError ||
|
|
5430
|
+
error instanceof JevRequestError ||
|
|
5431
|
+
error instanceof JevInvalidResponseError) {
|
|
5432
|
+
return finish("jev_unavailable", { jevRetried: error.message, reason: error.message });
|
|
5782
5433
|
}
|
|
5783
5434
|
throw error;
|
|
5784
5435
|
}
|
|
@@ -5802,9 +5453,6 @@ async function driveLoop(input) {
|
|
|
5802
5453
|
let missing = requiredFillableMissingFact(rows, drive.facts, drive.filledRefs, pageUrl, drive.goal);
|
|
5803
5454
|
let pageOptions = lastSelectOptions.get(session) ?? selectOptionsFromElements(session.lastElements);
|
|
5804
5455
|
const remainingFills = fillableCandidates(rows, drive.facts, includePayment, drive.filledRefs, pageUrl);
|
|
5805
|
-
let hasGoalDestination = rows.some((row) => rowCarriesGoalNoun(row, args.goal) &&
|
|
5806
|
-
isGoalDestinationRow(row) &&
|
|
5807
|
-
!isOffProductNavRow(row, pageUrl));
|
|
5808
5456
|
let terminalOnly = false;
|
|
5809
5457
|
// A rejected pre-act choice must reach the model on the refreshed page.
|
|
5810
5458
|
// None of the automatic choices below may consume that opportunity.
|
|
@@ -5923,49 +5571,6 @@ async function driveLoop(input) {
|
|
|
5923
5571
|
},
|
|
5924
5572
|
});
|
|
5925
5573
|
}
|
|
5926
|
-
if (isPreexistingSessionPage({
|
|
5927
|
-
rows,
|
|
5928
|
-
pageUrl,
|
|
5929
|
-
pageText: pageTextFromObservation(observation, [observation.dom ?? ""]),
|
|
5930
|
-
goal: args.goal,
|
|
5931
|
-
submittedThisDrive: drive.submittedThisDrive === true,
|
|
5932
|
-
})) {
|
|
5933
|
-
if (drive.preexistingRestarted === true ||
|
|
5934
|
-
rows.find((row) => isLogoutRow(row)) === undefined) {
|
|
5935
|
-
return finish("stuck", { reason: alreadySignedInReason() });
|
|
5936
|
-
}
|
|
5937
|
-
const logout = rows.find((row) => isLogoutRow(row));
|
|
5938
|
-
if (logout === undefined) {
|
|
5939
|
-
return finish("stuck", { reason: alreadySignedInReason() });
|
|
5940
|
-
}
|
|
5941
|
-
drive.preexistingRestarted = true;
|
|
5942
|
-
drive.boundFingerprint = driveProgressFingerprint(observation, rows, drive, session);
|
|
5943
|
-
drive.consumedActionKey = null;
|
|
5944
|
-
const applied = await applyDecision({
|
|
5945
|
-
kind: "act",
|
|
5946
|
-
action: { kind: "click", target: logout[0] },
|
|
5947
|
-
actionKey: logout[0],
|
|
5948
|
-
confidence: 1,
|
|
5949
|
-
});
|
|
5950
|
-
if (applied !== "continue")
|
|
5951
|
-
return applied;
|
|
5952
|
-
if (automaticDecisionRefused)
|
|
5953
|
-
break automaticDecisions;
|
|
5954
|
-
const safe = await actSafely(dependencies, sessionId, {
|
|
5955
|
-
kind: "goto",
|
|
5956
|
-
url: session.startUrl,
|
|
5957
|
-
});
|
|
5958
|
-
drive.lastDispatchFailure = safe.dispatchFailure ?? null;
|
|
5959
|
-
drive.lastOauthBounceReason = null;
|
|
5960
|
-
observation = safe.observation;
|
|
5961
|
-
if (observation.needs_user !== undefined)
|
|
5962
|
-
return finishOnWall(observation.needs_user);
|
|
5963
|
-
const restarted = await snapshotOrTimeout(framesIfNeeded());
|
|
5964
|
-
if (restarted !== "ok")
|
|
5965
|
-
return restarted;
|
|
5966
|
-
spendStep("preexisting_restart");
|
|
5967
|
-
continue;
|
|
5968
|
-
}
|
|
5969
5574
|
// A same-document stage swap (Shopify one-page checkout) and a hydrating
|
|
5970
5575
|
// checkout both leave the snapshot empty for a while, so spend the
|
|
5971
5576
|
// re-observation budget before asking anything. Past it the ordinary
|
|
@@ -6226,9 +5831,6 @@ async function driveLoop(input) {
|
|
|
6226
5831
|
else {
|
|
6227
5832
|
return finish("stuck", { reason: missingPay });
|
|
6228
5833
|
}
|
|
6229
|
-
if (goalExcludesOauth(args.goal) && pageOffersOnlyThirdPartySignup(rows)) {
|
|
6230
|
-
return finish("stuck", { reason: noOtherSignupPathReason() });
|
|
6231
|
-
}
|
|
6232
5834
|
drive.awaitingDecideAfterExplore = false;
|
|
6233
5835
|
// Rule 1: undo a narrowing input the drive itself typed before the loop
|
|
6234
5836
|
// decides anything else, then re-offer the control it removed.
|
|
@@ -6246,9 +5848,6 @@ async function driveLoop(input) {
|
|
|
6246
5848
|
missing = requiredFillableMissingFact(rows, drive.facts, drive.filledRefs, pageUrl, drive.goal);
|
|
6247
5849
|
pageOptions =
|
|
6248
5850
|
lastSelectOptions.get(session) ?? selectOptionsFromElements(session.lastElements);
|
|
6249
|
-
hasGoalDestination = rows.some((row) => rowCarriesGoalNoun(row, args.goal) &&
|
|
6250
|
-
isGoalDestinationRow(row) &&
|
|
6251
|
-
!isOffProductNavRow(row, pageUrl));
|
|
6252
5851
|
terminalOnly = false;
|
|
6253
5852
|
}
|
|
6254
5853
|
if (drive.jevCalls >= DRIVE_MAX_JEV_CALLS) {
|
|
@@ -6265,7 +5864,7 @@ async function driveLoop(input) {
|
|
|
6265
5864
|
...new Set([...(drive.exhaustedActionKeys ?? []), ...(drive.staleClickRefs ?? [])]),
|
|
6266
5865
|
];
|
|
6267
5866
|
await detectOfferedProviderSessions();
|
|
6268
|
-
const sets = driveTargetSets(rows, drive.facts, includePayment, drive.filledRefs, pageUrl, pageOptions, (text) =>
|
|
5867
|
+
const sets = driveTargetSets(rows, drive.facts, includePayment, drive.filledRefs, pageUrl, pageOptions, (text) => maskCardValues(session, text), skippedActions, {
|
|
6269
5868
|
headings: observation.semantic?.headings ?? [],
|
|
6270
5869
|
failedKeys: drive.failedActionKeys ?? [],
|
|
6271
5870
|
goal: drive.goal,
|
|
@@ -6425,61 +6024,6 @@ async function driveLoop(input) {
|
|
|
6425
6024
|
jev_question_count: questionCount,
|
|
6426
6025
|
jev_state_bytes: stateBytes,
|
|
6427
6026
|
};
|
|
6428
|
-
const inboxPlan = inboxSpecialPlan(rows, decision.kind, lastNonWaitWasClick(drive.trajectory), remainingFills.length, observation.url, pageTextFromObservation(observation, [observation.dom ?? ""]), drive.goal, drive.submittedThisDrive === true);
|
|
6429
|
-
if (inboxPlan !== undefined) {
|
|
6430
|
-
const lastClick = [...drive.trajectory]
|
|
6431
|
-
.reverse()
|
|
6432
|
-
.find((step) => step.action === "click" || step.action === "oauth_login");
|
|
6433
|
-
const applied = await applyDecision({
|
|
6434
|
-
kind: "act",
|
|
6435
|
-
action: inboxPlan.kind === "otp"
|
|
6436
|
-
? { kind: "type", target: inboxPlan.target, text: "" }
|
|
6437
|
-
: { kind: "click", target: lastClick?.target ?? "inbox_link" },
|
|
6438
|
-
actionKey: inboxPlan.kind === "otp" ? inboxPlan.target : "inbox_link",
|
|
6439
|
-
confidence: 1,
|
|
6440
|
-
special: "inbox",
|
|
6441
|
-
}, jevMs);
|
|
6442
|
-
if (applied !== "continue")
|
|
6443
|
-
return applied;
|
|
6444
|
-
spendStep("inbox_plan");
|
|
6445
|
-
continue;
|
|
6446
|
-
}
|
|
6447
|
-
if (goalSeeksKey(args.goal) && !pageShowsRevealedKey(rows)) {
|
|
6448
|
-
const visited = drive.visitedSectionKeys ?? [];
|
|
6449
|
-
const untriedEntries = untriedListedItemRows(rows, visited, pageUrl);
|
|
6450
|
-
const nextSection = nextExploreRow(rows, visited, pageUrl);
|
|
6451
|
-
const picked = decisionIsActionable(decision)
|
|
6452
|
-
? findRow(rows, decision.actionKey, pageUrl)
|
|
6453
|
-
: undefined;
|
|
6454
|
-
const pickedIsReveal = picked !== undefined && isRevealOrCopyRow(picked);
|
|
6455
|
-
const pickedIsPending = picked !== undefined && isPendingPageAction(picked, args.goal);
|
|
6456
|
-
const pickedIsUntriedEntry = picked !== undefined && untriedEntries.some((entry) => entry[0] === picked[0]);
|
|
6457
|
-
const pickedVisitedTab = picked !== undefined &&
|
|
6458
|
-
isSiblingSectionNavRow(picked, pageUrl, rows) &&
|
|
6459
|
-
visited.includes(sectionIdentity(picked, pageUrl));
|
|
6460
|
-
const takeExplore = nextSection !== undefined &&
|
|
6461
|
-
!pickedIsPending &&
|
|
6462
|
-
((untriedEntries.length > 0 && !pickedIsUntriedEntry && !pickedIsReveal) ||
|
|
6463
|
-
pickedVisitedTab ||
|
|
6464
|
-
(!decisionIsActionable(decision) && !hasGoalDestination));
|
|
6465
|
-
if (takeExplore && nextSection !== undefined) {
|
|
6466
|
-
const applied = await applyDecision({
|
|
6467
|
-
kind: "act",
|
|
6468
|
-
action: { kind: "click", target: nextSection[0] },
|
|
6469
|
-
actionKey: nextSection[0],
|
|
6470
|
-
confidence: 1,
|
|
6471
|
-
});
|
|
6472
|
-
if (applied !== "continue")
|
|
6473
|
-
return applied;
|
|
6474
|
-
drive.boundFingerprint = driveProgressFingerprint(observation, rows, drive, session);
|
|
6475
|
-
drive.consumedActionKey = null;
|
|
6476
|
-
spendStep("explore");
|
|
6477
|
-
continue;
|
|
6478
|
-
}
|
|
6479
|
-
if (!decisionIsActionable(decision) && !hasGoalDestination && visited.length > 0) {
|
|
6480
|
-
return finish("no_progress", { reason: sectionsTriedReason(visited) });
|
|
6481
|
-
}
|
|
6482
|
-
}
|
|
6483
6027
|
const applied = await applyDecision(decision, jevMs, true);
|
|
6484
6028
|
if (applied !== "continue")
|
|
6485
6029
|
return applied;
|