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,204 @@
1
+ import path from "node:path";
2
+ import { buildAgentVariants } from "./agentProfiles.js";
3
+ import { createAggregateRun } from "./aggregateReport.js";
4
+ import { buildCustomTaskSuite } from "./customTaskSuite.js";
5
+ import { runAuditJob } from "./runAuditJob.js";
6
+ import { SUBMISSION_TASKS_REQUIRED_MESSAGE } from "../submissions/customTasks.js";
7
+ import { SubmissionSchema } from "../submissions/types.js";
8
+ export async function processSubmissionBatch(args) {
9
+ if (args.submission.customTasks.length === 0) {
10
+ const failedSubmission = SubmissionSchema.parse({
11
+ ...args.submission,
12
+ status: "failed",
13
+ startedAt: args.submission.startedAt ?? new Date().toISOString(),
14
+ completedAt: new Date().toISOString(),
15
+ error: SUBMISSION_TASKS_REQUIRED_MESSAGE,
16
+ runId: null,
17
+ runDir: null,
18
+ reportSummary: null,
19
+ overallScore: null
20
+ });
21
+ await args.writeSubmission(failedSubmission);
22
+ return failedSubmission;
23
+ }
24
+ const baseSuite = buildCustomTaskSuite(args.submission.customTasks);
25
+ const variants = buildAgentVariants(args.submission.agentCount, baseSuite);
26
+ let currentSubmission = SubmissionSchema.parse({
27
+ ...args.submission,
28
+ agentCount: args.submission.agentCount || variants.length,
29
+ agentRuns: args.submission.agentRuns.length > 0 ? args.submission.agentRuns : variants.map((variant) => ({
30
+ id: variant.id,
31
+ index: variant.index,
32
+ label: variant.label,
33
+ profileLabel: variant.profileLabel,
34
+ personaName: variant.personaName,
35
+ personaVariantKey: variant.personaVariantKey,
36
+ status: "queued",
37
+ startedAt: null,
38
+ completedAt: null,
39
+ runId: null,
40
+ runDir: null,
41
+ error: null,
42
+ reportSummary: null,
43
+ overallScore: null
44
+ }))
45
+ });
46
+ let mutationChain = Promise.resolve();
47
+ const syncCounts = (submission) => SubmissionSchema.parse({
48
+ ...submission,
49
+ completedAgentCount: submission.agentRuns.filter((agentRun) => agentRun.status === "completed").length,
50
+ failedAgentCount: submission.agentRuns.filter((agentRun) => agentRun.status === "failed").length
51
+ });
52
+ const commit = async (mutator) => {
53
+ mutationChain = mutationChain.then(async () => {
54
+ const nextSubmission = structuredClone(currentSubmission);
55
+ mutator(nextSubmission);
56
+ currentSubmission = syncCounts(nextSubmission);
57
+ await args.writeSubmission(currentSubmission);
58
+ });
59
+ await mutationChain;
60
+ return currentSubmission;
61
+ };
62
+ const batchStartedAt = new Date().toISOString();
63
+ await commit((draft) => {
64
+ draft.status = "running";
65
+ draft.startedAt = batchStartedAt;
66
+ draft.completedAt = null;
67
+ draft.error = null;
68
+ draft.runId = null;
69
+ draft.runDir = null;
70
+ draft.reportSummary = null;
71
+ draft.overallScore = null;
72
+ draft.agentRuns = draft.agentRuns.map((agentRun) => ({
73
+ ...agentRun,
74
+ status: "queued",
75
+ startedAt: null,
76
+ completedAt: null,
77
+ runId: null,
78
+ runDir: null,
79
+ error: null,
80
+ reportSummary: null,
81
+ overallScore: null
82
+ }));
83
+ });
84
+ const agentOutcomes = await Promise.all(variants.map(async (variant) => {
85
+ const agentStartedAt = new Date().toISOString();
86
+ await commit((draft) => {
87
+ draft.agentRuns = draft.agentRuns.map((agentRun) => agentRun.id === variant.id
88
+ ? {
89
+ ...agentRun,
90
+ status: "running",
91
+ startedAt: agentStartedAt,
92
+ completedAt: null,
93
+ error: null
94
+ }
95
+ : agentRun);
96
+ });
97
+ try {
98
+ const result = await runAuditJob({
99
+ baseUrl: currentSubmission.url,
100
+ suiteOverride: variant.taskSuite,
101
+ headed: currentSubmission.headed,
102
+ mobile: currentSubmission.mobile,
103
+ ignoreHttpsErrors: currentSubmission.ignoreHttpsErrors,
104
+ tradeOptions: currentSubmission.tradeOptions,
105
+ extraInputs: {
106
+ source: args.source,
107
+ submissionId: currentSubmission.id,
108
+ reportToken: currentSubmission.reportToken,
109
+ expiresAt: currentSubmission.expiresAt,
110
+ batchRole: "child",
111
+ parentSubmissionId: currentSubmission.id,
112
+ agentCount: currentSubmission.agentCount,
113
+ instructionText: currentSubmission.instructionText,
114
+ instructionFileName: currentSubmission.instructionFileName,
115
+ agentIndex: variant.index,
116
+ agentLabel: variant.label,
117
+ agentProfileLabel: variant.profileLabel,
118
+ personaVariantKey: variant.personaVariantKey,
119
+ customTasks: currentSubmission.customTasks,
120
+ tradeOptions: currentSubmission.tradeOptions
121
+ }
122
+ });
123
+ const runId = path.basename(result.runDir);
124
+ await args.uploadRunArtifacts(runId, result.runDir);
125
+ const completedAt = new Date().toISOString();
126
+ await commit((draft) => {
127
+ draft.agentRuns = draft.agentRuns.map((agentRun) => agentRun.id === variant.id
128
+ ? {
129
+ ...agentRun,
130
+ status: "completed",
131
+ startedAt: agentStartedAt,
132
+ completedAt,
133
+ runId,
134
+ runDir: null,
135
+ error: null,
136
+ reportSummary: result.report.summary,
137
+ overallScore: result.report.overall_score
138
+ }
139
+ : agentRun);
140
+ });
141
+ return {
142
+ agentRun: {
143
+ id: variant.id,
144
+ index: variant.index,
145
+ label: variant.label,
146
+ profileLabel: variant.profileLabel,
147
+ personaName: variant.personaName,
148
+ personaVariantKey: variant.personaVariantKey,
149
+ status: "completed",
150
+ startedAt: agentStartedAt,
151
+ completedAt,
152
+ runId,
153
+ runDir: null,
154
+ error: null,
155
+ reportSummary: result.report.summary,
156
+ overallScore: result.report.overall_score
157
+ },
158
+ report: result.report,
159
+ taskResults: result.execution.taskResults,
160
+ accessibility: result.execution.accessibility,
161
+ siteChecks: result.execution.siteChecks,
162
+ runId
163
+ };
164
+ }
165
+ catch (error) {
166
+ const completedAt = new Date().toISOString();
167
+ const message = error instanceof Error ? error.message : "Unknown submission failure";
168
+ await commit((draft) => {
169
+ draft.agentRuns = draft.agentRuns.map((agentRun) => agentRun.id === variant.id
170
+ ? {
171
+ ...agentRun,
172
+ status: "failed",
173
+ startedAt: agentStartedAt,
174
+ completedAt,
175
+ error: message
176
+ }
177
+ : agentRun);
178
+ });
179
+ return null;
180
+ }
181
+ }));
182
+ const completedOutcomes = agentOutcomes.filter((outcome) => outcome !== null);
183
+ if (completedOutcomes.length === 0) {
184
+ await commit((draft) => {
185
+ draft.status = "failed";
186
+ draft.completedAt = new Date().toISOString();
187
+ draft.error = `All ${draft.agentCount} agent runs failed before an aggregate report could be produced.`;
188
+ });
189
+ return currentSubmission;
190
+ }
191
+ const aggregateSubmissionSnapshot = structuredClone(currentSubmission);
192
+ const aggregateRun = createAggregateRun(aggregateSubmissionSnapshot, completedOutcomes);
193
+ await args.uploadRunArtifacts(aggregateRun.runId, aggregateRun.runDir);
194
+ await commit((draft) => {
195
+ draft.status = "completed";
196
+ draft.completedAt = new Date().toISOString();
197
+ draft.runId = aggregateRun.runId;
198
+ draft.runDir = null;
199
+ draft.error = null;
200
+ draft.reportSummary = aggregateRun.report.summary;
201
+ draft.overallScore = aggregateRun.report.overall_score;
202
+ });
203
+ return currentSubmission;
204
+ }
@@ -0,0 +1,170 @@
1
+ import path from "node:path";
2
+ import { getAccessIdentityLabel, runWithAccessIdentityContext } from "../auth/profile.js";
3
+ import { clampRunDurationMs, config, deriveBrowserExecutionBudgetMs, deriveReportingReserveMs, resolveLlmRuntime } from "../config.js";
4
+ import { evaluateRun } from "./evaluator.js";
5
+ import { runTaskSuite } from "./runner.js";
6
+ import { generateClickReplay } from "../reporting/clickReplay.js";
7
+ import { renderHtmlReport } from "../reporting/html.js";
8
+ import { renderMarkdownReport } from "../reporting/markdown.js";
9
+ import { ensureDir, resolveRunDir, writeJson, writeText } from "../utils/files.js";
10
+ function summarizeSessionPath(filePath) {
11
+ const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
12
+ const relativePath = path.relative(process.cwd(), resolvedPath);
13
+ return relativePath && relativePath !== "" && !relativePath.startsWith("..")
14
+ ? relativePath
15
+ : path.basename(resolvedPath);
16
+ }
17
+ export async function runAuditJob(options) {
18
+ const accessIdentityContext = {
19
+ ...(typeof options.extraInputs?.agentIndex === "number" ? { agentIndex: options.extraInputs.agentIndex } : {}),
20
+ ...(typeof options.extraInputs?.agentLabel === "string" ? { agentLabel: options.extraInputs.agentLabel } : {}),
21
+ ...(typeof options.extraInputs?.agentProfileLabel === "string"
22
+ ? { agentProfileLabel: options.extraInputs.agentProfileLabel }
23
+ : {})
24
+ };
25
+ return await runWithAccessIdentityContext(accessIdentityContext, async () => {
26
+ const suite = options.suiteOverride;
27
+ const runDir = options.runDir ?? resolveRunDir(options.baseUrl);
28
+ ensureDir(runDir);
29
+ const inputsPath = path.join(runDir, "inputs.json");
30
+ const startedAt = new Date().toISOString();
31
+ const startedAtMs = Date.now();
32
+ const requestedMaxSessionDurationMs = options.maxSessionDurationMs ?? config.maxSessionDurationMs;
33
+ const maxRunDurationMs = clampRunDurationMs(requestedMaxSessionDurationMs);
34
+ const browserExecutionBudgetMs = deriveBrowserExecutionBudgetMs(maxRunDurationMs);
35
+ const reportingReserveMs = deriveReportingReserveMs(maxRunDurationMs);
36
+ const llmRuntime = resolveLlmRuntime({
37
+ ...(options.llmProvider ? { provider: options.llmProvider } : {}),
38
+ ...(options.model ? { model: options.model } : {}),
39
+ ...(options.ollamaBaseUrl ? { ollamaBaseUrl: options.ollamaBaseUrl } : {})
40
+ });
41
+ const storageStatePath = options.storageStatePath ?? config.playwrightStorageStatePath;
42
+ const saveStorageStatePath = options.saveStorageStatePath;
43
+ const instructionText = typeof options.extraInputs?.instructionText === "string"
44
+ ? options.extraInputs.instructionText
45
+ : suite.tasks.map((task) => task.goal).join("\n");
46
+ const accessIdentityName = getAccessIdentityLabel(accessIdentityContext);
47
+ const baseInputs = {
48
+ baseUrl: options.baseUrl,
49
+ persona: suite.persona.name,
50
+ headed: Boolean(options.headed),
51
+ mobile: Boolean(options.mobile),
52
+ ignoreHttpsErrors: Boolean(options.ignoreHttpsErrors),
53
+ llmProvider: llmRuntime.provider,
54
+ storageStateLoaded: Boolean(storageStatePath),
55
+ storageStateSource: storageStatePath ? summarizeSessionPath(storageStatePath) : null,
56
+ saveStorageStateRequested: Boolean(saveStorageStatePath),
57
+ saveStorageStateTarget: saveStorageStatePath ? summarizeSessionPath(saveStorageStatePath) : null,
58
+ model: llmRuntime.model,
59
+ startedAt,
60
+ maxRunDurationMs,
61
+ maxRunDurationSeconds: Math.round(maxRunDurationMs / 1000),
62
+ browserExecutionBudgetMs,
63
+ reportingReserveMs,
64
+ maxRunDurationClamped: maxRunDurationMs !== requestedMaxSessionDurationMs,
65
+ deviceTimezone: config.deviceTimezone,
66
+ synchronizedTimezone: config.deviceTimezone,
67
+ customTasks: suite.tasks.map((task) => task.goal),
68
+ accessIdentityName,
69
+ recordVideo: config.recordVideo,
70
+ ...(options.tradeOptions ? { tradeOptions: options.tradeOptions } : {}),
71
+ ...(options.extraInputs ?? {})
72
+ };
73
+ writeJson(inputsPath, baseInputs);
74
+ const execution = await runTaskSuite({
75
+ baseUrl: options.baseUrl,
76
+ suite,
77
+ runDir,
78
+ headed: Boolean(options.headed),
79
+ mobile: Boolean(options.mobile),
80
+ ignoreHttpsErrors: Boolean(options.ignoreHttpsErrors),
81
+ storageStatePath,
82
+ saveStorageStatePath,
83
+ maxSessionDurationMs: browserExecutionBudgetMs,
84
+ ...(options.tradeOptions ? { tradeOptions: options.tradeOptions } : {}),
85
+ provider: llmRuntime.provider,
86
+ model: llmRuntime.model,
87
+ ollamaBaseUrl: llmRuntime.ollamaBaseUrl
88
+ });
89
+ let clickReplayArtifact = null;
90
+ let clickReplayFrameCount = null;
91
+ let clickReplayDurationMs = null;
92
+ try {
93
+ const clickReplay = await generateClickReplay({
94
+ runDir,
95
+ taskResults: execution.taskResults
96
+ });
97
+ clickReplayArtifact = clickReplay?.artifactName ?? null;
98
+ clickReplayFrameCount = clickReplay?.frameCount ?? null;
99
+ clickReplayDurationMs = clickReplay?.durationMs ?? null;
100
+ }
101
+ catch {
102
+ // Replay generation is optional and should never block the main report.
103
+ }
104
+ writeJson(inputsPath, {
105
+ ...baseInputs,
106
+ ...(execution.siteBrief ? { siteBrief: execution.siteBrief } : {}),
107
+ browserTimezone: execution.browserTimezone,
108
+ synchronizedTimezone: execution.browserTimezone || execution.deviceTimezone,
109
+ ...(clickReplayArtifact ? { clickReplayArtifact } : {}),
110
+ ...(clickReplayFrameCount !== null ? { clickReplayFrameCount } : {}),
111
+ ...(clickReplayDurationMs !== null ? { clickReplayDurationMs } : {})
112
+ });
113
+ const remainingEvaluationBudgetMs = Math.max(0, maxRunDurationMs - (Date.now() - startedAtMs));
114
+ const report = await evaluateRun({
115
+ baseUrl: options.baseUrl,
116
+ suite,
117
+ siteBrief: execution.siteBrief,
118
+ taskResults: execution.taskResults,
119
+ rawEvents: execution.rawEvents,
120
+ accessibility: execution.accessibility,
121
+ mobile: Boolean(options.mobile),
122
+ timeoutMs: remainingEvaluationBudgetMs,
123
+ totalRunDurationMs: maxRunDurationMs,
124
+ llm: {
125
+ provider: llmRuntime.provider,
126
+ model: llmRuntime.model,
127
+ ollamaBaseUrl: llmRuntime.ollamaBaseUrl
128
+ }
129
+ });
130
+ writeJson(path.join(runDir, "report.json"), report);
131
+ writeText(path.join(runDir, "report.html"), renderHtmlReport({
132
+ website: options.baseUrl,
133
+ persona: suite.persona.name,
134
+ acceptedTasks: suite.tasks.map((task) => task.goal),
135
+ instructionText,
136
+ report,
137
+ taskResults: execution.taskResults,
138
+ accessibility: execution.accessibility,
139
+ siteChecks: execution.siteChecks,
140
+ siteBrief: execution.siteBrief,
141
+ rawEvents: execution.rawEvents,
142
+ runId: path.basename(runDir),
143
+ startedAt,
144
+ mobile: Boolean(options.mobile),
145
+ timeZone: execution.browserTimezone || execution.deviceTimezone,
146
+ clickReplayArtifact
147
+ }));
148
+ writeText(path.join(runDir, "report.md"), renderMarkdownReport({
149
+ website: options.baseUrl,
150
+ persona: suite.persona.name,
151
+ acceptedTasks: suite.tasks.map((task) => task.goal),
152
+ instructionText,
153
+ report,
154
+ taskResults: execution.taskResults,
155
+ accessibility: execution.accessibility,
156
+ siteChecks: execution.siteChecks,
157
+ siteBrief: execution.siteBrief,
158
+ rawEvents: execution.rawEvents,
159
+ startedAt,
160
+ mobile: Boolean(options.mobile),
161
+ timeZone: execution.browserTimezone || execution.deviceTimezone
162
+ }));
163
+ return {
164
+ startedAt,
165
+ runDir,
166
+ report,
167
+ execution
168
+ };
169
+ });
170
+ }