@veedubin/boomerang-v3 0.3.4 → 0.4.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.
@@ -18,7 +18,6 @@ permission:
18
18
  tool:
19
19
  "memini-ai-dev_*": allow
20
20
  "searxng_*": allow
21
- "sequential-thinking_*": allow
22
21
  "markitdown_*": allow
23
22
  "github-mcp_*": allow
24
23
  "playwright_*": allow
@@ -18,7 +18,6 @@ permission:
18
18
  tool:
19
19
  "memini-ai-dev_*": allow
20
20
  "searxng_*": allow
21
- "sequential-thinking_*": allow
22
21
  "markitdown_*": allow
23
22
  "github-mcp_*": allow
24
23
  "playwright_*": allow
@@ -49,7 +48,7 @@ You are the **Boomerang Architect** - the authority on design decisions, archite
49
48
  ## MANDATORY MEMORY PROTOCOL
50
49
 
51
50
  1. **Query memini-ai FIRST** - `memini-ai-dev_query_memories` for previous decisions
52
- 2. **Use sequential-thinking** - `sequential-thinking_sequentialthinking` for complex analysis
51
+ 2. **Use thought chains** - `memini-ai-dev_add_thought` for complex analysis
53
52
  3. **Query knowledge graph** - `memini-ai-dev_query_kg` for entity relationships
54
53
  4. **Save when complete** - `memini-ai-dev_add_memory` with key decisions
55
54
 
@@ -18,7 +18,6 @@ permission:
18
18
  tool:
19
19
  "memini-ai-dev_*": allow
20
20
  "searxng_*": allow
21
- "sequential-thinking_*": allow
22
21
  "markitdown_*": allow
23
22
  "github-mcp_*": allow
24
23
  "playwright_*": allow
@@ -49,7 +48,7 @@ Implement features, fix bugs, and write tests efficiently using the Context Pack
49
48
  ## MANDATORY MEMORY PROTOCOL
50
49
 
51
50
  1. **Query memini-ai FIRST** - Call `memini-ai-dev_query_memories` before doing ANY work
52
- 2. **Use sequential-thinking** - Call `sequential-thinking_sequentialthinking` for complex tasks
51
+ 2. **Use thought chains** - Call `memini-ai-dev_add_thought` for complex tasks
53
52
  3. **Save when complete** - Call `memini-ai-dev_add_memory` with a summary of your work
54
53
 
55
54
  ## Context Requirements
@@ -1,7 +1,7 @@
1
1
  ---
2
- description: Boomerang Explorer v3 - Fast file finding using devstral-2:cloud (Ollama Cloud) with memini-ai semantic search.
2
+ description: Boomerang Explorer v3 - Fast file finding using devstral-2:123b-cloud (Ollama Cloud) with memini-ai semantic search.
3
3
  mode: subagent
4
- model: ollama-cloud/devstral-2:cloud
4
+ model: ollama-cloud/devstral-2:123b-cloud
5
5
  steps: 30
6
6
  permission:
7
7
  read:
@@ -18,7 +18,6 @@ permission:
18
18
  tool:
19
19
  "memini-ai-dev_*": allow
20
20
  "searxng_*": allow
21
- "sequential-thinking_*": allow
22
21
  "markitdown_*": allow
23
22
  "github-mcp_*": allow
24
23
  "playwright_*": allow
@@ -18,7 +18,6 @@ permission:
18
18
  tool:
19
19
  "memini-ai-dev_*": allow
20
20
  "searxng_*": allow
21
- "sequential-thinking_*": allow
22
21
  "markitdown_*": allow
23
22
  "github-mcp_*": allow
24
23
  "playwright_*": allow
@@ -18,7 +18,6 @@ permission:
18
18
  tool:
19
19
  "memini-ai-dev_*": allow
20
20
  "searxng_*": allow
21
- "sequential-thinking_*": allow
22
21
  "markitdown_*": allow
23
22
  "github-mcp_*": allow
24
23
  "playwright_*": allow
@@ -18,7 +18,6 @@ permission:
18
18
  tool:
19
19
  "memini-ai-dev_*": allow
20
20
  "searxng_*": allow
21
- "sequential-thinking_*": allow
22
21
  "markitdown_*": allow
23
22
  "github-mcp_*": allow
24
23
  "playwright_*": allow
@@ -18,7 +18,6 @@ permission:
18
18
  tool:
19
19
  "memini-ai-dev_*": allow
20
20
  "searxng_*": allow
21
- "sequential-thinking_*": allow
22
21
  "markitdown_*": allow
23
22
  "github-mcp_*": allow
24
23
  "playwright_*": allow
@@ -18,7 +18,6 @@ permission:
18
18
  tool:
19
19
  "memini-ai-dev_*": allow
20
20
  "searxng_*": allow
21
- "sequential-thinking_*": allow
22
21
  "markitdown_*": allow
23
22
  "github-mcp_*": allow
24
23
  "playwright_*": allow
@@ -18,7 +18,6 @@ permission:
18
18
  tool:
19
19
  "memini-ai-dev_*": allow
20
20
  "searxng_*": allow
21
- "sequential-thinking_*": allow
22
21
  "markitdown_*": allow
23
22
  "github-mcp_*": allow
24
23
  "playwright_*": allow
@@ -18,7 +18,6 @@ permission:
18
18
  tool:
19
19
  "memini-ai-dev_*": allow
20
20
  "searxng_*": allow
21
- "sequential-thinking_*": allow
22
21
  "markitdown_*": allow
23
22
  "github-mcp_*": allow
24
23
  "playwright_*": allow
@@ -1,7 +1,7 @@
1
1
  ---
2
- description: Boomerang Writer v3 - Documentation specialist using gemma4:cloud (Ollama Cloud) with memini-ai for context.
2
+ description: Boomerang Writer v3 - Documentation specialist using gemma4:31b-cloud (Ollama Cloud) with memini-ai for context.
3
3
  mode: subagent
4
- model: ollama-cloud/gemma4:cloud
4
+ model: ollama-cloud/gemma4:31b-cloud
5
5
  steps: 40
6
6
  permission:
7
7
  read:
@@ -18,7 +18,6 @@ permission:
18
18
  tool:
19
19
  "memini-ai-dev_*": allow
20
20
  "searxng_*": allow
21
- "sequential-thinking_*": allow
22
21
  "markitdown_*": allow
23
22
  "github-mcp_*": allow
24
23
  "playwright_*": allow
@@ -18,7 +18,6 @@ permission:
18
18
  tool:
19
19
  "memini-ai-dev_*": allow
20
20
  "searxng_*": allow
21
- "sequential-thinking_*": allow
22
21
  "markitdown_*": allow
23
22
  "github-mcp_*": allow
24
23
  "playwright_*": allow
@@ -72,8 +71,8 @@ You are the **Boomerang v3 Orchestrator** - the central coordinator using memini
72
71
  Immediately call `memini-ai-dev_query_memories` with the user's request.
73
72
  Do not write any text before calling this tool.
74
73
 
75
- ### STEP 2: Use sequential thinking (MANDATORY SECOND ACTION)
76
- Immediately call `sequential-thinking_sequentialthinking` with your analysis.
74
+ ### STEP 2: Use thought chains (MANDATORY SECOND ACTION)
75
+ Immediately call `memini-ai-dev_add_thought` with your analysis. Note: This creates a `thinkingChainId` that must be passed to sub-agents in their Context Package.
77
76
 
78
77
  ### STEP 3: Plan (MANDATORY unless explicitly waived)
79
78
  Create an implementation plan UNLESS user says "skip planning", "just do it", "/boomerang-handoff", "do a handoff", or "no plan needed".
@@ -18,7 +18,6 @@ permission:
18
18
  tool:
19
19
  "memini-ai-dev_*": allow
20
20
  "searxng_*": allow
21
- "sequential-thinking_*": allow
22
21
  "markitdown_*": allow
23
22
  "github-mcp_*": allow
24
23
  "playwright_*": allow
@@ -18,7 +18,6 @@ permission:
18
18
  tool:
19
19
  "memini-ai-dev_*": allow
20
20
  "searxng_*": allow
21
- "sequential-thinking_*": allow
22
21
  "markitdown_*": allow
23
22
  "github-mcp_*": allow
24
23
  "playwright_*": allow
package/AGENTS.md CHANGED
@@ -19,11 +19,11 @@ Failure to use memini-ai causes context loss, duplicate work, and wasted tokens.
19
19
  | **boomerang** | boomerang-orchestrator | kimi-k2.6:cloud | Specifically built for swarm-based task orchestration and proactive autonomous delegation. |
20
20
  | **boomerang-coder** | boomerang-coder | glm-5.1:cloud | Flagship for agentic engineering; achieves SOTA on SWE-Bench Pro for complex, multi-file generation. |
21
21
  | **boomerang-architect** | boomerang-architect | deepseek-v4-pro:cloud | Offers frontier reasoning with dedicated "thinking modes" for analyzing complex architectural trade-offs. |
22
- | **boomerang-explorer** | boomerang-explorer | devstral-2:cloud | Explicitly designed to navigate codebases, trace dependencies, and map repository structures. |
22
+ | **boomerang-explorer** | boomerang-explorer | devstral-2:123b-cloud | Explicitly designed to navigate codebases, trace dependencies, and map repository structures. |
23
23
  | **boomerang-tester** | boomerang-tester | deepseek-v4-flash:cloud | Massive 1M context window for ingesting deep error logs and codebase context quickly and efficiently. |
24
24
  | **boomerang-linter** | boomerang-linter | qwen3-coder-next:cloud | Highly optimized for agentic coding workflows; blazing fast for syntax formatting and style checks. |
25
25
  | **boomerang-git** | boomerang-git | minimax-m2.7:cloud | Fast and highly reliable for standard professional productivity and executing structured terminal commands. |
26
- | **boomerang-writer** | boomerang-writer | gemma4:cloud | Frontier-level instruction following; excels at translating technical logic into clean, readable Markdown. |
26
+ | **boomerang-writer** | boomerang-writer | gemma4:31b-cloud | Frontier-level instruction following; excels at translating technical logic into clean, readable Markdown. |
27
27
  | **boomerang-scraper** | boomerang-scraper | qwen3.5:cloud | Strong, lightweight generalist with excellent tool-use capabilities for reliable data extraction. |
28
28
  | **boomerang-release** | boomerang-release | devstral-small-2:cloud | Fast 24B model perfect for targeted automation tasks like bumping versions and summarizing changelogs. |
29
29
  | **boomerang-agent-builder** | boomerang-agent-builder | glm-5.1:cloud | Excels at long-horizon tasks and ambiguous problems; ideal for writing and optimizing new agent logic. |
@@ -77,12 +77,12 @@ The orchestrator MUST delegate based on these rules. No exceptions.
77
77
  |-----------|------------------|-------|-------------------|
78
78
  | Complex planning / orchestration | `boomerang` | kimi-k2.6:cloud | `general` |
79
79
  | Architecture / design decisions | `boomerang-architect` | deepseek-v4-pro:cloud | `general`, `boomerang-coder` |
80
- | Documentation writing | `boomerang-writer` | gemma4:cloud | `general` |
80
+ | Documentation writing | `boomerang-writer` | gemma4:31b-cloud | `general` |
81
81
  | Session initialization | `boomerang-init` | kimi-k2.6:cloud | Everything else |
82
82
  | Session wrap-up / handoff | `boomerang-handoff` | kimi-k2.6:cloud | Everything else |
83
83
  | Skill/agent creation | `boomerang-agent-builder` | glm-5.1:cloud | `general` |
84
84
  | Fast code generation / bug fixes | `boomerang-coder` | glm-5.1:cloud | `general`, `boomerang-explorer` |
85
- | Code exploration / finding files | `boomerang-explorer` | devstral-2:cloud | Everything else |
85
+ | Code exploration / finding files | `boomerang-explorer` | devstral-2:123b-cloud | Everything else |
86
86
  | Writing / running tests | `boomerang-tester` | deepseek-v4-flash:cloud | `general`, `boomerang-coder` |
87
87
  | Linting / formatting | `boomerang-linter` | qwen3-coder-next:cloud | Everything else |
88
88
  | Git operations | `boomerang-git` | minimax-m2.7:cloud | Everything else |
@@ -139,7 +139,7 @@ All agents **MUST** follow the **8-Step Boomerang Protocol** — enforcement is
139
139
  ### 8-Step Protocol (MANDATORY)
140
140
 
141
141
  1. **Query Memory** — `memini-ai-dev_query_memories` FIRST
142
- 2. **Think** — `sequential-thinking_sequentialthinking` for complex tasks
142
+ 2. **Think** — `memini-ai-dev_add_thought` for complex tasks
143
143
  3. **Plan** — Create/refine implementation plan (MANDATORY unless user explicitly waives)
144
144
  4. **Delegate** — OpenCode executes selected agent with Context Package
145
145
  5. **Git Check** — Verify working tree state before code changes
@@ -209,7 +209,7 @@ Boomerang v3 uses **memini-ai** for memory — a Python-based semantic memory se
209
209
 
210
210
  All agents SHOULD:
211
211
  1. **Query memory FIRST** — `memini-ai-dev_query_memories` before work
212
- 2. **Use sequential-thinking** — For complex tasks
212
+ 2. **Use thought chains** — For complex tasks
213
213
  3. **Save results** — `memini-ai-dev_add_memory` when complete
214
214
 
215
215
  ### Trust-Weighted Memory
@@ -330,7 +330,7 @@ IDLE → MEMORY_QUERY → SEQUENTIAL_THINK → PLAN → DELEGATE → GIT_CHECK
330
330
  ### 8-Step Mandatory Protocol
331
331
 
332
332
  1. **MEMORY_QUERY** — MUST call `memini-ai-dev_query_memories` first
333
- 2. **SEQUENTIAL_THINK** — MUST call `sequential-thinking_sequentialthinking` for complex tasks
333
+ 2. **SEQUENTIAL_THINK** — MUST call `memini-ai-dev_add_thought` for complex tasks
334
334
  3. **PLAN** — MUST create plan or delegate to architect for build tasks
335
335
  4. **DELEGATE** — OpenCode handles agent execution
336
336
  5. **GIT_CHECK** — MUST verify working tree state before code changes
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Context Buffer Middleware — Transparent orchestrator integration
3
+ * for capturing and restoring agent invocation context via memini-ai.
4
+ *
5
+ * This middleware wraps around agent dispatch in the orchestrator,
6
+ * saving invocation context (output, tool calls, decisions, errors)
7
+ * as segmented memories in memini-ai. It also provides before/after
8
+ * hooks for telemetry and context injection.
9
+ *
10
+ * Design:
11
+ * - Segmenting: ~4 chars per token. Split at ~50K tokens = ~200K chars.
12
+ * - Memory metadata includes session_id, agent_name, segment_index.
13
+ * - All segments of an invocation are linked via DERIVED_FROM relationships.
14
+ * - The middleware is OPTIONAL — if not configured, the orchestrator
15
+ * operates identically to before this integration.
16
+ */
17
+ import { TelemetryClient } from './telemetry-client.js';
18
+ /** Estimated ratio: 1 token ≈ 4 characters. */
19
+ const CHARS_PER_TOKEN = 4;
20
+ /**
21
+ * Context buffer middleware that works transparently with the existing
22
+ * orchestrator to capture, segment, and restore agent invocation context.
23
+ */
24
+ export class ContextBufferMiddleware {
25
+ sessionId;
26
+ config;
27
+ memini;
28
+ telemetry;
29
+ segmentIds = [];
30
+ constructor(sessionId, config) {
31
+ this.sessionId = sessionId;
32
+ this.config = config;
33
+ this.memini = config.meminiClient;
34
+ this.telemetry = new TelemetryClient(config.telemetryEndpoint);
35
+ }
36
+ /**
37
+ * Called BEFORE an agent runs.
38
+ *
39
+ * Queries memini-ai for relevant context based on the task description,
40
+ * injects it into the agent's context package, and emits a start event
41
+ * to telemetry.
42
+ *
43
+ * @returns A context string to inject into the agent invocation, or
44
+ * an empty string if no relevant context is found.
45
+ */
46
+ async beforeInvocation(agentName, task, context) {
47
+ const _startTime = Date.now();
48
+ await this.ensureSessionRow(task);
49
+ await this.emitTelemetry({
50
+ event_type: 'invocation_start',
51
+ session_id: this.sessionId,
52
+ agent_name: agentName,
53
+ metadata: { task, contextProvided: context !== undefined },
54
+ });
55
+ let injectedContext = '';
56
+ try {
57
+ const results = await this.memini.search(task, {
58
+ topK: 5,
59
+ strategy: 'TIERED',
60
+ });
61
+ if (results.length > 0) {
62
+ const contextParts = results
63
+ .slice(0, 5)
64
+ .map((r) => `[trust=${r.entry.trustScore?.toFixed(2) ?? '0.50'}] ${r.entry.text}`)
65
+ .join('\n\n---\n\n');
66
+ injectedContext = `\n## Relevant Context from Previous Sessions\n\n${contextParts}\n`;
67
+ }
68
+ }
69
+ catch (err) {
70
+ console.warn('[ContextBuffer] beforeInvocation search failed:', err);
71
+ }
72
+ return injectedContext;
73
+ }
74
+ /**
75
+ * Called AFTER an agent completes (or fails).
76
+ *
77
+ * Captures the full invocation payload, segments if needed, saves
78
+ * each segment to memini-ai, links them via relationships, and emits
79
+ * an end event to telemetry.
80
+ *
81
+ * @returns Array of memory IDs for the saved segments.
82
+ */
83
+ async afterInvocation(agentName, result, durationMs) {
84
+ const success = 'success' in result ? result.success : true;
85
+ const error = 'success' in result && !result.success ? result.error : undefined;
86
+ await this.emitTelemetry({
87
+ event_type: success ? 'invocation_end' : 'invocation_error',
88
+ session_id: this.sessionId,
89
+ agent_name: agentName,
90
+ duration_ms: durationMs,
91
+ success,
92
+ error_message: error,
93
+ });
94
+ const payload = this.buildPayload(agentName, result, durationMs);
95
+ const memoryIds = await this.saveContextSegments(agentName, payload);
96
+ this.segmentIds = memoryIds;
97
+ // Link segments to each other via DERIVED_FROM relationships
98
+ await this.linkSegments(memoryIds);
99
+ return memoryIds;
100
+ }
101
+ /**
102
+ * Get the memory IDs saved during the current session.
103
+ */
104
+ getSegmentIds() {
105
+ return [...this.segmentIds];
106
+ }
107
+ // ---------------------------------------------------------------------------
108
+ // Private helpers
109
+ // ---------------------------------------------------------------------------
110
+ /**
111
+ * Build an AgentContextPayload from a task result.
112
+ */
113
+ buildPayload(agentName, result, durationMs) {
114
+ const now = Date.now();
115
+ if ('success' in result && !result.success) {
116
+ return {
117
+ output: '',
118
+ toolCalls: [],
119
+ filesModified: [],
120
+ decisions: [],
121
+ errors: [result.error],
122
+ startTime: now - durationMs,
123
+ endTime: now,
124
+ durationMs,
125
+ };
126
+ }
127
+ const taskResult = result;
128
+ return {
129
+ output: taskResult.output ?? '',
130
+ toolCalls: taskResult.toolCalls ?? [],
131
+ filesModified: taskResult.filesModified ?? [],
132
+ decisions: taskResult.decisions ?? [],
133
+ errors: taskResult.errors ?? [],
134
+ startTime: now - durationMs,
135
+ endTime: now,
136
+ durationMs,
137
+ };
138
+ }
139
+ /**
140
+ * Save context segments to memini-ai.
141
+ * If the summary exceeds the segment threshold, it is split into
142
+ * multiple segments, each saved separately.
143
+ */
144
+ async saveContextSegments(agentName, payload) {
145
+ const summary = this.generateContextSummary(agentName, payload);
146
+ const thresholdChars = this.config.segmentThreshold * CHARS_PER_TOKEN;
147
+ const segments = this.splitIfNeeded(summary, thresholdChars);
148
+ const memoryIds = [];
149
+ for (let i = 0; i < segments.length; i++) {
150
+ const segment = segments[i];
151
+ try {
152
+ const entry = await this.memini.addMemory({
153
+ text: segment,
154
+ sourceType: 'boomerang',
155
+ sourcePath: `context-buffer://${this.sessionId}/${agentName}`,
156
+ metadataJson: JSON.stringify({
157
+ type: 'context_segment',
158
+ session_id: this.sessionId,
159
+ agent_name: agentName,
160
+ segment_index: i,
161
+ total_segments: segments.length,
162
+ files_modified: payload.filesModified,
163
+ decisions: payload.decisions,
164
+ errors: payload.errors,
165
+ duration_ms: payload.durationMs,
166
+ success: payload.errors.length === 0,
167
+ }),
168
+ sessionId: this.sessionId,
169
+ });
170
+ memoryIds.push(entry.id);
171
+ }
172
+ catch (err) {
173
+ console.warn(`[ContextBuffer] Failed to save segment ${i}/${segments.length}:`, err);
174
+ }
175
+ }
176
+ return memoryIds;
177
+ }
178
+ /**
179
+ * Link segments via DERIVED_FROM relationships in memini-ai.
180
+ * Each segment (except the first) derives from the previous one.
181
+ */
182
+ async linkSegments(memoryIds) {
183
+ if (memoryIds.length < 2) {
184
+ return;
185
+ }
186
+ for (let i = 1; i < memoryIds.length; i++) {
187
+ try {
188
+ await this.memini.createRelationship(memoryIds[i], memoryIds[i - 1], 'DERIVED_FROM', 0.9);
189
+ }
190
+ catch (err) {
191
+ console.warn(`[ContextBuffer] Failed to link segments ${i - 1} → ${i}:`, err);
192
+ }
193
+ }
194
+ }
195
+ /**
196
+ * Split content into segments if it exceeds the threshold.
197
+ * Tries to split on paragraph boundaries (double newlines).
198
+ */
199
+ splitIfNeeded(content, thresholdChars) {
200
+ if (content.length <= thresholdChars) {
201
+ return [content];
202
+ }
203
+ const segments = [];
204
+ let remaining = content;
205
+ while (remaining.length > 0) {
206
+ if (remaining.length <= thresholdChars) {
207
+ segments.push(remaining);
208
+ break;
209
+ }
210
+ // Find a paragraph boundary near the threshold
211
+ const searchStart = Math.max(0, thresholdChars - 1000);
212
+ const searchEnd = Math.min(remaining.length, thresholdChars + 1000);
213
+ const searchRegion = remaining.slice(searchStart, searchEnd);
214
+ const newlinePos = searchRegion.indexOf('\n\n');
215
+ if (newlinePos !== -1) {
216
+ const splitPos = searchStart + newlinePos + 2;
217
+ segments.push(remaining.slice(0, splitPos));
218
+ remaining = remaining.slice(splitPos);
219
+ }
220
+ else {
221
+ // No good boundary; hard split at threshold
222
+ segments.push(remaining.slice(0, thresholdChars));
223
+ remaining = remaining.slice(thresholdChars);
224
+ }
225
+ }
226
+ return segments;
227
+ }
228
+ /**
229
+ * Generate a human-readable context summary from the agent payload.
230
+ */
231
+ generateContextSummary(agentName, payload) {
232
+ const lines = [
233
+ `# Agent Invocation: ${agentName}`,
234
+ `Duration: ${payload.durationMs}ms`,
235
+ `Status: ${payload.errors.length === 0 ? 'SUCCESS' : 'FAILED'}`,
236
+ '',
237
+ ];
238
+ if (payload.output) {
239
+ lines.push('## Output');
240
+ lines.push(payload.output);
241
+ lines.push('');
242
+ }
243
+ if (payload.filesModified.length > 0) {
244
+ lines.push('## Files Modified');
245
+ for (const f of payload.filesModified) {
246
+ lines.push(`- ${f}`);
247
+ }
248
+ lines.push('');
249
+ }
250
+ if (payload.decisions.length > 0) {
251
+ lines.push('## Decisions');
252
+ for (const d of payload.decisions) {
253
+ lines.push(`- ${d}`);
254
+ }
255
+ lines.push('');
256
+ }
257
+ if (payload.toolCalls.length > 0) {
258
+ lines.push('## Tool Calls');
259
+ for (const tc of payload.toolCalls) {
260
+ lines.push(`- ${tc.name}(${JSON.stringify(tc.arguments).slice(0, 200)})`);
261
+ }
262
+ lines.push('');
263
+ }
264
+ if (payload.errors.length > 0) {
265
+ lines.push('## Errors');
266
+ for (const e of payload.errors) {
267
+ lines.push(`- ${e}`);
268
+ }
269
+ lines.push('');
270
+ }
271
+ return lines.join('\n');
272
+ }
273
+ /**
274
+ * Ensure a session row exists in telemetry.
275
+ */
276
+ async ensureSessionRow(task) {
277
+ try {
278
+ await this.telemetry.createSession(this.sessionId, 'default', task);
279
+ }
280
+ catch {
281
+ // Session may already exist; ignore errors
282
+ }
283
+ }
284
+ /**
285
+ * Emit a telemetry event. Fire-and-forget — errors are logged only.
286
+ */
287
+ async emitTelemetry(event) {
288
+ await this.telemetry.emit(event);
289
+ }
290
+ }
package/dist/index.js CHANGED
@@ -2,13 +2,17 @@
2
2
  * Boomerang v3 — Multi-agent orchestration plugin for OpenCode
3
3
  *
4
4
  * Exports:
5
- * - orchestrator: Concurrency-aware orchestrator
5
+ * - orchestrator: Concurrency-aware orchestrator with context buffer
6
6
  * - plugin: Plugin metadata
7
7
  * - concurrency: TaskLimiter, RetryExecutor, TimeoutEnforcer
8
+ * - context-buffer: ContextBufferMiddleware
9
+ * - telemetry: TelemetryClient
8
10
  * - types: Core type definitions
9
11
  */
10
12
  export { BoomerangOrchestrator, createOrchestrator } from './orchestrator.js';
11
13
  export { TaskLimiter, executeWithRetry, executeWithTimeout, } from './concurrency/index.js';
14
+ export { ContextBufferMiddleware } from './context-buffer.js';
15
+ export { TelemetryClient } from './telemetry-client.js';
12
16
  export { TimeoutError, } from './types.js';
13
17
  export const orchestrator = {
14
18
  name: 'boomerang-v3',
@@ -552,7 +552,7 @@ export function getClient() {
552
552
  }
553
553
  return clientInstance;
554
554
  }
555
- export async function initializeClient(pythonPath, serverModule) {
555
+ export async function initializeClient(_pythonPath, _serverModule) {
556
556
  const client = getClient();
557
557
  await client.initialize();
558
558
  return client;
@@ -89,7 +89,7 @@ export async function challengeMemory(memoryId, challengeText, client) {
89
89
  /**
90
90
  * Adapt a raw memory from memini-ai to our MemoryEntry type
91
91
  */
92
- function adaptMemoryEntry(raw) {
92
+ function _adaptMemoryEntry(raw) {
93
93
  let timestamp;
94
94
  if (typeof raw.timestamp === 'number') {
95
95
  timestamp = raw.timestamp;
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { initializeClient, } from '../memini-client/index.js';
7
7
  import { DEFAULT_SEARCH_OPTIONS } from './schema.js';
8
- const PROJECT_ID = process.env.BOOMERANG_PROJECT_ID || 'boomerang-default';
8
+ const _PROJECT_ID = process.env.BOOMERANG_PROJECT_ID || 'boomerang-default';
9
9
  class MemorySystem {
10
10
  static instance = null;
11
11
  _initialized = false;
@@ -130,7 +130,7 @@ export async function triggerConsolidation(force = false, client) {
130
130
  /**
131
131
  * Adapt a raw memory from memini-ai to our MemoryEntry type
132
132
  */
133
- function adaptMemoryEntry(meminiEntry) {
133
+ function _adaptMemoryEntry(meminiEntry) {
134
134
  return {
135
135
  id: meminiEntry.id,
136
136
  text: meminiEntry.text,
@@ -10,7 +10,7 @@ const ROUTING_MATRIX = {
10
10
  'mcp-debug': 'mcp-specialist',
11
11
  'release': 'boomerang-release',
12
12
  };
13
- function validateAgentRouting(taskType, agentName) {
13
+ function _validateAgentRouting(taskType, agentName) {
14
14
  const expected = ROUTING_MATRIX[taskType];
15
15
  if (expected && expected !== agentName) {
16
16
  console.warn(`ROUTING VIOLATION: ${taskType} should use ${expected}, not ${agentName}`);
@@ -20,10 +20,12 @@ function validateAgentRouting(taskType, agentName) {
20
20
  }
21
21
  import { TimeoutError } from './types.js';
22
22
  import { TaskLimiter, executeWithRetry, executeWithTimeout, } from './concurrency/index.js';
23
+ import { ContextBufferMiddleware } from './context-buffer.js';
23
24
  export class BoomerangOrchestrator {
24
25
  taskLimiter;
25
26
  config;
26
- constructor(config) {
27
+ contextBuffer;
28
+ constructor(config, contextBufferConfig) {
27
29
  this.config = {
28
30
  maxConcurrentSubAgents: 2,
29
31
  defaultTimeoutMs: 60000,
@@ -39,6 +41,11 @@ export class BoomerangOrchestrator {
39
41
  ...config,
40
42
  };
41
43
  this.taskLimiter = new TaskLimiter(this.config.maxConcurrentSubAgents);
44
+ this.contextBuffer = null;
45
+ if (contextBufferConfig?.meminiClient) {
46
+ const sessionId = `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
47
+ this.contextBuffer = new ContextBufferMiddleware(sessionId, contextBufferConfig);
48
+ }
42
49
  }
43
50
  /**
44
51
  * Check if an agent can be dispatched without exceeding slot limits.
@@ -53,13 +60,36 @@ export class BoomerangOrchestrator {
53
60
  return this.taskLimiter.getSlotUsage();
54
61
  }
55
62
  /**
56
- * Dispatch a task with concurrency check, retry, and timeout enforcement.
63
+ * Get the context buffer middleware, if configured.
64
+ */
65
+ getContextBuffer() {
66
+ return this.contextBuffer;
67
+ }
68
+ /**
69
+ * Dispatch a task with concurrency check, retry, timeout enforcement,
70
+ * and optional context buffer middleware integration.
71
+ *
72
+ * When the context buffer is configured, it wraps the task execution
73
+ * with beforeInvocation/afterInvocation hooks for context capture.
57
74
  */
58
75
  async dispatchTask(agentName, task, timeoutMs) {
59
76
  if (!this.canDispatch(agentName)) {
60
77
  throw new Error(`Cannot dispatch "${agentName}": no available slots (max ${this.config.maxConcurrentSubAgents})`);
61
78
  }
62
79
  this.taskLimiter.registerAgent(agentName, timeoutMs ?? this.config.defaultTimeoutMs);
80
+ const startTime = Date.now();
81
+ let _injectedContext = '';
82
+ let _contextMemoryIds = [];
83
+ // Context buffer: before invocation
84
+ if (this.contextBuffer) {
85
+ try {
86
+ _injectedContext = await this.contextBuffer.beforeInvocation(agentName, task.toString().slice(0, 200) // Brief description
87
+ );
88
+ }
89
+ catch (err) {
90
+ console.warn('[Orchestrator] Context buffer beforeInvocation failed:', err);
91
+ }
92
+ }
63
93
  try {
64
94
  const retryResult = await executeWithRetry(() => executeWithTimeout(task, agentName, timeoutMs), {
65
95
  maxRetries: this.config.maxRetries,
@@ -67,12 +97,55 @@ export class BoomerangOrchestrator {
67
97
  maxDelayMs: this.config.retryMaxDelayMs,
68
98
  isRetryable: (err) => !(err instanceof TimeoutError),
69
99
  });
100
+ // Context buffer: after successful invocation
101
+ if (this.contextBuffer) {
102
+ try {
103
+ const taskResult = this.extractTaskResult(retryResult.result);
104
+ _contextMemoryIds = await this.contextBuffer.afterInvocation(agentName, taskResult, Date.now() - startTime);
105
+ }
106
+ catch (err) {
107
+ console.warn('[Orchestrator] Context buffer afterInvocation failed:', err);
108
+ }
109
+ }
70
110
  return retryResult;
71
111
  }
112
+ catch (err) {
113
+ // Context buffer: after failed invocation
114
+ if (this.contextBuffer) {
115
+ try {
116
+ const errorMessage = err instanceof Error ? err.message : String(err);
117
+ _contextMemoryIds = await this.contextBuffer.afterInvocation(agentName, { success: false, error: errorMessage }, Date.now() - startTime);
118
+ }
119
+ catch (ctxErr) {
120
+ console.warn('[Orchestrator] Context buffer afterInvocation (error) failed:', ctxErr);
121
+ }
122
+ }
123
+ throw err;
124
+ }
72
125
  finally {
73
126
  this.taskLimiter.releaseAgent(agentName);
74
127
  }
75
128
  }
129
+ /**
130
+ * Extract a TaskResult from a dispatch result value.
131
+ * Handles the case where T may or may not be a TaskResult-shaped object.
132
+ */
133
+ extractTaskResult(result) {
134
+ if (typeof result === 'object' && result !== null) {
135
+ const obj = result;
136
+ if ('output' in obj || 'filesModified' in obj || 'decisions' in obj || 'errors' in obj) {
137
+ return obj;
138
+ }
139
+ }
140
+ // Wrap raw results
141
+ return {
142
+ output: typeof result === 'string' ? result : JSON.stringify(result),
143
+ toolCalls: [],
144
+ filesModified: [],
145
+ decisions: [],
146
+ errors: [],
147
+ };
148
+ }
76
149
  /**
77
150
  * Reset all concurrency state (useful for testing and recovery).
78
151
  */
@@ -83,6 +156,6 @@ export class BoomerangOrchestrator {
83
156
  /**
84
157
  * Factory function to create a concurrency-aware orchestrator.
85
158
  */
86
- export function createOrchestrator(config) {
87
- return new BoomerangOrchestrator(config);
159
+ export function createOrchestrator(config, contextBufferConfig) {
160
+ return new BoomerangOrchestrator(config, contextBufferConfig);
88
161
  }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Telemetry Client — Simple HTTP client for emitting telemetry events
3
+ * to the observability proxy.
4
+ *
5
+ * Uses native fetch (Node 20+) with fire-and-forget semantics.
6
+ * Failures are logged but never throw, ensuring telemetry never blocks
7
+ * the orchestrator pipeline.
8
+ */
9
+ const DEFAULT_TIMEOUT_MS = 5000;
10
+ /**
11
+ * Simple HTTP telemetry client for the boomerang observability proxy.
12
+ *
13
+ * All methods are fire-and-forget: errors are logged but never propagated,
14
+ * so telemetry failures never block the orchestrator.
15
+ */
16
+ export class TelemetryClient {
17
+ endpoint;
18
+ timeoutMs;
19
+ constructor(endpoint, timeoutMs = DEFAULT_TIMEOUT_MS) {
20
+ this.endpoint = endpoint.replace(/\/+$/, '');
21
+ this.timeoutMs = timeoutMs;
22
+ }
23
+ /**
24
+ * Emit a telemetry event to the proxy.
25
+ * Fire-and-forget: logs errors but never throws.
26
+ */
27
+ async emit(event) {
28
+ try {
29
+ await this.post('/events', event);
30
+ }
31
+ catch (err) {
32
+ console.warn('[TelemetryClient] emit failed:', err);
33
+ }
34
+ }
35
+ /**
36
+ * Create a new session record in the telemetry proxy.
37
+ */
38
+ async createSession(sessionId, tenantId, taskDescription) {
39
+ try {
40
+ await this.post('/sessions', {
41
+ session_id: sessionId,
42
+ tenant_id: tenantId,
43
+ task_description: taskDescription,
44
+ });
45
+ }
46
+ catch (err) {
47
+ console.warn('[TelemetryClient] createSession failed:', err);
48
+ }
49
+ }
50
+ /**
51
+ * Update an existing session in the telemetry proxy.
52
+ */
53
+ async updateSession(sessionId, updates) {
54
+ try {
55
+ await this.patch(`/sessions/${sessionId}`, updates);
56
+ }
57
+ catch (err) {
58
+ console.warn('[TelemetryClient] updateSession failed:', err);
59
+ }
60
+ }
61
+ // ---------------------------------------------------------------------------
62
+ // Private helpers
63
+ // ---------------------------------------------------------------------------
64
+ async post(path, body) {
65
+ const url = `${this.endpoint}${path}`;
66
+ const controller = new AbortController();
67
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
68
+ try {
69
+ const response = await fetch(url, {
70
+ method: 'POST',
71
+ headers: { 'Content-Type': 'application/json' },
72
+ body: JSON.stringify(body),
73
+ signal: controller.signal,
74
+ });
75
+ return response;
76
+ }
77
+ finally {
78
+ clearTimeout(timer);
79
+ }
80
+ }
81
+ async patch(path, body) {
82
+ const url = `${this.endpoint}${path}`;
83
+ const controller = new AbortController();
84
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
85
+ try {
86
+ const response = await fetch(url, {
87
+ method: 'PATCH',
88
+ headers: { 'Content-Type': 'application/json' },
89
+ body: JSON.stringify(body),
90
+ signal: controller.signal,
91
+ });
92
+ return response;
93
+ }
94
+ finally {
95
+ clearTimeout(timer);
96
+ }
97
+ }
98
+ }
package/dist/types.js CHANGED
@@ -1,5 +1,8 @@
1
1
  /**
2
- * Boomerang v3 — Concurrency Type Definitions
2
+ * Boomerang v3 — Core Type Definitions
3
+ *
4
+ * Includes concurrency types, context buffer types, telemetry types,
5
+ * and shared interfaces used across the orchestrator and middleware layers.
3
6
  */
4
7
  export class TimeoutError extends Error {
5
8
  agentName;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veedubin/boomerang-v3",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "Multi-agent orchestration plugin for OpenCode with memini-ai memory",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -49,7 +49,7 @@ const PROVIDERS = {
49
49
  'kimi-k2.6:cloud': { name: 'Kimi K2.6 (Cloud)' },
50
50
  'glm-5.1:cloud': { name: 'GLM 5.1 (Cloud)' },
51
51
  'deepseek-v4-pro:cloud': { name: 'DeepSeek V4 Pro (Cloud)' },
52
- 'devstral-2:cloud': { name: 'Devstral 2 (Cloud)' },
52
+ 'devstral-2:123b-cloud': { name: 'Devstral 2 (Cloud)' },
53
53
  'deepseek-v4-flash:cloud': { name: 'DeepSeek V4 Flash (Cloud)' },
54
54
  'qwen3-coder-next:cloud': { name: 'Qwen3 Coder Next (Cloud)' },
55
55
  'minimax-m2.7:cloud': { name: 'MiniMax M2.7 (Cloud)' },