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.
Files changed (113) hide show
  1. package/README.md +261 -0
  2. package/package.json +95 -0
  3. package/src/agents/README.md +139 -0
  4. package/src/agents/adapters/anthropic.adapter.ts +166 -0
  5. package/src/agents/adapters/dalle.adapter.ts +145 -0
  6. package/src/agents/adapters/gemini.adapter.ts +134 -0
  7. package/src/agents/adapters/imagen.adapter.ts +106 -0
  8. package/src/agents/adapters/nano-banana.adapter.ts +129 -0
  9. package/src/agents/adapters/openai.adapter.ts +165 -0
  10. package/src/agents/adapters/veo.adapter.ts +130 -0
  11. package/src/agents/agent.schema.property.test.ts +379 -0
  12. package/src/agents/agent.schema.test.ts +148 -0
  13. package/src/agents/agent.schema.ts +263 -0
  14. package/src/agents/index.ts +60 -0
  15. package/src/agents/registered-agent.schema.ts +356 -0
  16. package/src/agents/registry.ts +97 -0
  17. package/src/agents/tournament-configs.property.test.ts +266 -0
  18. package/src/cli/README.md +145 -0
  19. package/src/cli/commands/define.ts +79 -0
  20. package/src/cli/commands/list.ts +46 -0
  21. package/src/cli/commands/logs.ts +83 -0
  22. package/src/cli/commands/run.ts +416 -0
  23. package/src/cli/commands/verify.ts +110 -0
  24. package/src/cli/index.ts +81 -0
  25. package/src/config/README.md +128 -0
  26. package/src/config/env.ts +262 -0
  27. package/src/config/index.ts +19 -0
  28. package/src/eval/README.md +318 -0
  29. package/src/eval/ai-judge.test.ts +435 -0
  30. package/src/eval/ai-judge.ts +368 -0
  31. package/src/eval/code-validators.ts +414 -0
  32. package/src/eval/evaluateOutcome.property.test.ts +1174 -0
  33. package/src/eval/evaluateOutcome.ts +591 -0
  34. package/src/eval/immigration-validators.ts +122 -0
  35. package/src/eval/index.ts +90 -0
  36. package/src/eval/judge-cache.ts +402 -0
  37. package/src/eval/tournament-validators.property.test.ts +439 -0
  38. package/src/eval/validators.property.test.ts +1118 -0
  39. package/src/eval/validators.ts +1199 -0
  40. package/src/eval/weighted-scorer.ts +285 -0
  41. package/src/index.ts +17 -0
  42. package/src/league/README.md +188 -0
  43. package/src/league/health-check.ts +353 -0
  44. package/src/league/index.ts +93 -0
  45. package/src/league/killAgent.ts +151 -0
  46. package/src/league/league.test.ts +1151 -0
  47. package/src/league/runLeague.ts +843 -0
  48. package/src/league/scoreAgent.ts +175 -0
  49. package/src/modules/omnibridge/__tests__/.gitkeep +1 -0
  50. package/src/modules/omnibridge/__tests__/auth-tunnel.property.test.ts +524 -0
  51. package/src/modules/omnibridge/__tests__/deterministic-logger.property.test.ts +965 -0
  52. package/src/modules/omnibridge/__tests__/ghost-api.property.test.ts +461 -0
  53. package/src/modules/omnibridge/__tests__/omnibridge-integration.test.ts +542 -0
  54. package/src/modules/omnibridge/__tests__/parallel-executor.property.test.ts +671 -0
  55. package/src/modules/omnibridge/__tests__/semantic-normalizer.property.test.ts +521 -0
  56. package/src/modules/omnibridge/__tests__/semantic-normalizer.test.ts +254 -0
  57. package/src/modules/omnibridge/__tests__/session-vault.property.test.ts +367 -0
  58. package/src/modules/omnibridge/__tests__/shadow-session.property.test.ts +523 -0
  59. package/src/modules/omnibridge/__tests__/triangulation-engine.property.test.ts +292 -0
  60. package/src/modules/omnibridge/__tests__/verification-engine.property.test.ts +769 -0
  61. package/src/modules/omnibridge/api/.gitkeep +1 -0
  62. package/src/modules/omnibridge/api/ghost-api.ts +1087 -0
  63. package/src/modules/omnibridge/auth/.gitkeep +1 -0
  64. package/src/modules/omnibridge/auth/auth-tunnel.ts +843 -0
  65. package/src/modules/omnibridge/auth/session-vault.ts +577 -0
  66. package/src/modules/omnibridge/core/.gitkeep +1 -0
  67. package/src/modules/omnibridge/core/semantic-normalizer.ts +702 -0
  68. package/src/modules/omnibridge/core/triangulation-engine.ts +530 -0
  69. package/src/modules/omnibridge/core/types.ts +610 -0
  70. package/src/modules/omnibridge/execution/.gitkeep +1 -0
  71. package/src/modules/omnibridge/execution/deterministic-logger.ts +629 -0
  72. package/src/modules/omnibridge/execution/parallel-executor.ts +542 -0
  73. package/src/modules/omnibridge/execution/shadow-session.ts +794 -0
  74. package/src/modules/omnibridge/index.ts +212 -0
  75. package/src/modules/omnibridge/omnibridge.ts +510 -0
  76. package/src/modules/omnibridge/verification/.gitkeep +1 -0
  77. package/src/modules/omnibridge/verification/verification-engine.ts +783 -0
  78. package/src/outcomes/README.md +75 -0
  79. package/src/outcomes/acquire-pilot-customer.ts +297 -0
  80. package/src/outcomes/code-delivery-outcomes.ts +89 -0
  81. package/src/outcomes/code-outcomes.ts +256 -0
  82. package/src/outcomes/code_review_battle.test.ts +135 -0
  83. package/src/outcomes/code_review_battle.ts +135 -0
  84. package/src/outcomes/cold_email_battle.ts +97 -0
  85. package/src/outcomes/content_creation_battle.ts +160 -0
  86. package/src/outcomes/f1_stem_opt_compliance.ts +61 -0
  87. package/src/outcomes/index.ts +107 -0
  88. package/src/outcomes/lead_gen_battle.test.ts +113 -0
  89. package/src/outcomes/lead_gen_battle.ts +99 -0
  90. package/src/outcomes/outcome.schema.property.test.ts +229 -0
  91. package/src/outcomes/outcome.schema.ts +187 -0
  92. package/src/outcomes/qualified_sales_interest.ts +118 -0
  93. package/src/outcomes/swarm_planner.property.test.ts +370 -0
  94. package/src/outcomes/swarm_planner.ts +96 -0
  95. package/src/outcomes/web_extraction.ts +234 -0
  96. package/src/runtime/README.md +220 -0
  97. package/src/runtime/agentRunner.test.ts +341 -0
  98. package/src/runtime/agentRunner.ts +746 -0
  99. package/src/runtime/claudeAdapter.ts +232 -0
  100. package/src/runtime/costTracker.ts +123 -0
  101. package/src/runtime/index.ts +34 -0
  102. package/src/runtime/modelAdapter.property.test.ts +305 -0
  103. package/src/runtime/modelAdapter.ts +144 -0
  104. package/src/runtime/openaiAdapter.ts +235 -0
  105. package/src/utils/README.md +122 -0
  106. package/src/utils/command-runner.ts +134 -0
  107. package/src/utils/cost-guard.ts +379 -0
  108. package/src/utils/errors.test.ts +290 -0
  109. package/src/utils/errors.ts +442 -0
  110. package/src/utils/index.ts +37 -0
  111. package/src/utils/logger.test.ts +361 -0
  112. package/src/utils/logger.ts +419 -0
  113. package/src/utils/output-parsers.ts +216 -0
@@ -0,0 +1,370 @@
1
+ /**
2
+ * Property-based tests for Planner Output Validity
3
+ *
4
+ * **Feature: mvp-unified-engine, Property 3: Planner Output Validity**
5
+ * **Validates: Requirements 3.2, 3.4, 3.5**
6
+ *
7
+ * Property 3: Planner Output Validity
8
+ * *For any* Planner agent output, the tasks array SHALL contain between 1 and 100 tasks with unique IDs.
9
+ */
10
+
11
+ import { describe, test, expect } from 'vitest';
12
+ import * as fc from 'fast-check';
13
+ import {
14
+ validatePlannerOutput,
15
+ validateUniqueTaskIds,
16
+ validateTaskSchema,
17
+ validatePlannerOutputComplete,
18
+ type PlannerOutputInput,
19
+ type PlannerTaskInput,
20
+ } from '../eval/validators.js';
21
+ import {
22
+ PLANNER_MIN_TASKS,
23
+ PLANNER_MAX_TASKS,
24
+ REQUIRED_TASK_FIELDS,
25
+ } from './swarm_planner.js';
26
+
27
+ /**
28
+ * Arbitrary for generating valid PlannerTask objects
29
+ */
30
+ const validTaskArb: fc.Arbitrary<PlannerTaskInput> = fc.record({
31
+ id: fc.uuid(),
32
+ description: fc.string({ minLength: 1, maxLength: 200 }),
33
+ input: fc.dictionary(fc.string({ minLength: 1, maxLength: 20 }), fc.jsonValue()),
34
+ expectedOutput: fc.option(fc.dictionary(fc.string({ minLength: 1, maxLength: 20 }), fc.jsonValue()), { nil: undefined }),
35
+ priority: fc.option(fc.integer({ min: 0, max: 100 }), { nil: undefined }),
36
+ });
37
+
38
+ /**
39
+ * Arbitrary for generating valid PlannerOutput objects with unique task IDs
40
+ */
41
+ const validPlannerOutputArb = (minTasks: number = 1, maxTasks: number = 100): fc.Arbitrary<PlannerOutputInput> =>
42
+ fc.integer({ min: minTasks, max: maxTasks }).chain((taskCount) =>
43
+ fc.tuple(
44
+ fc.array(fc.uuid(), { minLength: taskCount, maxLength: taskCount }).map((ids) => [...new Set(ids)]),
45
+ fc.array(fc.string({ minLength: 1, maxLength: 200 }), { minLength: taskCount, maxLength: taskCount }),
46
+ fc.array(fc.dictionary(fc.string({ minLength: 1, maxLength: 20 }), fc.jsonValue()), { minLength: taskCount, maxLength: taskCount }),
47
+ ).chain(([ids, descriptions, inputs]) => {
48
+ // Ensure we have enough unique IDs
49
+ const uniqueIds = [...new Set(ids)];
50
+ const actualCount = Math.min(uniqueIds.length, descriptions.length, inputs.length);
51
+ if (actualCount < minTasks) {
52
+ // Generate additional unique IDs if needed
53
+ while (uniqueIds.length < minTasks) {
54
+ uniqueIds.push(`task-${uniqueIds.length}-${Date.now()}`);
55
+ }
56
+ }
57
+ const tasks: PlannerTaskInput[] = uniqueIds.slice(0, Math.min(actualCount, maxTasks)).map((id, i) => ({
58
+ id,
59
+ description: descriptions[i] || `Task ${i}`,
60
+ input: inputs[i] || {},
61
+ }));
62
+ return fc.record({
63
+ tasks: fc.constant(tasks),
64
+ estimatedTimeMs: fc.integer({ min: 1000, max: 3600000 }),
65
+ reasoning: fc.string({ minLength: 1, maxLength: 500 }),
66
+ });
67
+ })
68
+ );
69
+
70
+ describe('Planner Output Validity - Property Tests', () => {
71
+ // **Feature: mvp-unified-engine, Property 3: Planner Output Validity**
72
+ // **Validates: Requirements 3.2, 3.5**
73
+ describe('validatePlannerOutput - Task Array Bounds', () => {
74
+ test('outputs with 1-100 tasks are valid', () => {
75
+ fc.assert(
76
+ fc.property(
77
+ validPlannerOutputArb(PLANNER_MIN_TASKS, PLANNER_MAX_TASKS),
78
+ (output) => {
79
+ const result = validatePlannerOutput(output, PLANNER_MIN_TASKS, PLANNER_MAX_TASKS);
80
+ expect(result.valid).toBe(true);
81
+ expect(result.errors).toHaveLength(0);
82
+ }
83
+ ),
84
+ { numRuns: 100 }
85
+ );
86
+ });
87
+
88
+ test('outputs with 0 tasks are invalid', () => {
89
+ const emptyOutput: PlannerOutputInput = {
90
+ tasks: [],
91
+ estimatedTimeMs: 1000,
92
+ reasoning: 'Empty output',
93
+ };
94
+ const result = validatePlannerOutput(emptyOutput, PLANNER_MIN_TASKS, PLANNER_MAX_TASKS);
95
+ expect(result.valid).toBe(false);
96
+ expect(result.errors.length).toBeGreaterThan(0);
97
+ expect(result.errors[0]).toContain('too small');
98
+ });
99
+
100
+ test('outputs with >100 tasks are invalid', () => {
101
+ fc.assert(
102
+ fc.property(
103
+ fc.integer({ min: 101, max: 200 }),
104
+ (taskCount) => {
105
+ const tasks: PlannerTaskInput[] = Array.from({ length: taskCount }, (_, i) => ({
106
+ id: `task-${i}`,
107
+ description: `Task ${i}`,
108
+ input: {},
109
+ }));
110
+ const output: PlannerOutputInput = {
111
+ tasks,
112
+ estimatedTimeMs: 1000,
113
+ reasoning: 'Too many tasks',
114
+ };
115
+ const result = validatePlannerOutput(output, PLANNER_MIN_TASKS, PLANNER_MAX_TASKS);
116
+ expect(result.valid).toBe(false);
117
+ expect(result.errors.length).toBeGreaterThan(0);
118
+ expect(result.errors[0]).toContain('too large');
119
+ }
120
+ ),
121
+ { numRuns: 100 }
122
+ );
123
+ });
124
+
125
+ test('boundary: exactly 1 task is valid', () => {
126
+ const output: PlannerOutputInput = {
127
+ tasks: [{ id: 'task-1', description: 'Single task', input: {} }],
128
+ estimatedTimeMs: 1000,
129
+ reasoning: 'Single task',
130
+ };
131
+ const result = validatePlannerOutput(output, PLANNER_MIN_TASKS, PLANNER_MAX_TASKS);
132
+ expect(result.valid).toBe(true);
133
+ expect(result.errors).toHaveLength(0);
134
+ });
135
+
136
+ test('boundary: exactly 100 tasks is valid', () => {
137
+ const tasks: PlannerTaskInput[] = Array.from({ length: 100 }, (_, i) => ({
138
+ id: `task-${i}`,
139
+ description: `Task ${i}`,
140
+ input: {},
141
+ }));
142
+ const output: PlannerOutputInput = {
143
+ tasks,
144
+ estimatedTimeMs: 1000,
145
+ reasoning: 'Max tasks',
146
+ };
147
+ const result = validatePlannerOutput(output, PLANNER_MIN_TASKS, PLANNER_MAX_TASKS);
148
+ expect(result.valid).toBe(true);
149
+ expect(result.errors).toHaveLength(0);
150
+ });
151
+ });
152
+
153
+ // **Feature: mvp-unified-engine, Property 3: Planner Output Validity**
154
+ // **Validates: Requirements 3.4**
155
+ describe('validateUniqueTaskIds - No Duplicate IDs', () => {
156
+ test('outputs with unique task IDs are valid', () => {
157
+ fc.assert(
158
+ fc.property(
159
+ validPlannerOutputArb(1, 50),
160
+ (output) => {
161
+ const result = validateUniqueTaskIds(output);
162
+ expect(result.valid).toBe(true);
163
+ expect(result.errors).toHaveLength(0);
164
+ }
165
+ ),
166
+ { numRuns: 100 }
167
+ );
168
+ });
169
+
170
+ test('outputs with duplicate task IDs are invalid', () => {
171
+ fc.assert(
172
+ fc.property(
173
+ fc.integer({ min: 2, max: 20 }),
174
+ fc.uuid(),
175
+ (taskCount, duplicateId) => {
176
+ const tasks: PlannerTaskInput[] = Array.from({ length: taskCount }, (_, i) => ({
177
+ id: i === 0 || i === 1 ? duplicateId : `task-${i}`,
178
+ description: `Task ${i}`,
179
+ input: {},
180
+ }));
181
+ const output: PlannerOutputInput = {
182
+ tasks,
183
+ estimatedTimeMs: 1000,
184
+ reasoning: 'Duplicate IDs',
185
+ };
186
+ const result = validateUniqueTaskIds(output);
187
+ expect(result.valid).toBe(false);
188
+ expect(result.errors.length).toBeGreaterThan(0);
189
+ expect(result.errors[0]).toContain('Duplicate task IDs');
190
+ }
191
+ ),
192
+ { numRuns: 100 }
193
+ );
194
+ });
195
+
196
+ test('single task always has unique ID', () => {
197
+ fc.assert(
198
+ fc.property(
199
+ fc.uuid(),
200
+ fc.string({ minLength: 1 }),
201
+ (id, description) => {
202
+ const output: PlannerOutputInput = {
203
+ tasks: [{ id, description, input: {} }],
204
+ estimatedTimeMs: 1000,
205
+ reasoning: 'Single task',
206
+ };
207
+ const result = validateUniqueTaskIds(output);
208
+ expect(result.valid).toBe(true);
209
+ expect(result.errors).toHaveLength(0);
210
+ }
211
+ ),
212
+ { numRuns: 100 }
213
+ );
214
+ });
215
+ });
216
+
217
+ // **Feature: mvp-unified-engine, Property 3: Planner Output Validity**
218
+ // **Validates: Requirements 3.3**
219
+ describe('validateTaskSchema - Required Fields', () => {
220
+ test('tasks with all required fields are valid', () => {
221
+ fc.assert(
222
+ fc.property(
223
+ validPlannerOutputArb(1, 20),
224
+ (output) => {
225
+ const result = validateTaskSchema(output, [...REQUIRED_TASK_FIELDS]);
226
+ expect(result.valid).toBe(true);
227
+ expect(result.errors).toHaveLength(0);
228
+ }
229
+ ),
230
+ { numRuns: 100 }
231
+ );
232
+ });
233
+
234
+ test('tasks missing id field are invalid', () => {
235
+ const output: PlannerOutputInput = {
236
+ tasks: [
237
+ { id: '', description: 'Task 1', input: {} } as PlannerTaskInput,
238
+ { description: 'Task 2', input: {} } as unknown as PlannerTaskInput,
239
+ ],
240
+ estimatedTimeMs: 1000,
241
+ reasoning: 'Missing id',
242
+ };
243
+ const result = validateTaskSchema(output, [...REQUIRED_TASK_FIELDS]);
244
+ expect(result.valid).toBe(false);
245
+ expect(result.errors.length).toBeGreaterThan(0);
246
+ });
247
+
248
+ test('tasks missing description field are invalid', () => {
249
+ const output: PlannerOutputInput = {
250
+ tasks: [
251
+ { id: 'task-1', input: {} } as unknown as PlannerTaskInput,
252
+ ],
253
+ estimatedTimeMs: 1000,
254
+ reasoning: 'Missing description',
255
+ };
256
+ const result = validateTaskSchema(output, [...REQUIRED_TASK_FIELDS]);
257
+ expect(result.valid).toBe(false);
258
+ expect(result.errors.length).toBeGreaterThan(0);
259
+ });
260
+
261
+ test('tasks missing input field are invalid', () => {
262
+ const output: PlannerOutputInput = {
263
+ tasks: [
264
+ { id: 'task-1', description: 'Task 1' } as unknown as PlannerTaskInput,
265
+ ],
266
+ estimatedTimeMs: 1000,
267
+ reasoning: 'Missing input',
268
+ };
269
+ const result = validateTaskSchema(output, [...REQUIRED_TASK_FIELDS]);
270
+ expect(result.valid).toBe(false);
271
+ expect(result.errors.length).toBeGreaterThan(0);
272
+ });
273
+
274
+ test('tasks with non-string id are invalid', () => {
275
+ const output = {
276
+ tasks: [
277
+ { id: 123, description: 'Task 1', input: {} },
278
+ ],
279
+ estimatedTimeMs: 1000,
280
+ reasoning: 'Non-string id',
281
+ } as unknown as PlannerOutputInput;
282
+ const result = validateTaskSchema(output, [...REQUIRED_TASK_FIELDS]);
283
+ expect(result.valid).toBe(false);
284
+ expect(result.errors.length).toBeGreaterThan(0);
285
+ });
286
+
287
+ test('tasks with non-object input are invalid', () => {
288
+ const output = {
289
+ tasks: [
290
+ { id: 'task-1', description: 'Task 1', input: 'not an object' },
291
+ ],
292
+ estimatedTimeMs: 1000,
293
+ reasoning: 'Non-object input',
294
+ } as unknown as PlannerOutputInput;
295
+ const result = validateTaskSchema(output, [...REQUIRED_TASK_FIELDS]);
296
+ expect(result.valid).toBe(false);
297
+ expect(result.errors.length).toBeGreaterThan(0);
298
+ });
299
+ });
300
+
301
+ // **Feature: mvp-unified-engine, Property 3: Planner Output Validity**
302
+ // **Validates: Requirements 3.2, 3.3, 3.4, 3.5**
303
+ describe('validatePlannerOutputComplete - Composite Validation', () => {
304
+ test('valid outputs pass all validations', () => {
305
+ fc.assert(
306
+ fc.property(
307
+ validPlannerOutputArb(1, 50),
308
+ (output) => {
309
+ const result = validatePlannerOutputComplete(output, PLANNER_MIN_TASKS, PLANNER_MAX_TASKS);
310
+ expect(result.valid).toBe(true);
311
+ expect(result.errors).toHaveLength(0);
312
+ }
313
+ ),
314
+ { numRuns: 100 }
315
+ );
316
+ });
317
+
318
+ test('validation is deterministic - same input produces same output', () => {
319
+ fc.assert(
320
+ fc.property(
321
+ validPlannerOutputArb(1, 20),
322
+ (output) => {
323
+ const result1 = validatePlannerOutputComplete(output, PLANNER_MIN_TASKS, PLANNER_MAX_TASKS);
324
+ const result2 = validatePlannerOutputComplete(output, PLANNER_MIN_TASKS, PLANNER_MAX_TASKS);
325
+ expect(result1.valid).toBe(result2.valid);
326
+ expect(result1.errors).toEqual(result2.errors);
327
+ }
328
+ ),
329
+ { numRuns: 100 }
330
+ );
331
+ });
332
+
333
+ test('validation result structure is correct', () => {
334
+ fc.assert(
335
+ fc.property(
336
+ validPlannerOutputArb(1, 20),
337
+ (output) => {
338
+ const result = validatePlannerOutputComplete(output, PLANNER_MIN_TASKS, PLANNER_MAX_TASKS);
339
+ expect(typeof result.valid).toBe('boolean');
340
+ expect(Array.isArray(result.errors)).toBe(true);
341
+ if (result.valid) {
342
+ expect(result.errors).toHaveLength(0);
343
+ }
344
+ }
345
+ ),
346
+ { numRuns: 100 }
347
+ );
348
+ });
349
+
350
+ test('null/undefined output is invalid', () => {
351
+ const result1 = validatePlannerOutputComplete(null as unknown as PlannerOutputInput);
352
+ expect(result1.valid).toBe(false);
353
+ expect(result1.errors.length).toBeGreaterThan(0);
354
+
355
+ const result2 = validatePlannerOutputComplete(undefined as unknown as PlannerOutputInput);
356
+ expect(result2.valid).toBe(false);
357
+ expect(result2.errors.length).toBeGreaterThan(0);
358
+ });
359
+
360
+ test('output without tasks array is invalid', () => {
361
+ const output = {
362
+ estimatedTimeMs: 1000,
363
+ reasoning: 'No tasks',
364
+ } as unknown as PlannerOutputInput;
365
+ const result = validatePlannerOutputComplete(output);
366
+ expect(result.valid).toBe(false);
367
+ expect(result.errors.length).toBeGreaterThan(0);
368
+ });
369
+ });
370
+ });
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Swarm Planner Outcome
3
+ *
4
+ * Defines success criteria for the Planner agent that decomposes
5
+ * high-level goals into atomic parallel tasks for Swarm execution.
6
+ *
7
+ * @module outcomes/swarm_planner
8
+ */
9
+
10
+ import type { Outcome } from './outcome.schema.js';
11
+
12
+ /**
13
+ * Represents a single task decomposed by the Planner agent.
14
+ */
15
+ export interface PlannerTask {
16
+ /** Unique task identifier */
17
+ id: string;
18
+ /** Human-readable description */
19
+ description: string;
20
+ /** Input data for the worker */
21
+ input: Record<string, unknown>;
22
+ /** Expected output schema (optional) */
23
+ expectedOutput?: Record<string, unknown>;
24
+ /** Priority (lower = higher priority) */
25
+ priority?: number;
26
+ }
27
+
28
+ /**
29
+ * Output produced by the Planner agent.
30
+ */
31
+ export interface PlannerOutput {
32
+ /** Array of decomposed tasks */
33
+ tasks: PlannerTask[];
34
+ /** Estimated total execution time in ms */
35
+ estimatedTimeMs: number;
36
+ /** Reasoning for decomposition */
37
+ reasoning: string;
38
+ }
39
+
40
+ /**
41
+ * Constraints for the Planner outcome.
42
+ */
43
+ export const PLANNER_MAX_ATTEMPTS = 1;
44
+ export const PLANNER_TIME_LIMIT_MS = 60000;
45
+ export const PLANNER_COST_CEILING = 1.0;
46
+ export const PLANNER_MIN_TASKS = 1;
47
+ export const PLANNER_MAX_TASKS = 100;
48
+ export const PLANNER_PAYOUT_AMOUNT = 5.0;
49
+
50
+ /**
51
+ * Required fields for each task in the Planner output.
52
+ */
53
+ export const REQUIRED_TASK_FIELDS = ['id', 'description', 'input'] as const;
54
+
55
+ /**
56
+ * Swarm Planner Outcome Definition
57
+ *
58
+ * Success criteria:
59
+ * 1. Valid task array (1-100 tasks)
60
+ * 2. No duplicate task IDs
61
+ * 3. Each task has required fields (id, description, input)
62
+ *
63
+ * @see Requirements 3.1, 3.2, 3.3, 3.4, 3.5
64
+ */
65
+ export const swarmPlannerOutcome: Outcome = {
66
+ name: 'swarm_planner',
67
+ description: 'Decompose high-level goal into atomic parallel tasks for Swarm execution',
68
+ payoutAmount: PLANNER_PAYOUT_AMOUNT,
69
+ maxAttempts: PLANNER_MAX_ATTEMPTS,
70
+ timeLimitMs: PLANNER_TIME_LIMIT_MS,
71
+ successCriteria: [
72
+ {
73
+ name: 'valid_task_array',
74
+ validator: 'validatePlannerOutput',
75
+ params: { minTasks: PLANNER_MIN_TASKS, maxTasks: PLANNER_MAX_TASKS },
76
+ },
77
+ {
78
+ name: 'no_duplicate_ids',
79
+ validator: 'validateUniqueTaskIds',
80
+ params: {},
81
+ },
82
+ {
83
+ name: 'valid_task_schema',
84
+ validator: 'validateTaskSchema',
85
+ params: { requiredFields: [...REQUIRED_TASK_FIELDS] },
86
+ },
87
+ ],
88
+ failureReasons: [
89
+ 'Task array is empty or exceeds maximum (1-100 tasks required)',
90
+ 'Duplicate task IDs found',
91
+ 'Task missing required fields (id, description, input)',
92
+ 'Invalid task structure',
93
+ ],
94
+ };
95
+
96
+ export default swarmPlannerOutcome;
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Web Extraction Outcome - Outcome type for OmniBridge web bounties
3
+ *
4
+ * Defines outcomes for web-based data extraction tasks that use
5
+ * OmniBridge's Ghost-API for transparent DOM interaction.
6
+ *
7
+ * @module outcomes/web_extraction
8
+ * @see Requirements 3.1-3.6, 7.1-7.6
9
+ */
10
+
11
+ import type { Outcome } from './outcome.schema.js';
12
+
13
+ /**
14
+ * Extended outcome type for web extraction bounties.
15
+ */
16
+ export interface WebExtractionOutcome extends Outcome {
17
+ /** Outcome type identifier */
18
+ type: 'web_extraction';
19
+ /** Target URL for extraction */
20
+ targetUrl: string;
21
+ /** Goal description in plain English */
22
+ goalDescription: string;
23
+ /** Expected output schema (optional, will be inferred) */
24
+ outputSchema?: Record<string, unknown>;
25
+ /** Required authentication domain */
26
+ authDomain?: string;
27
+ /** Allowed actions for capability token */
28
+ allowedActions?: string[];
29
+ /** Blocked actions for capability token */
30
+ blockedActions?: string[];
31
+ }
32
+
33
+ /**
34
+ * Type guard to check if an outcome is a web extraction outcome.
35
+ *
36
+ * @param outcome - Outcome to check
37
+ * @returns True if the outcome is a web extraction outcome
38
+ */
39
+ export function isWebExtractionOutcome(outcome: Outcome): outcome is WebExtractionOutcome {
40
+ return 'type' in outcome && (outcome as WebExtractionOutcome).type === 'web_extraction';
41
+ }
42
+
43
+ /**
44
+ * Creates a web extraction outcome.
45
+ *
46
+ * @param config - Configuration for the web extraction outcome
47
+ * @returns WebExtractionOutcome
48
+ *
49
+ * @example
50
+ * const outcome = createWebExtractionOutcome({
51
+ * name: 'fetch_invoices',
52
+ * description: 'Extract invoice data from portal',
53
+ * payoutAmount: 50,
54
+ * targetUrl: 'https://portal.example.com/invoices',
55
+ * goalDescription: 'Fetch all unpaid invoices from the last 30 days',
56
+ * });
57
+ */
58
+ export function createWebExtractionOutcome(config: {
59
+ name: string;
60
+ description: string;
61
+ payoutAmount: number;
62
+ maxAttempts?: number;
63
+ timeLimitMs?: number;
64
+ targetUrl: string;
65
+ goalDescription: string;
66
+ outputSchema?: Record<string, unknown>;
67
+ authDomain?: string;
68
+ allowedActions?: string[];
69
+ blockedActions?: string[];
70
+ }): WebExtractionOutcome {
71
+ return {
72
+ name: config.name,
73
+ description: config.description,
74
+ payoutAmount: config.payoutAmount,
75
+ maxAttempts: config.maxAttempts ?? 3,
76
+ timeLimitMs: config.timeLimitMs ?? 300000, // 5 minutes default
77
+ successCriteria: [
78
+ {
79
+ name: 'data_extracted',
80
+ validator: 'validateWebExtraction',
81
+ params: {
82
+ targetUrl: config.targetUrl,
83
+ goalDescription: config.goalDescription,
84
+ outputSchema: config.outputSchema,
85
+ },
86
+ },
87
+ ],
88
+ failureReasons: [
89
+ 'extraction_failed',
90
+ 'authentication_failed',
91
+ 'schema_drift',
92
+ 'timeout',
93
+ 'hallucination_detected',
94
+ ],
95
+ type: 'web_extraction',
96
+ targetUrl: config.targetUrl,
97
+ goalDescription: config.goalDescription,
98
+ outputSchema: config.outputSchema,
99
+ authDomain: config.authDomain,
100
+ allowedActions: config.allowedActions ?? ['read', 'navigate', 'extract'],
101
+ blockedActions: config.blockedActions ?? ['delete', 'modify', 'change_password'],
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Example web extraction outcomes for testing.
107
+ */
108
+ export const WEB_EXTRACTION_OUTCOMES = {
109
+ /**
110
+ * Invoice extraction from a billing portal.
111
+ */
112
+ fetchInvoices: createWebExtractionOutcome({
113
+ name: 'fetch_invoices',
114
+ description: 'Extract invoice data from billing portal',
115
+ payoutAmount: 50,
116
+ targetUrl: 'https://billing.example.com/invoices',
117
+ goalDescription: 'Fetch all unpaid invoices from the last 30 days with amounts and due dates',
118
+ outputSchema: {
119
+ type: 'object',
120
+ properties: {
121
+ invoices: {
122
+ type: 'array',
123
+ items: {
124
+ type: 'object',
125
+ properties: {
126
+ id: { type: 'string' },
127
+ amount: { type: 'number' },
128
+ dueDate: { type: 'string' },
129
+ status: { type: 'string' },
130
+ },
131
+ },
132
+ },
133
+ total: { type: 'number' },
134
+ },
135
+ },
136
+ authDomain: 'billing.example.com',
137
+ }),
138
+
139
+ /**
140
+ * Lead data extraction from CRM.
141
+ */
142
+ extractLeads: createWebExtractionOutcome({
143
+ name: 'extract_leads',
144
+ description: 'Extract lead information from CRM system',
145
+ payoutAmount: 100,
146
+ targetUrl: 'https://crm.example.com/leads',
147
+ goalDescription: 'Extract all leads with company name, contact info, and status',
148
+ outputSchema: {
149
+ type: 'object',
150
+ properties: {
151
+ leads: {
152
+ type: 'array',
153
+ items: {
154
+ type: 'object',
155
+ properties: {
156
+ company: { type: 'string' },
157
+ contact: { type: 'string' },
158
+ email: { type: 'string' },
159
+ status: { type: 'string' },
160
+ },
161
+ },
162
+ },
163
+ count: { type: 'number' },
164
+ },
165
+ },
166
+ authDomain: 'crm.example.com',
167
+ }),
168
+
169
+ /**
170
+ * Product pricing extraction from competitor site.
171
+ */
172
+ scrapeCompetitorPricing: createWebExtractionOutcome({
173
+ name: 'scrape_competitor_pricing',
174
+ description: 'Extract product pricing from competitor website',
175
+ payoutAmount: 75,
176
+ targetUrl: 'https://competitor.example.com/products',
177
+ goalDescription: 'Extract all product names, prices, and availability status',
178
+ outputSchema: {
179
+ type: 'object',
180
+ properties: {
181
+ products: {
182
+ type: 'array',
183
+ items: {
184
+ type: 'object',
185
+ properties: {
186
+ name: { type: 'string' },
187
+ price: { type: 'number' },
188
+ currency: { type: 'string' },
189
+ available: { type: 'boolean' },
190
+ },
191
+ },
192
+ },
193
+ lastUpdated: { type: 'string' },
194
+ },
195
+ },
196
+ // No auth required for public site
197
+ }),
198
+
199
+ /**
200
+ * AI Sales Tools Dataset - Verified company dataset for founders/ops teams.
201
+ * First wedge for Outcome-Verified Marketplace (Fiverr/Upwork 2.0).
202
+ */
203
+ aiSalesToolsDataset: createWebExtractionOutcome({
204
+ name: 'ai_sales_tools_dataset_v1',
205
+ description: 'Extract verified AI sales tools companies with domain, category, and sources',
206
+ payoutAmount: 150,
207
+ targetUrl: 'https://www.producthunt.com/topics/sales',
208
+ goalDescription: 'Extract at least 25 AI sales tools companies with name, domain, category, and source URLs. Deduplicate by domain.',
209
+ outputSchema: {
210
+ type: 'object',
211
+ properties: {
212
+ companies: {
213
+ type: 'array',
214
+ items: {
215
+ type: 'object',
216
+ properties: {
217
+ name: { type: 'string' },
218
+ domain: { type: 'string' },
219
+ category: { type: 'string' },
220
+ sourceUrls: {
221
+ type: 'array',
222
+ items: { type: 'string' }
223
+ },
224
+ },
225
+ required: ['name', 'domain', 'category', 'sourceUrls'],
226
+ },
227
+ },
228
+ generatedAt: { type: 'string' },
229
+ },
230
+ required: ['companies', 'generatedAt'],
231
+ },
232
+ // No auth required for public directories
233
+ }),
234
+ };