Guardian-Agent / artifacts /api-server /src /lib /agentic-mission.ts
Prince9868's picture
Hackathon submission: Guardian OpenEnv
5743bc2
Raw
History Blame Contribute Delete
54.4 kB
import { analyzeCheckoutContext } from "./checkout-analysis";
import { computeTrustTier, type TrustTier } from "./trust-rating";
import { analyzeDealIntelligence, type ProductCandidateInput } from "./deal-intelligence";
type MissionCategory = "hotel" | "airline" | "marketplace" | "car_rental";
type MissionTier = TrustTier;
type Recommendation = "proceed" | "proceed_with_caution" | "switch";
type ActionStatus = "completed" | "recommended" | "approval_required";
export type AgenticMissionRequest = {
domain: string;
url?: string;
pageType?: string;
bookingType?: string;
isBookingPlatform?: boolean;
route?: { from?: string; to?: string; display?: string } | null;
purchaseGoal: string;
budget?: number;
listedPrice?: number;
category?: MissionCategory;
preferences?: string[];
allowAccountCreation?: boolean;
allowAutoDeclineUpsells?: boolean;
compareAcrossSites?: boolean;
pageText?: string;
priceStrings?: string[];
buttonLabels?: string[];
timerElements?: string[];
stockAlerts?: string[];
variantUrgency?: string[];
productCandidates?: ProductCandidateInput[];
};
type MissionSignal = {
type: string;
label: string;
severity: "low" | "medium" | "high";
confidence: number;
evidence: string;
whyItMatters: string;
fix: string;
};
type MissionAction = {
title: string;
detail: string;
status: ActionStatus;
};
type GhostCheckoutStep = {
label: string;
status: "completed" | "planned" | "watch";
detail: string;
};
type GhostCheckoutLane = {
label: string;
amount: number;
confidence: "high" | "medium" | "low";
source: string;
};
type MissionAlternative = {
domain: string;
merchantName: string;
listedPrice: number;
estimatedTrueTotal: number;
trustScore: number;
trustTier: MissionTier;
bestValue: boolean;
why: string[];
};
type GhostCheckoutPlan = {
mode: "disabled" | "simulated_supervised";
status: string;
revealedTotal: number | null;
deltaFromHeadline: number | null;
hiddenCostLanes: GhostCheckoutLane[];
fakePressureSummary: string[];
saferPathMode: {
available: boolean;
label: string;
detail: string;
};
supervisedSteps: GhostCheckoutStep[];
deliveryThreshold?: {
threshold: number | null;
needsMore: number | null;
hasFreeDelivery: boolean;
note: string;
};
platformFeeRisk?: {
risk: "high" | "medium" | "low";
likelihood: number;
estimatedFee: number;
reasoning: string;
};
};
type MissionProfile = {
merchantName: string;
category: MissionCategory;
trustScore: number;
listedMarkupRate: number;
hiddenFeeRate: number;
alternativeDomains: string[];
signals: MissionSignal[];
credibilitySignals: string[];
};
type FeeEstimate = {
fees: number;
confidence: "high" | "medium" | "low";
breakdown: GhostCheckoutLane[];
platformFeeRisk: {
risk: "high" | "medium" | "low";
likelihood: number;
estimatedFee: number;
reasoning: string;
};
};
type RecommendationDetails = {
action: Recommendation;
summary: string;
reasoning: string[];
nextSteps: string[];
};
const PROFILE_MAP: Record<string, MissionProfile> = {
"booking.com": {
merchantName: "Booking.com",
category: "hotel",
trustScore: 29,
listedMarkupRate: 1,
hiddenFeeRate: 0.24,
alternativeDomains: ["hotels.com", "expedia.com", "airbnb.com"],
credibilitySignals: ["Large inventory coverage", "Strong hotel selection depth", "Reliable confirmation flows"],
signals: [
{
type: "false_urgency",
label: "False Urgency",
severity: "high",
confidence: 0.93,
evidence: "Countdown and price-lock language appears before taxes and mandatory charges are shown.",
whyItMatters: "It pressures the shopper into committing before the true total is visible.",
fix: "Show the timer only when the inventory event is verifiable for that session.",
},
{
type: "hidden_fees",
label: "Hidden Fees",
severity: "high",
confidence: 0.95,
evidence: "Resort and sustainability charges often surface late in the booking path.",
whyItMatters: "The shopper anchors on a low headline price and discovers the real total only after sunk cost kicks in.",
fix: "Roll mandatory charges into the first quoted price and keep them visible all the way through checkout.",
},
{
type: "prechecked_addons",
label: "Pre-Checked Add-Ons",
severity: "medium",
confidence: 0.9,
evidence: "Insurance and promotional extras are frequently shown as default-on options.",
whyItMatters: "It converts inattention into extra spend.",
fix: "Leave optional protection and upsells off by default.",
},
],
},
"makemytrip.com": {
merchantName: "MakeMyTrip",
category: "hotel",
trustScore: 34,
listedMarkupRate: 1,
hiddenFeeRate: 0.19,
alternativeDomains: ["goibibo.com", "booking.com", "agoda.com"],
credibilitySignals: ["Wide India travel inventory", "Fast hotel and flight packaging", "Established checkout infrastructure"],
signals: [
{
type: "false_scarcity",
label: "False Scarcity",
severity: "medium",
confidence: 0.88,
evidence: "Low-stock banners and recent-booking counters are commonly used near room selection.",
whyItMatters: "It increases FOMO before taxes and extras are fully understood.",
fix: "Use demand and availability messaging only when tied to verified live inventory.",
},
{
type: "hidden_fees",
label: "Late Fee Disclosure",
severity: "high",
confidence: 0.9,
evidence: "Convenience fees and taxes can remain unclear until late in the flow.",
whyItMatters: "The user may approve an option that is already over budget.",
fix: "Promote the final payable amount earlier in the journey.",
},
],
},
"united.com": {
merchantName: "United Airlines",
category: "airline",
trustScore: 43,
listedMarkupRate: 1,
hiddenFeeRate: 0.17,
alternativeDomains: ["delta.com", "southwest.com", "jetblue.com"],
credibilitySignals: ["Direct airline booking channel", "Stable reservation and refund records", "Clear post-purchase itinerary management"],
signals: [
{
type: "misdirection",
label: "Misdirection",
severity: "high",
confidence: 0.91,
evidence: "Seat upgrades and baggage options dominate the flow while baseline choices are visually downplayed.",
whyItMatters: "The cheapest acceptable path becomes harder to find than the upsell path.",
fix: "Make the standard path equal in prominence to the upgraded path.",
},
{
type: "prechecked_addons",
label: "Default Extras",
severity: "medium",
confidence: 0.86,
evidence: "Protection and expedited airport products are presented as default or strongly suggested add-ons.",
whyItMatters: "The shopper can end up paying for extras that are not aligned with their preferences.",
fix: "Ask for explicit consent before any optional add-on is applied.",
},
],
},
"amazon.com": {
merchantName: "Amazon",
category: "marketplace",
trustScore: 68,
listedMarkupRate: 1,
hiddenFeeRate: 0.08,
alternativeDomains: ["walmart.com", "bestbuy.com", "etsy.com"],
credibilitySignals: ["Strong fulfillment reliability", "Fast delivery estimates", "Large seller ecosystem with reviews"],
signals: [
{
type: "prechecked_addons",
label: "Subscription Steering",
severity: "medium",
confidence: 0.79,
evidence: "Prime trials and protection plans may be positioned as the default convenience path.",
whyItMatters: "A low-friction checkout can still quietly nudge users into recurring spend.",
fix: "Present trial and warranty options only after the baseline purchase path is complete.",
},
],
},
"amazon.in": {
merchantName: "Amazon India",
category: "marketplace",
trustScore: 80,
listedMarkupRate: 1,
hiddenFeeRate: 0.04,
alternativeDomains: ["flipkart.com", "ajio.com", "myntra.com"],
credibilitySignals: ["Transparent tax display on most PDP flows", "Strong delivery and fulfillment reliability in India", "Lower pattern intensity than fashion-led marketplaces"],
signals: [
{
type: "subscription_steering",
label: "Subscription Steering",
severity: "low",
confidence: 0.71,
evidence: "Prime and card-linked benefits may be highlighted before the baseline purchase choice.",
whyItMatters: "Shoppers can be nudged into extra commitments while trying to secure a deal quickly.",
fix: "Keep the buy path clear even when loyalty benefits are promoted.",
},
],
},
"myntra.com": {
merchantName: "Myntra",
category: "marketplace",
trustScore: 62,
listedMarkupRate: 1,
hiddenFeeRate: 0.13,
alternativeDomains: ["amazon.in", "flipkart.com", "ajio.com"],
credibilitySignals: ["Strong fashion assortment", "Readable PDP pricing for headline offers", "Reasonably clear refund and delivery messaging on supported items"],
signals: [
{
type: "false_scarcity",
label: "Scarcity Pressure",
severity: "medium",
confidence: 0.84,
evidence: "Low-stock chips and size-level urgency are used heavily around apparel variants.",
whyItMatters: "It can push the shopper to rush before checking whether the price is genuinely competitive elsewhere.",
fix: "Show variant inventory only when it is session-specific and validated against live stock.",
},
{
type: "price_opacity",
label: "Platform Fee Risk",
severity: "medium",
confidence: 0.76,
evidence: "Fashion marketplaces can introduce platform or shipping fees late for lower-value carts.",
whyItMatters: "The product page can look cheap while the payable total drifts upward at checkout.",
fix: "Preview platform fees and delivery thresholds before the shopper reaches the bag.",
},
],
},
"flipkart.com": {
merchantName: "Flipkart",
category: "marketplace",
trustScore: 75,
listedMarkupRate: 1,
hiddenFeeRate: 0.08,
alternativeDomains: ["amazon.in", "myntra.com", "ajio.com"],
credibilitySignals: ["Competitive marketplace pricing in India", "Better fee predictability on many standard carts", "Good delivery network coverage"],
signals: [
{
type: "upsell_pressure",
label: "Upsell Pressure",
severity: "low",
confidence: 0.68,
evidence: "Protection and exchange nudges can be added around cart and delivery preference steps.",
whyItMatters: "Shoppers can pay more than intended even when the headline deal is fine.",
fix: "Keep optional protection and exchange upgrades off unless chosen explicitly.",
},
],
},
"ajio.com": {
merchantName: "Ajio",
category: "marketplace",
trustScore: 72,
listedMarkupRate: 1,
hiddenFeeRate: 0.1,
alternativeDomains: ["amazon.in", "flipkart.com", "myntra.com"],
credibilitySignals: ["Relevant India fashion benchmark", "Competitive fashion discounts", "Moderate checkout complexity relative to peers"],
signals: [
{
type: "discount_anchoring",
label: "Heavy Discount Anchoring",
severity: "medium",
confidence: 0.74,
evidence: "Large markdown percentages are frequently foregrounded in fashion listings.",
whyItMatters: "Shoppers may focus on the size of the discount instead of the final payable value.",
fix: "Pair discount claims with recent price history or payable total context.",
},
],
},
"airbnb.com": {
merchantName: "Airbnb",
category: "hotel",
trustScore: 63,
listedMarkupRate: 1,
hiddenFeeRate: 0.22,
alternativeDomains: ["booking.com", "vrbo.com", "hotels.com"],
credibilitySignals: ["Strong listing depth", "Rich review data", "Good refund and host messaging history"],
signals: [
{
type: "hidden_fees",
label: "Cleaning and Service Fees",
severity: "medium",
confidence: 0.89,
evidence: "Cleaning and platform fees can materially shift the final price.",
whyItMatters: "The best-looking nightly rate may not be the best total price.",
fix: "Sort and compare by full stay total rather than headline nightly price.",
},
],
},
"etsy.com": {
merchantName: "Etsy",
category: "marketplace",
trustScore: 90,
listedMarkupRate: 1,
hiddenFeeRate: 0.04,
alternativeDomains: ["amazon.com", "ebay.com", "bestbuy.com"],
credibilitySignals: ["Transparent checkout copy", "Generally neutral purchase path", "Low prevalence of aggressive conversion patterns"],
signals: [],
},
};
const ALTERNATIVE_MAP: Record<string, { merchantName: string; trustScore: number; baseDelta: number; feeRate: number }> = {
"hotels.com": { merchantName: "Hotels.com", trustScore: 72, baseDelta: -0.04, feeRate: 0.14 },
"expedia.com": { merchantName: "Expedia", trustScore: 58, baseDelta: -0.03, feeRate: 0.17 },
"agoda.com": { merchantName: "Agoda", trustScore: 61, baseDelta: -0.02, feeRate: 0.16 },
"goibibo.com": { merchantName: "Goibibo", trustScore: 56, baseDelta: -0.05, feeRate: 0.15 },
"delta.com": { merchantName: "Delta", trustScore: 74, baseDelta: -0.01, feeRate: 0.11 },
"southwest.com": { merchantName: "Southwest", trustScore: 81, baseDelta: 0.02, feeRate: 0.09 },
"jetblue.com": { merchantName: "JetBlue", trustScore: 70, baseDelta: -0.02, feeRate: 0.1 },
"walmart.com": { merchantName: "Walmart", trustScore: 73, baseDelta: -0.05, feeRate: 0.05 },
"bestbuy.com": { merchantName: "Best Buy", trustScore: 77, baseDelta: -0.03, feeRate: 0.03 },
"vrbo.com": { merchantName: "Vrbo", trustScore: 66, baseDelta: -0.06, feeRate: 0.19 },
"ebay.com": { merchantName: "eBay", trustScore: 64, baseDelta: -0.04, feeRate: 0.06 },
"amazon.in": { merchantName: "Amazon India", trustScore: 80, baseDelta: -0.02, feeRate: 0.04 },
"flipkart.com": { merchantName: "Flipkart", trustScore: 75, baseDelta: -0.03, feeRate: 0.08 },
"myntra.com": { merchantName: "Myntra", trustScore: 62, baseDelta: 0, feeRate: 0.13 },
"ajio.com": { merchantName: "Ajio", trustScore: 72, baseDelta: 0.01, feeRate: 0.1 },
};
function normalizeDomain(input: string): string {
return input.trim().toLowerCase().replace(/^https?:\/\//, "").replace(/^www\./, "").split("/")[0] ?? "example.com";
}
function inferCategory(domain: string, requested?: MissionCategory): MissionCategory {
if (requested) return requested;
const profile = PROFILE_MAP[domain];
if (profile) return profile.category;
if (domain.includes("air") || domain.includes("flight")) return "airline";
if (domain.includes("hotel") || domain.includes("trip") || domain.includes("stay")) return "hotel";
if (domain.includes("rental")) return "car_rental";
return "marketplace";
}
function fallbackProfile(domain: string, category: MissionCategory): MissionProfile {
const trustScore = category === "hotel" ? 46 : category === "airline" ? 54 : category === "car_rental" ? 41 : 62;
const hiddenFeeRate = category === "hotel" ? 0.18 : category === "airline" ? 0.14 : category === "car_rental" ? 0.28 : 0.07;
return {
merchantName: domain,
category,
trustScore,
listedMarkupRate: 1,
hiddenFeeRate,
alternativeDomains:
category === "hotel"
? ["hotels.com", "expedia.com", "airbnb.com"]
: category === "airline"
? ["delta.com", "southwest.com", "jetblue.com"]
: ["walmart.com", "bestbuy.com", "etsy.com"],
credibilitySignals: ["Functional checkout path", "No verified catastrophic trust signal in the current profile"],
signals: [
{
type: "price_opacity",
label: "Price Opacity Risk",
severity: hiddenFeeRate >= 0.15 ? "high" : "medium",
confidence: 0.71,
evidence: "This category commonly adds fees late in checkout.",
whyItMatters: "The first price quote may not represent the final amount the shopper pays.",
fix: "Run ghost checkout before committing time or emotional energy to the flow.",
},
],
};
}
function roundCurrency(value: number): number {
return Math.round(value * 100) / 100;
}
function isKnownNonCommerceDomain(domain: string): boolean {
return [
"github.com",
"stackoverflow.com",
"wikipedia.org",
"docs.python.org",
"developer.mozilla.org",
"medium.com",
"notion.so",
"vercel.com",
].some((candidate) => domain === candidate || domain.endsWith(`.${candidate}`));
}
function isIndianCommerceDomain(domain: string): boolean {
return [
"myntra.com",
"flipkart.com",
"ajio.com",
"tatacliq.com",
"nykaa.com",
"meesho.com",
"amazon.in",
"makemytrip.com",
"goibibo.com",
].some((candidate) => domain === candidate || domain.endsWith(`.${candidate}`));
}
function detectFreeDeliveryThreshold(domain: string, currentPrice: number, pageText?: string) {
const normalizedDomain = normalizeDomain(domain);
let threshold: number | null = null;
if (normalizedDomain === "myntra.com" || normalizedDomain === "ajio.com") {
threshold = 999;
} else if (normalizedDomain === "flipkart.com") {
threshold = 500;
} else if (normalizedDomain === "amazon.in") {
threshold = 499;
}
if (threshold == null && pageText) {
const match = pageText.match(/₹\s*(\d+)\s*(more)?\s*for\s*free\s*(delivery|shipping)/i);
if (match?.[1]) {
threshold = Number(match[1]);
}
}
const hasFreeDelivery = threshold != null ? currentPrice >= threshold : false;
const needsMore = threshold != null && !hasFreeDelivery ? roundCurrency(threshold - currentPrice) : null;
return {
threshold,
needsMore,
hasFreeDelivery,
note:
threshold == null
? "No delivery threshold was confirmed from the current merchant profile."
: hasFreeDelivery
? `The current cart is already past the free-delivery threshold of ${threshold}.`
: `The current cart is below the free-delivery threshold of ${threshold}.`,
};
}
function assessPlatformFeeRisk(domain: string, cartValue: number) {
const normalizedDomain = normalizeDomain(domain);
if (normalizedDomain === "myntra.com") {
if (cartValue < 500) {
return {
risk: "high" as const,
likelihood: 0.8,
estimatedFee: 20,
reasoning: "Myntra frequently adds platform-fee pressure on low-value carts.",
};
}
if (cartValue < 999) {
return {
risk: "medium" as const,
likelihood: 0.55,
estimatedFee: 20,
reasoning: "Myntra carts below the delivery threshold often carry extra platform or shipping friction.",
};
}
}
if (normalizedDomain === "ajio.com") {
return {
risk: cartValue < 999 ? ("medium" as const) : ("low" as const),
likelihood: cartValue < 999 ? 0.6 : 0.2,
estimatedFee: cartValue < 999 ? 20 : 0,
reasoning: "Ajio can add shipping and convenience pressure below the main free-delivery threshold.",
};
}
if (normalizedDomain === "amazon.in") {
return {
risk: "low" as const,
likelihood: 0.1,
estimatedFee: 0,
reasoning: "Amazon India usually keeps platform-fee surprises relatively low.",
};
}
if (normalizedDomain === "flipkart.com") {
return {
risk: cartValue < 500 ? ("medium" as const) : ("low" as const),
likelihood: cartValue < 500 ? 0.35 : 0.15,
estimatedFee: 0,
reasoning: "Flipkart has lower fee opacity than fashion-first marketplaces, though lower carts may still absorb shipping.",
};
}
return {
risk: "low" as const,
likelihood: 0.2,
estimatedFee: 0,
reasoning: "No strong merchant-specific late-fee pattern is currently modeled.",
};
}
function inferShoppingIntent(domain: string, input: AgenticMissionRequest, category: MissionCategory): { isShoppingPage: boolean; reason: string } {
if (PROFILE_MAP[domain]) {
return { isShoppingPage: true, reason: "Recognized commerce domain profile." };
}
if (isKnownNonCommerceDomain(domain)) {
return { isShoppingPage: false, reason: "Known non-commerce domain." };
}
const pageText = String(input.pageText ?? "").toLowerCase();
const buttonLabels = Array.isArray(input.buttonLabels) ? input.buttonLabels.join(" ").toLowerCase() : "";
const priceStrings = Array.isArray(input.priceStrings) ? input.priceStrings : [];
const productCandidates = Array.isArray(input.productCandidates) ? input.productCandidates : [];
const keywordMatches = [
"add to cart",
"checkout",
"reserve now",
"book now",
"buy now",
"place order",
"payment",
"shipping",
"tax",
"insurance",
"seat selection",
"trip protection",
].filter((keyword) => pageText.includes(keyword) || buttonLabels.includes(keyword)).length;
const priceSignalCount = priceStrings.filter((value) => /\d/.test(value)).length;
const productSignalCount = productCandidates.filter((candidate) => typeof candidate?.title === "string" && candidate.title.trim().length > 0).length;
const hasProductUrlHint = /\/dp\/|\/gp\/product\/|\/product\//.test(String(input.url ?? "").toLowerCase());
if (
priceSignalCount >= 2 ||
keywordMatches >= 2 ||
productSignalCount >= 2 ||
(productSignalCount >= 1 && (priceSignalCount >= 1 || keywordMatches >= 1 || hasProductUrlHint))
) {
return { isShoppingPage: true, reason: "Detected commerce language or price signals in the current page context." };
}
if (category === "marketplace" && priceSignalCount === 0 && keywordMatches === 0) {
return { isShoppingPage: false, reason: "No meaningful commerce or checkout signals were found on the current page." };
}
return { isShoppingPage: true, reason: "Falling back to a generic commerce heuristic." };
}
function estimateGhostCheckoutCosts(
domain: string,
profile: MissionProfile,
listedPrice: number,
pageType?: string,
): FeeEstimate {
const normalizedDomain = normalizeDomain(domain);
const breakdown: GhostCheckoutLane[] = [];
let totalFees = 0;
const platformFeeRisk = assessPlatformFeeRisk(normalizedDomain, listedPrice);
if (normalizedDomain === "amazon.in" && profile.category === "marketplace") {
breakdown.push({
label: listedPrice >= 499 ? "Free delivery" : "Shipping charges",
amount: listedPrice >= 499 ? 0 : 40,
confidence: "high",
source:
listedPrice >= 499
? "Amazon India free-delivery threshold reached"
: "Amazon India shipping threshold estimate for lower-value carts",
});
if (pageType === "checkout") {
breakdown.push({
label: "Cash-on-delivery risk",
amount: 40,
confidence: "low",
source: "Optional COD fee estimate if pay-on-delivery is selected",
});
totalFees += 40;
}
totalFees += listedPrice >= 499 ? 0 : 40;
breakdown.push({
label: "Prime membership suggestion",
amount: 0,
confidence: "medium",
source: "Optional Prime upsell may appear, but it is not required for the baseline purchase path",
});
return {
fees: roundCurrency(totalFees),
confidence: "high",
breakdown,
platformFeeRisk,
};
}
if (isIndianCommerceDomain(normalizedDomain) && profile.category === "marketplace") {
if (listedPrice < 500) {
if (platformFeeRisk.estimatedFee > 0) {
breakdown.push({
label: "Platform fee",
amount: platformFeeRisk.estimatedFee,
confidence: platformFeeRisk.risk === "high" ? "high" : "medium",
source: platformFeeRisk.reasoning,
});
totalFees += platformFeeRisk.estimatedFee;
}
}
if (listedPrice < 999 && normalizedDomain !== "flipkart.com" && normalizedDomain !== "amazon.in") {
breakdown.push({
label: "Shipping charges",
amount: 50,
confidence: "medium",
source: "Merchant free-delivery threshold estimate for India fashion marketplaces",
});
totalFees += 50;
}
if (pageType === "checkout") {
breakdown.push({
label: "Cash-on-delivery risk",
amount: 40,
confidence: "low",
source: "Optional COD fee estimate if the shopper chooses pay-on-delivery",
});
totalFees += 40;
}
if (breakdown.length === 0) {
const fallbackFee = roundCurrency(listedPrice * profile.hiddenFeeRate);
if (fallbackFee > 0) {
breakdown.push({
label: "Estimated platform and tax drift",
amount: fallbackFee,
confidence: "medium",
source: "Fallback India marketplace fee model",
});
totalFees += fallbackFee;
}
}
return {
fees: roundCurrency(totalFees),
confidence: breakdown.some((lane) => lane.confidence === "high") ? "high" : "medium",
breakdown,
platformFeeRisk,
};
}
const defaultTax = roundCurrency(listedPrice * (profile.category === "marketplace" ? 0.08 : profile.hiddenFeeRate * 0.55));
if (defaultTax > 0) {
breakdown.push({
label: profile.category === "marketplace" ? "Taxes and platform fees" : "Taxes and mandatory fees",
amount: defaultTax,
confidence: profile.hiddenFeeRate >= 0.18 ? "high" : "medium",
source: "Category fee model + merchant trust profile",
});
totalFees += defaultTax;
}
const surcharge = roundCurrency(Math.max(0, listedPrice * profile.hiddenFeeRate - defaultTax));
if (surcharge > 0) {
breakdown.push({
label:
profile.category === "hotel"
? "Mandatory service or resort fees"
: profile.category === "airline"
? "Ancillary checkout pressure"
: "Late checkout surcharges",
amount: surcharge,
confidence: profile.hiddenFeeRate >= 0.18 ? "medium" : "low",
source: "Ghost-checkout simulation and shared checkout signals",
});
totalFees += surcharge;
}
return {
fees: roundCurrency(totalFees),
confidence: breakdown.some((lane) => lane.confidence === "high") ? "high" : "medium",
breakdown,
platformFeeRisk,
};
}
function buildAlternatives(
profile: MissionProfile,
listedPrice: number,
budget?: number,
context?: { isBookingPlatform?: boolean; bookingType?: string; route?: { display?: string } | null },
): MissionAlternative[] {
if (context?.isBookingPlatform) {
if (context.bookingType === "bus") {
const busOptions: MissionAlternative[] = [
{
domain: "abhibus.com",
merchantName: "Abhibus",
listedPrice: roundCurrency(listedPrice * 0.96),
estimatedTrueTotal: roundCurrency(listedPrice * 0.96 + 15),
trustScore: 78,
trustTier: computeTrustTier(78),
bestValue: false,
why: ["Lower platform-fee pressure than many aggregator flows", "Often competitive on India intercity bus routes"],
},
{
domain: "zingbus.com",
merchantName: "Zingbus",
listedPrice: roundCurrency(listedPrice * 0.95),
estimatedTrueTotal: roundCurrency(listedPrice * 0.95),
trustScore: 75,
trustTier: computeTrustTier(75),
bestValue: false,
why: ["Own-fleet pricing can avoid some aggregator fee layering", "Good benchmark when RedBus feels overpriced"],
},
{
domain: "makemytrip.com",
merchantName: "MakeMyTrip Bus",
listedPrice: roundCurrency(listedPrice * 0.98),
estimatedTrueTotal: roundCurrency(listedPrice * 0.98 + 30),
trustScore: 76,
trustTier: computeTrustTier(76),
bestValue: false,
why: ["Reasonable backup marketplace for route comparison", "Useful if support and route integration matter more than lowest fare"],
},
];
const best = [...busOptions].sort((a, b) => a.estimatedTrueTotal - b.estimatedTrueTotal)[0];
return busOptions.map((option) => ({ ...option, bestValue: option.domain === best.domain }));
}
if (context.bookingType === "hotel") {
const hotelOptions: MissionAlternative[] = [
{
domain: "booking.com",
merchantName: "Booking.com",
listedPrice: roundCurrency(listedPrice * 0.97),
estimatedTrueTotal: roundCurrency(listedPrice * 0.97),
trustScore: 82,
trustTier: computeTrustTier(82),
bestValue: false,
why: ["Useful as a broad hotel benchmark", "Often clearer cancellation and property comparison flow"],
},
{
domain: "agoda.com",
merchantName: "Agoda",
listedPrice: roundCurrency(listedPrice * 0.95),
estimatedTrueTotal: roundCurrency(listedPrice * 0.95),
trustScore: 79,
trustTier: computeTrustTier(79),
bestValue: false,
why: ["Often competitive in Asia-focused hotel pricing", "Worth checking when nightly rates look inflated elsewhere"],
},
{
domain: "makemytrip.com",
merchantName: "MakeMyTrip Hotels",
listedPrice: roundCurrency(listedPrice * 0.98),
estimatedTrueTotal: roundCurrency(listedPrice * 0.98),
trustScore: 77,
trustTier: computeTrustTier(77),
bestValue: false,
why: ["Useful local support benchmark", "Good fallback when international OTAs feel opaque"],
},
];
const best = [...hotelOptions].sort((a, b) => a.estimatedTrueTotal - b.estimatedTrueTotal)[0];
return hotelOptions.map((option) => ({ ...option, bestValue: option.domain === best.domain }));
}
if (context.bookingType === "flight") {
const flightOptions: MissionAlternative[] = [
{
domain: "google.com/travel/flights",
merchantName: "Google Flights",
listedPrice: roundCurrency(listedPrice * 0.98),
estimatedTrueTotal: roundCurrency(listedPrice * 0.98),
trustScore: 85,
trustTier: computeTrustTier(85),
bestValue: false,
why: ["Strong benchmark for fare transparency", "Useful to validate whether aggregator urgency is real"],
},
{
domain: "cleartrip.com",
merchantName: "Cleartrip",
listedPrice: roundCurrency(listedPrice * 0.97),
estimatedTrueTotal: roundCurrency(listedPrice * 0.97),
trustScore: 78,
trustTier: computeTrustTier(78),
bestValue: false,
why: ["Cleaner UI and fee structure than some travel aggregators", "Worth checking before trusting a rising fare"],
},
{
domain: "direct-airline.example",
merchantName: "Airline Direct",
listedPrice: roundCurrency(listedPrice * 0.99),
estimatedTrueTotal: roundCurrency(listedPrice * 0.99),
trustScore: 88,
trustTier: computeTrustTier(88),
bestValue: false,
why: ["Direct airline channels are often the most reliable reference", "Helpful to check whether an OTA is layering extra friction"],
},
];
const best = [...flightOptions].sort((a, b) => a.estimatedTrueTotal - b.estimatedTrueTotal)[0];
return flightOptions.map((option) => ({ ...option, bestValue: option.domain === best.domain }));
}
}
const options = profile.alternativeDomains.map((domain) => {
const model = ALTERNATIVE_MAP[domain] ?? {
merchantName: domain,
trustScore: 60,
baseDelta: -0.03,
feeRate: 0.09,
};
const altListed = roundCurrency(listedPrice * (1 + model.baseDelta));
const estimatedTrueTotal = roundCurrency(altListed * (1 + model.feeRate));
const trustTier = computeTrustTier(model.trustScore);
const cheaperThanBudget = typeof budget !== "number" || estimatedTrueTotal <= budget;
return {
domain,
merchantName: model.merchantName,
listedPrice: altListed,
estimatedTrueTotal,
trustScore: model.trustScore,
trustTier,
bestValue: false,
why: [
model.trustScore >= profile.trustScore ? "Higher trust profile than the current site" : "Useful as a competitive benchmark",
estimatedTrueTotal < listedPrice ? "Lower expected total than the current headline price" : "Comparable pricing with clearer trust characteristics",
cheaperThanBudget ? "Still inside the stated budget" : "Worth reviewing if the current site pushes you over budget",
],
};
});
const best = [...options].sort((a, b) => {
const scoreA = a.estimatedTrueTotal - a.trustScore * 0.45;
const scoreB = b.estimatedTrueTotal - b.trustScore * 0.45;
return scoreA - scoreB;
})[0];
return options.map((option) => ({
...option,
bestValue: option.domain === best.domain,
}));
}
function buildRecommendation(
profile: MissionProfile,
trustTier: MissionTier,
sharedAnalysis: ReturnType<typeof analyzeCheckoutContext>,
dealIntelligence: ReturnType<typeof analyzeDealIntelligence>,
estimatedTrueTotal: number,
listedPrice: number,
withinBudget: boolean | null,
alternatives: MissionAlternative[],
): RecommendationDetails {
const bestAlternative = alternatives.find((alternative) => alternative.bestValue);
const misleadingItems = dealIntelligence.items.filter(
(item) => item.status === "likely_misleading" && item.confidence !== "low",
);
const flags = Array.isArray(sharedAnalysis.manipulationFlags) ? sharedAnalysis.manipulationFlags : [];
const hasTimerReset = flags.some((flag) => flag.type === "timer_reset");
const hasPriceInflation = flags.some((flag) => flag.type === "price_inflation");
const hasSubscriptionSteering = flags.some((flag) => flag.type === "subscription_steering");
const delta = roundCurrency(estimatedTrueTotal - listedPrice);
if (
bestAlternative &&
bestAlternative.estimatedTrueTotal < estimatedTrueTotal * 0.95 &&
bestAlternative.trustScore >= profile.trustScore + 5
) {
return {
action: "switch",
summary: `${bestAlternative.merchantName} offers better value after fees`,
reasoning: [
`${bestAlternative.merchantName} looks meaningfully cheaper after fees than ${profile.merchantName}.`,
`${bestAlternative.merchantName} also carries a stronger trust score (${bestAlternative.trustScore} vs ${profile.trustScore}).`,
],
nextSteps: [
`Open ${bestAlternative.merchantName} and compare the same product there.`,
"Re-run Guardian Mission before buying.",
],
};
}
if (trustTier === "high_manipulation" || misleadingItems.length > 0 || withinBudget === false) {
return {
action: "switch",
summary: "The current path looks too risky to trust",
reasoning: [
"The current merchant or offer path shows strong manipulation or misleading-deal signals.",
...(withinBudget === false ? ["The estimated final total also exceeds the stated budget."] : []),
],
nextSteps: [
"Treat the current deal claims cautiously.",
"Compare at least one alternative merchant before committing.",
],
};
}
if (profile.trustScore >= 75 && (hasTimerReset || hasPriceInflation || hasSubscriptionSteering)) {
return {
action: "proceed_with_caution",
summary: "Trustworthy merchant, but manipulative tactics are still present",
reasoning: [
`${profile.merchantName} is generally a stronger merchant in this category.`,
...(hasTimerReset ? ["A countdown or urgency signal appears to reset, which makes the urgency hard to trust."] : []),
...(hasPriceInflation ? ["The discount framing may be overstated because the original price is too close to the current price."] : []),
...(hasSubscriptionSteering ? ["Membership or subscription benefits are being pushed before the baseline purchase path is settled."] : []),
],
nextSteps: [
"Verify the final total before purchase.",
"Ignore optional subscription pressure unless you truly want it.",
"Refresh once and re-check whether the urgency claim remains consistent.",
],
};
}
if (profile.trustScore >= 75 && delta < 50 && misleadingItems.length === 0) {
return {
action: "proceed",
summary: "Cleaner checkout path with limited hidden-cost drift",
reasoning: [
"The merchant trust profile is solid for this category.",
"Guardian did not find strong misleading-deal evidence on the current offer.",
"The hidden-cost drift looks relatively contained.",
],
nextSteps: ["Proceed, but keep Guardian active through the final payment step."],
};
}
return {
action: "proceed_with_caution",
summary: "Usable path, but some verification is still needed",
reasoning: [
"The current path is not clearly unsafe, but Guardian cannot fully verify every claim yet.",
"Optional extras and final checkout charges still deserve a closer look.",
],
nextSteps: [
"Verify the final total at checkout.",
"Avoid optional add-ons unless they match your preferences.",
"Compare one alternative if the deal still feels rushed or unclear.",
],
};
}
export function buildAgenticMission(input: AgenticMissionRequest) {
const domain = normalizeDomain(input.domain);
const category = inferCategory(domain, input.category);
const sharedAnalysis = analyzeCheckoutContext({
domain,
url: input.url,
pageText: input.pageText,
priceStrings: input.priceStrings,
buttonLabels: input.buttonLabels,
timerElements: input.timerElements,
stockAlerts: input.stockAlerts,
variantUrgency: input.variantUrgency,
productCandidates: input.productCandidates,
});
const dealIntelligence = analyzeDealIntelligence({
domain,
url: input.url,
pageType: input.pageType,
pageText: input.pageText,
hasTimer: input.timerElements?.length ? true : undefined,
timerElements: input.timerElements,
stockAlerts: input.stockAlerts,
productCandidates: input.productCandidates,
});
const shoppingIntent = sharedAnalysis.isShoppingPage
? { isShoppingPage: true, reason: "Shared checkout pipeline classified this page as commerce." }
: inferShoppingIntent(domain, input, category);
if (!shoppingIntent.isShoppingPage) {
return {
missionId: `mission-${domain.replace(/[^a-z0-9]+/g, "-")}`,
domain,
url: input.url ?? `https://${domain}`,
category: "non_commerce",
objective: input.purchaseGoal,
listedPrice: null,
estimatedTrueTotal: null,
savingsAtRisk: null,
budget: typeof input.budget === "number" ? roundCurrency(input.budget) : null,
withinBudget: null,
recommendation: "proceed",
commerceIntent: shoppingIntent,
trust: {
merchantName: domain,
score: 95,
tier: "gold",
verdict: "This page does not look like a shopping or checkout flow, so Guardian is not estimating a purchase total here.",
credibilitySignals: ["No checkout signals found", "No price or order flow detected", "Likely non-commerce page"],
},
accountAutomation: {
mode: "disabled",
status: "Account creation is not relevant on this page.",
guidance: "Guardian only activates checkout automation on pages that look like real shopping or booking flows.",
},
browserAutomation: {
runtime: "Chrome extension + supervised browser worker",
method: "Guardian first verifies that the current page is a commerce flow before attempting any price or trust mission.",
humanApprovalGates: [],
},
ghostCheckout: {
mode: "disabled",
status: "Ghost checkout is disabled because this page is not a live product, cart, booking, or checkout flow.",
revealedTotal: null,
deltaFromHeadline: null,
hiddenCostLanes: [],
fakePressureSummary: [],
saferPathMode: {
available: false,
label: "Stay on cheapest safe path",
detail: "Guardian only enables cheapest-safe-path guidance after it finds a real commerce flow.",
},
supervisedSteps: [],
},
manipulativeSignals: [],
alternatives: [],
bestAlternative: null,
actionLog: [
{
title: "Mission gated",
detail: `Skipped checkout mission on ${domain} because the page does not appear to be a shopping flow.`,
status: "completed",
},
],
nextBestAction: "Open a product, booking, cart, or checkout page before running Guardian Mission.",
summary: "Guardian detected that this is not a commerce page, so it did not estimate a price or compare shopping alternatives.",
recommendationDetails: {
action: "proceed",
summary: "No shopping flow detected",
reasoning: ["Guardian did not find a live commerce flow on this page."],
nextSteps: ["Open a product, booking, cart, or checkout page before running the mission again."],
},
preferences: input.preferences ?? [],
dealIntelligence,
};
}
const profile = PROFILE_MAP[domain] ?? fallbackProfile(domain, category);
const listedPrice = roundCurrency(
typeof input.listedPrice === "number" && Number.isFinite(input.listedPrice)
? input.listedPrice
: category === "hotel"
? 165
: category === "airline"
? 189
: category === "car_rental"
? 72
: 49.99,
);
const feeEstimate = estimateGhostCheckoutCosts(domain, profile, listedPrice, input.pageType);
const deliveryThreshold = detectFreeDeliveryThreshold(domain, listedPrice, input.pageText);
const estimatedTrueTotal = roundCurrency(listedPrice * profile.listedMarkupRate + feeEstimate.fees);
const savingsAtRisk = roundCurrency(estimatedTrueTotal - listedPrice);
const tier = computeTrustTier(profile.trustScore);
const withinBudget = typeof input.budget !== "number" ? null : estimatedTrueTotal <= input.budget;
const alternatives =
input.compareAcrossSites === false
? []
: buildAlternatives(profile, listedPrice, input.budget, {
isBookingPlatform: input.isBookingPlatform,
bookingType: input.bookingType,
route: input.route,
});
const bestAlternative = alternatives.find((alternative) => alternative.bestValue);
const recommendationDecision = buildRecommendation(
profile,
tier,
sharedAnalysis,
dealIntelligence,
estimatedTrueTotal,
listedPrice,
withinBudget,
alternatives,
);
const recommendation = recommendationDecision.action;
const accountAutomation =
input.allowAccountCreation
? {
mode: "allowed_with_approval",
status: "Approval gate required before any signup or credential submission.",
guidance: "Guardian can draft the signup path, generate an email alias, and stop for user approval before creating an account or storing session state.",
}
: {
mode: "manual_only",
status: "Account creation disabled for this mission.",
guidance: "Guardian will analyze the flow, compare alternatives, and prepare the safest next step without creating an account.",
};
const hiddenCostLanes: GhostCheckoutLane[] = feeEstimate.breakdown.filter((lane) => lane.amount > 0);
const fakePressureSummary = [
...(sharedAnalysis.falseUrgency.detected ? ["Flagged countdown or price-lock pressure before the real total was visible."] : []),
...(sharedAnalysis.falseScarcity.detected ? ["Flagged scarcity or crowd-pressure copy that could manufacture FOMO."] : []),
...profile.signals
.filter((signal) => signal.type === "false_urgency" || signal.type === "false_scarcity")
.map((signal) => signal.evidence),
].slice(0, 3);
const ghostCheckout: GhostCheckoutPlan = {
mode: "simulated_supervised",
status:
recommendation === "switch"
? "Ghost checkout found that the current path is too expensive or manipulative relative to available alternatives."
: "Ghost checkout estimated the final payable amount before the shopper commits time or account details.",
revealedTotal: estimatedTrueTotal,
deltaFromHeadline: savingsAtRisk,
hiddenCostLanes,
fakePressureSummary,
saferPathMode: {
available: true,
label: "Stay on cheapest safe path",
detail:
recommendation === "switch" && bestAlternative
? `Guardian would keep only the lowest-risk path active and redirect the shopper to ${bestAlternative.merchantName} after approval.`
: "Guardian would keep optional add-ons off, reject upsells, and continue only on the cheapest acceptable path.",
},
supervisedSteps: [
{
label: "Open a parallel ghost cart",
status: "completed",
detail: "Mirror the checkout flow in a supervised browser context without committing payment or account creation.",
},
{
label: "Reveal late fees and total",
status: "completed",
detail: `Estimate the real total at ${roundCurrency(estimatedTrueTotal)} before the shopper invests more effort.`,
},
...(deliveryThreshold.threshold != null
? [
{
label: "Check delivery threshold",
status: "completed" as const,
detail: deliveryThreshold.hasFreeDelivery
? `Current cart already clears the free-delivery threshold of ${deliveryThreshold.threshold}.`
: `Current cart is ${deliveryThreshold.needsMore} away from the free-delivery threshold of ${deliveryThreshold.threshold}.`,
},
]
: []),
{
label: "Strip optional extras",
status: input.allowAutoDeclineUpsells ? "completed" : "planned",
detail: input.allowAutoDeclineUpsells
? "Guardian is prepared to reject optional insurance, upgrades, and bundles."
: "Guardian will wait for approval before auto-declining optional extras.",
},
{
label: "Hold approval gates",
status: "watch",
detail: "Credentials, account creation, merchant switching, and payment remain blocked until the user explicitly approves them.",
},
],
deliveryThreshold: {
threshold: deliveryThreshold.threshold,
needsMore: deliveryThreshold.needsMore,
hasFreeDelivery: deliveryThreshold.hasFreeDelivery,
note: deliveryThreshold.note,
},
platformFeeRisk: feeEstimate.platformFeeRisk,
};
const actionLog: MissionAction[] = [
{
title: "Mission scoped",
detail: `Targeted ${profile.merchantName} for ${category.replace("_", " ")} checkout analysis with the goal: ${input.purchaseGoal}.`,
status: "completed",
},
{
title: "Ghost checkout planned",
detail: `Reveal the true total beyond the headline price by walking the checkout path and capturing mandatory fees.`,
status: "completed",
},
{
title: "Manipulation review",
detail: profile.signals.length
? `Flagged ${profile.signals.length} pressure pattern${profile.signals.length === 1 ? "" : "s"} that can distort the buying decision.`
: "No major dark-pattern profile was triggered for this site archetype.",
status: "completed",
},
{
title: "Platform fee risk",
detail: `${feeEstimate.platformFeeRisk.risk.toUpperCase()} risk: ${feeEstimate.platformFeeRisk.reasoning}`,
status: "completed",
},
{
title: "Cross-site comparison",
detail: alternatives.length
? `Compared the current offer against ${alternatives.length} alternative checkout paths.`
: "Alternative comparison skipped because cross-site checking was disabled.",
status: alternatives.length ? "completed" : "recommended",
},
{
title: "Upsell stripping",
detail: input.allowAutoDeclineUpsells
? "Prepared the agent to reject unnecessary add-ons and stay on the cheapest acceptable path."
: "Waiting for permission before the agent auto-declines optional upsells.",
status: input.allowAutoDeclineUpsells ? "completed" : "approval_required",
},
{
title: "Account creation guardrail",
detail: accountAutomation.status,
status: input.allowAccountCreation ? "approval_required" : "recommended",
},
];
const credibilityVerdict =
tier === "gold" || tier === "clean"
? "This merchant looks broadly credible, but Guardian still recommends verifying the final payable amount before checkout."
: tier === "neutral"
? "This merchant is usable, but the pricing path deserves a full review before trusting the headline deal."
: "This merchant shows enough manipulation risk that Guardian recommends checking alternatives before committing.";
return {
missionId: `mission-${domain.replace(/[^a-z0-9]+/g, "-")}`,
domain,
url: input.url ?? `https://${domain}/checkout`,
category,
objective: input.purchaseGoal,
listedPrice,
estimatedTrueTotal,
savingsAtRisk,
budget: typeof input.budget === "number" ? roundCurrency(input.budget) : null,
withinBudget,
recommendation,
commerceIntent: shoppingIntent,
trust: {
merchantName: profile.merchantName,
score: profile.trustScore,
tier,
verdict: credibilityVerdict,
credibilitySignals: profile.credibilitySignals,
},
accountAutomation,
browserAutomation: {
runtime: "Chrome extension + supervised browser worker",
method: "Use a persistent user-approved browser context for session continuity, then compare checkout paths with ghost-checkout reads before any buy action.",
humanApprovalGates: [
"Before creating accounts or submitting credentials",
"Before purchasing or saving payment methods",
"Before switching to an alternative merchant",
],
},
ghostCheckout,
manipulativeSignals: [
...profile.signals,
...(sharedAnalysis.falseUrgency.detected
? [
{
type: "false_urgency",
label: "False Urgency",
severity: "high" as const,
confidence: 0.82,
evidence: sharedAnalysis.falseUrgency.evidence,
whyItMatters: "The shopper may rush into the flow before the final price is understood.",
fix: "Verify whether the timer represents a real expiring offer.",
},
]
: []),
...(sharedAnalysis.falseScarcity.detected
? [
{
type: "false_scarcity",
label: "False Scarcity",
severity: "medium" as const,
confidence: 0.79,
evidence: sharedAnalysis.falseScarcity.evidence,
whyItMatters: "Artificial scarcity increases FOMO and pushes earlier commitment.",
fix: "Validate stock claims against real inventory before trusting them.",
},
]
: []),
...(sharedAnalysis.hiddenFees.detected
? [
{
type: "hidden_fees",
label: "Hidden Fees",
severity: "high" as const,
confidence: 0.84,
evidence: sharedAnalysis.hiddenFees.feeItems.length
? `Shared analysis found ${sharedAnalysis.hiddenFees.feeItems.map((item) => item.label).join(", ")}.`
: "Shared analysis found late fee disclosure risk.",
whyItMatters: "The headline deal may materially understate the final payable amount.",
fix: "Force a true-total estimate before recommending the current merchant.",
},
]
: []),
...(input.variantUrgency?.length
? [
{
type: "variant_pressure",
label: "Variant urgency",
severity: "medium" as const,
confidence: 0.78,
evidence: input.variantUrgency.slice(0, 2).join(" | "),
whyItMatters: "Size-level scarcity can create apparel-specific FOMO that feels more real than it actually is.",
fix: "Verify whether the urgency is variant-specific and persistent before treating it as a reason to rush.",
},
]
: []),
],
alternatives,
bestAlternative: bestAlternative ?? null,
actionLog,
recommendationDetails: recommendationDecision,
nextBestAction:
recommendation === "switch" && bestAlternative
? `Switch to ${bestAlternative.merchantName} and re-run the ghost checkout there before buying.`
: dealIntelligence.items.some((item) => item.status === "likely_misleading")
? "Treat the current limited deal claims as potentially misleading and compare at least one alternative before purchase."
: recommendation === "proceed_with_caution"
? "Proceed only after Guardian verifies the final price and strips optional extras."
: "Proceed, but keep Guardian active through the final payment step for last-second fee changes.",
summary:
recommendation === "switch"
? `${profile.merchantName} looks costly or manipulative relative to alternatives. Guardian recommends switching before you sink more time into this checkout.`
: recommendation === "proceed_with_caution"
? `${profile.merchantName} may still be usable, but the shopper should not trust the headline price or the conversion nudges without verification. ${recommendationDecision.summary}`
: `${profile.merchantName} is one of the safer available options, though Guardian should still verify the final price before purchase. ${recommendationDecision.summary}`,
preferences: input.preferences ?? [],
dealIntelligence,
};
}