opencode-swarm-plugin 0.38.0 → 0.40.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 (91) hide show
  1. package/.env +2 -0
  2. package/.hive/eval-results.json +26 -0
  3. package/.hive/issues.jsonl +27 -0
  4. package/.hive/memories.jsonl +23 -1
  5. package/.opencode/eval-history.jsonl +12 -0
  6. package/CHANGELOG.md +182 -0
  7. package/README.md +29 -12
  8. package/bin/swarm.test.ts +881 -0
  9. package/bin/swarm.ts +686 -0
  10. package/dist/compaction-hook.d.ts +8 -1
  11. package/dist/compaction-hook.d.ts.map +1 -1
  12. package/dist/compaction-observability.d.ts +173 -0
  13. package/dist/compaction-observability.d.ts.map +1 -0
  14. package/dist/compaction-prompt-scoring.d.ts +124 -0
  15. package/dist/compaction-prompt-scoring.d.ts.map +1 -0
  16. package/dist/eval-capture.d.ts +174 -1
  17. package/dist/eval-capture.d.ts.map +1 -1
  18. package/dist/eval-gates.d.ts +84 -0
  19. package/dist/eval-gates.d.ts.map +1 -0
  20. package/dist/eval-history.d.ts +117 -0
  21. package/dist/eval-history.d.ts.map +1 -0
  22. package/dist/eval-learning.d.ts +216 -0
  23. package/dist/eval-learning.d.ts.map +1 -0
  24. package/dist/hive.d.ts.map +1 -1
  25. package/dist/index.d.ts +80 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +16098 -651
  28. package/dist/plugin.js +16012 -756
  29. package/dist/post-compaction-tracker.d.ts +133 -0
  30. package/dist/post-compaction-tracker.d.ts.map +1 -0
  31. package/dist/schemas/task.d.ts +3 -3
  32. package/dist/swarm-orchestrate.d.ts +23 -0
  33. package/dist/swarm-orchestrate.d.ts.map +1 -1
  34. package/dist/swarm-prompts.d.ts +25 -1
  35. package/dist/swarm-prompts.d.ts.map +1 -1
  36. package/dist/swarm.d.ts +4 -0
  37. package/dist/swarm.d.ts.map +1 -1
  38. package/evals/README.md +702 -105
  39. package/evals/compaction-prompt.eval.ts +149 -0
  40. package/evals/coordinator-behavior.eval.ts +8 -8
  41. package/evals/fixtures/compaction-prompt-cases.ts +305 -0
  42. package/evals/lib/compaction-loader.test.ts +248 -0
  43. package/evals/lib/compaction-loader.ts +320 -0
  44. package/evals/lib/data-loader.test.ts +345 -0
  45. package/evals/lib/data-loader.ts +107 -6
  46. package/evals/scorers/compaction-prompt-scorers.ts +145 -0
  47. package/evals/scorers/compaction-scorers.ts +13 -13
  48. package/evals/scorers/coordinator-discipline.evalite-test.ts +166 -2
  49. package/evals/scorers/coordinator-discipline.ts +348 -15
  50. package/evals/scorers/index.test.ts +146 -0
  51. package/evals/scorers/index.ts +104 -0
  52. package/evals/swarm-decomposition.eval.ts +9 -2
  53. package/examples/commands/swarm.md +291 -21
  54. package/examples/plugin-wrapper-template.ts +117 -0
  55. package/package.json +7 -5
  56. package/scripts/migrate-unknown-sessions.ts +349 -0
  57. package/src/compaction-capture.integration.test.ts +257 -0
  58. package/src/compaction-hook.test.ts +42 -0
  59. package/src/compaction-hook.ts +315 -86
  60. package/src/compaction-observability.integration.test.ts +139 -0
  61. package/src/compaction-observability.test.ts +187 -0
  62. package/src/compaction-observability.ts +324 -0
  63. package/src/compaction-prompt-scorers.test.ts +299 -0
  64. package/src/compaction-prompt-scoring.ts +298 -0
  65. package/src/eval-capture.test.ts +626 -1
  66. package/src/eval-capture.ts +286 -2
  67. package/src/eval-gates.test.ts +306 -0
  68. package/src/eval-gates.ts +218 -0
  69. package/src/eval-history.test.ts +508 -0
  70. package/src/eval-history.ts +214 -0
  71. package/src/eval-learning.test.ts +378 -0
  72. package/src/eval-learning.ts +360 -0
  73. package/src/eval-runner.test.ts +96 -0
  74. package/src/eval-runner.ts +356 -0
  75. package/src/hive.ts +34 -0
  76. package/src/index.ts +115 -2
  77. package/src/memory.test.ts +110 -0
  78. package/src/memory.ts +34 -0
  79. package/src/post-compaction-tracker.test.ts +251 -0
  80. package/src/post-compaction-tracker.ts +237 -0
  81. package/src/swarm-decompose.ts +2 -2
  82. package/src/swarm-orchestrate.ts +2 -2
  83. package/src/swarm-prompts.ts +2 -2
  84. package/src/swarm-review.ts +3 -3
  85. package/dist/beads.d.ts +0 -386
  86. package/dist/beads.d.ts.map +0 -1
  87. package/dist/schemas/bead-events.d.ts +0 -698
  88. package/dist/schemas/bead-events.d.ts.map +0 -1
  89. package/dist/schemas/bead.d.ts +0 -255
  90. package/dist/schemas/bead.d.ts.map +0 -1
  91. /package/evals/{evalite.config.ts → evalite.config.ts.bak} +0 -0
package/src/memory.ts CHANGED
@@ -129,6 +129,39 @@ export interface OperationResult {
129
129
  readonly message?: string;
130
130
  }
131
131
 
132
+ /** Arguments for upsert operation */
133
+ export interface UpsertArgs {
134
+ readonly information: string;
135
+ readonly collection?: string;
136
+ readonly tags?: string;
137
+ readonly metadata?: string;
138
+ readonly confidence?: number;
139
+ /** Auto-generate tags using LLM. Default true */
140
+ readonly autoTag?: boolean;
141
+ /** Auto-link to related memories. Default true */
142
+ readonly autoLink?: boolean;
143
+ /** Extract entities (people, places, technologies). Default false */
144
+ readonly extractEntities?: boolean;
145
+ }
146
+
147
+ /** Auto-generated tags result */
148
+ export interface AutoTags {
149
+ readonly tags: string[];
150
+ readonly keywords: string[];
151
+ readonly category: string;
152
+ }
153
+
154
+ /** Result from upsert operation */
155
+ export interface UpsertResult {
156
+ readonly operation: "ADD" | "UPDATE" | "DELETE" | "NOOP";
157
+ readonly reason: string;
158
+ readonly memoryId?: string;
159
+ readonly affectedMemoryIds?: string[];
160
+ readonly autoTags?: AutoTags;
161
+ readonly linksCreated?: number;
162
+ readonly entitiesExtracted?: number;
163
+ }
164
+
132
165
  // ============================================================================
133
166
  // Auto-Migration Logic
134
167
  // ============================================================================
@@ -206,6 +239,7 @@ export interface MemoryAdapter {
206
239
  readonly list: (args: ListArgs) => Promise<Memory[]>;
207
240
  readonly stats: () => Promise<StatsResult>;
208
241
  readonly checkHealth: () => Promise<HealthResult>;
242
+ readonly upsert: (args: UpsertArgs) => Promise<UpsertResult>;
209
243
  }
210
244
 
211
245
  /**
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Post-Compaction Tool Call Tracker Tests
3
+ *
4
+ * TDD: RED → GREEN → REFACTOR
5
+ *
6
+ * Tests tracking of tool calls after compaction resumption.
7
+ * Emits resumption_started on first tool call, then tool_call_tracked for each call (max 20).
8
+ * Detects coordinator violations: Edit, Write, swarmmail_reserve are forbidden.
9
+ */
10
+ import { describe, test, expect, beforeEach, mock } from "bun:test";
11
+ import {
12
+ createPostCompactionTracker,
13
+ type PostCompactionTracker,
14
+ type ToolCallEvent,
15
+ } from "./post-compaction-tracker";
16
+
17
+ describe("PostCompactionTracker - TDD", () => {
18
+ let tracker: PostCompactionTracker;
19
+ let mockCapture: ReturnType<typeof mock>;
20
+
21
+ beforeEach(() => {
22
+ mockCapture = mock((event: any) => {});
23
+ tracker = createPostCompactionTracker({
24
+ sessionId: "test-session",
25
+ epicId: "mjkwehsqnbm",
26
+ onEvent: mockCapture,
27
+ });
28
+ });
29
+
30
+ // ============================================================================
31
+ // RED: Test resumption_started event
32
+ // ============================================================================
33
+
34
+ test("emits resumption_started on first tool call", () => {
35
+ const toolCall: ToolCallEvent = {
36
+ tool: "read",
37
+ args: { filePath: "/test/file.ts" },
38
+ timestamp: Date.now(),
39
+ };
40
+
41
+ tracker.trackToolCall(toolCall);
42
+
43
+ expect(mockCapture).toHaveBeenCalledTimes(2); // resumption_started + tool_call_tracked
44
+ const firstCall = mockCapture.mock.calls[0][0];
45
+ expect(firstCall.compaction_type).toBe("resumption_started");
46
+ expect(firstCall.payload.session_id).toBe("test-session");
47
+ expect(firstCall.payload.epic_id).toBe("mjkwehsqnbm");
48
+ });
49
+
50
+ test("resumption_started only emitted once", () => {
51
+ tracker.trackToolCall({
52
+ tool: "read",
53
+ args: {},
54
+ timestamp: Date.now(),
55
+ });
56
+ tracker.trackToolCall({
57
+ tool: "glob",
58
+ args: {},
59
+ timestamp: Date.now(),
60
+ });
61
+
62
+ // First call: resumption_started + tool_call_tracked
63
+ // Second call: tool_call_tracked only
64
+ expect(mockCapture).toHaveBeenCalledTimes(3);
65
+
66
+ const calls = mockCapture.mock.calls;
67
+ expect(calls[0][0].compaction_type).toBe("resumption_started");
68
+ expect(calls[1][0].compaction_type).toBe("tool_call_tracked");
69
+ expect(calls[2][0].compaction_type).toBe("tool_call_tracked");
70
+ });
71
+
72
+ // ============================================================================
73
+ // RED: Test tool_call_tracked event
74
+ // ============================================================================
75
+
76
+ test("emits tool_call_tracked for each of first 20 calls", () => {
77
+ for (let i = 0; i < 20; i++) {
78
+ tracker.trackToolCall({
79
+ tool: `tool-${i}`,
80
+ args: {},
81
+ timestamp: Date.now(),
82
+ });
83
+ }
84
+
85
+ // First call: resumption_started + tool_call_tracked = 2
86
+ // Next 19 calls: tool_call_tracked only = 19
87
+ // Total: 21 events (1 resumption_started + 20 tool_call_tracked)
88
+ expect(mockCapture).toHaveBeenCalledTimes(21);
89
+
90
+ const trackedEvents = mockCapture.mock.calls.filter(
91
+ (call: any) => call[0].compaction_type === "tool_call_tracked",
92
+ );
93
+ expect(trackedEvents).toHaveLength(20);
94
+ });
95
+
96
+ test("tool_call_tracked includes tool name and args", () => {
97
+ tracker.trackToolCall({
98
+ tool: "edit",
99
+ args: { filePath: "/test.ts", oldString: "foo", newString: "bar" },
100
+ timestamp: Date.now(),
101
+ });
102
+
103
+ const trackedEvent = mockCapture.mock.calls.find(
104
+ (call: any) => call[0].compaction_type === "tool_call_tracked",
105
+ )?.[0];
106
+
107
+ expect(trackedEvent).toBeDefined();
108
+ expect(trackedEvent.payload.tool).toBe("edit");
109
+ expect(trackedEvent.payload.args.filePath).toBe("/test.ts");
110
+ expect(trackedEvent.payload.call_number).toBe(1);
111
+ });
112
+
113
+ // ============================================================================
114
+ // RED: Test coordinator violation detection
115
+ // ============================================================================
116
+
117
+ test("detects Edit as coordinator violation", () => {
118
+ tracker.trackToolCall({
119
+ tool: "edit",
120
+ args: { filePath: "/test.ts", oldString: "a", newString: "b" },
121
+ timestamp: Date.now(),
122
+ });
123
+
124
+ const trackedEvent = mockCapture.mock.calls.find(
125
+ (call: any) => call[0].compaction_type === "tool_call_tracked",
126
+ )?.[0];
127
+
128
+ expect(trackedEvent.payload.is_coordinator_violation).toBe(true);
129
+ expect(trackedEvent.payload.violation_reason).toBe(
130
+ "Coordinators NEVER edit files - spawn worker instead",
131
+ );
132
+ });
133
+
134
+ test("detects Write as coordinator violation", () => {
135
+ tracker.trackToolCall({
136
+ tool: "write",
137
+ args: { filePath: "/new.ts", content: "export {}" },
138
+ timestamp: Date.now(),
139
+ });
140
+
141
+ const trackedEvent = mockCapture.mock.calls.find(
142
+ (call: any) => call[0].compaction_type === "tool_call_tracked",
143
+ )?.[0];
144
+
145
+ expect(trackedEvent.payload.is_coordinator_violation).toBe(true);
146
+ expect(trackedEvent.payload.violation_reason).toBe(
147
+ "Coordinators NEVER write files - spawn worker instead",
148
+ );
149
+ });
150
+
151
+ test("detects swarmmail_reserve as coordinator violation", () => {
152
+ tracker.trackToolCall({
153
+ tool: "swarmmail_reserve",
154
+ args: { paths: ["/src/**"], reason: "test" },
155
+ timestamp: Date.now(),
156
+ });
157
+
158
+ const trackedEvent = mockCapture.mock.calls.find(
159
+ (call: any) => call[0].compaction_type === "tool_call_tracked",
160
+ )?.[0];
161
+
162
+ expect(trackedEvent.payload.is_coordinator_violation).toBe(true);
163
+ expect(trackedEvent.payload.violation_reason).toBe(
164
+ "Coordinators NEVER reserve files - workers reserve files",
165
+ );
166
+ });
167
+
168
+ test("does not flag Read as violation", () => {
169
+ tracker.trackToolCall({
170
+ tool: "read",
171
+ args: { filePath: "/test.ts" },
172
+ timestamp: Date.now(),
173
+ });
174
+
175
+ const trackedEvent = mockCapture.mock.calls.find(
176
+ (call: any) => call[0].compaction_type === "tool_call_tracked",
177
+ )?.[0];
178
+
179
+ expect(trackedEvent.payload.is_coordinator_violation).toBe(false);
180
+ expect(trackedEvent.payload.violation_reason).toBeUndefined();
181
+ });
182
+
183
+ test("does not flag swarm_spawn_subtask as violation", () => {
184
+ tracker.trackToolCall({
185
+ tool: "swarm_spawn_subtask",
186
+ args: { bead_id: "bd-123", subtask_title: "Test" },
187
+ timestamp: Date.now(),
188
+ });
189
+
190
+ const trackedEvent = mockCapture.mock.calls.find(
191
+ (call: any) => call[0].compaction_type === "tool_call_tracked",
192
+ )?.[0];
193
+
194
+ expect(trackedEvent.payload.is_coordinator_violation).toBe(false);
195
+ });
196
+
197
+ // ============================================================================
198
+ // RED: Test tracking stops after 20 calls
199
+ // ============================================================================
200
+
201
+ test("stops tracking after 20 calls", () => {
202
+ for (let i = 0; i < 25; i++) {
203
+ tracker.trackToolCall({
204
+ tool: `tool-${i}`,
205
+ args: {},
206
+ timestamp: Date.now(),
207
+ });
208
+ }
209
+
210
+ // Should only track first 20: 1 resumption_started + 20 tool_call_tracked
211
+ expect(mockCapture).toHaveBeenCalledTimes(21);
212
+ });
213
+
214
+ test("returns tracking status", () => {
215
+ expect(tracker.isTracking()).toBe(true);
216
+
217
+ for (let i = 0; i < 20; i++) {
218
+ tracker.trackToolCall({
219
+ tool: `tool-${i}`,
220
+ args: {},
221
+ timestamp: Date.now(),
222
+ });
223
+ }
224
+
225
+ expect(tracker.isTracking()).toBe(false);
226
+ });
227
+
228
+ // ============================================================================
229
+ // RED: Test configurable limit
230
+ // ============================================================================
231
+
232
+ test("respects custom call limit", () => {
233
+ const customTracker = createPostCompactionTracker({
234
+ sessionId: "test",
235
+ epicId: "test",
236
+ onEvent: mockCapture,
237
+ maxCalls: 5,
238
+ });
239
+
240
+ for (let i = 0; i < 10; i++) {
241
+ customTracker.trackToolCall({
242
+ tool: `tool-${i}`,
243
+ args: {},
244
+ timestamp: Date.now(),
245
+ });
246
+ }
247
+
248
+ // 1 resumption_started + 5 tool_call_tracked
249
+ expect(mockCapture).toHaveBeenCalledTimes(6);
250
+ });
251
+ });
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Post-Compaction Tool Call Tracker
3
+ *
4
+ * Tracks tool calls after compaction resumption to detect coordinator violations
5
+ * and provide learning signals for eval-driven development.
6
+ *
7
+ * ## Purpose
8
+ *
9
+ * When context is compacted, the continuation agent needs observation to learn
10
+ * if it's following coordinator discipline. This tracker:
11
+ *
12
+ * 1. Emits resumption_started on first tool call (marks compaction exit)
13
+ * 2. Tracks up to N tool calls (default 20) with violation detection
14
+ * 3. Stops tracking after limit to avoid noise in long sessions
15
+ *
16
+ * ## Coordinator Violations Detected
17
+ *
18
+ * - **Edit/Write**: Coordinators NEVER edit files - spawn worker instead
19
+ * - **swarmmail_reserve/agentmail_reserve**: Workers reserve, not coordinators
20
+ *
21
+ * ## Integration
22
+ *
23
+ * Used by compaction hook to wire tool.call events → eval capture.
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const tracker = createPostCompactionTracker({
28
+ * sessionId: "session-123",
29
+ * epicId: "bd-epic-456",
30
+ * onEvent: captureCompactionEvent,
31
+ * });
32
+ *
33
+ * // Wire to OpenCode hook
34
+ * hooks["tool.call"] = (input) => {
35
+ * tracker.trackToolCall({
36
+ * tool: input.tool,
37
+ * args: input.args,
38
+ * timestamp: Date.now(),
39
+ * });
40
+ * };
41
+ * ```
42
+ */
43
+
44
+ /**
45
+ * Tool call event structure
46
+ */
47
+ export interface ToolCallEvent {
48
+ tool: string;
49
+ args: Record<string, unknown>;
50
+ timestamp: number;
51
+ }
52
+
53
+ /**
54
+ * Compaction event payload (matches eval-capture.ts structure)
55
+ */
56
+ export interface CompactionEvent {
57
+ session_id: string;
58
+ epic_id: string;
59
+ compaction_type:
60
+ | "detection_complete"
61
+ | "prompt_generated"
62
+ | "context_injected"
63
+ | "resumption_started"
64
+ | "tool_call_tracked";
65
+ payload: {
66
+ session_id?: string;
67
+ epic_id?: string;
68
+ tool?: string;
69
+ args?: Record<string, unknown>;
70
+ call_number?: number;
71
+ is_coordinator_violation?: boolean;
72
+ violation_reason?: string;
73
+ timestamp?: number;
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Tracker configuration
79
+ */
80
+ export interface PostCompactionTrackerConfig {
81
+ sessionId: string;
82
+ epicId: string;
83
+ onEvent: (event: CompactionEvent) => void;
84
+ maxCalls?: number;
85
+ }
86
+
87
+ /**
88
+ * Post-compaction tracker instance
89
+ */
90
+ export interface PostCompactionTracker {
91
+ trackToolCall(event: ToolCallEvent): void;
92
+ isTracking(): boolean;
93
+ }
94
+
95
+ // ============================================================================
96
+ // Constants
97
+ // ============================================================================
98
+
99
+ /**
100
+ * Default maximum number of tool calls to track
101
+ *
102
+ * Chosen to balance:
103
+ * - Enough data for pattern detection (20 calls is ~2-3 minutes of coordinator work)
104
+ * - Avoiding noise pollution in long sessions
105
+ */
106
+ export const DEFAULT_MAX_TRACKED_CALLS = 20;
107
+
108
+ // ============================================================================
109
+ // Coordinator Violation Detection
110
+ // ============================================================================
111
+
112
+ /**
113
+ * Tools that coordinators are NEVER allowed to use
114
+ *
115
+ * Key insight from semantic memory: coordinators lose identity after compaction
116
+ * and start doing implementation work. These violations are observable signals
117
+ * that the coordinator mandate wasn't preserved in continuation prompt.
118
+ */
119
+ const FORBIDDEN_COORDINATOR_TOOLS: Record<string, string> = {
120
+ edit: "Coordinators NEVER edit files - spawn worker instead",
121
+ write: "Coordinators NEVER write files - spawn worker instead",
122
+ swarmmail_reserve: "Coordinators NEVER reserve files - workers reserve files",
123
+ agentmail_reserve: "Coordinators NEVER reserve files - workers reserve files",
124
+ };
125
+
126
+ /**
127
+ * Check if tool call is a coordinator violation
128
+ *
129
+ * @param tool - Tool name from OpenCode tool.call hook
130
+ * @returns Violation status with reason if forbidden
131
+ *
132
+ * @example
133
+ * ```typescript
134
+ * const result = isCoordinatorViolation("edit");
135
+ * // { isViolation: true, reason: "Coordinators NEVER edit..." }
136
+ *
137
+ * const result = isCoordinatorViolation("read");
138
+ * // { isViolation: false }
139
+ * ```
140
+ */
141
+ export function isCoordinatorViolation(tool: string): {
142
+ isViolation: boolean;
143
+ reason?: string;
144
+ } {
145
+ const reason = FORBIDDEN_COORDINATOR_TOOLS[tool];
146
+ return {
147
+ isViolation: !!reason,
148
+ reason,
149
+ };
150
+ }
151
+
152
+ // ============================================================================
153
+ // Tracker Factory
154
+ // ============================================================================
155
+
156
+ /**
157
+ * Create a post-compaction tool call tracker
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * const tracker = createPostCompactionTracker({
162
+ * sessionId: "session-123",
163
+ * epicId: "bd-epic-456",
164
+ * onEvent: (event) => captureCompactionEvent(event),
165
+ * maxCalls: 20
166
+ * });
167
+ *
168
+ * // Track tool calls
169
+ * tracker.trackToolCall({
170
+ * tool: "read",
171
+ * args: { filePath: "/test.ts" },
172
+ * timestamp: Date.now()
173
+ * });
174
+ * ```
175
+ */
176
+ export function createPostCompactionTracker(
177
+ config: PostCompactionTrackerConfig,
178
+ ): PostCompactionTracker {
179
+ const {
180
+ sessionId,
181
+ epicId,
182
+ onEvent,
183
+ maxCalls = DEFAULT_MAX_TRACKED_CALLS,
184
+ } = config;
185
+
186
+ let callCount = 0;
187
+ let resumptionEmitted = false;
188
+
189
+ return {
190
+ trackToolCall(event: ToolCallEvent): void {
191
+ // Stop tracking after max calls reached
192
+ if (callCount >= maxCalls) {
193
+ return;
194
+ }
195
+
196
+ // Emit resumption_started on first call
197
+ if (!resumptionEmitted) {
198
+ onEvent({
199
+ session_id: sessionId,
200
+ epic_id: epicId,
201
+ compaction_type: "resumption_started",
202
+ payload: {
203
+ session_id: sessionId,
204
+ epic_id: epicId,
205
+ timestamp: event.timestamp,
206
+ },
207
+ });
208
+ resumptionEmitted = true;
209
+ }
210
+
211
+ // Increment before emitting so call_number is 1-based
212
+ callCount++;
213
+
214
+ // Check for coordinator violations
215
+ const violation = isCoordinatorViolation(event.tool);
216
+
217
+ // Emit tool_call_tracked event
218
+ onEvent({
219
+ session_id: sessionId,
220
+ epic_id: epicId,
221
+ compaction_type: "tool_call_tracked",
222
+ payload: {
223
+ tool: event.tool,
224
+ args: event.args,
225
+ call_number: callCount,
226
+ is_coordinator_violation: violation.isViolation,
227
+ violation_reason: violation.reason,
228
+ timestamp: event.timestamp,
229
+ },
230
+ });
231
+ },
232
+
233
+ isTracking(): boolean {
234
+ return callCount < maxCalls;
235
+ },
236
+ };
237
+ }
@@ -753,7 +753,7 @@ export const swarm_delegate_planning = tool({
753
753
  .default(true)
754
754
  .describe("Query CASS for similar past tasks (default: true)"),
755
755
  },
756
- async execute(args) {
756
+ async execute(args, _ctx) {
757
757
  // Import needed modules
758
758
  const { selectStrategy, formatStrategyGuidelines } =
759
759
  await import("./swarm-strategies");
@@ -777,7 +777,7 @@ export const swarm_delegate_planning = tool({
777
777
  // Capture strategy selection decision
778
778
  try {
779
779
  captureCoordinatorEvent({
780
- session_id: process.env.OPENCODE_SESSION_ID || "unknown",
780
+ session_id: _ctx.sessionID || "unknown",
781
781
  epic_id: "planning", // No epic ID yet - this is pre-decomposition
782
782
  timestamp: new Date().toISOString(),
783
783
  event_type: "DECISION",
@@ -1740,7 +1740,7 @@ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
1740
1740
  try {
1741
1741
  const durationMs = args.start_time ? Date.now() - args.start_time : 0;
1742
1742
  captureCoordinatorEvent({
1743
- session_id: process.env.OPENCODE_SESSION_ID || "unknown",
1743
+ session_id: _ctx.sessionID || "unknown",
1744
1744
  epic_id: epicId,
1745
1745
  timestamp: new Date().toISOString(),
1746
1746
  event_type: "OUTCOME",
@@ -1849,7 +1849,7 @@ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
1849
1849
  try {
1850
1850
  const durationMs = args.start_time ? Date.now() - args.start_time : 0;
1851
1851
  captureCoordinatorEvent({
1852
- session_id: process.env.OPENCODE_SESSION_ID || "unknown",
1852
+ session_id: _ctx.sessionID || "unknown",
1853
1853
  epic_id: epicId,
1854
1854
  timestamp: new Date().toISOString(),
1855
1855
  event_type: "OUTCOME",
@@ -1358,7 +1358,7 @@ export const swarm_spawn_subtask = tool({
1358
1358
  .optional()
1359
1359
  .describe("Optional explicit model override (auto-selected if not provided)"),
1360
1360
  },
1361
- async execute(args) {
1361
+ async execute(args, _ctx) {
1362
1362
  const prompt = formatSubtaskPromptV2({
1363
1363
  bead_id: args.bead_id,
1364
1364
  epic_id: args.epic_id,
@@ -1404,7 +1404,7 @@ export const swarm_spawn_subtask = tool({
1404
1404
  // Capture worker spawn decision
1405
1405
  try {
1406
1406
  captureCoordinatorEvent({
1407
- session_id: process.env.OPENCODE_SESSION_ID || "unknown",
1407
+ session_id: _ctx.sessionID || "unknown",
1408
1408
  epic_id: args.epic_id,
1409
1409
  timestamp: new Date().toISOString(),
1410
1410
  event_type: "DECISION",
@@ -470,7 +470,7 @@ export const swarm_review_feedback = tool({
470
470
  .optional()
471
471
  .describe("JSON array of ReviewIssue objects (for needs_changes)"),
472
472
  },
473
- async execute(args): Promise<string> {
473
+ async execute(args, _ctx): Promise<string> {
474
474
  // Parse issues if provided
475
475
  let parsedIssues: ReviewIssue[] = [];
476
476
  if (args.issues) {
@@ -512,7 +512,7 @@ export const swarm_review_feedback = tool({
512
512
  // Capture review approval decision
513
513
  try {
514
514
  captureCoordinatorEvent({
515
- session_id: process.env.OPENCODE_SESSION_ID || "unknown",
515
+ session_id: _ctx.sessionID || "unknown",
516
516
  epic_id: epicId,
517
517
  timestamp: new Date().toISOString(),
518
518
  event_type: "DECISION",
@@ -562,7 +562,7 @@ You may now complete the task with \`swarm_complete\`.`,
562
562
  // Capture review rejection decision
563
563
  try {
564
564
  captureCoordinatorEvent({
565
- session_id: process.env.OPENCODE_SESSION_ID || "unknown",
565
+ session_id: _ctx.sessionID || "unknown",
566
566
  epic_id: epicId,
567
567
  timestamp: new Date().toISOString(),
568
568
  event_type: "DECISION",