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,361 @@
1
+ import { z } from "zod";
2
+ function normalizeToTenPointScore(value) {
3
+ const scaled = value > 10 ? value / 10 : value;
4
+ return Math.min(10, Math.max(1, Math.round(scaled)));
5
+ }
6
+ const TenPointScoreSchema = z
7
+ .number()
8
+ .finite()
9
+ .min(0)
10
+ .max(100)
11
+ .transform((value) => normalizeToTenPointScore(value))
12
+ .pipe(z.number().int().min(1).max(10));
13
+ export const ActionTypeSchema = z.enum([
14
+ "click",
15
+ "type",
16
+ "scroll",
17
+ "wait",
18
+ "back",
19
+ "extract",
20
+ "trade",
21
+ "pay",
22
+ "stop"
23
+ ]);
24
+ export const FrictionSchema = z.enum(["none", "low", "medium", "high"]);
25
+ export const PlannerDecisionSchema = z.object({
26
+ thought: z.string().min(1),
27
+ stepNumber: z.number().int().positive().nullable().default(null),
28
+ instructionQuote: z.string().default(""),
29
+ action: ActionTypeSchema,
30
+ target_id: z.string().default(""),
31
+ target: z.string().default(""),
32
+ text: z.string().default(""),
33
+ expectation: z.string().min(1),
34
+ friction: FrictionSchema
35
+ });
36
+ export const TaskSchema = z.object({
37
+ name: z.string().min(1),
38
+ goal: z.string().min(1),
39
+ success_condition: z.string().min(1),
40
+ failure_signals: z.array(z.string()).min(1),
41
+ gameplay: z
42
+ .object({
43
+ rounds: z.number().int().positive().optional(),
44
+ requireHowToPlay: z.boolean().optional()
45
+ })
46
+ .optional()
47
+ });
48
+ export const PersonaSchema = z.object({
49
+ name: z.string().min(1),
50
+ intent: z.string().min(1),
51
+ constraints: z.array(z.string()).min(1)
52
+ });
53
+ export const TaskSuiteSchema = z.object({
54
+ persona: PersonaSchema,
55
+ tasks: z.array(TaskSchema).min(1)
56
+ });
57
+ export const SiteBriefSchema = z.object({
58
+ sitePurpose: z.string(),
59
+ intendedUserActions: z.array(z.string()).default([]),
60
+ summary: z.string(),
61
+ evidence: z.array(z.string()).default([])
62
+ });
63
+ export const InteractiveElementSchema = z.object({
64
+ agentId: z.string(),
65
+ role: z.string(),
66
+ tag: z.string(),
67
+ type: z.string().optional(),
68
+ text: z.string(),
69
+ href: z.string().optional(),
70
+ disabled: z.boolean()
71
+ });
72
+ export const PageStateSchema = z.object({
73
+ title: z.string(),
74
+ url: z.string(),
75
+ visibleText: z.string(),
76
+ visibleLines: z.array(z.string()),
77
+ formFields: z.array(z.object({
78
+ agentId: z.string(),
79
+ label: z.string(),
80
+ placeholder: z.string(),
81
+ name: z.string(),
82
+ id: z.string(),
83
+ tag: z.string(),
84
+ inputType: z.string(),
85
+ autocomplete: z.string().default(""),
86
+ inputMode: z.string().default(""),
87
+ value: z.string(),
88
+ required: z.boolean(),
89
+ checked: z.boolean().optional(),
90
+ maxLength: z.number().int().positive().nullable().optional(),
91
+ options: z.array(z.string()).default([])
92
+ })),
93
+ interactive: z.array(InteractiveElementSchema),
94
+ numberedElements: z.array(z.string()).default([]),
95
+ headings: z.array(z.string()),
96
+ formsPresent: z.boolean(),
97
+ modalHints: z.array(z.string())
98
+ });
99
+ export const ClickIndicatorSchema = z.object({
100
+ x: z.number().finite(),
101
+ y: z.number().finite(),
102
+ width: z.number().finite().positive(),
103
+ height: z.number().finite().positive(),
104
+ targetLabel: z.string().min(1)
105
+ });
106
+ export const InteractionResultSchema = z.object({
107
+ success: z.boolean(),
108
+ stop: z.boolean().optional(),
109
+ note: z.string(),
110
+ matchedBy: z.string().optional(),
111
+ elapsedMs: z.number().int().nonnegative().optional(),
112
+ destinationUrl: z.string().optional(),
113
+ destinationTitle: z.string().optional(),
114
+ stateChanged: z.boolean().optional(),
115
+ visibleTextSnippet: z.string().optional(),
116
+ clickIndicator: ClickIndicatorSchema.optional(),
117
+ beforeScreenshotPath: z.string().optional(),
118
+ afterScreenshotPath: z.string().optional()
119
+ });
120
+ export const TaskHistoryEntrySchema = z.object({
121
+ time: z.string(),
122
+ task: z.string(),
123
+ step: z.number().int().positive(),
124
+ url: z.string(),
125
+ title: z.string(),
126
+ decision: PlannerDecisionSchema,
127
+ result: InteractionResultSchema
128
+ });
129
+ export const TaskRunResultSchema = z.object({
130
+ name: z.string(),
131
+ status: z.enum(["success", "partial_success", "failed"]),
132
+ finalUrl: z.string(),
133
+ finalTitle: z.string(),
134
+ history: z.array(TaskHistoryEntrySchema),
135
+ reason: z.string()
136
+ });
137
+ export const AccessibilityViolationSchema = z.object({
138
+ id: z.string(),
139
+ impact: z.string().nullable().optional(),
140
+ description: z.string(),
141
+ help: z.string(),
142
+ nodes: z.number().int().nonnegative()
143
+ });
144
+ export const AccessibilityResultSchema = z.object({
145
+ violations: z.array(AccessibilityViolationSchema),
146
+ error: z.string().optional()
147
+ });
148
+ export const CoverageStateSchema = z.enum(["verified", "inferred", "blocked"]);
149
+ export const CoverageNoteSchema = z.object({
150
+ status: CoverageStateSchema,
151
+ summary: z.string(),
152
+ evidence: z.array(z.string()).default([]),
153
+ blockers: z.array(z.string()).default([])
154
+ });
155
+ export const LinkCheckSchema = z.object({
156
+ url: z.string(),
157
+ ok: z.boolean(),
158
+ statusCode: z.number().int().positive().nullable(),
159
+ note: z.string()
160
+ });
161
+ export const SeoCrawlSummarySchema = z.object({
162
+ totalPagesAudited: z.number().int().nonnegative(),
163
+ crawlDepthReached: z.number().int().nonnegative(),
164
+ pagesSkipped: z.number().int().nonnegative(),
165
+ skipReasons: z.array(z.string()).default([])
166
+ });
167
+ export const SeoPageStatsSchema = z.object({
168
+ pagesMissingTitle: z.number().int().nonnegative(),
169
+ pagesBadTitleLength: z.number().int().nonnegative(),
170
+ pagesMissingMetaDescription: z.number().int().nonnegative(),
171
+ pagesBadMetaDescriptionLength: z.number().int().nonnegative(),
172
+ pagesMissingCanonical: z.number().int().nonnegative(),
173
+ pagesNonSelfCanonical: z.number().int().nonnegative(),
174
+ noindexPages: z.number().int().nonnegative(),
175
+ nofollowPages: z.number().int().nonnegative(),
176
+ pagesMissingViewport: z.number().int().nonnegative(),
177
+ pagesMissingCharset: z.number().int().nonnegative(),
178
+ pagesWithStructuredData: z.number().int().nonnegative(),
179
+ pagesMissingOpenGraphBasics: z.number().int().nonnegative(),
180
+ pagesMissingTwitterCard: z.number().int().nonnegative(),
181
+ pagesWithUrlIssues: z.number().int().nonnegative(),
182
+ pagesMissingH1: z.number().int().nonnegative(),
183
+ pagesWithMultipleH1: z.number().int().nonnegative(),
184
+ pagesWithHeadingOrderIssues: z.number().int().nonnegative(),
185
+ pagesLowWordCount: z.number().int().nonnegative(),
186
+ pagesThinOrPlaceholder: z.number().int().nonnegative(),
187
+ pagesWithGenericAnchors: z.number().int().nonnegative(),
188
+ imagesMissingAlt: z.number().int().nonnegative(),
189
+ imagesWithNonDescriptiveFilenames: z.number().int().nonnegative(),
190
+ pagesWithRenderBlockingHeadScripts: z.number().int().nonnegative(),
191
+ pagesWithNonLazyImages: z.number().int().nonnegative(),
192
+ pagesWithResourceHints: z.number().int().nonnegative(),
193
+ pagesMissingLang: z.number().int().nonnegative(),
194
+ pagesWithUnlabeledInputs: z.number().int().nonnegative(),
195
+ pagesWithUnlabeledInteractive: z.number().int().nonnegative(),
196
+ pagesMissingSkipNav: z.number().int().nonnegative()
197
+ });
198
+ export const SeoAuditedPageSchema = z.object({
199
+ url: z.string(),
200
+ depth: z.number().int().nonnegative(),
201
+ statusCode: z.number().int().positive().nullable(),
202
+ issueCount: z.number().int().nonnegative(),
203
+ wordCount: z.number().int().nonnegative(),
204
+ titleLength: z.number().int().nonnegative(),
205
+ metaDescriptionLength: z.number().int().nonnegative(),
206
+ h1Count: z.number().int().nonnegative(),
207
+ notableIssues: z.array(z.string()).default([])
208
+ });
209
+ export const HeaderCheckSchema = z.object({
210
+ name: z.string(),
211
+ present: z.boolean(),
212
+ value: z.string().nullable(),
213
+ note: z.string()
214
+ });
215
+ export const PageProbePerformanceSchema = z.object({
216
+ domContentLoadedMs: z.number().finite().nullable(),
217
+ loadMs: z.number().finite().nullable(),
218
+ firstContentfulPaintMs: z.number().finite().nullable(),
219
+ largestContentfulPaintMs: z.number().finite().nullable(),
220
+ cumulativeLayoutShift: z.number().finite().nullable()
221
+ });
222
+ export const PageProbeSchema = z.object({
223
+ viewport: z.enum(["desktop", "mobile"]),
224
+ finalUrl: z.string(),
225
+ title: z.string(),
226
+ loadOk: z.boolean(),
227
+ note: z.string(),
228
+ statusCode: z.number().int().positive().nullable(),
229
+ metaDescription: z.string().nullable(),
230
+ canonical: z.string().nullable(),
231
+ h1Count: z.number().int().nonnegative(),
232
+ h2Count: z.number().int().nonnegative(),
233
+ structuredDataCount: z.number().int().nonnegative(),
234
+ visibleLinkCount: z.number().int().nonnegative(),
235
+ internalLinkSamples: z.array(z.string()).default([]),
236
+ ctaSamples: z.array(z.string()).default([]),
237
+ formCount: z.number().int().nonnegative(),
238
+ submitControlCount: z.number().int().nonnegative(),
239
+ trustSignalCount: z.number().int().nonnegative(),
240
+ trustSignalSamples: z.array(z.string()).default([]),
241
+ wordCount: z.number().int().nonnegative(),
242
+ readabilityScore: z.number().finite().nullable(),
243
+ readabilityLabel: z.string(),
244
+ longParagraphCount: z.number().int().nonnegative(),
245
+ mediaCount: z.number().int().nonnegative(),
246
+ horizontalOverflow: z.boolean(),
247
+ tapTargetIssueCount: z.number().int().nonnegative(),
248
+ smallTextIssueCount: z.number().int().nonnegative(),
249
+ navigationLinkCount: z.number().int().nonnegative(),
250
+ heroText: z.string().nullable(),
251
+ performance: PageProbePerformanceSchema,
252
+ evidence: z.array(z.string()).default([])
253
+ });
254
+ export const SiteChecksSchema = z.object({
255
+ generatedAt: z.string(),
256
+ baseUrl: z.string(),
257
+ finalResolvedUrl: z.string().nullable(),
258
+ coverage: z.object({
259
+ performance: CoverageNoteSchema,
260
+ seo: CoverageNoteSchema,
261
+ uiux: CoverageNoteSchema,
262
+ security: CoverageNoteSchema,
263
+ technicalHealth: CoverageNoteSchema,
264
+ mobileOptimization: CoverageNoteSchema,
265
+ contentQuality: CoverageNoteSchema,
266
+ cro: CoverageNoteSchema
267
+ }),
268
+ performance: z.object({
269
+ desktop: PageProbeSchema.nullable(),
270
+ mobile: PageProbeSchema.nullable(),
271
+ failedRequestCount: z.number().int().nonnegative(),
272
+ imageFailureCount: z.number().int().nonnegative(),
273
+ apiFailureCount: z.number().int().nonnegative(),
274
+ navigationErrorCount: z.number().int().nonnegative(),
275
+ stalledInteractionCount: z.number().int().nonnegative(),
276
+ evidence: z.array(z.string()).default([])
277
+ }),
278
+ seo: z.object({
279
+ robotsTxt: LinkCheckSchema,
280
+ sitemap: LinkCheckSchema,
281
+ brokenLinkCount: z.number().int().nonnegative(),
282
+ checkedLinkCount: z.number().int().nonnegative(),
283
+ brokenLinks: z.array(LinkCheckSchema).default([]),
284
+ crawlSummary: SeoCrawlSummarySchema.optional(),
285
+ pageStats: SeoPageStatsSchema.optional(),
286
+ auditedPages: z.array(SeoAuditedPageSchema).optional(),
287
+ evidence: z.array(z.string()).default([])
288
+ }),
289
+ security: z.object({
290
+ https: z.boolean(),
291
+ secureTransportVerified: z.boolean(),
292
+ initialStatusCode: z.number().int().positive().nullable(),
293
+ securityHeaders: z.array(HeaderCheckSchema).default([]),
294
+ missingHeaders: z.array(z.string()).default([]),
295
+ evidence: z.array(z.string()).default([])
296
+ }),
297
+ technicalHealth: z.object({
298
+ framework: z.string().nullable(),
299
+ consoleErrorCount: z.number().int().nonnegative(),
300
+ consoleWarningCount: z.number().int().nonnegative(),
301
+ pageErrorCount: z.number().int().nonnegative(),
302
+ apiFailureCount: z.number().int().nonnegative(),
303
+ evidence: z.array(z.string()).default([])
304
+ }),
305
+ mobileOptimization: z.object({
306
+ desktop: PageProbeSchema.nullable(),
307
+ mobile: PageProbeSchema.nullable(),
308
+ responsiveVerdict: z.enum(["responsive", "mixed", "poor", "blocked"]),
309
+ evidence: z.array(z.string()).default([])
310
+ }),
311
+ contentQuality: z.object({
312
+ readabilityScore: z.number().finite().nullable(),
313
+ readabilityLabel: z.string(),
314
+ wordCount: z.number().int().nonnegative(),
315
+ longParagraphCount: z.number().int().nonnegative(),
316
+ mediaCount: z.number().int().nonnegative(),
317
+ evidence: z.array(z.string()).default([])
318
+ }),
319
+ cro: z.object({
320
+ ctaCount: z.number().int().nonnegative(),
321
+ primaryCtas: z.array(z.string()).default([]),
322
+ formCount: z.number().int().nonnegative(),
323
+ submitControlCount: z.number().int().nonnegative(),
324
+ trustSignalCount: z.number().int().nonnegative(),
325
+ evidence: z.array(z.string()).default([])
326
+ })
327
+ });
328
+ export const GameplaySummarySchema = z.object({
329
+ roundsRequested: z.number().int().positive(),
330
+ roundsRecorded: z.number().int().nonnegative(),
331
+ wins: z.number().int().nonnegative(),
332
+ losses: z.number().int().nonnegative(),
333
+ draws: z.number().int().nonnegative(),
334
+ inconclusiveRounds: z.number().int().nonnegative(),
335
+ howToPlayConfirmed: z.boolean(),
336
+ replayConfirmed: z.boolean(),
337
+ summary: z.string(),
338
+ evidence: z.array(z.string()).default([])
339
+ });
340
+ export const FinalReportSchema = z.object({
341
+ overall_score: TenPointScoreSchema,
342
+ summary: z.string(),
343
+ scores: z.object({
344
+ clarity: TenPointScoreSchema,
345
+ navigation: TenPointScoreSchema,
346
+ trust: TenPointScoreSchema,
347
+ friction: TenPointScoreSchema,
348
+ conversion_readiness: TenPointScoreSchema,
349
+ accessibility_basics: TenPointScoreSchema
350
+ }),
351
+ strengths: z.array(z.string()),
352
+ weaknesses: z.array(z.string()),
353
+ task_results: z.array(z.object({
354
+ name: z.string(),
355
+ status: z.enum(["success", "partial_success", "failed"]),
356
+ reason: z.string(),
357
+ evidence: z.array(z.string())
358
+ })),
359
+ top_fixes: z.array(z.string()),
360
+ gameplay_summary: GameplaySummarySchema.optional()
361
+ });
@@ -0,0 +1,196 @@
1
+ export const LEGACY_CUSTOM_TASK_INPUT_COUNT = 5;
2
+ export const MAX_ACCEPTED_TASK_COUNT = 12;
3
+ export const MAX_CUSTOM_TASK_LENGTH = 420;
4
+ export const MAX_INSTRUCTION_SOURCE_LENGTH = 16000;
5
+ export const SUBMISSION_TASKS_REQUIRED_MESSAGE = "Paste instructions into the task box or upload a text or JSON instruction file before starting a run.";
6
+ function normalizeTaskText(value) {
7
+ const collapsed = value.replace(/\s+/g, " ").trim();
8
+ if (collapsed.length <= MAX_CUSTOM_TASK_LENGTH) {
9
+ return collapsed;
10
+ }
11
+ return `${collapsed.slice(0, MAX_CUSTOM_TASK_LENGTH - 3).trimEnd()}...`;
12
+ }
13
+ function normalizeInstructionSourceText(value) {
14
+ const normalized = value
15
+ .replace(/\r\n/g, "\n")
16
+ .replace(/\r/g, "\n")
17
+ .split("\n")
18
+ .map((line) => line.trim())
19
+ .join("\n")
20
+ .replace(/\n{3,}/g, "\n\n")
21
+ .trim();
22
+ if (normalized.length <= MAX_INSTRUCTION_SOURCE_LENGTH) {
23
+ return normalized;
24
+ }
25
+ return normalized.slice(0, MAX_INSTRUCTION_SOURCE_LENGTH).trimEnd();
26
+ }
27
+ function stripBulletPrefix(value) {
28
+ return value.replace(/^\s*(?:[-*•]+|\d+[.)]|task\s+\d+[:.)])\s*/i, "").trim();
29
+ }
30
+ function normalizeTaskCandidate(value) {
31
+ return normalizeTaskText(stripBulletPrefix(value.replace(/^[;,\s]+|[;,\s]+$/g, "")));
32
+ }
33
+ function looksLikeNairaCryptoExchangeSpec(value) {
34
+ return (/\bbuy\s+flow\b/i.test(value) &&
35
+ /\bsell\s+flow\b/i.test(value) &&
36
+ /\bnaira\b/i.test(value) &&
37
+ /\bcrypto\b/i.test(value) &&
38
+ /\blogging|monitoring|events?\b/i.test(value));
39
+ }
40
+ function buildNairaCryptoExchangeTasks() {
41
+ return [
42
+ [
43
+ "Click Buy;",
44
+ "enter a harmless Naira amount; confirm the crypto preview updates; select a token; select a network; provide a harmless test wallet address; click Next; verify a Naira payment account card is shown; copy the account number if a copy control is available; stop before making any real payment."
45
+ ].join(" "),
46
+ [
47
+ "Click Sell;",
48
+ "enter a harmless crypto amount; confirm the Naira payout preview updates; click Next; provide a harmless test bank account number when requested; verify the business crypto wallet address is shown; copy the crypto address if a copy control is available; stop before sending any real crypto."
49
+ ].join(" "),
50
+ [
51
+ "Check exchange-flow monitoring evidence:",
52
+ "while testing Buy and Sell, look for visible console logs, analytics/debug messages, or emitted events for amount entry, wallet address submission, bank account submission, account or wallet details displayed, clipboard actions, transfer trigger attempts, crypto payout initiation, and Naira payout initiation. Report which monitoring events were observed or missing."
53
+ ].join(" ")
54
+ ];
55
+ }
56
+ function isFileLike(value) {
57
+ return Boolean(value) && typeof value !== "string" && typeof value.text === "function";
58
+ }
59
+ function getStringValue(form, name) {
60
+ const value = form.get(name);
61
+ return typeof value === "string" ? value : "";
62
+ }
63
+ function extractTaskStringsFromJsonValue(value) {
64
+ if (typeof value === "string") {
65
+ return [value];
66
+ }
67
+ if (Array.isArray(value)) {
68
+ return value.flatMap((item) => extractTaskStringsFromJsonValue(item));
69
+ }
70
+ if (!value || typeof value !== "object") {
71
+ return [];
72
+ }
73
+ const record = value;
74
+ for (const key of ["goal", "task", "instruction", "prompt", "step"]) {
75
+ if (typeof record[key] === "string") {
76
+ return [record[key]];
77
+ }
78
+ }
79
+ for (const key of ["tasks", "instructions", "steps", "items"]) {
80
+ if (record[key] !== undefined) {
81
+ return extractTaskStringsFromJsonValue(record[key]);
82
+ }
83
+ }
84
+ return [];
85
+ }
86
+ function parseJsonInstructionSource(raw) {
87
+ const trimmed = raw.trim();
88
+ if (!trimmed.startsWith("[") && !trimmed.startsWith("{")) {
89
+ return [];
90
+ }
91
+ try {
92
+ return extractTaskStringsFromJsonValue(JSON.parse(trimmed));
93
+ }
94
+ catch {
95
+ return [];
96
+ }
97
+ }
98
+ function splitInstructionText(raw) {
99
+ const normalized = normalizeInstructionSourceText(raw);
100
+ if (!normalized) {
101
+ return [];
102
+ }
103
+ if (looksLikeNairaCryptoExchangeSpec(normalized)) {
104
+ return buildNairaCryptoExchangeTasks();
105
+ }
106
+ const jsonTasks = parseJsonInstructionSource(normalized);
107
+ if (jsonTasks.length > 0) {
108
+ return jsonTasks;
109
+ }
110
+ const numberedInlineSegments = normalized
111
+ .split(/(?=(?:^|\s)(?:\d+[.)]|[-*•])\s+)/g)
112
+ .map((segment) => segment.trim())
113
+ .filter(Boolean);
114
+ if (numberedInlineSegments.length > 1) {
115
+ return numberedInlineSegments;
116
+ }
117
+ const lines = normalized
118
+ .split(/\n+/)
119
+ .map((line) => line.trim())
120
+ .filter(Boolean);
121
+ if (lines.length > 1) {
122
+ return lines.flatMap((line) => {
123
+ const stripped = stripBulletPrefix(line);
124
+ if (!stripped) {
125
+ return [];
126
+ }
127
+ const semicolonParts = stripped
128
+ .split(/\s*;\s+/)
129
+ .map((part) => part.trim())
130
+ .filter((part) => part.length >= 8);
131
+ return semicolonParts.length > 1 ? semicolonParts : [stripped];
132
+ });
133
+ }
134
+ const semicolonParts = normalized
135
+ .split(/\s*;\s+/)
136
+ .map((part) => part.trim())
137
+ .filter((part) => part.length >= 8);
138
+ if (semicolonParts.length > 1) {
139
+ return semicolonParts;
140
+ }
141
+ return [normalized];
142
+ }
143
+ export function normalizeCustomTasks(values) {
144
+ const tasks = [];
145
+ const seen = new Set();
146
+ for (const value of values) {
147
+ const normalized = normalizeTaskCandidate(value ?? "");
148
+ if (!normalized) {
149
+ continue;
150
+ }
151
+ const dedupeKey = normalized.toLocaleLowerCase();
152
+ if (seen.has(dedupeKey)) {
153
+ continue;
154
+ }
155
+ seen.add(dedupeKey);
156
+ tasks.push(normalized);
157
+ if (tasks.length >= MAX_ACCEPTED_TASK_COUNT) {
158
+ break;
159
+ }
160
+ }
161
+ return tasks;
162
+ }
163
+ export function readLegacyTaskInputs(form) {
164
+ return Array.from({ length: LEGACY_CUSTOM_TASK_INPUT_COUNT }, (_, index) => getStringValue(form, `task-${index + 1}`));
165
+ }
166
+ export async function readSubmittedInstructionSource(form) {
167
+ const manualInstructionText = normalizeInstructionSourceText(getStringValue(form, "instructions"));
168
+ const legacyInstructionText = normalizeInstructionSourceText(readLegacyTaskInputs(form).filter(Boolean).join("\n"));
169
+ const uploadedFile = form.get("instructions_file");
170
+ const uploadedInstructionText = isFileLike(uploadedFile)
171
+ ? normalizeInstructionSourceText(await uploadedFile.text())
172
+ : "";
173
+ const instructionFileName = isFileLike(uploadedFile) && uploadedFile.name ? uploadedFile.name : null;
174
+ const combinedInstructionText = normalizeInstructionSourceText([manualInstructionText, uploadedInstructionText, legacyInstructionText].filter(Boolean).join("\n\n"));
175
+ const tasksFromText = normalizeCustomTasks([
176
+ ...extractTasksFromInstructionSource(manualInstructionText),
177
+ ...extractTasksFromInstructionSource(uploadedInstructionText),
178
+ ...extractTasksFromInstructionSource(legacyInstructionText)
179
+ ]);
180
+ return {
181
+ customTasks: tasksFromText,
182
+ instructionText: combinedInstructionText,
183
+ instructionFileName,
184
+ uploadedInstructionText
185
+ };
186
+ }
187
+ export function parseInstructionText(raw) {
188
+ return splitInstructionText(raw);
189
+ }
190
+ function extractTasksFromInstructionSource(raw) {
191
+ if (!raw.trim()) {
192
+ return [];
193
+ }
194
+ const jsonTasks = parseJsonInstructionSource(raw);
195
+ return jsonTasks.length > 0 ? jsonTasks : parseInstructionText(raw);
196
+ }