outcome-cli 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 +261 -0
- package/package.json +95 -0
- package/src/agents/README.md +139 -0
- package/src/agents/adapters/anthropic.adapter.ts +166 -0
- package/src/agents/adapters/dalle.adapter.ts +145 -0
- package/src/agents/adapters/gemini.adapter.ts +134 -0
- package/src/agents/adapters/imagen.adapter.ts +106 -0
- package/src/agents/adapters/nano-banana.adapter.ts +129 -0
- package/src/agents/adapters/openai.adapter.ts +165 -0
- package/src/agents/adapters/veo.adapter.ts +130 -0
- package/src/agents/agent.schema.property.test.ts +379 -0
- package/src/agents/agent.schema.test.ts +148 -0
- package/src/agents/agent.schema.ts +263 -0
- package/src/agents/index.ts +60 -0
- package/src/agents/registered-agent.schema.ts +356 -0
- package/src/agents/registry.ts +97 -0
- package/src/agents/tournament-configs.property.test.ts +266 -0
- package/src/cli/README.md +145 -0
- package/src/cli/commands/define.ts +79 -0
- package/src/cli/commands/list.ts +46 -0
- package/src/cli/commands/logs.ts +83 -0
- package/src/cli/commands/run.ts +416 -0
- package/src/cli/commands/verify.ts +110 -0
- package/src/cli/index.ts +81 -0
- package/src/config/README.md +128 -0
- package/src/config/env.ts +262 -0
- package/src/config/index.ts +19 -0
- package/src/eval/README.md +318 -0
- package/src/eval/ai-judge.test.ts +435 -0
- package/src/eval/ai-judge.ts +368 -0
- package/src/eval/code-validators.ts +414 -0
- package/src/eval/evaluateOutcome.property.test.ts +1174 -0
- package/src/eval/evaluateOutcome.ts +591 -0
- package/src/eval/immigration-validators.ts +122 -0
- package/src/eval/index.ts +90 -0
- package/src/eval/judge-cache.ts +402 -0
- package/src/eval/tournament-validators.property.test.ts +439 -0
- package/src/eval/validators.property.test.ts +1118 -0
- package/src/eval/validators.ts +1199 -0
- package/src/eval/weighted-scorer.ts +285 -0
- package/src/index.ts +17 -0
- package/src/league/README.md +188 -0
- package/src/league/health-check.ts +353 -0
- package/src/league/index.ts +93 -0
- package/src/league/killAgent.ts +151 -0
- package/src/league/league.test.ts +1151 -0
- package/src/league/runLeague.ts +843 -0
- package/src/league/scoreAgent.ts +175 -0
- package/src/modules/omnibridge/__tests__/.gitkeep +1 -0
- package/src/modules/omnibridge/__tests__/auth-tunnel.property.test.ts +524 -0
- package/src/modules/omnibridge/__tests__/deterministic-logger.property.test.ts +965 -0
- package/src/modules/omnibridge/__tests__/ghost-api.property.test.ts +461 -0
- package/src/modules/omnibridge/__tests__/omnibridge-integration.test.ts +542 -0
- package/src/modules/omnibridge/__tests__/parallel-executor.property.test.ts +671 -0
- package/src/modules/omnibridge/__tests__/semantic-normalizer.property.test.ts +521 -0
- package/src/modules/omnibridge/__tests__/semantic-normalizer.test.ts +254 -0
- package/src/modules/omnibridge/__tests__/session-vault.property.test.ts +367 -0
- package/src/modules/omnibridge/__tests__/shadow-session.property.test.ts +523 -0
- package/src/modules/omnibridge/__tests__/triangulation-engine.property.test.ts +292 -0
- package/src/modules/omnibridge/__tests__/verification-engine.property.test.ts +769 -0
- package/src/modules/omnibridge/api/.gitkeep +1 -0
- package/src/modules/omnibridge/api/ghost-api.ts +1087 -0
- package/src/modules/omnibridge/auth/.gitkeep +1 -0
- package/src/modules/omnibridge/auth/auth-tunnel.ts +843 -0
- package/src/modules/omnibridge/auth/session-vault.ts +577 -0
- package/src/modules/omnibridge/core/.gitkeep +1 -0
- package/src/modules/omnibridge/core/semantic-normalizer.ts +702 -0
- package/src/modules/omnibridge/core/triangulation-engine.ts +530 -0
- package/src/modules/omnibridge/core/types.ts +610 -0
- package/src/modules/omnibridge/execution/.gitkeep +1 -0
- package/src/modules/omnibridge/execution/deterministic-logger.ts +629 -0
- package/src/modules/omnibridge/execution/parallel-executor.ts +542 -0
- package/src/modules/omnibridge/execution/shadow-session.ts +794 -0
- package/src/modules/omnibridge/index.ts +212 -0
- package/src/modules/omnibridge/omnibridge.ts +510 -0
- package/src/modules/omnibridge/verification/.gitkeep +1 -0
- package/src/modules/omnibridge/verification/verification-engine.ts +783 -0
- package/src/outcomes/README.md +75 -0
- package/src/outcomes/acquire-pilot-customer.ts +297 -0
- package/src/outcomes/code-delivery-outcomes.ts +89 -0
- package/src/outcomes/code-outcomes.ts +256 -0
- package/src/outcomes/code_review_battle.test.ts +135 -0
- package/src/outcomes/code_review_battle.ts +135 -0
- package/src/outcomes/cold_email_battle.ts +97 -0
- package/src/outcomes/content_creation_battle.ts +160 -0
- package/src/outcomes/f1_stem_opt_compliance.ts +61 -0
- package/src/outcomes/index.ts +107 -0
- package/src/outcomes/lead_gen_battle.test.ts +113 -0
- package/src/outcomes/lead_gen_battle.ts +99 -0
- package/src/outcomes/outcome.schema.property.test.ts +229 -0
- package/src/outcomes/outcome.schema.ts +187 -0
- package/src/outcomes/qualified_sales_interest.ts +118 -0
- package/src/outcomes/swarm_planner.property.test.ts +370 -0
- package/src/outcomes/swarm_planner.ts +96 -0
- package/src/outcomes/web_extraction.ts +234 -0
- package/src/runtime/README.md +220 -0
- package/src/runtime/agentRunner.test.ts +341 -0
- package/src/runtime/agentRunner.ts +746 -0
- package/src/runtime/claudeAdapter.ts +232 -0
- package/src/runtime/costTracker.ts +123 -0
- package/src/runtime/index.ts +34 -0
- package/src/runtime/modelAdapter.property.test.ts +305 -0
- package/src/runtime/modelAdapter.ts +144 -0
- package/src/runtime/openaiAdapter.ts +235 -0
- package/src/utils/README.md +122 -0
- package/src/utils/command-runner.ts +134 -0
- package/src/utils/cost-guard.ts +379 -0
- package/src/utils/errors.test.ts +290 -0
- package/src/utils/errors.ts +442 -0
- package/src/utils/index.ts +37 -0
- package/src/utils/logger.test.ts +361 -0
- package/src/utils/logger.ts +419 -0
- package/src/utils/output-parsers.ts +216 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Creation Battle Outcome
|
|
3
|
+
*
|
|
4
|
+
* Visual content generation competition where AI agents create
|
|
5
|
+
* images, graphics, and visual assets for marketing campaigns.
|
|
6
|
+
*
|
|
7
|
+
* @module outcomes/content_creation_battle
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Outcome } from './outcome.schema.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Payout amount for successfully completing a content creation battle.
|
|
14
|
+
*/
|
|
15
|
+
export const CONTENT_CREATION_PAYOUT = 50;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Maximum number of attempts an agent can make.
|
|
19
|
+
*/
|
|
20
|
+
export const CONTENT_CREATION_MAX_ATTEMPTS = 3;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Time limit for creating content (5 minutes).
|
|
24
|
+
*/
|
|
25
|
+
export const CONTENT_CREATION_TIME_LIMIT_MS = 300000;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Content creation brief for the battle
|
|
29
|
+
*/
|
|
30
|
+
export interface ContentBrief {
|
|
31
|
+
/** Type of content to create */
|
|
32
|
+
contentType: 'social_media' | 'hero_banner' | 'product_showcase' | 'brand_illustration';
|
|
33
|
+
/** Target audience description */
|
|
34
|
+
audience: string;
|
|
35
|
+
/** Brand or company information */
|
|
36
|
+
brand: {
|
|
37
|
+
name: string;
|
|
38
|
+
industry: string;
|
|
39
|
+
tone: 'professional' | 'casual' | 'playful' | 'luxury' | 'tech';
|
|
40
|
+
colors?: string[];
|
|
41
|
+
};
|
|
42
|
+
/** Specific requirements */
|
|
43
|
+
requirements: {
|
|
44
|
+
dimensions: string; // e.g., "1080x1080", "1920x1080"
|
|
45
|
+
style?: string; // e.g., "minimalist", "vibrant", "corporate"
|
|
46
|
+
mustInclude?: string[]; // Elements that must be present
|
|
47
|
+
mustAvoid?: string[]; // Elements to avoid
|
|
48
|
+
};
|
|
49
|
+
/** Campaign context */
|
|
50
|
+
campaign: {
|
|
51
|
+
objective: string; // e.g., "increase brand awareness", "drive sales"
|
|
52
|
+
message: string; // Key message to communicate
|
|
53
|
+
callToAction?: string; // CTA if applicable
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Content creation artifact produced by an agent
|
|
59
|
+
*/
|
|
60
|
+
export interface ContentCreationArtifact {
|
|
61
|
+
/** Generated images */
|
|
62
|
+
images: Array<{
|
|
63
|
+
imageBytes: string; // Base64 encoded
|
|
64
|
+
mimeType: string;
|
|
65
|
+
description: string;
|
|
66
|
+
}>;
|
|
67
|
+
/** Creative brief interpretation */
|
|
68
|
+
concept: {
|
|
69
|
+
title: string;
|
|
70
|
+
description: string;
|
|
71
|
+
rationale: string; // Why this approach works for the brief
|
|
72
|
+
};
|
|
73
|
+
/** Technical specifications */
|
|
74
|
+
specs: {
|
|
75
|
+
dimensions: string;
|
|
76
|
+
style: string;
|
|
77
|
+
colorPalette: string[];
|
|
78
|
+
};
|
|
79
|
+
/** Prompt used for generation */
|
|
80
|
+
prompt: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Default content brief for battles
|
|
85
|
+
*/
|
|
86
|
+
export const DEFAULT_CONTENT_BRIEF: ContentBrief = {
|
|
87
|
+
contentType: 'social_media',
|
|
88
|
+
audience: 'Tech-savvy entrepreneurs and startup founders',
|
|
89
|
+
brand: {
|
|
90
|
+
name: 'WAI Championship',
|
|
91
|
+
industry: 'AI/Technology',
|
|
92
|
+
tone: 'tech',
|
|
93
|
+
colors: ['#8B5CF6', '#10B981', '#3B82F6']
|
|
94
|
+
},
|
|
95
|
+
requirements: {
|
|
96
|
+
dimensions: '1080x1080',
|
|
97
|
+
style: 'modern tech aesthetic',
|
|
98
|
+
mustInclude: ['AI agents', 'competition theme'],
|
|
99
|
+
mustAvoid: ['cluttered design', 'outdated graphics']
|
|
100
|
+
},
|
|
101
|
+
campaign: {
|
|
102
|
+
objective: 'increase platform awareness',
|
|
103
|
+
message: 'AI agents compete for real bounties',
|
|
104
|
+
callToAction: 'Join the Championship'
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Content Creation Battle Outcome Definition
|
|
110
|
+
*
|
|
111
|
+
* Success criteria:
|
|
112
|
+
* 1. Images match specified dimensions
|
|
113
|
+
* 2. Content aligns with brand guidelines
|
|
114
|
+
* 3. Includes required elements
|
|
115
|
+
* 4. Avoids prohibited elements
|
|
116
|
+
* 5. Demonstrates creative interpretation of brief
|
|
117
|
+
*/
|
|
118
|
+
export const contentCreationBattle: Outcome = {
|
|
119
|
+
name: 'content_creation_battle',
|
|
120
|
+
description: 'Visual Content Creation Battle - Generate compelling marketing visuals that align with brand guidelines and campaign objectives.',
|
|
121
|
+
payoutAmount: CONTENT_CREATION_PAYOUT,
|
|
122
|
+
maxAttempts: CONTENT_CREATION_MAX_ATTEMPTS,
|
|
123
|
+
timeLimitMs: CONTENT_CREATION_TIME_LIMIT_MS,
|
|
124
|
+
successCriteria: [
|
|
125
|
+
{
|
|
126
|
+
name: 'correct_dimensions',
|
|
127
|
+
validator: 'validateImageDimensions',
|
|
128
|
+
params: { expectedDimensions: '1080x1080' }
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: 'brand_alignment',
|
|
132
|
+
validator: 'validateBrandAlignment',
|
|
133
|
+
params: { brandGuidelines: DEFAULT_CONTENT_BRIEF.brand }
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: 'required_elements',
|
|
137
|
+
validator: 'validateRequiredElements',
|
|
138
|
+
params: { requiredElements: DEFAULT_CONTENT_BRIEF.requirements.mustInclude }
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'creative_quality',
|
|
142
|
+
validator: 'validateCreativeQuality',
|
|
143
|
+
params: { minQualityScore: 7 }
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: 'technical_specs',
|
|
147
|
+
validator: 'validateTechnicalSpecs',
|
|
148
|
+
params: { imageFormat: 'png', minResolution: 1080 }
|
|
149
|
+
}
|
|
150
|
+
],
|
|
151
|
+
failureReasons: [
|
|
152
|
+
'Images do not match required dimensions',
|
|
153
|
+
'Content does not align with brand guidelines',
|
|
154
|
+
'Missing required elements from brief',
|
|
155
|
+
'Creative quality below acceptable threshold',
|
|
156
|
+
'Technical specifications not met'
|
|
157
|
+
]
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export default contentCreationBattle;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { Outcome } from './outcome.schema.js';
|
|
2
|
+
|
|
3
|
+
export const f1StemOPTCompliance: Outcome = {
|
|
4
|
+
name: 'f1_stem_opt_compliance',
|
|
5
|
+
description: 'Verify Form I-983 Training Plan meets all USCIS requirements for F1 STEM OPT extension',
|
|
6
|
+
payoutAmount: 199,
|
|
7
|
+
maxAttempts: 3,
|
|
8
|
+
timeLimitMs: 120000,
|
|
9
|
+
successCriteria: [
|
|
10
|
+
{
|
|
11
|
+
name: 'required_student_fields',
|
|
12
|
+
validator: 'validateI983RequiredFields',
|
|
13
|
+
params: {
|
|
14
|
+
requiredFields: ['student_name', 'student_email', 'sevis_id', 'school_name', 'degree_level', 'cip_code']
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'required_employer_fields',
|
|
19
|
+
validator: 'validateI983RequiredFields',
|
|
20
|
+
params: {
|
|
21
|
+
requiredFields: ['employer_name', 'employer_ein', 'employer_everify', 'employer_address']
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'required_training_fields',
|
|
26
|
+
validator: 'validateI983RequiredFields',
|
|
27
|
+
params: {
|
|
28
|
+
requiredFields: ['position_title', 'training_start_date', 'training_end_date', 'training_description']
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'training_start_date_range',
|
|
33
|
+
validator: 'validateOPTDateRange',
|
|
34
|
+
params: { field: 'training_start_date', minDaysFromNow: 0, maxDaysFromNow: 540 }
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'training_end_date_range',
|
|
38
|
+
validator: 'validateOPTDateRange',
|
|
39
|
+
params: { field: 'training_end_date', minDaysFromNow: 30, maxDaysFromNow: 540 }
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'everify_format',
|
|
43
|
+
validator: 'validateEVerifyFormat',
|
|
44
|
+
params: { field: 'employer_everify' }
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'training_description_length',
|
|
48
|
+
validator: 'validateTrainingDescriptionLength',
|
|
49
|
+
params: { field: 'training_description', minWords: 50 }
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
failureReasons: [
|
|
53
|
+
'Missing required student information',
|
|
54
|
+
'Missing required employer information',
|
|
55
|
+
'Missing required training plan details',
|
|
56
|
+
'OPT dates are outside valid range (must be within 540 days)',
|
|
57
|
+
'E-Verify number format is invalid (must be 8 digits)',
|
|
58
|
+
'Training description is too short (minimum 50 words)',
|
|
59
|
+
'Form fields are incomplete or incorrectly formatted'
|
|
60
|
+
]
|
|
61
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outcomes Module Exports
|
|
3
|
+
*
|
|
4
|
+
* Includes both sales outcomes and SWE-bench style code outcomes.
|
|
5
|
+
*
|
|
6
|
+
* @module outcomes
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Schema and validation
|
|
10
|
+
export {
|
|
11
|
+
type Outcome,
|
|
12
|
+
type SuccessCriterion,
|
|
13
|
+
type ValidationResult,
|
|
14
|
+
validateOutcome,
|
|
15
|
+
isOutcome,
|
|
16
|
+
} from './outcome.schema.js';
|
|
17
|
+
|
|
18
|
+
// Sales outcomes (original)
|
|
19
|
+
export {
|
|
20
|
+
qualifiedSalesInterest,
|
|
21
|
+
PAYOUT_AMOUNT,
|
|
22
|
+
MAX_ATTEMPTS,
|
|
23
|
+
TIME_LIMIT_MS,
|
|
24
|
+
BUYING_INTENT_KEYWORDS,
|
|
25
|
+
MIN_COMPANY_SIZE,
|
|
26
|
+
EXCLUDED_ROLES,
|
|
27
|
+
MIN_MESSAGE_WORDS,
|
|
28
|
+
} from './qualified_sales_interest.js';
|
|
29
|
+
|
|
30
|
+
// SWE-bench style code outcomes
|
|
31
|
+
export {
|
|
32
|
+
type CodeOutcome,
|
|
33
|
+
CODE_OUTCOMES,
|
|
34
|
+
getCodeOutcomesByLevel,
|
|
35
|
+
getCodeOutcomesByCategory,
|
|
36
|
+
getCodeOutcomesByLanguage,
|
|
37
|
+
} from './code-outcomes.js';
|
|
38
|
+
|
|
39
|
+
// Outcome-based code delivery (feature/refactor/test generation)
|
|
40
|
+
export {
|
|
41
|
+
featureImplementationOutcome,
|
|
42
|
+
refactorTaskOutcome,
|
|
43
|
+
testGenerationOutcome,
|
|
44
|
+
CODE_DELIVERY_OUTCOMES,
|
|
45
|
+
} from './code-delivery-outcomes.js';
|
|
46
|
+
|
|
47
|
+
// Snowdevil Hunter Squad outcome
|
|
48
|
+
export {
|
|
49
|
+
acquirePilotCustomerOutcome,
|
|
50
|
+
PILOT_PAYOUT_AMOUNT,
|
|
51
|
+
PILOT_MAX_ATTEMPTS,
|
|
52
|
+
PILOT_TIME_LIMIT_MS,
|
|
53
|
+
POSITIVE_REPLY_KEYWORDS,
|
|
54
|
+
PAIN_SIGNALS,
|
|
55
|
+
MIN_OUTREACH_LENGTH,
|
|
56
|
+
MAX_OUTREACH_LENGTH,
|
|
57
|
+
validateContactInfo,
|
|
58
|
+
validatePainSignal,
|
|
59
|
+
validateOutreachSent,
|
|
60
|
+
validatePositiveReply,
|
|
61
|
+
} from './acquire-pilot-customer.js';
|
|
62
|
+
|
|
63
|
+
// Tournament Seed Bounties - Code Review Battle
|
|
64
|
+
export {
|
|
65
|
+
codeReviewBattle,
|
|
66
|
+
CODE_REVIEW_PAYOUT,
|
|
67
|
+
CODE_REVIEW_MAX_ATTEMPTS,
|
|
68
|
+
CODE_REVIEW_TIME_LIMIT_MS,
|
|
69
|
+
type CodeReviewIssue,
|
|
70
|
+
type CodeReviewComment,
|
|
71
|
+
type RefactorSuggestion,
|
|
72
|
+
type CodeReviewArtifact,
|
|
73
|
+
} from './code_review_battle.js';
|
|
74
|
+
|
|
75
|
+
// Tournament Seed Bounties - Lead Gen Battle
|
|
76
|
+
export {
|
|
77
|
+
leadGenBattle,
|
|
78
|
+
LEAD_GEN_PAYOUT,
|
|
79
|
+
LEAD_GEN_MAX_ATTEMPTS,
|
|
80
|
+
LEAD_GEN_TIME_LIMIT_MS,
|
|
81
|
+
type LeadGenArtifact,
|
|
82
|
+
} from './lead_gen_battle.js';
|
|
83
|
+
|
|
84
|
+
// Web Extraction Outcomes (OmniBridge integration)
|
|
85
|
+
export {
|
|
86
|
+
type WebExtractionOutcome,
|
|
87
|
+
isWebExtractionOutcome,
|
|
88
|
+
createWebExtractionOutcome,
|
|
89
|
+
WEB_EXTRACTION_OUTCOMES,
|
|
90
|
+
} from './web_extraction.js';
|
|
91
|
+
|
|
92
|
+
// Swarm Planner Outcome (MVP Unified Engine)
|
|
93
|
+
export {
|
|
94
|
+
swarmPlannerOutcome,
|
|
95
|
+
PLANNER_MAX_ATTEMPTS,
|
|
96
|
+
PLANNER_TIME_LIMIT_MS,
|
|
97
|
+
PLANNER_COST_CEILING,
|
|
98
|
+
PLANNER_MIN_TASKS,
|
|
99
|
+
PLANNER_MAX_TASKS,
|
|
100
|
+
PLANNER_PAYOUT_AMOUNT,
|
|
101
|
+
REQUIRED_TASK_FIELDS,
|
|
102
|
+
type PlannerTask,
|
|
103
|
+
type PlannerOutput,
|
|
104
|
+
} from './swarm_planner.js';
|
|
105
|
+
|
|
106
|
+
// F1 STEM OPT Compliance Checker
|
|
107
|
+
export { f1StemOPTCompliance } from './f1_stem_opt_compliance.js';
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for Lead Gen Battle Outcome Configuration
|
|
3
|
+
*
|
|
4
|
+
* Validates that the lead gen battle outcome is properly configured
|
|
5
|
+
* with correct payout, attempts, time limits, and schema compliance.
|
|
6
|
+
*
|
|
7
|
+
* @module outcomes/lead_gen_battle.test
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, test, expect } from 'vitest';
|
|
11
|
+
import {
|
|
12
|
+
leadGenBattle,
|
|
13
|
+
LEAD_GEN_PAYOUT,
|
|
14
|
+
LEAD_GEN_MAX_ATTEMPTS,
|
|
15
|
+
LEAD_GEN_TIME_LIMIT_MS,
|
|
16
|
+
type LeadGenArtifact,
|
|
17
|
+
} from './lead_gen_battle.js';
|
|
18
|
+
import { validateOutcome } from './outcome.schema.js';
|
|
19
|
+
|
|
20
|
+
describe('Lead Gen Battle Outcome Configuration', () => {
|
|
21
|
+
test('has correct payout amount', () => {
|
|
22
|
+
expect(leadGenBattle.payoutAmount).toBe(1);
|
|
23
|
+
expect(leadGenBattle.payoutAmount).toBe(LEAD_GEN_PAYOUT);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('has correct maximum attempts', () => {
|
|
27
|
+
expect(leadGenBattle.maxAttempts).toBe(10);
|
|
28
|
+
expect(leadGenBattle.maxAttempts).toBe(LEAD_GEN_MAX_ATTEMPTS);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('has correct time limit', () => {
|
|
32
|
+
expect(leadGenBattle.timeLimitMs).toBe(60000); // 1 minute
|
|
33
|
+
expect(leadGenBattle.timeLimitMs).toBe(LEAD_GEN_TIME_LIMIT_MS);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('validates against OutcomeSchema', () => {
|
|
37
|
+
const result = validateOutcome(leadGenBattle);
|
|
38
|
+
expect(result.valid).toBe(true);
|
|
39
|
+
expect(result.errors).toHaveLength(0);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('has correct outcome name', () => {
|
|
43
|
+
expect(leadGenBattle.name).toBe('lead_gen_battle');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('has descriptive description', () => {
|
|
47
|
+
expect(leadGenBattle.description).toContain('Lead Gen Precision Battle');
|
|
48
|
+
expect(leadGenBattle.description).toContain('qualified leads');
|
|
49
|
+
expect(leadGenBattle.description).toContain('valid email');
|
|
50
|
+
expect(leadGenBattle.description).toContain('LinkedIn URL');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('has all required success criteria', () => {
|
|
54
|
+
expect(leadGenBattle.successCriteria).toHaveLength(4);
|
|
55
|
+
|
|
56
|
+
const criteriaNames = leadGenBattle.successCriteria.map(c => c.name);
|
|
57
|
+
expect(criteriaNames).toContain('valid_email');
|
|
58
|
+
expect(criteriaNames).toContain('company_size');
|
|
59
|
+
expect(criteriaNames).toContain('valid_role');
|
|
60
|
+
expect(criteriaNames).toContain('valid_linkedin');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('has correct validator configurations', () => {
|
|
64
|
+
const emailCriterion = leadGenBattle.successCriteria.find(c => c.name === 'valid_email');
|
|
65
|
+
expect(emailCriterion).toBeDefined();
|
|
66
|
+
expect(emailCriterion?.validator).toBe('validateEmail');
|
|
67
|
+
expect(emailCriterion?.params).toEqual({});
|
|
68
|
+
|
|
69
|
+
const companySizeCriterion = leadGenBattle.successCriteria.find(c => c.name === 'company_size');
|
|
70
|
+
expect(companySizeCriterion).toBeDefined();
|
|
71
|
+
expect(companySizeCriterion?.validator).toBe('validateCompanySize');
|
|
72
|
+
expect(companySizeCriterion?.params).toEqual({ minimum: 50 });
|
|
73
|
+
|
|
74
|
+
const roleCriterion = leadGenBattle.successCriteria.find(c => c.name === 'valid_role');
|
|
75
|
+
expect(roleCriterion).toBeDefined();
|
|
76
|
+
expect(roleCriterion?.validator).toBe('validateRole');
|
|
77
|
+
expect(roleCriterion?.params).toEqual({ excludedRoles: ['intern', 'student'] });
|
|
78
|
+
|
|
79
|
+
const linkedInCriterion = leadGenBattle.successCriteria.find(c => c.name === 'valid_linkedin');
|
|
80
|
+
expect(linkedInCriterion).toBeDefined();
|
|
81
|
+
expect(linkedInCriterion?.validator).toBe('validateLinkedIn');
|
|
82
|
+
expect(linkedInCriterion?.params).toEqual({});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('has appropriate failure reasons', () => {
|
|
86
|
+
expect(leadGenBattle.failureReasons).toHaveLength(4);
|
|
87
|
+
expect(leadGenBattle.failureReasons).toContain('Invalid email format');
|
|
88
|
+
expect(leadGenBattle.failureReasons).toContain('Company too small - must have at least 50 employees');
|
|
89
|
+
expect(leadGenBattle.failureReasons).toContain('Invalid role - interns and students are not qualified leads');
|
|
90
|
+
expect(leadGenBattle.failureReasons).toContain('Invalid LinkedIn URL - must start with https://www.linkedin.com/in/');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('LeadGenArtifact interface is properly typed', () => {
|
|
94
|
+
// This is a compile-time test - if it compiles, the interface is correct
|
|
95
|
+
const validArtifact: LeadGenArtifact = {
|
|
96
|
+
email: 'user@example.com',
|
|
97
|
+
companySize: 100,
|
|
98
|
+
role: 'Engineering Manager',
|
|
99
|
+
linkedIn: 'https://www.linkedin.com/in/user'
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
expect(typeof validArtifact.email).toBe('string');
|
|
103
|
+
expect(typeof validArtifact.companySize).toBe('number');
|
|
104
|
+
expect(typeof validArtifact.role).toBe('string');
|
|
105
|
+
expect(typeof validArtifact.linkedIn).toBe('string');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('constants are properly exported', () => {
|
|
109
|
+
expect(LEAD_GEN_PAYOUT).toBe(1);
|
|
110
|
+
expect(LEAD_GEN_MAX_ATTEMPTS).toBe(10);
|
|
111
|
+
expect(LEAD_GEN_TIME_LIMIT_MS).toBe(60000);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lead Gen Battle Outcome
|
|
3
|
+
*
|
|
4
|
+
* Tournament Seed bounty for the WAI Championship Q1 Tournament.
|
|
5
|
+
* Agents compete to generate qualified leads with precision validation
|
|
6
|
+
* including valid email, company size, role, and LinkedIn URL.
|
|
7
|
+
*
|
|
8
|
+
* @module outcomes/lead_gen_battle
|
|
9
|
+
* @see Requirements 2.1, 2.2, 2.3, 8.2
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Outcome } from './outcome.schema.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Payout amount for successfully completing a lead gen battle.
|
|
16
|
+
* Set to $1 as per Requirements 2.1.
|
|
17
|
+
*/
|
|
18
|
+
export const LEAD_GEN_PAYOUT = 1;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Maximum number of attempts an agent can make to achieve this outcome.
|
|
22
|
+
* Set to 10 as per Requirements 2.2.
|
|
23
|
+
*/
|
|
24
|
+
export const LEAD_GEN_MAX_ATTEMPTS = 10;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Time limit for achieving the outcome (1 minute in milliseconds).
|
|
28
|
+
* Set to 60000ms as per Requirements 2.3.
|
|
29
|
+
*/
|
|
30
|
+
export const LEAD_GEN_TIME_LIMIT_MS = 60000;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Represents a lead artifact produced by an agent.
|
|
34
|
+
* Contains all required fields for lead qualification validation.
|
|
35
|
+
*/
|
|
36
|
+
export interface LeadGenArtifact {
|
|
37
|
+
/** Email address of the lead (must match standard email regex) */
|
|
38
|
+
email: string;
|
|
39
|
+
/** Company size in number of employees (must be >= 50) */
|
|
40
|
+
companySize: number;
|
|
41
|
+
/** Role/title of the lead (must not be "intern" or "student") */
|
|
42
|
+
role: string;
|
|
43
|
+
/** LinkedIn profile URL (must start with "https://www.linkedin.com/in/") */
|
|
44
|
+
linkedIn: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Lead Gen Battle Outcome Definition
|
|
49
|
+
*
|
|
50
|
+
* Success requires meeting ALL 4 criteria:
|
|
51
|
+
* 1. Valid email format matching /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
52
|
+
* 2. Company size of at least 50 employees
|
|
53
|
+
* 3. Role is not "intern" or "student" (case-insensitive)
|
|
54
|
+
* 4. LinkedIn URL starts with "https://www.linkedin.com/in/"
|
|
55
|
+
*
|
|
56
|
+
* @see Requirements 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 8.2
|
|
57
|
+
*/
|
|
58
|
+
export const leadGenBattle: Outcome = {
|
|
59
|
+
name: 'lead_gen_battle',
|
|
60
|
+
description:
|
|
61
|
+
'Lead Gen Precision Battle - Generate qualified leads with valid email, company size >= 50, non-student/intern role, and valid LinkedIn URL.',
|
|
62
|
+
payoutAmount: LEAD_GEN_PAYOUT,
|
|
63
|
+
maxAttempts: LEAD_GEN_MAX_ATTEMPTS,
|
|
64
|
+
timeLimitMs: LEAD_GEN_TIME_LIMIT_MS,
|
|
65
|
+
successCriteria: [
|
|
66
|
+
{
|
|
67
|
+
// Requirement 2.4: Verify email has valid syntax
|
|
68
|
+
name: 'valid_email',
|
|
69
|
+
validator: 'validateEmail',
|
|
70
|
+
params: {},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
// Requirement 2.5: Verify company size is at least 50 employees
|
|
74
|
+
name: 'company_size',
|
|
75
|
+
validator: 'validateCompanySize',
|
|
76
|
+
params: { minimum: 50 },
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
// Requirement 2.6: Verify role is not "Student" or "Intern"
|
|
80
|
+
name: 'valid_role',
|
|
81
|
+
validator: 'validateRole',
|
|
82
|
+
params: { excludedRoles: ['intern', 'student'] },
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
// Requirement 2.7: Verify LinkedIn URL starts with correct prefix
|
|
86
|
+
name: 'valid_linkedin',
|
|
87
|
+
validator: 'validateLinkedIn',
|
|
88
|
+
params: {},
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
failureReasons: [
|
|
92
|
+
'Invalid email format',
|
|
93
|
+
'Company too small - must have at least 50 employees',
|
|
94
|
+
'Invalid role - interns and students are not qualified leads',
|
|
95
|
+
'Invalid LinkedIn URL - must start with https://www.linkedin.com/in/',
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export default leadGenBattle;
|