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.
- package/README.md +689 -0
- package/dist/auth/credentialStore.js +62 -0
- package/dist/auth/inbox.js +193 -0
- package/dist/auth/profile.js +379 -0
- package/dist/auth/runner.js +1124 -0
- package/dist/backend/dashboardData.js +194 -0
- package/dist/backend/runArtifacts.js +48 -0
- package/dist/backend/runRepository.js +93 -0
- package/dist/bin.js +2 -0
- package/dist/cli/backfillSiteChecks.js +143 -0
- package/dist/cli/run.js +309 -0
- package/dist/cli/trade.js +69 -0
- package/dist/config.js +199 -0
- package/dist/core/agentProfiles.js +55 -0
- package/dist/core/aggregateReport.js +382 -0
- package/dist/core/audit.js +30 -0
- package/dist/core/customTaskSuite.js +148 -0
- package/dist/core/evaluator.js +217 -0
- package/dist/core/executor.js +788 -0
- package/dist/core/fallbackReport.js +335 -0
- package/dist/core/formHeuristics.js +411 -0
- package/dist/core/gameplaySummary.js +164 -0
- package/dist/core/interaction.js +202 -0
- package/dist/core/pageState.js +201 -0
- package/dist/core/planner.js +1669 -0
- package/dist/core/processSubmissionBatch.js +204 -0
- package/dist/core/runAuditJob.js +170 -0
- package/dist/core/runner.js +2352 -0
- package/dist/core/siteBrief.js +107 -0
- package/dist/core/siteChecks.js +1526 -0
- package/dist/core/taskDirectives.js +279 -0
- package/dist/core/taskHeuristics.js +263 -0
- package/dist/dashboard/client.js +1256 -0
- package/dist/dashboard/contracts.js +95 -0
- package/dist/dashboard/narrative.js +277 -0
- package/dist/dashboard/server.js +458 -0
- package/dist/dashboard/theme.js +888 -0
- package/dist/index.js +84 -0
- package/dist/llm/client.js +188 -0
- package/dist/paystack/account.js +123 -0
- package/dist/paystack/client.js +100 -0
- package/dist/paystack/index.js +13 -0
- package/dist/paystack/test-paystack.js +83 -0
- package/dist/paystack/transfer.js +138 -0
- package/dist/paystack/types.js +74 -0
- package/dist/paystack/webhook.js +121 -0
- package/dist/prompts/browserAgent.js +124 -0
- package/dist/prompts/reviewer.js +71 -0
- package/dist/reporting/clickReplay.js +290 -0
- package/dist/reporting/html.js +930 -0
- package/dist/reporting/markdown.js +238 -0
- package/dist/reporting/template.js +1141 -0
- package/dist/schemas/types.js +361 -0
- package/dist/submissions/customTasks.js +196 -0
- package/dist/submissions/html.js +770 -0
- package/dist/submissions/model.js +56 -0
- package/dist/submissions/publicUrl.js +76 -0
- package/dist/submissions/service.js +74 -0
- package/dist/submissions/store.js +37 -0
- package/dist/submissions/types.js +65 -0
- package/dist/trade/engine.js +241 -0
- package/dist/trade/evm/erc20.js +44 -0
- package/dist/trade/extractor.js +148 -0
- package/dist/trade/policy.js +35 -0
- package/dist/trade/session.js +31 -0
- package/dist/trade/types.js +107 -0
- package/dist/trade/validator.js +148 -0
- package/dist/utils/files.js +59 -0
- package/dist/utils/log.js +24 -0
- package/dist/utils/playwrightCompat.js +14 -0
- package/dist/utils/time.js +3 -0
- package/dist/wallet/provider.js +345 -0
- package/dist/wallet/relay.js +129 -0
- package/dist/wallet/wallet.js +178 -0
- package/docs/01-installation.md +134 -0
- package/docs/02-running-your-first-audit.md +136 -0
- package/docs/03-configuration.md +233 -0
- package/docs/04-how-the-agent-thinks.md +41 -0
- package/docs/05-extending-personas-and-tasks.md +42 -0
- package/docs/06-hardening-for-production.md +92 -0
- package/package.json +60 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import sharp from "sharp";
|
|
4
|
+
import WebP from "node-webpmux";
|
|
5
|
+
export const CLICK_REPLAY_ARTIFACT = "click-replay.webp";
|
|
6
|
+
const MAX_REPLAY_WIDTH = 640;
|
|
7
|
+
const MAX_REPLAY_HEIGHT = 720;
|
|
8
|
+
const BEFORE_FRAME_DURATION_MS = 320;
|
|
9
|
+
const AFTER_FRAME_DURATION_MS = 760;
|
|
10
|
+
const MAX_REPLAY_DURATION_MS = 15000;
|
|
11
|
+
const MIN_FRAME_DURATION_MS = 80;
|
|
12
|
+
function normalizeText(value) {
|
|
13
|
+
return value.replace(/\s+/g, " ").trim();
|
|
14
|
+
}
|
|
15
|
+
function clamp(value, min, max) {
|
|
16
|
+
return Math.min(max, Math.max(min, value));
|
|
17
|
+
}
|
|
18
|
+
function escapeXml(value) {
|
|
19
|
+
return value
|
|
20
|
+
.replaceAll("&", "&")
|
|
21
|
+
.replaceAll("<", "<")
|
|
22
|
+
.replaceAll(">", ">")
|
|
23
|
+
.replaceAll("\"", """)
|
|
24
|
+
.replaceAll("'", "'");
|
|
25
|
+
}
|
|
26
|
+
function truncateText(value, maxLength) {
|
|
27
|
+
if (value.length <= maxLength) {
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
return `${value.slice(0, Math.max(1, maxLength - 1)).trimEnd()}...`;
|
|
31
|
+
}
|
|
32
|
+
function describeReplayActivity(entry) {
|
|
33
|
+
const target = normalizeText(entry.result.clickIndicator?.targetLabel ?? entry.decision.target);
|
|
34
|
+
const fallbackTarget = target || "visible page";
|
|
35
|
+
const resultLabel = entry.result.success === false ? "failed" : entry.result.stop ? "stopped" : "done";
|
|
36
|
+
switch (entry.decision.action) {
|
|
37
|
+
case "click":
|
|
38
|
+
return `Step ${entry.step}: Click ${truncateText(fallbackTarget, 42)} (${resultLabel})`;
|
|
39
|
+
case "type":
|
|
40
|
+
return `Step ${entry.step}: Type in ${truncateText(fallbackTarget, 42)} (${resultLabel})`;
|
|
41
|
+
case "scroll":
|
|
42
|
+
return `Step ${entry.step}: Scroll page (${resultLabel})`;
|
|
43
|
+
case "wait":
|
|
44
|
+
return `Step ${entry.step}: Wait for page (${resultLabel})`;
|
|
45
|
+
case "back":
|
|
46
|
+
return `Step ${entry.step}: Go back (${resultLabel})`;
|
|
47
|
+
case "extract":
|
|
48
|
+
return `Step ${entry.step}: Capture page state (${resultLabel})`;
|
|
49
|
+
case "trade":
|
|
50
|
+
return `Step ${entry.step}: Wallet trade handoff (${resultLabel})`;
|
|
51
|
+
case "stop":
|
|
52
|
+
return `Step ${entry.step}: Stop path (${resultLabel})`;
|
|
53
|
+
default:
|
|
54
|
+
return `Step ${entry.step}: ${entry.decision.action} (${resultLabel})`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function collectReplaySources(args) {
|
|
58
|
+
const sources = [];
|
|
59
|
+
const activities = args.taskResults.flatMap((task) => task.history.map((entry) => describeReplayActivity(entry)));
|
|
60
|
+
let activityIndex = 0;
|
|
61
|
+
function takeActivities() {
|
|
62
|
+
if (activities.length === 0) {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
const remainingSources = Math.max(1, expectedVisualSourceCount - sources.length);
|
|
66
|
+
const remainingActivities = activities.length - activityIndex;
|
|
67
|
+
const takeCount = Math.max(1, Math.ceil(remainingActivities / remainingSources));
|
|
68
|
+
const nextActivities = activities.slice(activityIndex, activityIndex + takeCount);
|
|
69
|
+
activityIndex += nextActivities.length;
|
|
70
|
+
return nextActivities;
|
|
71
|
+
}
|
|
72
|
+
const expectedVisualSourceCount = args.taskResults.reduce((count, task) => count +
|
|
73
|
+
task.history.reduce((entryCount, entry) => {
|
|
74
|
+
if (entry.decision.action !== "click") {
|
|
75
|
+
return entryCount;
|
|
76
|
+
}
|
|
77
|
+
return (entryCount +
|
|
78
|
+
(entry.result.beforeScreenshotPath ? 1 : 0) +
|
|
79
|
+
(entry.result.afterScreenshotPath ? 1 : 0));
|
|
80
|
+
}, 0), 0);
|
|
81
|
+
for (const task of args.taskResults) {
|
|
82
|
+
for (const entry of task.history) {
|
|
83
|
+
if (entry.decision.action !== "click") {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const beforePath = entry.result.beforeScreenshotPath
|
|
87
|
+
? path.join(args.runDir, entry.result.beforeScreenshotPath)
|
|
88
|
+
: null;
|
|
89
|
+
const afterPath = entry.result.afterScreenshotPath
|
|
90
|
+
? path.join(args.runDir, entry.result.afterScreenshotPath)
|
|
91
|
+
: null;
|
|
92
|
+
if (beforePath && fs.existsSync(beforePath)) {
|
|
93
|
+
sources.push({
|
|
94
|
+
filePath: beforePath,
|
|
95
|
+
durationMs: BEFORE_FRAME_DURATION_MS,
|
|
96
|
+
annotations: takeActivities(),
|
|
97
|
+
...(entry.result.clickIndicator ? { clickIndicator: entry.result.clickIndicator } : {})
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (afterPath && fs.existsSync(afterPath)) {
|
|
101
|
+
sources.push({
|
|
102
|
+
filePath: afterPath,
|
|
103
|
+
durationMs: AFTER_FRAME_DURATION_MS,
|
|
104
|
+
annotations: takeActivities()
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (let sourceIndex = 0; activityIndex < activities.length && sources.length > 0; sourceIndex += 1) {
|
|
110
|
+
const source = sources[sourceIndex % sources.length];
|
|
111
|
+
const activity = activities[activityIndex];
|
|
112
|
+
if (!source || !activity) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
source.annotations = [...(source.annotations ?? []), activity];
|
|
116
|
+
activityIndex += 1;
|
|
117
|
+
}
|
|
118
|
+
return sources;
|
|
119
|
+
}
|
|
120
|
+
function normalizeDurations(sources) {
|
|
121
|
+
const totalDurationMs = sources.reduce((sum, source) => sum + source.durationMs, 0);
|
|
122
|
+
if (totalDurationMs <= MAX_REPLAY_DURATION_MS) {
|
|
123
|
+
return sources;
|
|
124
|
+
}
|
|
125
|
+
const scale = MAX_REPLAY_DURATION_MS / totalDurationMs;
|
|
126
|
+
return sources.map((source) => ({
|
|
127
|
+
...source,
|
|
128
|
+
durationMs: Math.max(MIN_FRAME_DURATION_MS, Math.round(source.durationMs * scale))
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
async function resolveReplayCanvas(sources) {
|
|
132
|
+
for (const source of sources) {
|
|
133
|
+
const metadata = await sharp(source.filePath).metadata();
|
|
134
|
+
if (!metadata.width || !metadata.height) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const scale = Math.min(1, MAX_REPLAY_WIDTH / metadata.width, MAX_REPLAY_HEIGHT / metadata.height);
|
|
138
|
+
return {
|
|
139
|
+
width: Math.max(1, Math.round(metadata.width * scale)),
|
|
140
|
+
height: Math.max(1, Math.round(metadata.height * scale))
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
function resolveResizeTransform(args) {
|
|
146
|
+
const scale = Math.min(1, args.targetWidth / args.sourceWidth, args.targetHeight / args.sourceHeight);
|
|
147
|
+
const renderedWidth = Math.max(1, Math.round(args.sourceWidth * scale));
|
|
148
|
+
const renderedHeight = Math.max(1, Math.round(args.sourceHeight * scale));
|
|
149
|
+
return {
|
|
150
|
+
scale,
|
|
151
|
+
renderedWidth,
|
|
152
|
+
renderedHeight,
|
|
153
|
+
offsetX: Math.round((args.targetWidth - renderedWidth) / 2),
|
|
154
|
+
offsetY: Math.round((args.targetHeight - renderedHeight) / 2)
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function buildOverlayBuffer(args) {
|
|
158
|
+
const labels = (args.annotations ?? [])
|
|
159
|
+
.map((annotation) => truncateText(normalizeText(annotation), 76))
|
|
160
|
+
.filter((annotation) => annotation.length > 0)
|
|
161
|
+
.slice(0, 5);
|
|
162
|
+
const fontSize = clamp(Math.round(args.canvasWidth * 0.028), 16, 24);
|
|
163
|
+
const labelPaddingX = 14;
|
|
164
|
+
const labelLineHeight = Math.round(fontSize * 1.24);
|
|
165
|
+
const labelHeight = labels.length > 0 ? labels.length * labelLineHeight + 18 : 0;
|
|
166
|
+
const longestLabel = labels.reduce((longest, label) => Math.max(longest, label.length), 0);
|
|
167
|
+
const labelWidth = labels.length > 0
|
|
168
|
+
? Math.min(args.canvasWidth - 24, Math.max(240, Math.round(longestLabel * (fontSize * 0.58)) + labelPaddingX * 2))
|
|
169
|
+
: 0;
|
|
170
|
+
let highlightMarkup = "";
|
|
171
|
+
if (args.clickIndicator && args.sourceWidth && args.sourceHeight) {
|
|
172
|
+
const transform = resolveResizeTransform({
|
|
173
|
+
sourceWidth: args.sourceWidth,
|
|
174
|
+
sourceHeight: args.sourceHeight,
|
|
175
|
+
targetWidth: args.canvasWidth,
|
|
176
|
+
targetHeight: args.canvasHeight
|
|
177
|
+
});
|
|
178
|
+
const padding = clamp(Math.round(Math.min(args.canvasWidth, args.canvasHeight) * 0.012), 8, 18);
|
|
179
|
+
const rawX = transform.offsetX + args.clickIndicator.x * transform.scale;
|
|
180
|
+
const rawY = transform.offsetY + args.clickIndicator.y * transform.scale;
|
|
181
|
+
const rawWidth = Math.max(18, args.clickIndicator.width * transform.scale);
|
|
182
|
+
const rawHeight = Math.max(18, args.clickIndicator.height * transform.scale);
|
|
183
|
+
const boxX = clamp(rawX - padding, 0, args.canvasWidth - 2);
|
|
184
|
+
const boxY = clamp(rawY - padding, 0, args.canvasHeight - 2);
|
|
185
|
+
const boxWidth = clamp(rawWidth + padding * 2, 18, args.canvasWidth - boxX);
|
|
186
|
+
const boxHeight = clamp(rawHeight + padding * 2, 18, args.canvasHeight - boxY);
|
|
187
|
+
const centerX = clamp(rawX + rawWidth / 2, 0, args.canvasWidth);
|
|
188
|
+
const centerY = clamp(rawY + rawHeight / 2, 0, args.canvasHeight);
|
|
189
|
+
const outerRadius = clamp(Math.max(boxWidth, boxHeight) * 0.5, 24, 72);
|
|
190
|
+
const innerRadius = clamp(Math.max(boxWidth, boxHeight) * 0.3, 14, 40);
|
|
191
|
+
const centerRadius = clamp(Math.min(boxWidth, boxHeight) * 0.18, 8, 14);
|
|
192
|
+
const strokeWidth = clamp(Math.round(Math.min(args.canvasWidth, args.canvasHeight) * 0.006), 3, 7);
|
|
193
|
+
const cornerRadius = clamp(Math.round(Math.min(boxWidth, boxHeight) * 0.16), 10, 18);
|
|
194
|
+
highlightMarkup = `
|
|
195
|
+
<rect x="${boxX.toFixed(1)}" y="${boxY.toFixed(1)}" width="${boxWidth.toFixed(1)}" height="${boxHeight.toFixed(1)}" rx="${cornerRadius}" fill="#f59e0b" fill-opacity="0.18" stroke="#ffffff" stroke-opacity="0.98" stroke-width="${strokeWidth + 2}" />
|
|
196
|
+
<rect x="${boxX.toFixed(1)}" y="${boxY.toFixed(1)}" width="${boxWidth.toFixed(1)}" height="${boxHeight.toFixed(1)}" rx="${cornerRadius}" fill="none" stroke="#f97316" stroke-opacity="0.98" stroke-width="${strokeWidth}" />
|
|
197
|
+
<circle cx="${centerX.toFixed(1)}" cy="${centerY.toFixed(1)}" r="${outerRadius.toFixed(1)}" fill="none" stroke="#ffffff" stroke-opacity="0.95" stroke-width="${strokeWidth + 2}" />
|
|
198
|
+
<circle cx="${centerX.toFixed(1)}" cy="${centerY.toFixed(1)}" r="${outerRadius.toFixed(1)}" fill="none" stroke="#ef4444" stroke-opacity="0.88" stroke-width="${strokeWidth}" />
|
|
199
|
+
<circle cx="${centerX.toFixed(1)}" cy="${centerY.toFixed(1)}" r="${innerRadius.toFixed(1)}" fill="none" stroke="#facc15" stroke-opacity="0.9" stroke-width="${Math.max(2, strokeWidth - 1)}" />
|
|
200
|
+
<circle cx="${centerX.toFixed(1)}" cy="${centerY.toFixed(1)}" r="${centerRadius.toFixed(1)}" fill="#ef4444" fill-opacity="0.96" stroke="#ffffff" stroke-width="3" />
|
|
201
|
+
`;
|
|
202
|
+
}
|
|
203
|
+
if (!highlightMarkup && labels.length === 0) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
const labelMarkup = labels.length > 0
|
|
207
|
+
? `
|
|
208
|
+
<g>
|
|
209
|
+
<rect x="16" y="16" width="${labelWidth}" height="${labelHeight}" rx="16" fill="#111827" fill-opacity="0.84" stroke="#ffffff" stroke-opacity="0.25" stroke-width="1" />
|
|
210
|
+
${labels
|
|
211
|
+
.map((label, index) => `<text x="${16 + labelPaddingX}" y="${16 + 12 + fontSize + index * labelLineHeight}" font-family="Arial, Helvetica, sans-serif" font-size="${fontSize}" font-weight="700" fill="#ffffff">${escapeXml(label)}</text>`)
|
|
212
|
+
.join("")}
|
|
213
|
+
</g>
|
|
214
|
+
`
|
|
215
|
+
: "";
|
|
216
|
+
const svg = `
|
|
217
|
+
<svg width="${args.canvasWidth}" height="${args.canvasHeight}" viewBox="0 0 ${args.canvasWidth} ${args.canvasHeight}" xmlns="http://www.w3.org/2000/svg">
|
|
218
|
+
${highlightMarkup}
|
|
219
|
+
${labelMarkup}
|
|
220
|
+
</svg>
|
|
221
|
+
`;
|
|
222
|
+
return Buffer.from(svg);
|
|
223
|
+
}
|
|
224
|
+
async function buildReplayFrameBuffer(args) {
|
|
225
|
+
const metadata = await sharp(args.source.filePath).metadata();
|
|
226
|
+
let frame = sharp(args.source.filePath)
|
|
227
|
+
.rotate()
|
|
228
|
+
.resize({
|
|
229
|
+
width: args.width,
|
|
230
|
+
height: args.height,
|
|
231
|
+
fit: "contain",
|
|
232
|
+
background: "#ffffff",
|
|
233
|
+
withoutEnlargement: true
|
|
234
|
+
})
|
|
235
|
+
.flatten({ background: "#ffffff" });
|
|
236
|
+
const overlayBuffer = buildOverlayBuffer({
|
|
237
|
+
canvasWidth: args.width,
|
|
238
|
+
canvasHeight: args.height,
|
|
239
|
+
...(metadata.width ? { sourceWidth: metadata.width } : {}),
|
|
240
|
+
...(metadata.height ? { sourceHeight: metadata.height } : {}),
|
|
241
|
+
...(args.source.annotations ? { annotations: args.source.annotations } : {}),
|
|
242
|
+
...(args.source.clickIndicator ? { clickIndicator: args.source.clickIndicator } : {})
|
|
243
|
+
});
|
|
244
|
+
if (overlayBuffer) {
|
|
245
|
+
frame = frame.composite([{ input: overlayBuffer }]);
|
|
246
|
+
}
|
|
247
|
+
return frame
|
|
248
|
+
.webp({
|
|
249
|
+
quality: 58,
|
|
250
|
+
effort: 4
|
|
251
|
+
})
|
|
252
|
+
.toBuffer();
|
|
253
|
+
}
|
|
254
|
+
export async function generateClickReplay(args) {
|
|
255
|
+
const rawSources = collectReplaySources(args);
|
|
256
|
+
if (rawSources.length === 0) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
const sources = normalizeDurations(rawSources);
|
|
260
|
+
const canvas = await resolveReplayCanvas(sources);
|
|
261
|
+
if (!canvas) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
const frames = [];
|
|
265
|
+
for (const source of sources) {
|
|
266
|
+
const buffer = await buildReplayFrameBuffer({
|
|
267
|
+
source,
|
|
268
|
+
width: canvas.width,
|
|
269
|
+
height: canvas.height
|
|
270
|
+
});
|
|
271
|
+
const frame = await WebP.Image.generateFrame({
|
|
272
|
+
buffer,
|
|
273
|
+
delay: source.durationMs
|
|
274
|
+
});
|
|
275
|
+
frames.push(frame);
|
|
276
|
+
}
|
|
277
|
+
const outputPath = path.join(args.runDir, CLICK_REPLAY_ARTIFACT);
|
|
278
|
+
await WebP.Image.save(outputPath, {
|
|
279
|
+
width: canvas.width,
|
|
280
|
+
height: canvas.height,
|
|
281
|
+
frames,
|
|
282
|
+
bgColor: [255, 255, 255, 255],
|
|
283
|
+
loops: 0
|
|
284
|
+
});
|
|
285
|
+
return {
|
|
286
|
+
artifactName: CLICK_REPLAY_ARTIFACT,
|
|
287
|
+
durationMs: sources.reduce((sum, source) => sum + source.durationMs, 0),
|
|
288
|
+
frameCount: frames.length
|
|
289
|
+
};
|
|
290
|
+
}
|