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,95 @@
1
+ import { z } from "zod";
2
+ import { AccessibilityResultSchema, FinalReportSchema, SiteBriefSchema, SiteChecksSchema, TaskHistoryEntrySchema } from "../schemas/types.js";
3
+ import { SubmissionAgentRunSchema } from "../submissions/types.js";
4
+ import { TradeRunOptionsSchema } from "../trade/types.js";
5
+ const TenPointNullableScoreSchema = z
6
+ .number()
7
+ .finite()
8
+ .min(0)
9
+ .max(100)
10
+ .transform((value) => {
11
+ const scaled = value > 10 ? value / 10 : value;
12
+ return Math.min(10, Math.max(1, Math.round(scaled)));
13
+ })
14
+ .pipe(z.number().int().min(1).max(10))
15
+ .nullable();
16
+ export const RunInputsSchema = z.object({
17
+ baseUrl: z.string(),
18
+ persona: z.string().optional(),
19
+ headed: z.boolean(),
20
+ mobile: z.boolean(),
21
+ ignoreHttpsErrors: z.boolean().optional(),
22
+ tradeOptions: TradeRunOptionsSchema.optional(),
23
+ llmProvider: z.enum(["openai", "ollama"]).optional(),
24
+ model: z.string().optional(),
25
+ startedAt: z.string().optional(),
26
+ maxRunDurationMs: z.number().int().positive().optional(),
27
+ maxRunDurationSeconds: z.number().int().positive().optional(),
28
+ browserExecutionBudgetMs: z.number().int().positive().optional(),
29
+ reportingReserveMs: z.number().int().nonnegative().optional(),
30
+ maxRunDurationClamped: z.boolean().optional(),
31
+ deviceTimezone: z.string().optional(),
32
+ browserTimezone: z.string().optional(),
33
+ synchronizedTimezone: z.string().optional(),
34
+ clickReplayArtifact: z.string().optional(),
35
+ clickReplayFrameCount: z.number().int().positive().optional(),
36
+ clickReplayDurationMs: z.number().int().positive().optional(),
37
+ videoArtifact: z.string().optional(),
38
+ instructionText: z.string().optional(),
39
+ instructionFileName: z.string().nullable().optional(),
40
+ siteBrief: SiteBriefSchema.optional(),
41
+ batchRole: z.enum(["single", "child", "aggregate"]).default("single"),
42
+ parentSubmissionId: z.string().optional(),
43
+ agentCount: z.number().int().min(1).max(5).default(1),
44
+ completedAgentCount: z.number().int().nonnegative().default(0),
45
+ failedAgentCount: z.number().int().nonnegative().default(0),
46
+ customTasks: z.array(z.string()).default([]),
47
+ agentIndex: z.number().int().min(1).max(5).optional(),
48
+ agentLabel: z.string().optional(),
49
+ agentProfileLabel: z.string().optional(),
50
+ personaVariantKey: z.string().optional(),
51
+ aggregatedFromRunIds: z.array(z.string()).default([]),
52
+ agentRuns: z.array(SubmissionAgentRunSchema).max(5).default([])
53
+ });
54
+ export const DashboardRunSummarySchema = z.object({
55
+ id: z.string(),
56
+ baseUrl: z.string(),
57
+ host: z.string(),
58
+ startedAt: z.string().nullable(),
59
+ headed: z.boolean(),
60
+ mobile: z.boolean(),
61
+ llmProvider: z.enum(["openai", "ollama"]).nullable().default(null),
62
+ model: z.string().nullable(),
63
+ persona: z.string().nullable(),
64
+ overallScore: TenPointNullableScoreSchema,
65
+ summary: z.string().nullable(),
66
+ taskCount: z.number().int().nonnegative(),
67
+ accessibilityViolationCount: z.number().int().nonnegative().nullable(),
68
+ batchRole: z.enum(["single", "child", "aggregate"]).default("single"),
69
+ agentCount: z.number().int().min(1).max(5).default(1),
70
+ completedAgentCount: z.number().int().nonnegative().default(0),
71
+ failedAgentCount: z.number().int().nonnegative().default(0),
72
+ agentLabel: z.string().nullable().default(null),
73
+ agentProfileLabel: z.string().nullable().default(null)
74
+ });
75
+ export const DashboardTaskHistoryEntrySchema = TaskHistoryEntrySchema.extend({});
76
+ export const DashboardTaskSchema = z.object({
77
+ name: z.string(),
78
+ status: z.enum(["success", "partial_success", "failed"]),
79
+ reason: z.string(),
80
+ evidence: z.array(z.string()),
81
+ finalUrl: z.string(),
82
+ finalTitle: z.string(),
83
+ history: z.array(DashboardTaskHistoryEntrySchema)
84
+ });
85
+ export const DashboardRunDetailSchema = z.object({
86
+ id: z.string(),
87
+ host: z.string(),
88
+ inputs: RunInputsSchema.nullable(),
89
+ report: FinalReportSchema.nullable(),
90
+ accessibility: AccessibilityResultSchema.nullable(),
91
+ siteChecks: SiteChecksSchema.nullable(),
92
+ tasks: z.array(DashboardTaskSchema),
93
+ rawEventCount: z.number().int().nonnegative(),
94
+ warnings: z.array(z.string())
95
+ });
@@ -0,0 +1,277 @@
1
+ const INTERNAL_REPORT_PATTERNS = [
2
+ /model evaluator did not finish/i,
3
+ /final output was synthesized directly from recorded evidence/i,
4
+ /model-based evaluation step could not finish cleanly/i,
5
+ /remaining run budget/i,
6
+ /wall-clock budget/i,
7
+ /request timed out/i,
8
+ /current quota/i,
9
+ /429\b/i
10
+ ];
11
+ const NO_CHANGE_CLICK_PATTERN = /Clicked '([^']+)' but the page showed no clear visible change(?: after \d+ms)?/i;
12
+ const SUCCESS_CLICK_PATTERN = /Clicked '([^']+)' and reached '([^']+)'(?: after \d+ms)?/i;
13
+ function normalizeWhitespace(value) {
14
+ return value.replace(/\s+/g, " ").trim();
15
+ }
16
+ function stripTrailingPeriod(value) {
17
+ return value.replace(/[.!\s]+$/g, "").trim();
18
+ }
19
+ function ensureSentence(value) {
20
+ const trimmed = normalizeWhitespace(value);
21
+ if (!trimmed) {
22
+ return "";
23
+ }
24
+ return /[.!?]$/.test(trimmed) ? trimmed : `${trimmed}.`;
25
+ }
26
+ function lowerFirst(value) {
27
+ if (!value) {
28
+ return value;
29
+ }
30
+ return value.charAt(0).toLowerCase() + value.slice(1);
31
+ }
32
+ function toTitleCaseWords(value) {
33
+ return value
34
+ .split(" ")
35
+ .filter(Boolean)
36
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
37
+ .join(" ");
38
+ }
39
+ function formatQuotedList(labels) {
40
+ if (labels.length === 0) {
41
+ return "";
42
+ }
43
+ if (labels.length === 1) {
44
+ return `"${labels[0]}"`;
45
+ }
46
+ if (labels.length === 2) {
47
+ return `"${labels[0]}" and "${labels[1]}"`;
48
+ }
49
+ return `${labels.slice(0, -1).map((label) => `"${label}"`).join(", ")}, and "${labels.at(-1) ?? ""}"`;
50
+ }
51
+ function problemLabelPriority(label) {
52
+ const normalized = label.toLowerCase();
53
+ if (/\bhome\b/.test(normalized)) {
54
+ return 0;
55
+ }
56
+ if (/\b(menu|nav|navigation|about|contact|pricing|developers|docs|swap|login|sign in)\b/.test(normalized)) {
57
+ return 1;
58
+ }
59
+ if (/\b(start|get started|launch|convert|buy|shop)\b/.test(normalized)) {
60
+ return 3;
61
+ }
62
+ return 2;
63
+ }
64
+ function readSiteLabel(detail) {
65
+ const baseUrl = detail.inputs?.baseUrl;
66
+ if (!baseUrl) {
67
+ return detail.host;
68
+ }
69
+ try {
70
+ return new URL(baseUrl).hostname.replace(/^www\./, "") || detail.host;
71
+ }
72
+ catch {
73
+ return detail.host;
74
+ }
75
+ }
76
+ function overallFeeling(score) {
77
+ if ((score ?? 0) >= 8) {
78
+ return "smooth";
79
+ }
80
+ if ((score ?? 0) >= 6) {
81
+ return "mostly okay";
82
+ }
83
+ if ((score ?? 0) >= 4) {
84
+ return "mixed";
85
+ }
86
+ return "frustrating";
87
+ }
88
+ function dedupeStrings(items) {
89
+ const seen = new Set();
90
+ const results = [];
91
+ for (const item of items) {
92
+ const normalized = normalizeWhitespace(item).toLowerCase();
93
+ if (!normalized || seen.has(normalized)) {
94
+ continue;
95
+ }
96
+ seen.add(normalized);
97
+ results.push(normalizeWhitespace(item));
98
+ }
99
+ return results;
100
+ }
101
+ function cleanObservation(value) {
102
+ const withoutAgentPrefix = value.replace(/^Agent \d+ \([^)]+\):\s*/i, "");
103
+ const withoutStepPrefix = withoutAgentPrefix.replace(/^Step \d+:\s*/i, "");
104
+ const withoutTaskPrefix = withoutStepPrefix.replace(/^[^:]{1,80}:\s+(?=(Clicked|Waited|Stopped|Accessibility|Run limitation))/i, "");
105
+ return normalizeWhitespace(withoutTaskPrefix);
106
+ }
107
+ export function isVisitorFacingItem(value) {
108
+ const cleaned = cleanObservation(value);
109
+ if (!cleaned) {
110
+ return false;
111
+ }
112
+ return !INTERNAL_REPORT_PATTERNS.some((pattern) => pattern.test(cleaned));
113
+ }
114
+ export function filterVisitorFacingItems(items) {
115
+ return dedupeStrings(items.map((item) => cleanObservation(item)).filter((item) => isVisitorFacingItem(item)));
116
+ }
117
+ function describeObservation(observation) {
118
+ const cleaned = cleanObservation(observation);
119
+ if (!isVisitorFacingItem(cleaned)) {
120
+ return null;
121
+ }
122
+ const noChangeMatch = cleaned.match(NO_CHANGE_CLICK_PATTERN);
123
+ if (noChangeMatch) {
124
+ return `When I clicked "${noChangeMatch[1] ?? "that item"}", nothing clearly changed, so it was hard to tell whether that navigation was working.`;
125
+ }
126
+ const successMatch = cleaned.match(SUCCESS_CLICK_PATTERN);
127
+ if (successMatch) {
128
+ return `Clicking "${successMatch[1] ?? "that item"}" did take me to "${successMatch[2] ?? "the next page"}", so not every path was broken.`;
129
+ }
130
+ if (/Stopped early after repeated unchanged page states with no meaningful progress/i.test(cleaned)) {
131
+ return "After a few tries, I stopped because the page kept looking unchanged and I was no longer making meaningful progress.";
132
+ }
133
+ const accessibilityMatch = cleaned.match(/^Accessibility:\s*(.+?)\s*\((\d+)\s+affected nodes\)\.?$/i);
134
+ if (accessibilityMatch) {
135
+ return `The accessibility scan also flagged ${lowerFirst(stripTrailingPeriod(accessibilityMatch[1] ?? "an accessibility issue"))} in ${accessibilityMatch[2] ?? "some"} places.`;
136
+ }
137
+ if (/^Run limitation:/i.test(cleaned)) {
138
+ return ensureSentence(cleaned.replace(/^Run limitation:\s*/i, "Some parts of the visit were inconclusive because "));
139
+ }
140
+ return ensureSentence(cleaned);
141
+ }
142
+ function buildIntro(detail) {
143
+ const siteLabel = readSiteLabel(detail);
144
+ const score = detail.report?.overall_score ?? null;
145
+ const batchRole = detail.inputs?.batchRole ?? "single";
146
+ const mobileLabel = detail.inputs?.mobile ? " on a mobile-sized screen" : "";
147
+ if (batchRole === "aggregate") {
148
+ return `I checked ${siteLabel} from a few different visitor perspectives, and overall the experience felt ${overallFeeling(score)}.`;
149
+ }
150
+ return `I visited ${siteLabel}${mobileLabel}, and overall the experience felt ${overallFeeling(score)}.`;
151
+ }
152
+ function collectNoResponseLabels(detail) {
153
+ const labels = detail.tasks.flatMap((task) => {
154
+ const fromHistory = task.history
155
+ .map((entry) => entry.result.note.match(NO_CHANGE_CLICK_PATTERN)?.[1] ?? "")
156
+ .filter(Boolean);
157
+ const fromEvidence = task.evidence
158
+ .map((evidence) => cleanObservation(evidence).match(NO_CHANGE_CLICK_PATTERN)?.[1] ?? "")
159
+ .filter(Boolean);
160
+ return [...fromHistory, ...fromEvidence];
161
+ });
162
+ return dedupeStrings(labels)
163
+ .sort((left, right) => problemLabelPriority(left) - problemLabelPriority(right) || left.localeCompare(right))
164
+ .slice(0, 4);
165
+ }
166
+ function buildNoResponseSentence(detail) {
167
+ const labels = collectNoResponseLabels(detail);
168
+ if (labels.length === 0) {
169
+ return null;
170
+ }
171
+ if (labels.length === 1) {
172
+ return `When I clicked "${labels[0]}", nothing clearly changed, so it was hard to tell whether that navigation was working.`;
173
+ }
174
+ const [firstLabel, ...otherLabels] = labels;
175
+ return `When I clicked "${firstLabel}", nothing clearly changed, and I ran into the same problem with ${formatQuotedList(otherLabels)}.`;
176
+ }
177
+ function collectPositiveLabels(detail) {
178
+ const labels = detail.tasks.flatMap((task) => {
179
+ const fromHistory = task.history
180
+ .map((entry) => entry.result.note.match(SUCCESS_CLICK_PATTERN)?.[1] ?? "")
181
+ .filter(Boolean);
182
+ const fromEvidence = task.evidence
183
+ .map((evidence) => cleanObservation(evidence).match(SUCCESS_CLICK_PATTERN)?.[1] ?? "")
184
+ .filter(Boolean);
185
+ return [...fromHistory, ...fromEvidence];
186
+ });
187
+ return dedupeStrings(labels).slice(0, 3);
188
+ }
189
+ function buildPositiveSentence(detail) {
190
+ const labels = collectPositiveLabels(detail);
191
+ if (labels.length === 0) {
192
+ const strengths = filterVisitorFacingItems(detail.report?.strengths ?? []);
193
+ const firstStrength = strengths[0];
194
+ return firstStrength ? ensureSentence(firstStrength) : null;
195
+ }
196
+ return `Some paths still worked: ${formatQuotedList(labels)} all led somewhere clear enough to keep the visit moving.`;
197
+ }
198
+ function buildAccessibilitySentence(detail) {
199
+ const primaryViolation = detail.accessibility?.violations?.[0];
200
+ if (primaryViolation) {
201
+ if (primaryViolation.id === "color-contrast" || /color contrast/i.test(primaryViolation.help)) {
202
+ return `The accessibility scan also flagged low color contrast in ${primaryViolation.nodes} places, so some text may be harder to read than it should be.`;
203
+ }
204
+ return `The accessibility scan also flagged ${lowerFirst(stripTrailingPeriod(primaryViolation.help))} in ${primaryViolation.nodes} places.`;
205
+ }
206
+ const accessibilityObservation = filterVisitorFacingItems(detail.report?.weaknesses ?? []).find((item) => item.startsWith("Accessibility:"));
207
+ return accessibilityObservation ? describeObservation(accessibilityObservation) : null;
208
+ }
209
+ function buildSupportingIssueSentences(detail) {
210
+ const observations = filterVisitorFacingItems([
211
+ ...detail.tasks.flatMap((task) => task.evidence),
212
+ ...(detail.report?.weaknesses ?? []),
213
+ ...detail.tasks.flatMap((task) => task.history.map((entry) => entry.result.note))
214
+ ]);
215
+ const sentences = [];
216
+ const seen = new Set();
217
+ for (const observation of observations) {
218
+ const sentence = describeObservation(observation);
219
+ if (!sentence) {
220
+ continue;
221
+ }
222
+ if (NO_CHANGE_CLICK_PATTERN.test(cleanObservation(observation))) {
223
+ continue;
224
+ }
225
+ const key = sentence.toLowerCase();
226
+ if (seen.has(key)) {
227
+ continue;
228
+ }
229
+ seen.add(key);
230
+ sentences.push(sentence);
231
+ if (sentences.length >= 2) {
232
+ break;
233
+ }
234
+ }
235
+ return sentences;
236
+ }
237
+ function buildFallbackSentence(detail) {
238
+ const firstTask = detail.tasks[0];
239
+ if (firstTask && isVisitorFacingItem(firstTask.reason)) {
240
+ return ensureSentence(firstTask.reason);
241
+ }
242
+ if (detail.report?.summary) {
243
+ return ensureSentence(detail.report.summary);
244
+ }
245
+ return "I did not capture enough clean interaction evidence to describe the visit in more detail yet.";
246
+ }
247
+ export function buildVisitRecap(detail) {
248
+ const paragraphs = [];
249
+ const intro = buildIntro(detail);
250
+ const mainIssue = buildNoResponseSentence(detail);
251
+ const supportingIssues = buildSupportingIssueSentences(detail);
252
+ const positive = buildPositiveSentence(detail);
253
+ const accessibility = buildAccessibilitySentence(detail);
254
+ paragraphs.push(intro);
255
+ const issueParagraph = [mainIssue, ...supportingIssues].filter(Boolean).join(" ");
256
+ if (issueParagraph) {
257
+ paragraphs.push(issueParagraph);
258
+ }
259
+ const closingParagraph = [positive, accessibility].filter(Boolean).join(" ");
260
+ if (closingParagraph) {
261
+ paragraphs.push(closingParagraph);
262
+ }
263
+ if (paragraphs.length === 1) {
264
+ paragraphs.push(buildFallbackSentence(detail));
265
+ }
266
+ return paragraphs;
267
+ }
268
+ export function buildVisitSummary(detail) {
269
+ return buildVisitRecap(detail).slice(0, 2).join(" ");
270
+ }
271
+ export function humanizeAgentPerspectiveLabel(label) {
272
+ if (!label) {
273
+ return null;
274
+ }
275
+ const cleaned = normalizeWhitespace(label.replace(/\s+-\s+/g, " ").replace(/\bfirst-time\b/gi, "first time"));
276
+ return cleaned ? toTitleCaseWords(cleaned) : null;
277
+ }