site-agent-pro 1.0.0

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.
Files changed (81) hide show
  1. package/README.md +689 -0
  2. package/dist/auth/credentialStore.js +62 -0
  3. package/dist/auth/inbox.js +193 -0
  4. package/dist/auth/profile.js +379 -0
  5. package/dist/auth/runner.js +1124 -0
  6. package/dist/backend/dashboardData.js +194 -0
  7. package/dist/backend/runArtifacts.js +48 -0
  8. package/dist/backend/runRepository.js +93 -0
  9. package/dist/bin.js +2 -0
  10. package/dist/cli/backfillSiteChecks.js +143 -0
  11. package/dist/cli/run.js +309 -0
  12. package/dist/cli/trade.js +69 -0
  13. package/dist/config.js +199 -0
  14. package/dist/core/agentProfiles.js +55 -0
  15. package/dist/core/aggregateReport.js +382 -0
  16. package/dist/core/audit.js +30 -0
  17. package/dist/core/customTaskSuite.js +148 -0
  18. package/dist/core/evaluator.js +217 -0
  19. package/dist/core/executor.js +788 -0
  20. package/dist/core/fallbackReport.js +335 -0
  21. package/dist/core/formHeuristics.js +411 -0
  22. package/dist/core/gameplaySummary.js +164 -0
  23. package/dist/core/interaction.js +202 -0
  24. package/dist/core/pageState.js +201 -0
  25. package/dist/core/planner.js +1669 -0
  26. package/dist/core/processSubmissionBatch.js +204 -0
  27. package/dist/core/runAuditJob.js +170 -0
  28. package/dist/core/runner.js +2352 -0
  29. package/dist/core/siteBrief.js +107 -0
  30. package/dist/core/siteChecks.js +1526 -0
  31. package/dist/core/taskDirectives.js +279 -0
  32. package/dist/core/taskHeuristics.js +263 -0
  33. package/dist/dashboard/client.js +1256 -0
  34. package/dist/dashboard/contracts.js +95 -0
  35. package/dist/dashboard/narrative.js +277 -0
  36. package/dist/dashboard/server.js +458 -0
  37. package/dist/dashboard/theme.js +888 -0
  38. package/dist/index.js +84 -0
  39. package/dist/llm/client.js +188 -0
  40. package/dist/paystack/account.js +123 -0
  41. package/dist/paystack/client.js +100 -0
  42. package/dist/paystack/index.js +13 -0
  43. package/dist/paystack/test-paystack.js +83 -0
  44. package/dist/paystack/transfer.js +138 -0
  45. package/dist/paystack/types.js +74 -0
  46. package/dist/paystack/webhook.js +121 -0
  47. package/dist/prompts/browserAgent.js +124 -0
  48. package/dist/prompts/reviewer.js +71 -0
  49. package/dist/reporting/clickReplay.js +290 -0
  50. package/dist/reporting/html.js +930 -0
  51. package/dist/reporting/markdown.js +238 -0
  52. package/dist/reporting/template.js +1141 -0
  53. package/dist/schemas/types.js +361 -0
  54. package/dist/submissions/customTasks.js +196 -0
  55. package/dist/submissions/html.js +770 -0
  56. package/dist/submissions/model.js +56 -0
  57. package/dist/submissions/publicUrl.js +76 -0
  58. package/dist/submissions/service.js +74 -0
  59. package/dist/submissions/store.js +37 -0
  60. package/dist/submissions/types.js +65 -0
  61. package/dist/trade/engine.js +241 -0
  62. package/dist/trade/evm/erc20.js +44 -0
  63. package/dist/trade/extractor.js +148 -0
  64. package/dist/trade/policy.js +35 -0
  65. package/dist/trade/session.js +31 -0
  66. package/dist/trade/types.js +107 -0
  67. package/dist/trade/validator.js +148 -0
  68. package/dist/utils/files.js +59 -0
  69. package/dist/utils/log.js +24 -0
  70. package/dist/utils/playwrightCompat.js +14 -0
  71. package/dist/utils/time.js +3 -0
  72. package/dist/wallet/provider.js +345 -0
  73. package/dist/wallet/relay.js +129 -0
  74. package/dist/wallet/wallet.js +178 -0
  75. package/docs/01-installation.md +134 -0
  76. package/docs/02-running-your-first-audit.md +136 -0
  77. package/docs/03-configuration.md +233 -0
  78. package/docs/04-how-the-agent-thinks.md +41 -0
  79. package/docs/05-extending-personas-and-tasks.md +42 -0
  80. package/docs/06-hardening-for-production.md +92 -0
  81. package/package.json +60 -0
@@ -0,0 +1,62 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ const CREDENTIALS_FILE = path.resolve(process.cwd(), ".auth", "credentials.json");
4
+ function getOriginKey(urlString) {
5
+ try {
6
+ const url = new URL(urlString);
7
+ return /^https?:$/i.test(url.protocol) ? url.origin.toLowerCase() : null;
8
+ }
9
+ catch {
10
+ return null;
11
+ }
12
+ }
13
+ function getLegacyHostnameKey(urlString) {
14
+ try {
15
+ return new URL(urlString).hostname.toLowerCase();
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ function loadCredentials() {
22
+ try {
23
+ if (fs.existsSync(CREDENTIALS_FILE)) {
24
+ const content = fs.readFileSync(CREDENTIALS_FILE, "utf-8");
25
+ const parsed = JSON.parse(content);
26
+ return parsed && typeof parsed === "object" ? parsed : {};
27
+ }
28
+ }
29
+ catch (error) {
30
+ console.error(`Failed to read credentials from ${CREDENTIALS_FILE}`, error);
31
+ }
32
+ return {};
33
+ }
34
+ export function saveStoredIdentity(url, identity) {
35
+ const credentialKey = getOriginKey(url);
36
+ if (!credentialKey)
37
+ return;
38
+ const credentials = loadCredentials();
39
+ credentials[credentialKey] = identity;
40
+ try {
41
+ const dir = path.dirname(CREDENTIALS_FILE);
42
+ if (!fs.existsSync(dir)) {
43
+ fs.mkdirSync(dir, { recursive: true });
44
+ }
45
+ fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), "utf-8");
46
+ }
47
+ catch (error) {
48
+ console.error(`Failed to save credentials to ${CREDENTIALS_FILE}`, error);
49
+ }
50
+ }
51
+ export function getStoredIdentity(url) {
52
+ const credentials = loadCredentials();
53
+ const originKey = getOriginKey(url);
54
+ if (originKey && credentials[originKey]) {
55
+ return credentials[originKey] || null;
56
+ }
57
+ const legacyHostnameKey = getLegacyHostnameKey(url);
58
+ return legacyHostnameKey ? credentials[legacyHostnameKey] || null : null;
59
+ }
60
+ export function hasStoredIdentity(url) {
61
+ return getStoredIdentity(url) !== null;
62
+ }
@@ -0,0 +1,193 @@
1
+ import { ImapFlow } from "imapflow";
2
+ import { simpleParser } from "mailparser";
3
+ import { authSettings } from "./profile.js";
4
+ function normalizeText(value) {
5
+ return value.replace(/\s+/g, " ").trim();
6
+ }
7
+ function sleep(ms) {
8
+ return new Promise((resolve) => setTimeout(resolve, ms));
9
+ }
10
+ function createClient(mailbox) {
11
+ return new ImapFlow({
12
+ host: mailbox.host,
13
+ port: mailbox.port,
14
+ secure: mailbox.secure,
15
+ auth: {
16
+ user: mailbox.user,
17
+ pass: mailbox.password
18
+ },
19
+ logger: false,
20
+ disableAutoIdle: true
21
+ });
22
+ }
23
+ function sanitizeUrlCandidate(value) {
24
+ const trimmed = value.trim().replace(/[)>.,;]+$/g, "");
25
+ if (!/^https?:\/\//i.test(trimmed)) {
26
+ return null;
27
+ }
28
+ try {
29
+ return new URL(trimmed).toString();
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ function extractUrls(source) {
36
+ const matches = source.match(/https?:\/\/[^\s"'<>]+/gi) ?? [];
37
+ const unique = new Set();
38
+ for (const match of matches) {
39
+ const sanitized = sanitizeUrlCandidate(match);
40
+ if (sanitized) {
41
+ unique.add(sanitized);
42
+ }
43
+ }
44
+ return Array.from(unique);
45
+ }
46
+ function hostMatches(candidateUrl, siteHost) {
47
+ try {
48
+ const candidateHost = new URL(candidateUrl).hostname.replace(/^www\./, "").toLowerCase();
49
+ const normalizedSiteHost = siteHost.replace(/^www\./, "").toLowerCase();
50
+ return candidateHost === normalizedSiteHost || candidateHost.endsWith(`.${normalizedSiteHost}`);
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ }
56
+ function extractVerificationLink(source, siteHost) {
57
+ const urls = extractUrls(source);
58
+ const scored = urls
59
+ .map((url) => {
60
+ const lower = url.toLowerCase();
61
+ let score = 0;
62
+ if (hostMatches(url, siteHost)) {
63
+ score += 50;
64
+ }
65
+ if (/verify|verification|confirm|activate|magic|signin|sign-in|login|auth|token/.test(lower)) {
66
+ score += 25;
67
+ }
68
+ if (/unsubscribe|privacy|preferences|support/.test(lower)) {
69
+ score -= 25;
70
+ }
71
+ return { url, score };
72
+ })
73
+ .sort((left, right) => right.score - left.score);
74
+ return scored[0]?.score && scored[0].score > 0 ? scored[0].url : undefined;
75
+ }
76
+ function extractOtpCode(source, otpLength) {
77
+ const contextualPatterns = [
78
+ new RegExp(`(?:otp|one[ -]?time|verification|security|auth|passcode|code)[^\\d]{0,40}(\\d{${otpLength}})`, "i"),
79
+ new RegExp(`(\\d{${otpLength}})[^\\d]{0,40}(?:otp|one[ -]?time|verification|security|auth|passcode|code)`, "i")
80
+ ];
81
+ for (const pattern of contextualPatterns) {
82
+ const match = source.match(pattern);
83
+ if (match?.[1]) {
84
+ return match[1];
85
+ }
86
+ }
87
+ const exactLengthMatch = source.match(new RegExp(`(?<!\\d)(\\d{${otpLength}})(?!\\d)`));
88
+ if (exactLengthMatch?.[1]) {
89
+ return exactLengthMatch[1];
90
+ }
91
+ const genericMatch = source.match(/(?<!\d)(\d{4,8})(?!\d)/);
92
+ return genericMatch?.[1];
93
+ }
94
+ function messageMatchesFilters(args) {
95
+ const subject = args.subject.toLowerCase();
96
+ const from = args.from.toLowerCase();
97
+ const text = args.sourceText.toLowerCase();
98
+ if (authSettings.emailFromFilter && !from.includes(authSettings.emailFromFilter.toLowerCase())) {
99
+ return false;
100
+ }
101
+ if (authSettings.emailSubjectFilter && !subject.includes(authSettings.emailSubjectFilter.toLowerCase())) {
102
+ return false;
103
+ }
104
+ if (authSettings.emailFromFilter || authSettings.emailSubjectFilter) {
105
+ return true;
106
+ }
107
+ return /verify|verification|confirm|activate|otp|one-time|passcode|code|magic link|login/.test(`${subject} ${from} ${text}`);
108
+ }
109
+ export async function captureInboxCheckpoint(mailbox) {
110
+ const client = createClient(mailbox);
111
+ try {
112
+ await client.connect();
113
+ const lock = await client.getMailboxLock(mailbox.mailbox, { readOnly: true });
114
+ try {
115
+ const mailboxState = client.mailbox === false ? null : client.mailbox;
116
+ return {
117
+ uidNext: mailboxState?.uidNext ?? 1,
118
+ capturedAt: new Date().toISOString()
119
+ };
120
+ }
121
+ finally {
122
+ lock.release();
123
+ }
124
+ }
125
+ finally {
126
+ await client.logout().catch(() => undefined);
127
+ }
128
+ }
129
+ export async function waitForVerificationEmail(args) {
130
+ const timeoutMs = args.timeoutMs ?? authSettings.emailPollTimeoutMs;
131
+ const pollIntervalMs = args.pollIntervalMs ?? authSettings.emailPollIntervalMs;
132
+ const otpLength = args.otpLength ?? authSettings.otpLength;
133
+ const deadline = Date.now() + timeoutMs;
134
+ const client = createClient(args.mailbox);
135
+ try {
136
+ await client.connect();
137
+ while (Date.now() < deadline) {
138
+ const lock = await client.getMailboxLock(args.mailbox.mailbox, { readOnly: true });
139
+ try {
140
+ const uidList = (await client.search({ uid: `${args.checkpoint.uidNext}:*` }, { uid: true })) || [];
141
+ const sortedUids = Array.isArray(uidList) ? [...uidList].sort((left, right) => right - left) : [];
142
+ if (sortedUids.length > 0) {
143
+ const messages = await client.fetchAll(sortedUids.slice(0, 12), {
144
+ uid: true,
145
+ envelope: true,
146
+ source: true
147
+ }, { uid: true });
148
+ for (const message of messages.sort((left, right) => right.uid - left.uid)) {
149
+ if (!message.source) {
150
+ continue;
151
+ }
152
+ const parsed = await simpleParser(message.source);
153
+ const subject = normalizeText(parsed.subject || message.envelope?.subject || "");
154
+ const from = normalizeText(parsed.from?.text ||
155
+ message.envelope?.from?.map((entry) => entry.address || entry.name || "").join(", ") ||
156
+ "");
157
+ const sourceText = normalizeText([parsed.text || "", typeof parsed.html === "string" ? parsed.html : ""].join("\n"));
158
+ if (args.recipientEmail) {
159
+ const toAddresses = (message.envelope?.to?.map((entry) => entry.address?.toLowerCase()).filter(Boolean) ?? []);
160
+ if (!toAddresses.includes(args.recipientEmail.toLowerCase())) {
161
+ continue;
162
+ }
163
+ }
164
+ if (!messageMatchesFilters({ subject, from, sourceText })) {
165
+ continue;
166
+ }
167
+ const verificationLink = extractVerificationLink(sourceText, args.siteHost);
168
+ const otpCode = extractOtpCode(sourceText, otpLength);
169
+ if (!verificationLink && !otpCode) {
170
+ continue;
171
+ }
172
+ return {
173
+ uid: message.uid,
174
+ receivedAt: (parsed.date || message.envelope?.date || new Date()).toISOString(),
175
+ subject,
176
+ from,
177
+ ...(otpCode ? { otpCode } : {}),
178
+ ...(verificationLink ? { verificationLink } : {})
179
+ };
180
+ }
181
+ }
182
+ }
183
+ finally {
184
+ lock.release();
185
+ }
186
+ await sleep(pollIntervalMs);
187
+ }
188
+ }
189
+ finally {
190
+ await client.logout().catch(() => undefined);
191
+ }
192
+ throw new Error(`Timed out after ${Math.round(timeoutMs / 1000)} seconds waiting for a verification email in ${args.mailbox.mailbox}.`);
193
+ }
@@ -0,0 +1,379 @@
1
+ import crypto from "node:crypto";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+ import dotenv from "dotenv";
4
+ import path from "node:path";
5
+ import { z } from "zod";
6
+ import { config } from "../config.js";
7
+ import { getStoredIdentity, hasStoredIdentity } from "./credentialStore.js";
8
+ dotenv.config();
9
+ function normalizeOptionalString(value) {
10
+ const trimmed = value?.trim();
11
+ return trimmed ? trimmed : undefined;
12
+ }
13
+ const AuthEnvSchema = z.object({
14
+ AUTH_TEST_EMAIL: z
15
+ .string()
16
+ .optional()
17
+ .transform((value) => normalizeOptionalString(value)),
18
+ AUTH_TEST_USERNAME: z
19
+ .string()
20
+ .optional()
21
+ .transform((value) => normalizeOptionalString(value)),
22
+ AUTH_TEST_PASSWORD: z
23
+ .string()
24
+ .optional()
25
+ .transform((value) => normalizeOptionalString(value)),
26
+ AUTH_TEST_FIRST_NAME: z
27
+ .string()
28
+ .optional()
29
+ .transform((value) => normalizeOptionalString(value)),
30
+ AUTH_TEST_LAST_NAME: z
31
+ .string()
32
+ .optional()
33
+ .transform((value) => normalizeOptionalString(value)),
34
+ AUTH_TEST_PHONE: z
35
+ .string()
36
+ .optional()
37
+ .transform((value) => normalizeOptionalString(value)),
38
+ AUTH_TEST_ADDRESS_LINE1: z
39
+ .string()
40
+ .optional()
41
+ .transform((value) => normalizeOptionalString(value)),
42
+ AUTH_TEST_ADDRESS_LINE2: z
43
+ .string()
44
+ .optional()
45
+ .transform((value) => normalizeOptionalString(value)),
46
+ AUTH_TEST_CITY: z
47
+ .string()
48
+ .optional()
49
+ .transform((value) => normalizeOptionalString(value)),
50
+ AUTH_TEST_STATE: z
51
+ .string()
52
+ .optional()
53
+ .transform((value) => normalizeOptionalString(value)),
54
+ AUTH_TEST_POSTAL_CODE: z
55
+ .string()
56
+ .optional()
57
+ .transform((value) => normalizeOptionalString(value)),
58
+ AUTH_TEST_COUNTRY: z
59
+ .string()
60
+ .optional()
61
+ .transform((value) => normalizeOptionalString(value)),
62
+ AUTH_TEST_COMPANY: z
63
+ .string()
64
+ .optional()
65
+ .transform((value) => normalizeOptionalString(value)),
66
+ AUTH_IMAP_HOST: z
67
+ .string()
68
+ .optional()
69
+ .transform((value) => normalizeOptionalString(value)),
70
+ AUTH_IMAP_PORT: z.coerce.number().int().positive().default(993),
71
+ AUTH_IMAP_SECURE: z
72
+ .string()
73
+ .optional()
74
+ .transform((value) => value !== "false"),
75
+ AUTH_IMAP_USER: z
76
+ .string()
77
+ .optional()
78
+ .transform((value) => normalizeOptionalString(value)),
79
+ AUTH_IMAP_PASSWORD: z
80
+ .string()
81
+ .optional()
82
+ .transform((value) => normalizeOptionalString(value)),
83
+ AUTH_IMAP_MAILBOX: z.string().default("INBOX").transform((value) => value.trim() || "INBOX"),
84
+ AUTH_EMAIL_POLL_TIMEOUT_MS: z.coerce.number().int().positive().default(180000),
85
+ AUTH_EMAIL_POLL_INTERVAL_MS: z.coerce.number().int().positive().default(5000),
86
+ AUTH_OTP_LENGTH: z.coerce.number().int().min(4).max(8).default(6),
87
+ AUTH_EMAIL_FROM_FILTER: z
88
+ .string()
89
+ .optional()
90
+ .transform((value) => normalizeOptionalString(value)),
91
+ AUTH_EMAIL_SUBJECT_FILTER: z
92
+ .string()
93
+ .optional()
94
+ .transform((value) => normalizeOptionalString(value)),
95
+ AUTH_GENERATED_IDENTITY_MAX_ATTEMPTS: z.coerce.number().int().min(1).max(10).default(5),
96
+ AUTH_SIGNUP_URL: z
97
+ .string()
98
+ .optional()
99
+ .transform((value) => normalizeOptionalString(value)),
100
+ AUTH_LOGIN_URL: z
101
+ .string()
102
+ .optional()
103
+ .transform((value) => normalizeOptionalString(value)),
104
+ AUTH_ACCESS_URL: z
105
+ .string()
106
+ .optional()
107
+ .transform((value) => normalizeOptionalString(value)),
108
+ AUTH_SESSION_STATE_PATH: z
109
+ .string()
110
+ .optional()
111
+ .transform((value) => normalizeOptionalString(value)),
112
+ AUTH_EMAIL_DOMAIN: z
113
+ .string()
114
+ .optional()
115
+ .transform((value) => normalizeOptionalString(value))
116
+ });
117
+ const authOverrides = {};
118
+ function getAuthEnv() {
119
+ const base = AuthEnvSchema.parse(process.env);
120
+ return {
121
+ ...base,
122
+ ...authOverrides
123
+ };
124
+ }
125
+ const ACCESS_IDENTITY_CONTEXT = new AsyncLocalStorage();
126
+ const HARD_CODED_AGENT_IDENTITIES = [
127
+ {
128
+ firstName: "Atlas",
129
+ lastName: "Sentinel",
130
+ emailLocalPart: "site-agent-pro-atlas",
131
+ phone: "+12025550111",
132
+ company: "Site Agent Pro Atlas QA"
133
+ },
134
+ {
135
+ firstName: "Beacon",
136
+ lastName: "Sentinel",
137
+ emailLocalPart: "site-agent-pro-beacon",
138
+ phone: "+12025550122",
139
+ company: "Site Agent Pro Beacon QA"
140
+ },
141
+ {
142
+ firstName: "Cipher",
143
+ lastName: "Sentinel",
144
+ emailLocalPart: "site-agent-pro-cipher",
145
+ phone: "+12025550133",
146
+ company: "Site Agent Pro Cipher QA"
147
+ },
148
+ {
149
+ firstName: "Drift",
150
+ lastName: "Sentinel",
151
+ emailLocalPart: "site-agent-pro-drift",
152
+ phone: "+12025550144",
153
+ company: "Site Agent Pro Drift QA"
154
+ },
155
+ {
156
+ firstName: "Echo",
157
+ lastName: "Sentinel",
158
+ emailLocalPart: "site-agent-pro-echo",
159
+ phone: "+12025550155",
160
+ company: "Site Agent Pro Echo QA"
161
+ }
162
+ ];
163
+ const generatedAccessIdentities = new Map();
164
+ export const authSettings = {
165
+ get signupUrl() { return getAuthEnv().AUTH_SIGNUP_URL; },
166
+ get loginUrl() { return getAuthEnv().AUTH_LOGIN_URL; },
167
+ get accessUrl() { return getAuthEnv().AUTH_ACCESS_URL; },
168
+ get emailPollTimeoutMs() { return getAuthEnv().AUTH_EMAIL_POLL_TIMEOUT_MS; },
169
+ get emailPollIntervalMs() { return getAuthEnv().AUTH_EMAIL_POLL_INTERVAL_MS; },
170
+ get otpLength() { return getAuthEnv().AUTH_OTP_LENGTH; },
171
+ get emailFromFilter() { return getAuthEnv().AUTH_EMAIL_FROM_FILTER; },
172
+ get emailSubjectFilter() { return getAuthEnv().AUTH_EMAIL_SUBJECT_FILTER; },
173
+ get generatedIdentityMaxAttempts() { return getAuthEnv().AUTH_GENERATED_IDENTITY_MAX_ATTEMPTS; }
174
+ };
175
+ export function updateAuthSettings(updates) {
176
+ Object.assign(authOverrides, updates);
177
+ }
178
+ function resolveAgentSlot(args) {
179
+ const rawIndex = args?.agentIndex;
180
+ const normalized = typeof rawIndex === "number" && Number.isFinite(rawIndex) ? Math.round(rawIndex) : 1;
181
+ return Math.min(HARD_CODED_AGENT_IDENTITIES.length, Math.max(1, normalized));
182
+ }
183
+ function getActiveAccessIdentityContext() {
184
+ return ACCESS_IDENTITY_CONTEXT.getStore() ?? {};
185
+ }
186
+ function resolveAgentIdentityTemplate(args) {
187
+ return HARD_CODED_AGENT_IDENTITIES[resolveAgentSlot(args) - 1] ?? HARD_CODED_AGENT_IDENTITIES[0];
188
+ }
189
+ function buildScopedEmail(baseEmail, template) {
190
+ if (!baseEmail) {
191
+ if (getAuthEnv().AUTH_EMAIL_DOMAIN) {
192
+ const seed = crypto.randomBytes(4).toString("hex");
193
+ return `${template.emailLocalPart}-${seed}@${getAuthEnv().AUTH_EMAIL_DOMAIN}`;
194
+ }
195
+ return `${template.emailLocalPart}@example.com`;
196
+ }
197
+ const parts = splitEmailAddress(baseEmail);
198
+ if (!parts) {
199
+ return baseEmail;
200
+ }
201
+ const baseLocal = parts.localPart.split("+", 1)[0] || parts.localPart;
202
+ return `${baseLocal}+${template.emailLocalPart}@${parts.domain}`;
203
+ }
204
+ export async function runWithAccessIdentityContext(context, fn) {
205
+ return await ACCESS_IDENTITY_CONTEXT.run(context, fn);
206
+ }
207
+ export function getAccessIdentityLabel(context) {
208
+ const template = resolveAgentIdentityTemplate(context ?? getActiveAccessIdentityContext());
209
+ return `${template.firstName} ${template.lastName}`;
210
+ }
211
+ export function isAuthBootstrapConfigured(url) {
212
+ return Boolean((url && hasStoredIdentity(url)) || (getAuthEnv().AUTH_TEST_EMAIL && getAuthEnv().AUTH_TEST_PASSWORD));
213
+ }
214
+ export function requireAuthIdentity() {
215
+ const env = getAuthEnv();
216
+ if (!env.AUTH_TEST_EMAIL) {
217
+ throw new Error("AUTH_TEST_EMAIL is required when --auth-flow is enabled.");
218
+ }
219
+ if (!env.AUTH_TEST_PASSWORD) {
220
+ throw new Error("AUTH_TEST_PASSWORD is required when --auth-flow is enabled.");
221
+ }
222
+ const template = resolveAgentIdentityTemplate(getActiveAccessIdentityContext());
223
+ return {
224
+ email: buildScopedEmail(env.AUTH_TEST_EMAIL, template),
225
+ username: env.AUTH_TEST_USERNAME,
226
+ password: env.AUTH_TEST_PASSWORD,
227
+ firstName: template.firstName,
228
+ lastName: template.lastName,
229
+ fullName: `${template.firstName} ${template.lastName}`.trim(),
230
+ phone: env.AUTH_TEST_PHONE ?? template.phone,
231
+ addressLine1: env.AUTH_TEST_ADDRESS_LINE1 ?? "123 Test Lane",
232
+ addressLine2: env.AUTH_TEST_ADDRESS_LINE2 ?? "Suite 100",
233
+ city: env.AUTH_TEST_CITY ?? "Austin",
234
+ state: env.AUTH_TEST_STATE ?? "Texas",
235
+ postalCode: env.AUTH_TEST_POSTAL_CODE ?? "78701",
236
+ country: env.AUTH_TEST_COUNTRY ?? "United States",
237
+ company: env.AUTH_TEST_COMPANY ?? template.company
238
+ };
239
+ }
240
+ function splitEmailAddress(email) {
241
+ const atIndex = email.lastIndexOf("@");
242
+ if (atIndex <= 0 || atIndex === email.length - 1) {
243
+ return null;
244
+ }
245
+ return {
246
+ localPart: email.slice(0, atIndex),
247
+ domain: email.slice(atIndex + 1)
248
+ };
249
+ }
250
+ function buildRetrySeed() {
251
+ return crypto.randomBytes(4).toString("hex");
252
+ }
253
+ function buildVariantPhoneNumber(phone, attempt) {
254
+ if (attempt <= 1) {
255
+ return phone;
256
+ }
257
+ const digits = phone.replace(/\D/g, "");
258
+ if (digits.length < 7) {
259
+ return phone;
260
+ }
261
+ const attemptDigits = String(attempt).padStart(4, "0").slice(-4);
262
+ const prefix = digits.slice(0, Math.max(0, digits.length - 4));
263
+ const normalized = `${prefix}${attemptDigits}`;
264
+ return phone.trim().startsWith("+") ? `+${normalized}` : normalized;
265
+ }
266
+ function buildVariantEmail(baseEmail, attempt, retrySeed) {
267
+ if (attempt <= 1) {
268
+ return baseEmail;
269
+ }
270
+ const parts = splitEmailAddress(baseEmail);
271
+ if (!parts) {
272
+ return baseEmail;
273
+ }
274
+ return `${parts.localPart}+siteagent-${retrySeed}-${attempt}@${parts.domain}`;
275
+ }
276
+ function buildVariantIdentity(baseIdentity, attempt, retrySeed) {
277
+ if (attempt <= 1) {
278
+ return baseIdentity;
279
+ }
280
+ return {
281
+ email: buildVariantEmail(baseIdentity.email, attempt, retrySeed),
282
+ username: baseIdentity.username,
283
+ password: baseIdentity.password,
284
+ firstName: baseIdentity.firstName,
285
+ lastName: baseIdentity.lastName,
286
+ fullName: baseIdentity.fullName,
287
+ phone: buildVariantPhoneNumber(baseIdentity.phone, attempt),
288
+ addressLine1: baseIdentity.addressLine1,
289
+ addressLine2: baseIdentity.addressLine2,
290
+ city: baseIdentity.city,
291
+ state: baseIdentity.state,
292
+ postalCode: baseIdentity.postalCode,
293
+ country: baseIdentity.country,
294
+ company: baseIdentity.company
295
+ };
296
+ }
297
+ export function createAuthIdentityPlan(url) {
298
+ if (url) {
299
+ const storedIdentity = getStoredIdentity(url);
300
+ if (storedIdentity) {
301
+ return {
302
+ maxAttempts: 1,
303
+ identities: [storedIdentity],
304
+ source: "stored"
305
+ };
306
+ }
307
+ }
308
+ const baseIdentity = requireAuthIdentity();
309
+ const retrySeed = buildRetrySeed();
310
+ const maxAttempts = authSettings.generatedIdentityMaxAttempts;
311
+ return {
312
+ maxAttempts,
313
+ identities: Array.from({ length: maxAttempts }, (_, index) => buildVariantIdentity(baseIdentity, index + 1, retrySeed)),
314
+ source: "configured"
315
+ };
316
+ }
317
+ export function getPreferredAccessIdentity(url) {
318
+ if (url) {
319
+ const storedIdentity = getStoredIdentity(url);
320
+ if (storedIdentity) {
321
+ return storedIdentity;
322
+ }
323
+ }
324
+ if (isAuthBootstrapConfigured()) {
325
+ return requireAuthIdentity();
326
+ }
327
+ const context = getActiveAccessIdentityContext();
328
+ const agentSlot = resolveAgentSlot(context);
329
+ if (!generatedAccessIdentities.has(agentSlot)) {
330
+ const retrySeed = buildRetrySeed();
331
+ const template = resolveAgentIdentityTemplate(context);
332
+ const emailDomain = getAuthEnv().AUTH_EMAIL_DOMAIN;
333
+ generatedAccessIdentities.set(agentSlot, {
334
+ email: emailDomain
335
+ ? `${template.emailLocalPart}-${retrySeed}@${emailDomain}`
336
+ : `${template.emailLocalPart}+${retrySeed}@example.com`,
337
+ password: `SiteAgent!${retrySeed.slice(0, 4)}9`,
338
+ firstName: template.firstName,
339
+ lastName: template.lastName,
340
+ fullName: `${template.firstName} ${template.lastName}`,
341
+ phone: template.phone,
342
+ addressLine1: "123 Test Lane",
343
+ addressLine2: "Suite 100",
344
+ city: "Austin",
345
+ state: "Texas",
346
+ postalCode: "78701",
347
+ country: "United States",
348
+ company: template.company
349
+ });
350
+ }
351
+ return generatedAccessIdentities.get(agentSlot);
352
+ }
353
+ export function getMailboxConfig() {
354
+ const env = getAuthEnv();
355
+ if (!env.AUTH_IMAP_HOST || !env.AUTH_IMAP_USER || !env.AUTH_IMAP_PASSWORD) {
356
+ return null;
357
+ }
358
+ return {
359
+ host: env.AUTH_IMAP_HOST,
360
+ port: env.AUTH_IMAP_PORT,
361
+ secure: env.AUTH_IMAP_SECURE,
362
+ user: env.AUTH_IMAP_USER,
363
+ password: env.AUTH_IMAP_PASSWORD,
364
+ mailbox: env.AUTH_IMAP_MAILBOX
365
+ };
366
+ }
367
+ export function requireMailboxConfig() {
368
+ const mailbox = getMailboxConfig();
369
+ if (!mailbox) {
370
+ throw new Error("AUTH_IMAP_HOST, AUTH_IMAP_USER, and AUTH_IMAP_PASSWORD are required when the auth flow needs to read OTP or verification emails.");
371
+ }
372
+ return mailbox;
373
+ }
374
+ export function resolveAuthSessionStatePath() {
375
+ const configuredPath = getAuthEnv().AUTH_SESSION_STATE_PATH ??
376
+ config.playwrightStorageStatePath ??
377
+ path.join(".auth", "session.json");
378
+ return path.isAbsolute(configuredPath) ? configuredPath : path.resolve(process.cwd(), configuredPath);
379
+ }