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,279 @@
|
|
|
1
|
+
import { normalizeTaskText } from "./taskHeuristics.js";
|
|
2
|
+
const ACTION_WORD_PATTERN = "(?:click|tap|press|open|select|choose|copy|fill(?:\\s+(?:out|up|in))?|enter|type|input|provide|submit|create|register|sign\\s*up|signup|join|scroll|swipe|wait|pause|hold|go back|back|stop|halt)";
|
|
3
|
+
function cleanDirectiveText(value) {
|
|
4
|
+
return normalizeTaskText(value
|
|
5
|
+
.replace(/^["'`]+|["'`]+$/g, "")
|
|
6
|
+
.replace(/^(?:task|step)\s+\d+[:.)-]?\s*/i, "")
|
|
7
|
+
.replace(/[.?!,]+$/g, ""));
|
|
8
|
+
}
|
|
9
|
+
function cleanDirectiveTarget(value) {
|
|
10
|
+
return cleanDirectiveText(value
|
|
11
|
+
.replace(/^(?:on|into|to)\s+/i, "")
|
|
12
|
+
.replace(/^(?:the|a|an|your|my|this|that)\s+/i, "")
|
|
13
|
+
.replace(/^(?:only|just)\s+/i, "")
|
|
14
|
+
.replace(/\s+(?:tab|tabs|button|buttons|link|links|option|options|page|pages|screen|screens|section|sections|menu|item|items|cta|card|cards)\b.*$/i, ""));
|
|
15
|
+
}
|
|
16
|
+
function splitDirectiveSegments(taskText) {
|
|
17
|
+
const normalized = normalizeTaskText(taskText)
|
|
18
|
+
.replace(new RegExp(`\\b(?:and then|then|after that|afterwards)\\b`, "gi"), "; ")
|
|
19
|
+
.replace(new RegExp(`\\bnext\\b(?=\\s*${ACTION_WORD_PATTERN}\\b)`, "gi"), "; ")
|
|
20
|
+
.replace(new RegExp(`\\band\\b(?=\\s*${ACTION_WORD_PATTERN}\\b)`, "gi"), "; ")
|
|
21
|
+
.replace(new RegExp(`,(?=\\s*${ACTION_WORD_PATTERN}\\b)`, "gi"), "; ");
|
|
22
|
+
return normalized
|
|
23
|
+
.split(/[;]+/)
|
|
24
|
+
.map((segment) => segment
|
|
25
|
+
.replace(/^(?:first|second|third|fourth|fifth)\b[:,]?\s*/i, "")
|
|
26
|
+
.replace(/^(?:task|step)\s+\d+[:.)-]?\s*/i, "")
|
|
27
|
+
.trim())
|
|
28
|
+
.filter(Boolean);
|
|
29
|
+
}
|
|
30
|
+
function extractClickTargets(remainder) {
|
|
31
|
+
const cleanedRemainder = cleanDirectiveText(remainder.replace(/^(?:on|into|to)\s+/i, ""));
|
|
32
|
+
if (!cleanedRemainder) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
const pluralControlMatch = cleanedRemainder.match(/(.+?)\s+(?:tabs|buttons|links|options|cards|sections|pages|screens)\b/i);
|
|
36
|
+
const listSource = pluralControlMatch?.[1] ?? cleanedRemainder;
|
|
37
|
+
if (pluralControlMatch && /,|\band\b/i.test(listSource)) {
|
|
38
|
+
const splitTargets = listSource
|
|
39
|
+
.split(/\s*,\s*|\s+\band\b\s+/i)
|
|
40
|
+
.map((part) => cleanDirectiveTarget(part))
|
|
41
|
+
.filter(Boolean);
|
|
42
|
+
if (splitTargets.length > 1) {
|
|
43
|
+
return splitTargets;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const singleTarget = cleanDirectiveTarget(cleanedRemainder);
|
|
47
|
+
return singleTarget ? [singleTarget] : [];
|
|
48
|
+
}
|
|
49
|
+
function isGenericVisibleFormInstruction(remainder) {
|
|
50
|
+
const normalized = cleanDirectiveText(remainder);
|
|
51
|
+
if (!normalized) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
return (/^(?:it|them|the form)(?:\s+out)?$/i.test(normalized) ||
|
|
55
|
+
/(?:all|every)\s+(?:the\s+)?(?:visible\s+|required\s+)?(?:details?|fields?|inputs?|boxes?|blanks?|information|info|questions?|form(?:\s+fields?)?)/i.test(normalized) ||
|
|
56
|
+
/visible\s+(?:details?|fields?|inputs?|information|form)/i.test(normalized));
|
|
57
|
+
}
|
|
58
|
+
function isCompleteVisibleFormInstruction(segment) {
|
|
59
|
+
const normalized = cleanDirectiveText(segment);
|
|
60
|
+
if (!/^complete\b/i.test(normalized)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const remainder = normalized.replace(/^complete\b\s*/i, "");
|
|
64
|
+
return (isGenericVisibleFormInstruction(remainder) ||
|
|
65
|
+
/^(?:the\s+)?(?:(?:sign\s*up|signup|registration|account(?:\s+creation)?)\s+)?form(?:\s+out)?$/i.test(remainder));
|
|
66
|
+
}
|
|
67
|
+
function extractCompactExchangeAmountDirective(raw) {
|
|
68
|
+
const match = raw.match(/^(buy|sell)(?:\s+me)?(?:\s+(?:crypto|cypto|coin|token))?(?:\s+worth)?\s+([0-9]+(?:\.[0-9]+)?)\s*([A-Za-z₦]+)?$/i);
|
|
69
|
+
if (!match?.[1] || !match[2]) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const action = match[1].toLowerCase();
|
|
73
|
+
const unit = (match[3] ?? "").toLowerCase();
|
|
74
|
+
const target = action === "buy" || /\b(?:ngn|naira|₦)\b/i.test(unit) ? "Naira amount" : "crypto amount";
|
|
75
|
+
return {
|
|
76
|
+
action: "type_field",
|
|
77
|
+
raw,
|
|
78
|
+
target,
|
|
79
|
+
value: match[2]
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function extractExplicitFieldValueDirective(raw) {
|
|
83
|
+
const intoMatch = raw.match(/^(?:fill(?:\s+(?:out|up|in))?|enter|type|input|provide)\s+["'“]?(.+?)["'”]?\s+(?:into|in)\s+(?:the\s+|your\s+)?(.+?)(?:\s+(?:field|box|input|textbox|text box|value|details?))?$/i);
|
|
84
|
+
if (intoMatch?.[1] && intoMatch[2]) {
|
|
85
|
+
const value = cleanDirectiveText(intoMatch[1]);
|
|
86
|
+
const target = cleanDirectiveTarget(intoMatch[2]);
|
|
87
|
+
if (value && target && !isGenericVisibleFormInstruction(target)) {
|
|
88
|
+
return {
|
|
89
|
+
action: "type_field",
|
|
90
|
+
raw,
|
|
91
|
+
target,
|
|
92
|
+
value
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const match = raw.match(/^(?:fill(?:\s+(?:out|up|in))?|enter|type|input|provide)\s+(?:the\s+|your\s+)?(.+?)\s+(?:with|as|to)\s+["'“]?(.+?)["'”]?$/i);
|
|
97
|
+
if (!match?.[1] || !match[2]) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const target = cleanDirectiveTarget(match[1]);
|
|
101
|
+
const value = cleanDirectiveText(match[2]);
|
|
102
|
+
if (isGenericVisibleFormInstruction(`${target} ${value}`) || isGenericVisibleFormInstruction(target)) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
if (!target || !value) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
action: "type_field",
|
|
110
|
+
raw,
|
|
111
|
+
target,
|
|
112
|
+
value
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function extractBareNumericEntryDirective(raw) {
|
|
116
|
+
const match = raw.match(/^(?:enter|type|input|provide|fill(?:\s+(?:out|up|in))?)\s+([0-9]+(?:\.[0-9]+)?)\s*([A-Za-z₦]+)?$/i);
|
|
117
|
+
if (!match?.[1]) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const unit = (match[2] ?? "").toLowerCase();
|
|
121
|
+
return {
|
|
122
|
+
action: "type_field",
|
|
123
|
+
raw,
|
|
124
|
+
target: /\b(?:crypto|usdt|btc|eth|usdc)\b/i.test(unit) ? "crypto amount" : "amount",
|
|
125
|
+
value: match[1]
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
export function parseTaskDirectives(taskText) {
|
|
129
|
+
const directives = [];
|
|
130
|
+
for (const segment of splitDirectiveSegments(taskText)) {
|
|
131
|
+
const raw = cleanDirectiveText(segment);
|
|
132
|
+
if (!raw) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (isCompleteVisibleFormInstruction(raw)) {
|
|
136
|
+
directives.push({
|
|
137
|
+
action: "fill_visible_form",
|
|
138
|
+
raw,
|
|
139
|
+
target: ""
|
|
140
|
+
});
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const compactExchangeAmountDirective = extractCompactExchangeAmountDirective(raw);
|
|
144
|
+
if (compactExchangeAmountDirective) {
|
|
145
|
+
directives.push(compactExchangeAmountDirective);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const explicitFieldValueDirective = extractExplicitFieldValueDirective(raw);
|
|
149
|
+
if (explicitFieldValueDirective) {
|
|
150
|
+
directives.push(explicitFieldValueDirective);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const bareNumericEntryDirective = extractBareNumericEntryDirective(raw);
|
|
154
|
+
if (bareNumericEntryDirective) {
|
|
155
|
+
directives.push(bareNumericEntryDirective);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const match = segment.match(/^(click|tap|press|open|select|choose|copy|fill(?:\s+(?:out|up|in))?|enter|type|input|provide|submit|create|register|sign\s*up|signup|join|scroll|swipe|wait|pause|hold|go back|back|stop|halt)\b\s*(.*)$/i);
|
|
159
|
+
if (!match) {
|
|
160
|
+
directives.push({
|
|
161
|
+
action: "unstructured",
|
|
162
|
+
raw,
|
|
163
|
+
target: raw
|
|
164
|
+
});
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const verb = (match[1] ?? "").toLowerCase();
|
|
168
|
+
const remainder = match[2] ?? "";
|
|
169
|
+
if (!verb) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (verb === "copy") {
|
|
173
|
+
directives.push({
|
|
174
|
+
action: "click",
|
|
175
|
+
raw,
|
|
176
|
+
target: "Copy"
|
|
177
|
+
});
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (["click", "tap", "press", "open", "select", "choose"].includes(verb)) {
|
|
181
|
+
for (const target of extractClickTargets(remainder)) {
|
|
182
|
+
directives.push({
|
|
183
|
+
action: "click",
|
|
184
|
+
raw,
|
|
185
|
+
target
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (["fill", "fill out", "fill up", "fill in", "enter", "type", "input", "provide"].includes(verb)) {
|
|
191
|
+
if (isGenericVisibleFormInstruction(remainder)) {
|
|
192
|
+
directives.push({
|
|
193
|
+
action: "fill_visible_form",
|
|
194
|
+
raw,
|
|
195
|
+
target: ""
|
|
196
|
+
});
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const target = cleanDirectiveTarget(remainder.replace(/\s+(?:field|box|input|value|details?)\b.*$/i, ""));
|
|
200
|
+
if (target) {
|
|
201
|
+
directives.push({
|
|
202
|
+
action: "type_field",
|
|
203
|
+
raw,
|
|
204
|
+
target
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (verb === "submit" ||
|
|
210
|
+
((verb === "create" || verb === "register" || verb === "sign up" || verb === "signup" || verb === "join") &&
|
|
211
|
+
/\b(?:account|profile|registration|sign[- ]?up|signup|membership)\b/i.test(remainder || raw))) {
|
|
212
|
+
directives.push({
|
|
213
|
+
action: "submit",
|
|
214
|
+
raw,
|
|
215
|
+
target: ""
|
|
216
|
+
});
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (verb === "scroll" || verb === "swipe") {
|
|
220
|
+
directives.push({
|
|
221
|
+
action: "scroll",
|
|
222
|
+
raw,
|
|
223
|
+
target: ""
|
|
224
|
+
});
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (["wait", "pause", "hold"].includes(verb)) {
|
|
228
|
+
directives.push({
|
|
229
|
+
action: "wait",
|
|
230
|
+
raw,
|
|
231
|
+
target: ""
|
|
232
|
+
});
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (verb === "go back" || verb === "back") {
|
|
236
|
+
directives.push({
|
|
237
|
+
action: "back",
|
|
238
|
+
raw,
|
|
239
|
+
target: ""
|
|
240
|
+
});
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (verb === "stop" || verb === "halt") {
|
|
244
|
+
directives.push({
|
|
245
|
+
action: "stop",
|
|
246
|
+
raw,
|
|
247
|
+
target: ""
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return directives;
|
|
252
|
+
}
|
|
253
|
+
export function describeTaskDirective(directive) {
|
|
254
|
+
switch (directive.action) {
|
|
255
|
+
case "click":
|
|
256
|
+
return `Click the visible control labeled '${directive.target}' before any later step.`;
|
|
257
|
+
case "type_field":
|
|
258
|
+
return `Fill the visible field matching '${directive.target}'.`;
|
|
259
|
+
case "fill_visible_form":
|
|
260
|
+
return "Fill the visible form fields in on-page order until no safe pending visible fields remain.";
|
|
261
|
+
case "submit":
|
|
262
|
+
return "Submit the form only after the earlier form-filling steps are complete.";
|
|
263
|
+
case "scroll":
|
|
264
|
+
return "Scroll once, then reassess the next visible step.";
|
|
265
|
+
case "wait":
|
|
266
|
+
return "Wait briefly before taking any later action.";
|
|
267
|
+
case "back":
|
|
268
|
+
return "Go back once before taking any later action.";
|
|
269
|
+
case "stop":
|
|
270
|
+
return "Stop execution immediately and perform no further actions.";
|
|
271
|
+
case "unstructured":
|
|
272
|
+
return `Follow the user's instruction: '${directive.target}'.`;
|
|
273
|
+
default:
|
|
274
|
+
return "";
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
export function buildTaskDirectiveSummary(taskText) {
|
|
278
|
+
return parseTaskDirectives(taskText).map((directive, index) => `${index + 1}. ${describeTaskDirective(directive)}`);
|
|
279
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
const TASK_STOP_WORDS = new Set([
|
|
2
|
+
"a",
|
|
3
|
+
"an",
|
|
4
|
+
"and",
|
|
5
|
+
"are",
|
|
6
|
+
"at",
|
|
7
|
+
"be",
|
|
8
|
+
"by",
|
|
9
|
+
"check",
|
|
10
|
+
"click",
|
|
11
|
+
"do",
|
|
12
|
+
"does",
|
|
13
|
+
"every",
|
|
14
|
+
"for",
|
|
15
|
+
"from",
|
|
16
|
+
"go",
|
|
17
|
+
"how",
|
|
18
|
+
"i",
|
|
19
|
+
"if",
|
|
20
|
+
"in",
|
|
21
|
+
"interact",
|
|
22
|
+
"into",
|
|
23
|
+
"is",
|
|
24
|
+
"it",
|
|
25
|
+
"me",
|
|
26
|
+
"of",
|
|
27
|
+
"on",
|
|
28
|
+
"or",
|
|
29
|
+
"please",
|
|
30
|
+
"read",
|
|
31
|
+
"report",
|
|
32
|
+
"step",
|
|
33
|
+
"stepwise",
|
|
34
|
+
"steps",
|
|
35
|
+
"supplied",
|
|
36
|
+
"that",
|
|
37
|
+
"the",
|
|
38
|
+
"them",
|
|
39
|
+
"through",
|
|
40
|
+
"to",
|
|
41
|
+
"was",
|
|
42
|
+
"were",
|
|
43
|
+
"what",
|
|
44
|
+
"when",
|
|
45
|
+
"whether",
|
|
46
|
+
"with",
|
|
47
|
+
"you",
|
|
48
|
+
"your"
|
|
49
|
+
]);
|
|
50
|
+
const INSTRUCTION_PATTERNS = [/\bhow to play\b/i, /\binstructions?\b/i, /\brules?\b/i, /\btutorial\b/i, /\bhow it works\b/i];
|
|
51
|
+
const BUTTON_COVERAGE_PATTERNS = [/\bevery button\b/i, /\ball buttons?\b/i, /\beach button\b/i, /\binteract with .*buttons?\b/i];
|
|
52
|
+
const ENGAGEMENT_PATTERNS = [/\bengage\b/i, /\binteract\b/i, /\binteraction\b/i, /\btry it\b/i, /\buse it\b/i];
|
|
53
|
+
const PLAY_ACTION_PATTERNS = [
|
|
54
|
+
/\bplay\b/i,
|
|
55
|
+
/\bplace bet\b/i,
|
|
56
|
+
/\bbet\b/i,
|
|
57
|
+
/\bstart\b/i,
|
|
58
|
+
/\bspin\b/i,
|
|
59
|
+
/\bdeal\b/i,
|
|
60
|
+
/\blaunch\b/i,
|
|
61
|
+
/\bcash ?out\b/i,
|
|
62
|
+
/\bretry\b/i,
|
|
63
|
+
/\brestart\b/i,
|
|
64
|
+
/\bplay again\b/i,
|
|
65
|
+
/\bnew game\b/i,
|
|
66
|
+
/\bgo\b/i
|
|
67
|
+
];
|
|
68
|
+
const GAMEPLAY_PATTERNS = [
|
|
69
|
+
/\bgame\b/i,
|
|
70
|
+
/\bplay\b/i,
|
|
71
|
+
/\bbet\b/i,
|
|
72
|
+
/\bround\b/i,
|
|
73
|
+
/\bscore\b/i,
|
|
74
|
+
/\bwin\b/i,
|
|
75
|
+
/\bwins\b/i,
|
|
76
|
+
/\bloss\b/i,
|
|
77
|
+
/\blost\b/i,
|
|
78
|
+
/\blose\b/i,
|
|
79
|
+
/\bdraw\b/i,
|
|
80
|
+
/\bcrash\b/i,
|
|
81
|
+
/\bmultiplier\b/i
|
|
82
|
+
];
|
|
83
|
+
const OUTCOME_PATTERNS = [
|
|
84
|
+
/\byou win\b/i,
|
|
85
|
+
/\bvictory\b/i,
|
|
86
|
+
/\bwon\b/i,
|
|
87
|
+
/\bwinner\b/i,
|
|
88
|
+
/\blevel complete\b/i,
|
|
89
|
+
/\byou lose\b/i,
|
|
90
|
+
/\blost\b/i,
|
|
91
|
+
/\bdefeat\b/i,
|
|
92
|
+
/\bgame over\b/i,
|
|
93
|
+
/\bbetter luck next time\b/i,
|
|
94
|
+
/\bdraw\b/i,
|
|
95
|
+
/\btie\b/i,
|
|
96
|
+
/\bstalemate\b/i
|
|
97
|
+
];
|
|
98
|
+
const BROAD_NAVIGATION_PATTERNS = [/\bgo through\b/i, /\bexplore\b/i, /\bbrowse\b/i, /\breview\b/i, /\bvisit\b/i, /\bwalk through\b/i];
|
|
99
|
+
function hasPattern(patterns, value) {
|
|
100
|
+
return patterns.some((pattern) => pattern.test(value));
|
|
101
|
+
}
|
|
102
|
+
export function normalizeTaskText(value) {
|
|
103
|
+
return value.replace(/\s+/g, " ").trim();
|
|
104
|
+
}
|
|
105
|
+
export function extractTaskKeywords(value) {
|
|
106
|
+
return [...new Set(normalizeTaskText(value)
|
|
107
|
+
.toLowerCase()
|
|
108
|
+
.split(/[^a-z0-9$%.+-]+/)
|
|
109
|
+
.map((part) => part.trim())
|
|
110
|
+
.filter((part) => part.length >= 3 && !TASK_STOP_WORDS.has(part)))];
|
|
111
|
+
}
|
|
112
|
+
function parseRequestedRounds(taskText) {
|
|
113
|
+
const explicitRoundCount = taskText.match(/\b(\d+)\s+(?:rounds?|games?|times?)\b/i);
|
|
114
|
+
if (explicitRoundCount) {
|
|
115
|
+
const rounds = Number(explicitRoundCount[1]);
|
|
116
|
+
return Number.isFinite(rounds) && rounds > 0 ? rounds : undefined;
|
|
117
|
+
}
|
|
118
|
+
if (/\bhow many times\b/i.test(taskText) || /\bwins?\b.*\bloss(?:es)?\b/i.test(taskText)) {
|
|
119
|
+
return 3;
|
|
120
|
+
}
|
|
121
|
+
if (hasPattern(GAMEPLAY_PATTERNS, taskText)) {
|
|
122
|
+
return 1;
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
export function classifyTaskText(taskText) {
|
|
127
|
+
const normalized = normalizeTaskText(taskText);
|
|
128
|
+
const instructionFocus = hasPattern(INSTRUCTION_PATTERNS, normalized);
|
|
129
|
+
const buttonCoverage = hasPattern(BUTTON_COVERAGE_PATTERNS, normalized);
|
|
130
|
+
const gameplay = hasPattern(GAMEPLAY_PATTERNS, normalized);
|
|
131
|
+
const engagement = gameplay || buttonCoverage || hasPattern(ENGAGEMENT_PATTERNS, normalized);
|
|
132
|
+
const outcomeReporting = /\bhow many times\b/i.test(normalized) || /\bwins?\b.*\bloss(?:es)?\b/i.test(normalized);
|
|
133
|
+
const requestedRounds = gameplay ? parseRequestedRounds(normalized) : undefined;
|
|
134
|
+
return {
|
|
135
|
+
broadNavigation: hasPattern(BROAD_NAVIGATION_PATTERNS, normalized),
|
|
136
|
+
buttonCoverage,
|
|
137
|
+
engagement,
|
|
138
|
+
gameplay,
|
|
139
|
+
instructionFocus,
|
|
140
|
+
outcomeReporting,
|
|
141
|
+
...(requestedRounds ? { requestedRounds } : {}),
|
|
142
|
+
requireHowToPlay: instructionFocus
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
export function inferGameplayConfigFromTask(taskText) {
|
|
146
|
+
const profile = classifyTaskText(taskText);
|
|
147
|
+
if (!profile.gameplay) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
const gameplay = {
|
|
151
|
+
...(profile.requestedRounds ? { rounds: profile.requestedRounds } : {}),
|
|
152
|
+
...(profile.requireHowToPlay ? { requireHowToPlay: true } : {})
|
|
153
|
+
};
|
|
154
|
+
return Object.keys(gameplay).length > 0 ? gameplay : {};
|
|
155
|
+
}
|
|
156
|
+
export function textHasInstructionCue(value) {
|
|
157
|
+
return hasPattern(INSTRUCTION_PATTERNS, value);
|
|
158
|
+
}
|
|
159
|
+
export function textHasOutcomeCue(value) {
|
|
160
|
+
return hasPattern(OUTCOME_PATTERNS, value);
|
|
161
|
+
}
|
|
162
|
+
export function textHasPlayActionCue(value) {
|
|
163
|
+
return hasPattern(PLAY_ACTION_PATTERNS, value);
|
|
164
|
+
}
|
|
165
|
+
export function hasTaskKeywordEvidence(taskText, values) {
|
|
166
|
+
const keywords = extractTaskKeywords(taskText);
|
|
167
|
+
if (keywords.length === 0) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
const blob = normalizeTaskText(values.join(" ")).toLowerCase();
|
|
171
|
+
return keywords.some((keyword) => blob.includes(keyword));
|
|
172
|
+
}
|
|
173
|
+
function countKeywordMatches(label, taskText) {
|
|
174
|
+
const keywords = extractTaskKeywords(taskText);
|
|
175
|
+
if (keywords.length === 0) {
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
const normalizedLabel = normalizeTaskText(label).toLowerCase();
|
|
179
|
+
return keywords.filter((keyword) => normalizedLabel.includes(keyword)).length;
|
|
180
|
+
}
|
|
181
|
+
function labelMatchesPatterns(label, patterns) {
|
|
182
|
+
return hasPattern(patterns, label);
|
|
183
|
+
}
|
|
184
|
+
function isGenericUtilityLabel(label) {
|
|
185
|
+
return /^(?:home|about|contact|privacy|terms|menu|close|cancel)$/i.test(label);
|
|
186
|
+
}
|
|
187
|
+
export function isRegressiveTaskControlLabel(label) {
|
|
188
|
+
return /^(?:update|edit|change|reset|clear)\s+(?:profile|details?|settings?)$/i.test(label) ||
|
|
189
|
+
/^(?:log ?out|sign ?out|back|previous)$/i.test(label);
|
|
190
|
+
}
|
|
191
|
+
export function scoreInteractiveForTask(args) {
|
|
192
|
+
const taskProfile = classifyTaskText(args.task.goal);
|
|
193
|
+
const label = normalizeTaskText(args.item.text || "");
|
|
194
|
+
if (!label || args.item.disabled) {
|
|
195
|
+
return Number.NEGATIVE_INFINITY;
|
|
196
|
+
}
|
|
197
|
+
const attemptedTargetIds = new Set(args.history
|
|
198
|
+
.map((entry) => normalizeTaskText(entry.decision.target_id || ""))
|
|
199
|
+
.filter(Boolean));
|
|
200
|
+
if (attemptedTargetIds.has(args.item.agentId)) {
|
|
201
|
+
return -1000;
|
|
202
|
+
}
|
|
203
|
+
const attemptedTargets = new Set(args.history
|
|
204
|
+
.map((entry) => normalizeTaskText(entry.decision.target).toLowerCase())
|
|
205
|
+
.filter(Boolean));
|
|
206
|
+
if (attemptedTargets.has(label.toLowerCase())) {
|
|
207
|
+
return -1000;
|
|
208
|
+
}
|
|
209
|
+
let score = 0;
|
|
210
|
+
const role = args.item.role.toLowerCase();
|
|
211
|
+
const tag = args.item.tag.toLowerCase();
|
|
212
|
+
if (role === "button" || tag === "button") {
|
|
213
|
+
score += 18;
|
|
214
|
+
}
|
|
215
|
+
else if (role === "tab" || role === "menuitem") {
|
|
216
|
+
score += 14;
|
|
217
|
+
}
|
|
218
|
+
else if (role === "link" || tag === "a") {
|
|
219
|
+
score += 10;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
score += 4;
|
|
223
|
+
}
|
|
224
|
+
if (taskProfile.buttonCoverage && (role === "button" || tag === "button")) {
|
|
225
|
+
score += 40;
|
|
226
|
+
}
|
|
227
|
+
if (taskProfile.engagement && (role === "button" || tag === "button")) {
|
|
228
|
+
score += 22;
|
|
229
|
+
}
|
|
230
|
+
if (taskProfile.instructionFocus && labelMatchesPatterns(label, INSTRUCTION_PATTERNS)) {
|
|
231
|
+
score += 120;
|
|
232
|
+
}
|
|
233
|
+
const isInstructionLabel = labelMatchesPatterns(label, INSTRUCTION_PATTERNS);
|
|
234
|
+
if ((taskProfile.gameplay || taskProfile.engagement) && labelMatchesPatterns(label, PLAY_ACTION_PATTERNS)) {
|
|
235
|
+
score += taskProfile.gameplay
|
|
236
|
+
? isInstructionLabel ? (taskProfile.instructionFocus ? 36 : 6) : 110
|
|
237
|
+
: 90;
|
|
238
|
+
}
|
|
239
|
+
if (taskProfile.gameplay && !taskProfile.instructionFocus && isInstructionLabel) {
|
|
240
|
+
score += 10;
|
|
241
|
+
}
|
|
242
|
+
if (taskProfile.gameplay && /\$\d+|^\d+(?:\.\d+)?x$/i.test(label)) {
|
|
243
|
+
score += 24;
|
|
244
|
+
}
|
|
245
|
+
if (taskProfile.outcomeReporting && /(?:history|results?|stats?|dashboard|recent)/i.test(label)) {
|
|
246
|
+
score += 50;
|
|
247
|
+
}
|
|
248
|
+
const keywordMatches = countKeywordMatches(label, args.task.goal);
|
|
249
|
+
score += keywordMatches * 30;
|
|
250
|
+
if (taskProfile.engagement && isRegressiveTaskControlLabel(label)) {
|
|
251
|
+
score -= 140;
|
|
252
|
+
}
|
|
253
|
+
if (isGenericUtilityLabel(label) && keywordMatches === 0) {
|
|
254
|
+
score -= 18;
|
|
255
|
+
}
|
|
256
|
+
if (/^https?:\/\//i.test(label)) {
|
|
257
|
+
score -= 24;
|
|
258
|
+
}
|
|
259
|
+
if (label.length <= 2 && !/\$\d+|^\d+(?:\.\d+)?x$/i.test(label)) {
|
|
260
|
+
score -= 8;
|
|
261
|
+
}
|
|
262
|
+
return score;
|
|
263
|
+
}
|