opencode-swarm-plugin 0.19.0 → 0.21.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/.beads/issues.jsonl +205 -0
- package/INTEGRATION_EXAMPLE.md +66 -0
- package/README.md +127 -562
- package/dist/index.js +3842 -2917
- package/dist/plugin.js +3824 -2918
- package/docs/analysis/subagent-coordination-patterns.md +2 -0
- package/evals/README.md +116 -0
- package/evals/evalite.config.ts +15 -0
- package/evals/example.eval.ts +32 -0
- package/evals/fixtures/decomposition-cases.ts +105 -0
- package/evals/lib/data-loader.test.ts +288 -0
- package/evals/lib/data-loader.ts +111 -0
- package/evals/lib/llm.ts +115 -0
- package/evals/scorers/index.ts +200 -0
- package/evals/scorers/outcome-scorers.test.ts +27 -0
- package/evals/scorers/outcome-scorers.ts +349 -0
- package/evals/swarm-decomposition.eval.ts +112 -0
- package/package.json +8 -1
- package/src/agent-mail.ts +7 -7
- package/src/beads.ts +49 -0
- package/src/eval-capture.ts +487 -0
- package/src/index.ts +53 -3
- package/src/output-guardrails.test.ts +438 -0
- package/src/output-guardrails.ts +381 -0
- package/src/pattern-maturity.test.ts +1160 -0
- package/src/schemas/index.ts +18 -0
- package/src/schemas/swarm-context.ts +115 -0
- package/src/streams/events.test.ts +296 -0
- package/src/streams/events.ts +115 -0
- package/src/streams/migrations.test.ts +24 -20
- package/src/streams/migrations.ts +51 -0
- package/src/streams/projections.ts +187 -0
- package/src/streams/store.ts +275 -0
- package/src/swarm-mail.ts +7 -7
- package/src/swarm-orchestrate.ts +430 -1
- package/src/swarm-prompts.ts +84 -12
package/src/schemas/index.ts
CHANGED
|
@@ -125,3 +125,21 @@ export {
|
|
|
125
125
|
type QueryMandatesArgs,
|
|
126
126
|
type ScoreCalculationResult,
|
|
127
127
|
} from "./mandate";
|
|
128
|
+
|
|
129
|
+
// Swarm context schemas
|
|
130
|
+
export {
|
|
131
|
+
SwarmStrategySchema,
|
|
132
|
+
SwarmDirectivesSchema,
|
|
133
|
+
SwarmRecoverySchema,
|
|
134
|
+
SwarmBeadContextSchema,
|
|
135
|
+
CreateSwarmContextArgsSchema,
|
|
136
|
+
UpdateSwarmContextArgsSchema,
|
|
137
|
+
QuerySwarmContextsArgsSchema,
|
|
138
|
+
type SwarmStrategy,
|
|
139
|
+
type SwarmDirectives,
|
|
140
|
+
type SwarmRecovery,
|
|
141
|
+
type SwarmBeadContext,
|
|
142
|
+
type CreateSwarmContextArgs,
|
|
143
|
+
type UpdateSwarmContextArgs,
|
|
144
|
+
type QuerySwarmContextsArgs,
|
|
145
|
+
} from "./swarm-context";
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swarm Context Schemas
|
|
3
|
+
*
|
|
4
|
+
* These schemas define the structure for storing and recovering swarm execution context.
|
|
5
|
+
* Used for checkpoint/recovery, continuation after crashes, and swarm state management.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Decomposition strategy used for the swarm
|
|
11
|
+
*/
|
|
12
|
+
export const SwarmStrategySchema = z.enum([
|
|
13
|
+
"file-based",
|
|
14
|
+
"feature-based",
|
|
15
|
+
"risk-based",
|
|
16
|
+
]);
|
|
17
|
+
export type SwarmStrategy = z.infer<typeof SwarmStrategySchema>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Shared directives and context for all agents in a swarm
|
|
21
|
+
*/
|
|
22
|
+
export const SwarmDirectivesSchema = z.object({
|
|
23
|
+
/** Context shared with all agents (API contracts, conventions, arch decisions) */
|
|
24
|
+
shared_context: z.string(),
|
|
25
|
+
/** Skills to load in agent context (e.g., ['testing-patterns', 'swarm-coordination']) */
|
|
26
|
+
skills_to_load: z.array(z.string()).default([]),
|
|
27
|
+
/** Notes from coordinator to agents (gotchas, important context) */
|
|
28
|
+
coordinator_notes: z.string().default(""),
|
|
29
|
+
});
|
|
30
|
+
export type SwarmDirectives = z.infer<typeof SwarmDirectivesSchema>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Recovery state for checkpoint/resume
|
|
34
|
+
*/
|
|
35
|
+
export const SwarmRecoverySchema = z.object({
|
|
36
|
+
/** Last known checkpoint (ISO-8601 timestamp or checkpoint ID) */
|
|
37
|
+
last_checkpoint: z.string(),
|
|
38
|
+
/** Files modified since checkpoint (for rollback/recovery) */
|
|
39
|
+
files_modified: z.array(z.string()).default([]),
|
|
40
|
+
/** Progress percentage (0-100) */
|
|
41
|
+
progress_percent: z.number().min(0).max(100).default(0),
|
|
42
|
+
/** Last status message from agent */
|
|
43
|
+
last_message: z.string().default(""),
|
|
44
|
+
/** Error context if agent failed (for retry/recovery) */
|
|
45
|
+
error_context: z.string().optional(),
|
|
46
|
+
});
|
|
47
|
+
export type SwarmRecovery = z.infer<typeof SwarmRecoverySchema>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Complete context for a single bead in a swarm
|
|
51
|
+
*
|
|
52
|
+
* Stored in swarm_contexts table for recovery, continuation, and state management.
|
|
53
|
+
*/
|
|
54
|
+
export const SwarmBeadContextSchema = z.object({
|
|
55
|
+
/** ID of the swarm context record */
|
|
56
|
+
id: z.string(),
|
|
57
|
+
/** Epic this bead belongs to */
|
|
58
|
+
epic_id: z.string(),
|
|
59
|
+
/** Bead ID being executed */
|
|
60
|
+
bead_id: z.string(),
|
|
61
|
+
/** Decomposition strategy used */
|
|
62
|
+
strategy: SwarmStrategySchema,
|
|
63
|
+
/** Files this bead is responsible for */
|
|
64
|
+
files: z.array(z.string()),
|
|
65
|
+
/** Bead IDs this task depends on */
|
|
66
|
+
dependencies: z.array(z.string()).default([]),
|
|
67
|
+
/** Shared directives and context */
|
|
68
|
+
directives: SwarmDirectivesSchema,
|
|
69
|
+
/** Recovery state */
|
|
70
|
+
recovery: SwarmRecoverySchema,
|
|
71
|
+
/** Creation timestamp (epoch ms) */
|
|
72
|
+
created_at: z.number().int().positive(),
|
|
73
|
+
/** Last update timestamp (epoch ms) */
|
|
74
|
+
updated_at: z.number().int().positive(),
|
|
75
|
+
});
|
|
76
|
+
export type SwarmBeadContext = z.infer<typeof SwarmBeadContextSchema>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Args for creating a swarm context
|
|
80
|
+
*/
|
|
81
|
+
export const CreateSwarmContextArgsSchema = SwarmBeadContextSchema.omit({
|
|
82
|
+
id: true,
|
|
83
|
+
created_at: true,
|
|
84
|
+
updated_at: true,
|
|
85
|
+
});
|
|
86
|
+
export type CreateSwarmContextArgs = z.infer<
|
|
87
|
+
typeof CreateSwarmContextArgsSchema
|
|
88
|
+
>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Args for updating a swarm context
|
|
92
|
+
*/
|
|
93
|
+
export const UpdateSwarmContextArgsSchema = z.object({
|
|
94
|
+
id: z.string(),
|
|
95
|
+
recovery: SwarmRecoverySchema.partial().optional(),
|
|
96
|
+
files: z.array(z.string()).optional(),
|
|
97
|
+
dependencies: z.array(z.string()).optional(),
|
|
98
|
+
directives: SwarmDirectivesSchema.partial().optional(),
|
|
99
|
+
});
|
|
100
|
+
export type UpdateSwarmContextArgs = z.infer<
|
|
101
|
+
typeof UpdateSwarmContextArgsSchema
|
|
102
|
+
>;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Args for querying swarm contexts
|
|
106
|
+
*/
|
|
107
|
+
export const QuerySwarmContextsArgsSchema = z.object({
|
|
108
|
+
epic_id: z.string().optional(),
|
|
109
|
+
bead_id: z.string().optional(),
|
|
110
|
+
strategy: SwarmStrategySchema.optional(),
|
|
111
|
+
has_errors: z.boolean().optional(), // Filter by presence of error_context
|
|
112
|
+
});
|
|
113
|
+
export type QuerySwarmContextsArgs = z.infer<
|
|
114
|
+
typeof QuerySwarmContextsArgsSchema
|
|
115
|
+
>;
|
|
@@ -21,6 +21,9 @@ import {
|
|
|
21
21
|
TaskProgressEventSchema,
|
|
22
22
|
TaskCompletedEventSchema,
|
|
23
23
|
TaskBlockedEventSchema,
|
|
24
|
+
DecompositionGeneratedEventSchema,
|
|
25
|
+
SubtaskOutcomeEventSchema,
|
|
26
|
+
HumanFeedbackEventSchema,
|
|
24
27
|
createEvent,
|
|
25
28
|
isEventType,
|
|
26
29
|
type AgentEvent,
|
|
@@ -361,6 +364,299 @@ describe("TaskBlockedEventSchema", () => {
|
|
|
361
364
|
});
|
|
362
365
|
});
|
|
363
366
|
|
|
367
|
+
describe("DecompositionGeneratedEventSchema", () => {
|
|
368
|
+
it("validates a complete decomposition_generated event", () => {
|
|
369
|
+
const event = {
|
|
370
|
+
type: "decomposition_generated",
|
|
371
|
+
project_key: "/test/project",
|
|
372
|
+
timestamp: Date.now(),
|
|
373
|
+
epic_id: "bd-123",
|
|
374
|
+
task: "Add user authentication",
|
|
375
|
+
context: "OAuth integration for GitHub",
|
|
376
|
+
strategy: "feature-based",
|
|
377
|
+
epic_title: "User Authentication",
|
|
378
|
+
subtasks: [
|
|
379
|
+
{
|
|
380
|
+
title: "Create OAuth flow",
|
|
381
|
+
files: ["src/auth/oauth.ts"],
|
|
382
|
+
priority: 2,
|
|
383
|
+
},
|
|
384
|
+
{ title: "Add login UI", files: ["src/ui/login.tsx"], priority: 1 },
|
|
385
|
+
],
|
|
386
|
+
};
|
|
387
|
+
expect(() => DecompositionGeneratedEventSchema.parse(event)).not.toThrow();
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("validates without optional context", () => {
|
|
391
|
+
const event = {
|
|
392
|
+
type: "decomposition_generated",
|
|
393
|
+
project_key: "/test/project",
|
|
394
|
+
timestamp: Date.now(),
|
|
395
|
+
epic_id: "bd-123",
|
|
396
|
+
task: "Add user authentication",
|
|
397
|
+
strategy: "file-based",
|
|
398
|
+
epic_title: "User Authentication",
|
|
399
|
+
subtasks: [{ title: "Create OAuth flow", files: ["src/auth/oauth.ts"] }],
|
|
400
|
+
};
|
|
401
|
+
expect(() => DecompositionGeneratedEventSchema.parse(event)).not.toThrow();
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it("validates strategy enum values", () => {
|
|
405
|
+
const validStrategies = ["file-based", "feature-based", "risk-based"];
|
|
406
|
+
for (const strategy of validStrategies) {
|
|
407
|
+
const event = {
|
|
408
|
+
type: "decomposition_generated",
|
|
409
|
+
project_key: "/test/project",
|
|
410
|
+
timestamp: Date.now(),
|
|
411
|
+
epic_id: "bd-123",
|
|
412
|
+
task: "Test task",
|
|
413
|
+
strategy,
|
|
414
|
+
epic_title: "Test",
|
|
415
|
+
subtasks: [{ title: "Subtask", files: ["test.ts"] }],
|
|
416
|
+
};
|
|
417
|
+
expect(() =>
|
|
418
|
+
DecompositionGeneratedEventSchema.parse(event),
|
|
419
|
+
).not.toThrow();
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it("rejects invalid strategy value", () => {
|
|
424
|
+
const event = {
|
|
425
|
+
type: "decomposition_generated",
|
|
426
|
+
project_key: "/test/project",
|
|
427
|
+
timestamp: Date.now(),
|
|
428
|
+
epic_id: "bd-123",
|
|
429
|
+
task: "Test task",
|
|
430
|
+
strategy: "invalid-strategy",
|
|
431
|
+
epic_title: "Test",
|
|
432
|
+
subtasks: [{ title: "Subtask", files: ["test.ts"] }],
|
|
433
|
+
};
|
|
434
|
+
expect(() => DecompositionGeneratedEventSchema.parse(event)).toThrow();
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it("validates subtask priority bounds", () => {
|
|
438
|
+
const baseEvent = {
|
|
439
|
+
type: "decomposition_generated",
|
|
440
|
+
project_key: "/test/project",
|
|
441
|
+
timestamp: Date.now(),
|
|
442
|
+
epic_id: "bd-123",
|
|
443
|
+
task: "Test",
|
|
444
|
+
strategy: "file-based",
|
|
445
|
+
epic_title: "Test",
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
// Valid: 0
|
|
449
|
+
expect(() =>
|
|
450
|
+
DecompositionGeneratedEventSchema.parse({
|
|
451
|
+
...baseEvent,
|
|
452
|
+
subtasks: [{ title: "Test", files: ["test.ts"], priority: 0 }],
|
|
453
|
+
}),
|
|
454
|
+
).not.toThrow();
|
|
455
|
+
|
|
456
|
+
// Valid: 3
|
|
457
|
+
expect(() =>
|
|
458
|
+
DecompositionGeneratedEventSchema.parse({
|
|
459
|
+
...baseEvent,
|
|
460
|
+
subtasks: [{ title: "Test", files: ["test.ts"], priority: 3 }],
|
|
461
|
+
}),
|
|
462
|
+
).not.toThrow();
|
|
463
|
+
|
|
464
|
+
// Invalid: -1
|
|
465
|
+
expect(() =>
|
|
466
|
+
DecompositionGeneratedEventSchema.parse({
|
|
467
|
+
...baseEvent,
|
|
468
|
+
subtasks: [{ title: "Test", files: ["test.ts"], priority: -1 }],
|
|
469
|
+
}),
|
|
470
|
+
).toThrow();
|
|
471
|
+
|
|
472
|
+
// Invalid: 4
|
|
473
|
+
expect(() =>
|
|
474
|
+
DecompositionGeneratedEventSchema.parse({
|
|
475
|
+
...baseEvent,
|
|
476
|
+
subtasks: [{ title: "Test", files: ["test.ts"], priority: 4 }],
|
|
477
|
+
}),
|
|
478
|
+
).toThrow();
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it("rejects empty subtasks array", () => {
|
|
482
|
+
const event = {
|
|
483
|
+
type: "decomposition_generated",
|
|
484
|
+
project_key: "/test/project",
|
|
485
|
+
timestamp: Date.now(),
|
|
486
|
+
epic_id: "bd-123",
|
|
487
|
+
task: "Test",
|
|
488
|
+
strategy: "file-based",
|
|
489
|
+
epic_title: "Test",
|
|
490
|
+
subtasks: [],
|
|
491
|
+
};
|
|
492
|
+
// Empty subtasks is valid per schema but semantically questionable
|
|
493
|
+
expect(() => DecompositionGeneratedEventSchema.parse(event)).not.toThrow();
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
describe("SubtaskOutcomeEventSchema", () => {
|
|
498
|
+
it("validates a complete subtask_outcome event", () => {
|
|
499
|
+
const event = {
|
|
500
|
+
type: "subtask_outcome",
|
|
501
|
+
project_key: "/test/project",
|
|
502
|
+
timestamp: Date.now(),
|
|
503
|
+
epic_id: "bd-123",
|
|
504
|
+
bead_id: "bd-123.1",
|
|
505
|
+
planned_files: ["src/auth.ts", "src/config.ts"],
|
|
506
|
+
actual_files: ["src/auth.ts", "src/config.ts", "src/utils.ts"],
|
|
507
|
+
duration_ms: 45000,
|
|
508
|
+
error_count: 2,
|
|
509
|
+
retry_count: 1,
|
|
510
|
+
success: true,
|
|
511
|
+
};
|
|
512
|
+
expect(() => SubtaskOutcomeEventSchema.parse(event)).not.toThrow();
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it("applies defaults for error_count and retry_count", () => {
|
|
516
|
+
const event = {
|
|
517
|
+
type: "subtask_outcome",
|
|
518
|
+
project_key: "/test/project",
|
|
519
|
+
timestamp: Date.now(),
|
|
520
|
+
epic_id: "bd-123",
|
|
521
|
+
bead_id: "bd-123.1",
|
|
522
|
+
planned_files: ["src/auth.ts"],
|
|
523
|
+
actual_files: ["src/auth.ts"],
|
|
524
|
+
duration_ms: 10000,
|
|
525
|
+
success: true,
|
|
526
|
+
};
|
|
527
|
+
const parsed = SubtaskOutcomeEventSchema.parse(event);
|
|
528
|
+
expect(parsed.error_count).toBe(0);
|
|
529
|
+
expect(parsed.retry_count).toBe(0);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it("validates duration_ms is non-negative", () => {
|
|
533
|
+
const baseEvent = {
|
|
534
|
+
type: "subtask_outcome",
|
|
535
|
+
project_key: "/test/project",
|
|
536
|
+
timestamp: Date.now(),
|
|
537
|
+
epic_id: "bd-123",
|
|
538
|
+
bead_id: "bd-123.1",
|
|
539
|
+
planned_files: ["test.ts"],
|
|
540
|
+
actual_files: ["test.ts"],
|
|
541
|
+
success: true,
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// Valid: 0
|
|
545
|
+
expect(() =>
|
|
546
|
+
SubtaskOutcomeEventSchema.parse({ ...baseEvent, duration_ms: 0 }),
|
|
547
|
+
).not.toThrow();
|
|
548
|
+
|
|
549
|
+
// Valid: positive
|
|
550
|
+
expect(() =>
|
|
551
|
+
SubtaskOutcomeEventSchema.parse({ ...baseEvent, duration_ms: 1000 }),
|
|
552
|
+
).not.toThrow();
|
|
553
|
+
|
|
554
|
+
// Invalid: negative
|
|
555
|
+
expect(() =>
|
|
556
|
+
SubtaskOutcomeEventSchema.parse({ ...baseEvent, duration_ms: -1 }),
|
|
557
|
+
).toThrow();
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it("validates error_count is non-negative", () => {
|
|
561
|
+
const baseEvent = {
|
|
562
|
+
type: "subtask_outcome",
|
|
563
|
+
project_key: "/test/project",
|
|
564
|
+
timestamp: Date.now(),
|
|
565
|
+
epic_id: "bd-123",
|
|
566
|
+
bead_id: "bd-123.1",
|
|
567
|
+
planned_files: ["test.ts"],
|
|
568
|
+
actual_files: ["test.ts"],
|
|
569
|
+
duration_ms: 1000,
|
|
570
|
+
success: true,
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
// Invalid: negative
|
|
574
|
+
expect(() =>
|
|
575
|
+
SubtaskOutcomeEventSchema.parse({ ...baseEvent, error_count: -1 }),
|
|
576
|
+
).toThrow();
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it("handles file lists with different lengths", () => {
|
|
580
|
+
const event = {
|
|
581
|
+
type: "subtask_outcome",
|
|
582
|
+
project_key: "/test/project",
|
|
583
|
+
timestamp: Date.now(),
|
|
584
|
+
epic_id: "bd-123",
|
|
585
|
+
bead_id: "bd-123.1",
|
|
586
|
+
planned_files: ["a.ts", "b.ts"],
|
|
587
|
+
actual_files: ["a.ts", "b.ts", "c.ts", "d.ts"],
|
|
588
|
+
duration_ms: 5000,
|
|
589
|
+
success: true,
|
|
590
|
+
};
|
|
591
|
+
expect(() => SubtaskOutcomeEventSchema.parse(event)).not.toThrow();
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
describe("HumanFeedbackEventSchema", () => {
|
|
596
|
+
it("validates a complete human_feedback event", () => {
|
|
597
|
+
const event = {
|
|
598
|
+
type: "human_feedback",
|
|
599
|
+
project_key: "/test/project",
|
|
600
|
+
timestamp: Date.now(),
|
|
601
|
+
epic_id: "bd-123",
|
|
602
|
+
accepted: true,
|
|
603
|
+
modified: false,
|
|
604
|
+
notes: "Looks good, no changes needed",
|
|
605
|
+
};
|
|
606
|
+
expect(() => HumanFeedbackEventSchema.parse(event)).not.toThrow();
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
it("validates accepted with modification", () => {
|
|
610
|
+
const event = {
|
|
611
|
+
type: "human_feedback",
|
|
612
|
+
project_key: "/test/project",
|
|
613
|
+
timestamp: Date.now(),
|
|
614
|
+
epic_id: "bd-123",
|
|
615
|
+
accepted: true,
|
|
616
|
+
modified: true,
|
|
617
|
+
notes: "Changed priority on subtask 2",
|
|
618
|
+
};
|
|
619
|
+
expect(() => HumanFeedbackEventSchema.parse(event)).not.toThrow();
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
it("validates rejected feedback", () => {
|
|
623
|
+
const event = {
|
|
624
|
+
type: "human_feedback",
|
|
625
|
+
project_key: "/test/project",
|
|
626
|
+
timestamp: Date.now(),
|
|
627
|
+
epic_id: "bd-123",
|
|
628
|
+
accepted: false,
|
|
629
|
+
modified: false,
|
|
630
|
+
notes: "Decomposition too granular, needs consolidation",
|
|
631
|
+
};
|
|
632
|
+
expect(() => HumanFeedbackEventSchema.parse(event)).not.toThrow();
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it("applies default for modified", () => {
|
|
636
|
+
const event = {
|
|
637
|
+
type: "human_feedback",
|
|
638
|
+
project_key: "/test/project",
|
|
639
|
+
timestamp: Date.now(),
|
|
640
|
+
epic_id: "bd-123",
|
|
641
|
+
accepted: true,
|
|
642
|
+
};
|
|
643
|
+
const parsed = HumanFeedbackEventSchema.parse(event);
|
|
644
|
+
expect(parsed.modified).toBe(false);
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
it("validates without notes", () => {
|
|
648
|
+
const event = {
|
|
649
|
+
type: "human_feedback",
|
|
650
|
+
project_key: "/test/project",
|
|
651
|
+
timestamp: Date.now(),
|
|
652
|
+
epic_id: "bd-123",
|
|
653
|
+
accepted: true,
|
|
654
|
+
modified: false,
|
|
655
|
+
};
|
|
656
|
+
expect(() => HumanFeedbackEventSchema.parse(event)).not.toThrow();
|
|
657
|
+
});
|
|
658
|
+
});
|
|
659
|
+
|
|
364
660
|
// ============================================================================
|
|
365
661
|
// Discriminated Union Tests
|
|
366
662
|
// ============================================================================
|
package/src/streams/events.ts
CHANGED
|
@@ -141,6 +141,85 @@ export const TaskBlockedEventSchema = BaseEventSchema.extend({
|
|
|
141
141
|
reason: z.string(),
|
|
142
142
|
});
|
|
143
143
|
|
|
144
|
+
// ============================================================================
|
|
145
|
+
// Eval Capture Events (for learning system)
|
|
146
|
+
// ============================================================================
|
|
147
|
+
|
|
148
|
+
export const DecompositionGeneratedEventSchema = BaseEventSchema.extend({
|
|
149
|
+
type: z.literal("decomposition_generated"),
|
|
150
|
+
epic_id: z.string(),
|
|
151
|
+
task: z.string(),
|
|
152
|
+
context: z.string().optional(),
|
|
153
|
+
strategy: z.enum(["file-based", "feature-based", "risk-based"]),
|
|
154
|
+
epic_title: z.string(),
|
|
155
|
+
subtasks: z.array(
|
|
156
|
+
z.object({
|
|
157
|
+
title: z.string(),
|
|
158
|
+
files: z.array(z.string()),
|
|
159
|
+
priority: z.number().min(0).max(3).optional(),
|
|
160
|
+
}),
|
|
161
|
+
),
|
|
162
|
+
recovery_context: z
|
|
163
|
+
.object({
|
|
164
|
+
shared_context: z.string().optional(),
|
|
165
|
+
skills_to_load: z.array(z.string()).optional(),
|
|
166
|
+
coordinator_notes: z.string().optional(),
|
|
167
|
+
})
|
|
168
|
+
.optional(),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
export const SubtaskOutcomeEventSchema = BaseEventSchema.extend({
|
|
172
|
+
type: z.literal("subtask_outcome"),
|
|
173
|
+
epic_id: z.string(),
|
|
174
|
+
bead_id: z.string(),
|
|
175
|
+
planned_files: z.array(z.string()),
|
|
176
|
+
actual_files: z.array(z.string()),
|
|
177
|
+
duration_ms: z.number().min(0),
|
|
178
|
+
error_count: z.number().min(0).default(0),
|
|
179
|
+
retry_count: z.number().min(0).default(0),
|
|
180
|
+
success: z.boolean(),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
export const HumanFeedbackEventSchema = BaseEventSchema.extend({
|
|
184
|
+
type: z.literal("human_feedback"),
|
|
185
|
+
epic_id: z.string(),
|
|
186
|
+
accepted: z.boolean(),
|
|
187
|
+
modified: z.boolean().default(false),
|
|
188
|
+
notes: z.string().optional(),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// ============================================================================
|
|
192
|
+
// Swarm Checkpoint Events (for recovery and coordination)
|
|
193
|
+
// ============================================================================
|
|
194
|
+
|
|
195
|
+
export const SwarmCheckpointedEventSchema = BaseEventSchema.extend({
|
|
196
|
+
type: z.literal("swarm_checkpointed"),
|
|
197
|
+
epic_id: z.string(),
|
|
198
|
+
bead_id: z.string(),
|
|
199
|
+
strategy: z.enum(["file-based", "feature-based", "risk-based"]),
|
|
200
|
+
files: z.array(z.string()),
|
|
201
|
+
dependencies: z.array(z.string()),
|
|
202
|
+
directives: z.object({
|
|
203
|
+
shared_context: z.string().optional(),
|
|
204
|
+
skills_to_load: z.array(z.string()).optional(),
|
|
205
|
+
coordinator_notes: z.string().optional(),
|
|
206
|
+
}),
|
|
207
|
+
recovery: z.object({
|
|
208
|
+
last_checkpoint: z.number(),
|
|
209
|
+
files_modified: z.array(z.string()),
|
|
210
|
+
progress_percent: z.number().min(0).max(100),
|
|
211
|
+
last_message: z.string().optional(),
|
|
212
|
+
error_context: z.string().optional(),
|
|
213
|
+
}),
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
export const SwarmRecoveredEventSchema = BaseEventSchema.extend({
|
|
217
|
+
type: z.literal("swarm_recovered"),
|
|
218
|
+
epic_id: z.string(),
|
|
219
|
+
bead_id: z.string(),
|
|
220
|
+
recovered_from_checkpoint: z.number(), // timestamp
|
|
221
|
+
});
|
|
222
|
+
|
|
144
223
|
// ============================================================================
|
|
145
224
|
// Union Type
|
|
146
225
|
// ============================================================================
|
|
@@ -157,6 +236,11 @@ export const AgentEventSchema = z.discriminatedUnion("type", [
|
|
|
157
236
|
TaskProgressEventSchema,
|
|
158
237
|
TaskCompletedEventSchema,
|
|
159
238
|
TaskBlockedEventSchema,
|
|
239
|
+
DecompositionGeneratedEventSchema,
|
|
240
|
+
SubtaskOutcomeEventSchema,
|
|
241
|
+
HumanFeedbackEventSchema,
|
|
242
|
+
SwarmCheckpointedEventSchema,
|
|
243
|
+
SwarmRecoveredEventSchema,
|
|
160
244
|
]);
|
|
161
245
|
|
|
162
246
|
export type AgentEvent = z.infer<typeof AgentEventSchema>;
|
|
@@ -173,6 +257,37 @@ export type TaskStartedEvent = z.infer<typeof TaskStartedEventSchema>;
|
|
|
173
257
|
export type TaskProgressEvent = z.infer<typeof TaskProgressEventSchema>;
|
|
174
258
|
export type TaskCompletedEvent = z.infer<typeof TaskCompletedEventSchema>;
|
|
175
259
|
export type TaskBlockedEvent = z.infer<typeof TaskBlockedEventSchema>;
|
|
260
|
+
export type DecompositionGeneratedEvent = z.infer<
|
|
261
|
+
typeof DecompositionGeneratedEventSchema
|
|
262
|
+
>;
|
|
263
|
+
export type SubtaskOutcomeEvent = z.infer<typeof SubtaskOutcomeEventSchema>;
|
|
264
|
+
export type HumanFeedbackEvent = z.infer<typeof HumanFeedbackEventSchema>;
|
|
265
|
+
export type SwarmCheckpointedEvent = z.infer<
|
|
266
|
+
typeof SwarmCheckpointedEventSchema
|
|
267
|
+
>;
|
|
268
|
+
export type SwarmRecoveredEvent = z.infer<typeof SwarmRecoveredEventSchema>;
|
|
269
|
+
|
|
270
|
+
// ============================================================================
|
|
271
|
+
// Session State Types
|
|
272
|
+
// ============================================================================
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Shared session state for Agent Mail and Swarm Mail
|
|
276
|
+
*
|
|
277
|
+
* Common fields for tracking agent coordination session across both
|
|
278
|
+
* the MCP-based implementation (agent-mail) and the embedded event-sourced
|
|
279
|
+
* implementation (swarm-mail).
|
|
280
|
+
*/
|
|
281
|
+
export interface MailSessionState {
|
|
282
|
+
/** Project key (usually absolute path) */
|
|
283
|
+
projectKey: string;
|
|
284
|
+
/** Agent name for this session */
|
|
285
|
+
agentName: string;
|
|
286
|
+
/** Active reservation IDs */
|
|
287
|
+
reservations: number[];
|
|
288
|
+
/** Session start timestamp (ISO-8601) */
|
|
289
|
+
startedAt: string;
|
|
290
|
+
}
|
|
176
291
|
|
|
177
292
|
// ============================================================================
|
|
178
293
|
// Event Helpers
|