opencode-swarm-plugin 0.18.0 → 0.19.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 +71 -61
- package/.github/workflows/ci.yml +5 -1
- package/README.md +48 -4
- package/dist/index.js +6643 -6326
- package/dist/plugin.js +2726 -2404
- package/package.json +1 -1
- package/src/agent-mail.ts +13 -0
- package/src/anti-patterns.test.ts +1167 -0
- package/src/anti-patterns.ts +29 -11
- package/src/pattern-maturity.ts +51 -13
- package/src/plugin.ts +15 -3
- package/src/schemas/bead.ts +35 -4
- package/src/schemas/evaluation.ts +18 -6
- package/src/schemas/index.ts +25 -2
- package/src/schemas/task.ts +49 -21
- package/src/streams/debug.ts +101 -3
- package/src/streams/index.ts +58 -1
- package/src/streams/migrations.ts +46 -4
- package/src/streams/store.integration.test.ts +110 -0
- package/src/streams/store.ts +311 -126
- package/src/structured.test.ts +1046 -0
- package/src/structured.ts +74 -27
- package/src/swarm-decompose.ts +912 -0
- package/src/swarm-orchestrate.ts +1869 -0
- package/src/swarm-prompts.ts +756 -0
- package/src/swarm-strategies.ts +407 -0
- package/src/swarm.ts +23 -3876
- package/src/tool-availability.ts +29 -6
- package/test-bug-fixes.ts +86 -0
package/src/anti-patterns.ts
CHANGED
|
@@ -19,10 +19,15 @@ export const PatternKindSchema = z.enum(["pattern", "anti_pattern"]);
|
|
|
19
19
|
export type PatternKind = z.infer<typeof PatternKindSchema>;
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
22
|
+
* Decomposition pattern with success/failure tracking.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* Field relationships:
|
|
25
|
+
* - `kind`: Tracks pattern lifecycle ("pattern" → "anti_pattern" when failure rate exceeds threshold)
|
|
26
|
+
* - `is_negative`: Derived boolean flag for quick filtering (true when kind === "anti_pattern")
|
|
27
|
+
*
|
|
28
|
+
* Both fields exist because:
|
|
29
|
+
* - `kind` is the source of truth for pattern status
|
|
30
|
+
* - `is_negative` enables efficient filtering without string comparison
|
|
26
31
|
*/
|
|
27
32
|
export const DecompositionPatternSchema = z.object({
|
|
28
33
|
/** Unique ID for this pattern */
|
|
@@ -69,6 +74,9 @@ export type PatternInversionResult = z.infer<
|
|
|
69
74
|
// Configuration
|
|
70
75
|
// ============================================================================
|
|
71
76
|
|
|
77
|
+
/** Maximum number of example beads to keep per pattern */
|
|
78
|
+
const MAX_EXAMPLE_BEADS = 10;
|
|
79
|
+
|
|
72
80
|
/**
|
|
73
81
|
* Configuration for anti-pattern detection
|
|
74
82
|
*/
|
|
@@ -186,7 +194,7 @@ export function recordPatternObservation(
|
|
|
186
194
|
failure_count: success ? pattern.failure_count : pattern.failure_count + 1,
|
|
187
195
|
updated_at: new Date().toISOString(),
|
|
188
196
|
example_beads: beadId
|
|
189
|
-
? [...pattern.example_beads.slice(-
|
|
197
|
+
? [...pattern.example_beads.slice(-(MAX_EXAMPLE_BEADS - 1)), beadId]
|
|
190
198
|
: pattern.example_beads,
|
|
191
199
|
};
|
|
192
200
|
|
|
@@ -216,8 +224,16 @@ export function recordPatternObservation(
|
|
|
216
224
|
export function extractPatternsFromDescription(description: string): string[] {
|
|
217
225
|
const patterns: string[] = [];
|
|
218
226
|
|
|
219
|
-
|
|
220
|
-
|
|
227
|
+
/**
|
|
228
|
+
* Regex patterns for detecting common decomposition strategies.
|
|
229
|
+
*
|
|
230
|
+
* Detection is keyword-based and not exhaustive - patterns can be
|
|
231
|
+
* manually created for novel strategies not covered here.
|
|
232
|
+
*
|
|
233
|
+
* Each pattern maps a regex to a strategy name that will be extracted
|
|
234
|
+
* from task descriptions during pattern observation.
|
|
235
|
+
*/
|
|
236
|
+
const strategyPatterns: Array<{ regex: RegExp; pattern: string }> = [
|
|
221
237
|
{
|
|
222
238
|
regex: /split(?:ting)?\s+by\s+file\s+type/i,
|
|
223
239
|
pattern: "Split by file type",
|
|
@@ -322,15 +338,17 @@ export function formatAntiPatternsForPrompt(
|
|
|
322
338
|
}
|
|
323
339
|
|
|
324
340
|
/**
|
|
325
|
-
* Format successful patterns for inclusion in
|
|
341
|
+
* Format successful patterns for inclusion in prompts.
|
|
326
342
|
*
|
|
327
|
-
* @param patterns -
|
|
328
|
-
* @param minSuccessRate - Minimum success rate to include (0
|
|
329
|
-
*
|
|
343
|
+
* @param patterns - Array of decomposition patterns to filter and format
|
|
344
|
+
* @param minSuccessRate - Minimum success rate to include (default 0.7 = 70%).
|
|
345
|
+
* Chosen to filter out patterns with marginal track records - only patterns
|
|
346
|
+
* that succeed at least 70% of the time are recommended.
|
|
347
|
+
* @returns Formatted string of successful patterns for prompt injection
|
|
330
348
|
*/
|
|
331
349
|
export function formatSuccessfulPatternsForPrompt(
|
|
332
350
|
patterns: DecompositionPattern[],
|
|
333
|
-
minSuccessRate
|
|
351
|
+
minSuccessRate = 0.7,
|
|
334
352
|
): string {
|
|
335
353
|
const successful = patterns.filter((p) => {
|
|
336
354
|
if (p.kind === "anti_pattern") return false;
|
package/src/pattern-maturity.ts
CHANGED
|
@@ -12,6 +12,16 @@
|
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { calculateDecayedValue } from "./learning";
|
|
14
14
|
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Constants
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Tolerance for floating-point comparisons.
|
|
21
|
+
* Used when comparing success rates to avoid floating-point precision issues.
|
|
22
|
+
*/
|
|
23
|
+
const FLOAT_EPSILON = 0.01;
|
|
24
|
+
|
|
15
25
|
// ============================================================================
|
|
16
26
|
// Schemas
|
|
17
27
|
// ============================================================================
|
|
@@ -164,26 +174,26 @@ export function calculateMaturityState(
|
|
|
164
174
|
);
|
|
165
175
|
|
|
166
176
|
const total = decayedHelpful + decayedHarmful;
|
|
167
|
-
|
|
168
|
-
const safeTotal = total >
|
|
177
|
+
// Use FLOAT_EPSILON constant (defined at module level)
|
|
178
|
+
const safeTotal = total > FLOAT_EPSILON ? total : 0;
|
|
169
179
|
const harmfulRatio = safeTotal > 0 ? decayedHarmful / safeTotal : 0;
|
|
170
180
|
|
|
171
181
|
// Deprecated: high harmful ratio with enough feedback
|
|
172
182
|
if (
|
|
173
183
|
harmfulRatio > config.deprecationThreshold &&
|
|
174
|
-
safeTotal >= config.minFeedback -
|
|
184
|
+
safeTotal >= config.minFeedback - FLOAT_EPSILON
|
|
175
185
|
) {
|
|
176
186
|
return "deprecated";
|
|
177
187
|
}
|
|
178
188
|
|
|
179
189
|
// Candidate: not enough feedback yet
|
|
180
|
-
if (safeTotal < config.minFeedback -
|
|
190
|
+
if (safeTotal < config.minFeedback - FLOAT_EPSILON) {
|
|
181
191
|
return "candidate";
|
|
182
192
|
}
|
|
183
193
|
|
|
184
194
|
// Proven: strong positive signal
|
|
185
195
|
if (
|
|
186
|
-
decayedHelpful >= config.minHelpful -
|
|
196
|
+
decayedHelpful >= config.minHelpful - FLOAT_EPSILON &&
|
|
187
197
|
harmfulRatio < config.maxHarmful
|
|
188
198
|
) {
|
|
189
199
|
return "proven";
|
|
@@ -210,14 +220,23 @@ export function createPatternMaturity(patternId: string): PatternMaturity {
|
|
|
210
220
|
}
|
|
211
221
|
|
|
212
222
|
/**
|
|
213
|
-
* Update pattern maturity with new feedback
|
|
223
|
+
* Update pattern maturity with new feedback.
|
|
224
|
+
*
|
|
225
|
+
* Side Effects:
|
|
226
|
+
* - Sets `promoted_at` timestamp on first entry into 'proven' status
|
|
227
|
+
* - Sets `deprecated_at` timestamp on first entry into 'deprecated' status
|
|
228
|
+
* - Updates `helpful_count` and `harmful_count` based on feedback events
|
|
229
|
+
* - Recalculates `state` based on decayed feedback counts
|
|
214
230
|
*
|
|
215
|
-
*
|
|
231
|
+
* State Transitions:
|
|
232
|
+
* - candidate → established: After minFeedback observations (default 3)
|
|
233
|
+
* - established → proven: When decayedHelpful >= minHelpful (5) AND harmfulRatio < maxHarmful (15%)
|
|
234
|
+
* - any → deprecated: When harmfulRatio > deprecationThreshold (30%) AND total >= minFeedback
|
|
216
235
|
*
|
|
217
236
|
* @param maturity - Current maturity record
|
|
218
237
|
* @param feedbackEvents - All feedback events for this pattern
|
|
219
238
|
* @param config - Maturity configuration
|
|
220
|
-
* @returns Updated maturity record
|
|
239
|
+
* @returns Updated maturity record with new state
|
|
221
240
|
*/
|
|
222
241
|
export function updatePatternMaturity(
|
|
223
242
|
maturity: PatternMaturity,
|
|
@@ -269,7 +288,16 @@ export function promotePattern(maturity: PatternMaturity): PatternMaturity {
|
|
|
269
288
|
}
|
|
270
289
|
|
|
271
290
|
if (maturity.state === "proven") {
|
|
272
|
-
|
|
291
|
+
console.warn(
|
|
292
|
+
`[PatternMaturity] Pattern already proven: ${maturity.pattern_id}`,
|
|
293
|
+
);
|
|
294
|
+
return maturity; // No-op but warn
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (maturity.state === "candidate" && maturity.helpful_count < 3) {
|
|
298
|
+
console.warn(
|
|
299
|
+
`[PatternMaturity] Promoting candidate with insufficient data: ${maturity.pattern_id} (${maturity.helpful_count} helpful observations)`,
|
|
300
|
+
);
|
|
273
301
|
}
|
|
274
302
|
|
|
275
303
|
const now = new Date().toISOString();
|
|
@@ -309,12 +337,16 @@ export function deprecatePattern(
|
|
|
309
337
|
}
|
|
310
338
|
|
|
311
339
|
/**
|
|
312
|
-
* Get
|
|
340
|
+
* Get weight multiplier based on pattern maturity status.
|
|
313
341
|
*
|
|
314
|
-
*
|
|
342
|
+
* Multipliers chosen to:
|
|
343
|
+
* - Heavily penalize deprecated patterns (0x) - never recommend
|
|
344
|
+
* - Slightly boost proven patterns (1.5x) - reward validated success
|
|
345
|
+
* - Penalize unvalidated candidates (0.5x) - reduce impact until proven
|
|
346
|
+
* - Neutral for established (1.0x) - baseline weight
|
|
315
347
|
*
|
|
316
|
-
* @param state -
|
|
317
|
-
* @returns
|
|
348
|
+
* @param state - Pattern maturity status
|
|
349
|
+
* @returns Multiplier to apply to pattern weight
|
|
318
350
|
*/
|
|
319
351
|
export function getMaturityMultiplier(state: MaturityState): number {
|
|
320
352
|
const multipliers: Record<MaturityState, number> = {
|
|
@@ -336,6 +368,12 @@ export function getMaturityMultiplier(state: MaturityState): number {
|
|
|
336
368
|
*/
|
|
337
369
|
export function formatMaturityForPrompt(maturity: PatternMaturity): string {
|
|
338
370
|
const total = maturity.helpful_count + maturity.harmful_count;
|
|
371
|
+
|
|
372
|
+
// Don't show percentages for insufficient data
|
|
373
|
+
if (total < 3) {
|
|
374
|
+
return `[LIMITED DATA - ${total} observation${total !== 1 ? "s" : ""}]`;
|
|
375
|
+
}
|
|
376
|
+
|
|
339
377
|
const harmfulRatio =
|
|
340
378
|
total > 0 ? Math.round((maturity.harmful_count / total) * 100) : 0;
|
|
341
379
|
const helpfulRatio =
|
package/src/plugin.ts
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenCode Plugin Entry Point
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* CRITICAL: Only export the plugin function from this file.
|
|
5
|
+
*
|
|
6
|
+
* OpenCode's plugin loader calls ALL exports as functions during initialization.
|
|
7
|
+
* Exporting classes, constants, or non-function values will cause the plugin
|
|
8
|
+
* to fail to load with cryptic errors.
|
|
9
|
+
*
|
|
10
|
+
* If you need to export utilities for external use, add them to src/index.ts instead.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* // ✅ CORRECT - only export the plugin function
|
|
14
|
+
* export default SwarmPlugin;
|
|
15
|
+
*
|
|
16
|
+
* // ❌ WRONG - will break plugin loading
|
|
17
|
+
* export const VERSION = "1.0.0";
|
|
18
|
+
* export class Helper {}
|
|
7
19
|
*/
|
|
8
20
|
import { SwarmPlugin } from "./index";
|
|
9
21
|
|
package/src/schemas/bead.ts
CHANGED
|
@@ -42,19 +42,42 @@ export type BeadDependency = z.infer<typeof BeadDependencySchema>;
|
|
|
42
42
|
* - Custom subtask: `{project}-{custom-id}.{suffix}` (e.g., `migrate-egghead-phase-0.e2e-test`)
|
|
43
43
|
*/
|
|
44
44
|
export const BeadSchema = z.object({
|
|
45
|
+
/**
|
|
46
|
+
* Bead ID format: project-slug-hash with optional subtask index.
|
|
47
|
+
*
|
|
48
|
+
* Pattern: `project-name-xxxxx` or `project-name-xxxxx.N`
|
|
49
|
+
* Examples:
|
|
50
|
+
* - `my-project-abc12` (main bead)
|
|
51
|
+
* - `my-project-abc12.1` (first subtask)
|
|
52
|
+
* - `my-project-abc12.2` (second subtask)
|
|
53
|
+
*/
|
|
45
54
|
id: z
|
|
46
55
|
.string()
|
|
47
|
-
.regex(
|
|
56
|
+
.regex(
|
|
57
|
+
/^[a-z0-9]+(-[a-z0-9]+)+(\.[\w-]+)?$/,
|
|
58
|
+
"Invalid bead ID format (expected: project-slug-hash or project-slug-hash.N)",
|
|
59
|
+
),
|
|
48
60
|
title: z.string().min(1, "Title required"),
|
|
49
61
|
description: z.string().optional().default(""),
|
|
50
62
|
status: BeadStatusSchema.default("open"),
|
|
51
63
|
priority: z.number().int().min(0).max(3).default(2),
|
|
52
64
|
issue_type: BeadTypeSchema.default("task"),
|
|
53
|
-
created_at: z.string().datetime({
|
|
54
|
-
|
|
65
|
+
created_at: z.string().datetime({
|
|
66
|
+
offset: true,
|
|
67
|
+
message:
|
|
68
|
+
"Must be ISO-8601 datetime with timezone (e.g., 2024-01-15T10:30:00Z)",
|
|
69
|
+
}),
|
|
70
|
+
updated_at: z
|
|
71
|
+
.string()
|
|
72
|
+
.datetime({
|
|
73
|
+
offset: true,
|
|
74
|
+
message:
|
|
75
|
+
"Must be ISO-8601 datetime with timezone (e.g., 2024-01-15T10:30:00Z)",
|
|
76
|
+
})
|
|
77
|
+
.optional(),
|
|
55
78
|
closed_at: z.string().datetime({ offset: true }).optional(),
|
|
56
79
|
parent_id: z.string().optional(),
|
|
57
|
-
dependencies: z.array(BeadDependencySchema).
|
|
80
|
+
dependencies: z.array(BeadDependencySchema).default([]),
|
|
58
81
|
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
59
82
|
});
|
|
60
83
|
export type Bead = z.infer<typeof BeadSchema>;
|
|
@@ -111,6 +134,14 @@ export const SubtaskSpecSchema = z.object({
|
|
|
111
134
|
description: z.string().optional().default(""),
|
|
112
135
|
files: z.array(z.string()).default([]),
|
|
113
136
|
dependencies: z.array(z.number().int().min(0)).default([]), // Indices of other subtasks
|
|
137
|
+
/**
|
|
138
|
+
* Complexity estimate on 1-5 scale:
|
|
139
|
+
* 1 = trivial (typo fix, simple rename)
|
|
140
|
+
* 2 = simple (single function change)
|
|
141
|
+
* 3 = moderate (multi-file, some coordination)
|
|
142
|
+
* 4 = complex (significant refactoring)
|
|
143
|
+
* 5 = very complex (architectural change)
|
|
144
|
+
*/
|
|
114
145
|
estimated_complexity: z.number().int().min(1).max(5).default(3),
|
|
115
146
|
});
|
|
116
147
|
export type SubtaskSpec = z.infer<typeof SubtaskSpecSchema>;
|
|
@@ -12,9 +12,15 @@
|
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* Evaluation of a single criterion.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
17
|
+
* @example
|
|
18
|
+
* // Passing criterion
|
|
19
|
+
* { passed: true, feedback: "All types validated", score: 0.95 }
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Failing criterion
|
|
23
|
+
* { passed: false, feedback: "Missing error handling in auth flow", score: 0.3 }
|
|
18
24
|
*/
|
|
19
25
|
export const CriterionEvaluationSchema = z.object({
|
|
20
26
|
passed: z.boolean(),
|
|
@@ -31,7 +37,11 @@ export type CriterionEvaluation = z.infer<typeof CriterionEvaluationSchema>;
|
|
|
31
37
|
*/
|
|
32
38
|
export const WeightedCriterionEvaluationSchema =
|
|
33
39
|
CriterionEvaluationSchema.extend({
|
|
34
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Current weight after 90-day half-life decay.
|
|
42
|
+
* Range: 0-1 where 1 = recent/validated, 0 = old/unreliable.
|
|
43
|
+
* Weights decay over time unless revalidated via semantic-memory_validate.
|
|
44
|
+
*/
|
|
35
45
|
weight: z.number().min(0).max(1).default(1),
|
|
36
46
|
/** Weighted score = score * weight */
|
|
37
47
|
weighted_score: z.number().min(0).max(1).optional(),
|
|
@@ -75,9 +85,11 @@ export type DefaultCriterion = (typeof DEFAULT_CRITERIA)[number];
|
|
|
75
85
|
* Evaluation request arguments
|
|
76
86
|
*/
|
|
77
87
|
export const EvaluationRequestSchema = z.object({
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
bead_id: z.string(),
|
|
89
|
+
subtask_title: z.string(),
|
|
90
|
+
files_touched: z.array(z.string()),
|
|
91
|
+
/** ISO-8601 timestamp when evaluation was requested */
|
|
92
|
+
requested_at: z.string().datetime().optional(),
|
|
81
93
|
});
|
|
82
94
|
export type EvaluationRequest = z.infer<typeof EvaluationRequestSchema>;
|
|
83
95
|
|
package/src/schemas/index.ts
CHANGED
|
@@ -1,7 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Schema
|
|
2
|
+
* Schema Definitions - Central export point for all Zod schemas
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* This module re-exports all schema definitions used throughout the plugin.
|
|
5
|
+
* Schemas are organized by domain:
|
|
6
|
+
*
|
|
7
|
+
* ## Bead Schemas (Issue Tracking)
|
|
8
|
+
* - `BeadSchema` - Core bead/issue definition
|
|
9
|
+
* - `BeadStatusSchema` - Status enum (open, in_progress, blocked, closed)
|
|
10
|
+
* - `BeadTypeSchema` - Type enum (bug, feature, task, epic, chore)
|
|
11
|
+
* - `SubtaskSpecSchema` - Subtask specification for epic creation
|
|
12
|
+
*
|
|
13
|
+
* ## Task Schemas (Swarm Decomposition)
|
|
14
|
+
* - `TaskDecompositionSchema` - Full task breakdown
|
|
15
|
+
* - `DecomposedSubtaskSchema` - Individual subtask definition
|
|
16
|
+
* - `BeadTreeSchema` - Epic + subtasks structure
|
|
17
|
+
*
|
|
18
|
+
* ## Evaluation Schemas (Agent Self-Assessment)
|
|
19
|
+
* - `EvaluationSchema` - Complete evaluation with criteria
|
|
20
|
+
* - `CriterionEvaluationSchema` - Single criterion result
|
|
21
|
+
*
|
|
22
|
+
* ## Progress Schemas (Swarm Coordination)
|
|
23
|
+
* - `SwarmStatusSchema` - Overall swarm progress
|
|
24
|
+
* - `AgentProgressSchema` - Individual agent status
|
|
25
|
+
* - `SpawnedAgentSchema` - Spawned agent metadata
|
|
26
|
+
*
|
|
27
|
+
* @module schemas
|
|
5
28
|
*/
|
|
6
29
|
|
|
7
30
|
// Bead schemas
|
package/src/schemas/task.ts
CHANGED
|
@@ -7,13 +7,19 @@
|
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Effort estimation
|
|
10
|
+
* Effort estimation for subtasks.
|
|
11
|
+
*
|
|
12
|
+
* Time ranges:
|
|
13
|
+
* - `trivial`: < 5 minutes (simple rename, typo fix)
|
|
14
|
+
* - `small`: 5-30 minutes (single function, simple feature)
|
|
15
|
+
* - `medium`: 30 min - 2 hours (multi-file change, moderate complexity)
|
|
16
|
+
* - `large`: 2+ hours (significant feature, refactoring)
|
|
11
17
|
*/
|
|
12
18
|
export const EffortLevelSchema = z.enum([
|
|
13
|
-
"trivial",
|
|
14
|
-
"small",
|
|
15
|
-
"medium",
|
|
16
|
-
"large",
|
|
19
|
+
"trivial",
|
|
20
|
+
"small",
|
|
21
|
+
"medium",
|
|
22
|
+
"large",
|
|
17
23
|
]);
|
|
18
24
|
export type EffortLevel = z.infer<typeof EffortLevelSchema>;
|
|
19
25
|
|
|
@@ -35,6 +41,7 @@ export const DecomposedSubtaskSchema = z.object({
|
|
|
35
41
|
description: z.string(),
|
|
36
42
|
files: z.array(z.string()), // File paths this subtask will modify
|
|
37
43
|
estimated_effort: EffortLevelSchema,
|
|
44
|
+
/** Potential risks or complications (e.g., 'tight coupling', 'data migration required', 'breaking change') */
|
|
38
45
|
risks: z.array(z.string()).optional().default([]),
|
|
39
46
|
});
|
|
40
47
|
export type DecomposedSubtask = z.infer<typeof DecomposedSubtaskSchema>;
|
|
@@ -43,8 +50,10 @@ export type DecomposedSubtask = z.infer<typeof DecomposedSubtaskSchema>;
|
|
|
43
50
|
* Dependency between subtasks
|
|
44
51
|
*/
|
|
45
52
|
export const SubtaskDependencySchema = z.object({
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
/** Zero-based index of the dependency source subtask */
|
|
54
|
+
from: z.number().int().min(0),
|
|
55
|
+
/** Zero-based index of the dependency target subtask */
|
|
56
|
+
to: z.number().int().min(0),
|
|
48
57
|
type: DependencyTypeSchema,
|
|
49
58
|
});
|
|
50
59
|
export type SubtaskDependency = z.infer<typeof SubtaskDependencySchema>;
|
|
@@ -56,10 +65,15 @@ export type SubtaskDependency = z.infer<typeof SubtaskDependencySchema>;
|
|
|
56
65
|
*/
|
|
57
66
|
export const TaskDecompositionSchema = z.object({
|
|
58
67
|
task: z.string(), // Original task description
|
|
59
|
-
|
|
68
|
+
/** Rationale for this decomposition strategy (why these subtasks, why this order) */
|
|
69
|
+
reasoning: z.string().optional(),
|
|
60
70
|
subtasks: z.array(DecomposedSubtaskSchema).min(1),
|
|
61
71
|
dependencies: z.array(SubtaskDependencySchema).optional().default([]),
|
|
62
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Context shared with all spawned agents.
|
|
74
|
+
* Examples: API contracts, shared types, project conventions, architectural decisions.
|
|
75
|
+
*/
|
|
76
|
+
shared_context: z.string().optional(),
|
|
63
77
|
});
|
|
64
78
|
export type TaskDecomposition = z.infer<typeof TaskDecompositionSchema>;
|
|
65
79
|
|
|
@@ -78,11 +92,19 @@ export type DecomposeArgs = z.infer<typeof DecomposeArgsSchema>;
|
|
|
78
92
|
*/
|
|
79
93
|
export const SpawnedAgentSchema = z.object({
|
|
80
94
|
bead_id: z.string(),
|
|
81
|
-
|
|
95
|
+
/**
|
|
96
|
+
* Agent Mail assigned name (e.g., 'BlueLake', 'CrimsonRiver').
|
|
97
|
+
* Generated by Agent Mail on session init.
|
|
98
|
+
*/
|
|
99
|
+
agent_name: z.string(),
|
|
82
100
|
task_id: z.string().optional(), // OpenCode task ID
|
|
83
101
|
status: z.enum(["pending", "running", "completed", "failed"]),
|
|
84
102
|
files: z.array(z.string()), // Reserved files
|
|
85
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Agent Mail reservation IDs for file locking.
|
|
105
|
+
* Used to release locks on task completion via agentmail_release.
|
|
106
|
+
*/
|
|
107
|
+
reservation_ids: z.array(z.number()).optional(),
|
|
86
108
|
});
|
|
87
109
|
export type SpawnedAgent = z.infer<typeof SpawnedAgentSchema>;
|
|
88
110
|
|
|
@@ -101,16 +123,22 @@ export type SwarmSpawnResult = z.infer<typeof SwarmSpawnResultSchema>;
|
|
|
101
123
|
/**
|
|
102
124
|
* Progress update from an agent
|
|
103
125
|
*/
|
|
104
|
-
export const AgentProgressSchema = z
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
})
|
|
126
|
+
export const AgentProgressSchema = z
|
|
127
|
+
.object({
|
|
128
|
+
bead_id: z.string(),
|
|
129
|
+
agent_name: z.string(),
|
|
130
|
+
status: z.enum(["in_progress", "blocked", "completed", "failed"]),
|
|
131
|
+
progress_percent: z.number().min(0).max(100).optional(),
|
|
132
|
+
message: z.string().optional(),
|
|
133
|
+
files_touched: z.array(z.string()).optional(),
|
|
134
|
+
blockers: z.array(z.string()).optional(),
|
|
135
|
+
timestamp: z.string().datetime({ offset: true }), // ISO-8601 with timezone
|
|
136
|
+
})
|
|
137
|
+
.refine(
|
|
138
|
+
(data) =>
|
|
139
|
+
data.status !== "blocked" || (data.blockers && data.blockers.length > 0),
|
|
140
|
+
{ message: "blockers array required when status is 'blocked'" },
|
|
141
|
+
);
|
|
114
142
|
export type AgentProgress = z.infer<typeof AgentProgressSchema>;
|
|
115
143
|
|
|
116
144
|
/**
|
package/src/streams/debug.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Useful for debugging issues and understanding system behavior.
|
|
6
6
|
*/
|
|
7
7
|
import { getDatabase, getDatabaseStats } from "./index";
|
|
8
|
-
import { readEvents, getLatestSequence } from "./store";
|
|
8
|
+
import { readEvents, getLatestSequence, replayEventsBatched } from "./store";
|
|
9
9
|
import { getAgent, getActiveReservations, getMessage } from "./projections";
|
|
10
10
|
import type { AgentEvent } from "./events";
|
|
11
11
|
|
|
@@ -231,11 +231,27 @@ function getAgentFromEvent(event: AgentEvent): string {
|
|
|
231
231
|
|
|
232
232
|
/**
|
|
233
233
|
* Get recent events with filtering
|
|
234
|
+
*
|
|
235
|
+
* For large event logs (>100k events), consider using batchSize option
|
|
236
|
+
* to paginate through results instead of loading all events.
|
|
234
237
|
*/
|
|
235
238
|
export async function debugEvents(
|
|
236
|
-
options: DebugEventsOptions,
|
|
239
|
+
options: DebugEventsOptions & { batchSize?: number },
|
|
237
240
|
): Promise<DebugEventsResult> {
|
|
238
|
-
const {
|
|
241
|
+
const {
|
|
242
|
+
projectPath,
|
|
243
|
+
types,
|
|
244
|
+
agentName,
|
|
245
|
+
limit = 50,
|
|
246
|
+
since,
|
|
247
|
+
until,
|
|
248
|
+
batchSize,
|
|
249
|
+
} = options;
|
|
250
|
+
|
|
251
|
+
// If batchSize is specified, use pagination to avoid OOM
|
|
252
|
+
if (batchSize && batchSize > 0) {
|
|
253
|
+
return await debugEventsPaginated({ ...options, batchSize });
|
|
254
|
+
}
|
|
239
255
|
|
|
240
256
|
// Get all events first (we'll filter in memory for agent name)
|
|
241
257
|
const allEvents = await readEvents(
|
|
@@ -284,6 +300,88 @@ export async function debugEvents(
|
|
|
284
300
|
};
|
|
285
301
|
}
|
|
286
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Get events using pagination to avoid OOM on large logs
|
|
305
|
+
*/
|
|
306
|
+
async function debugEventsPaginated(
|
|
307
|
+
options: DebugEventsOptions & { batchSize: number },
|
|
308
|
+
): Promise<DebugEventsResult> {
|
|
309
|
+
const {
|
|
310
|
+
projectPath,
|
|
311
|
+
types,
|
|
312
|
+
agentName,
|
|
313
|
+
limit = 50,
|
|
314
|
+
since,
|
|
315
|
+
until,
|
|
316
|
+
batchSize,
|
|
317
|
+
} = options;
|
|
318
|
+
|
|
319
|
+
const allEvents: Array<AgentEvent & { id: number; sequence: number }> = [];
|
|
320
|
+
let offset = 0;
|
|
321
|
+
let hasMore = true;
|
|
322
|
+
|
|
323
|
+
// Fetch in batches until we have enough events or run out
|
|
324
|
+
while (hasMore && allEvents.length < limit) {
|
|
325
|
+
const batch = await readEvents(
|
|
326
|
+
{
|
|
327
|
+
projectKey: projectPath,
|
|
328
|
+
types,
|
|
329
|
+
since,
|
|
330
|
+
until,
|
|
331
|
+
limit: batchSize,
|
|
332
|
+
offset,
|
|
333
|
+
},
|
|
334
|
+
projectPath,
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
if (batch.length === 0) {
|
|
338
|
+
hasMore = false;
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Filter by agent name if specified
|
|
343
|
+
const filtered = agentName
|
|
344
|
+
? batch.filter((e) => {
|
|
345
|
+
if ("agent_name" in e && e.agent_name === agentName) return true;
|
|
346
|
+
if ("from_agent" in e && e.from_agent === agentName) return true;
|
|
347
|
+
if ("to_agents" in e && e.to_agents?.includes(agentName)) return true;
|
|
348
|
+
return false;
|
|
349
|
+
})
|
|
350
|
+
: batch;
|
|
351
|
+
|
|
352
|
+
allEvents.push(...filtered);
|
|
353
|
+
offset += batchSize;
|
|
354
|
+
|
|
355
|
+
console.log(
|
|
356
|
+
`[SwarmMail] Fetched ${allEvents.length} events (batch size: ${batchSize})`,
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Sort by sequence descending (most recent first)
|
|
361
|
+
allEvents.sort((a, b) => b.sequence - a.sequence);
|
|
362
|
+
|
|
363
|
+
// Apply limit
|
|
364
|
+
const limitedEvents = allEvents.slice(0, limit);
|
|
365
|
+
|
|
366
|
+
// Format for output
|
|
367
|
+
const events: DebugEventResult[] = limitedEvents.map((e) => {
|
|
368
|
+
const { id, sequence, type, timestamp, project_key, ...rest } = e;
|
|
369
|
+
return {
|
|
370
|
+
id,
|
|
371
|
+
sequence,
|
|
372
|
+
type,
|
|
373
|
+
timestamp,
|
|
374
|
+
timestamp_human: formatTimestamp(timestamp),
|
|
375
|
+
...rest,
|
|
376
|
+
};
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
events,
|
|
381
|
+
total: allEvents.length,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
287
385
|
/**
|
|
288
386
|
* Get detailed agent information
|
|
289
387
|
*/
|