lynkr 9.9.1 → 9.10.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 (67) hide show
  1. package/README.md +50 -9
  2. package/bin/cli.js +2 -0
  3. package/bin/lynkr-init.js +4 -12
  4. package/bin/lynkr-reset.js +71 -0
  5. package/config/model-tiers.json +199 -52
  6. package/package.json +2 -2
  7. package/scripts/validate-difficulty-classifier.js +27 -6
  8. package/src/api/providers-handler.js +0 -1
  9. package/src/api/router.js +275 -160
  10. package/src/clients/databricks.js +95 -6
  11. package/src/config/index.js +3 -43
  12. package/src/orchestrator/index.js +117 -1235
  13. package/src/orchestrator/passthrough-stream.js +382 -0
  14. package/src/orchestrator/sse-transformer.js +408 -0
  15. package/src/routing/affinity-store.js +17 -3
  16. package/src/routing/difficulty-classifier.js +52 -10
  17. package/src/routing/index.js +35 -3
  18. package/src/routing/intent-score.js +20 -2
  19. package/src/routing/session-affinity.js +8 -1
  20. package/src/routing/side-channel-detector.js +103 -0
  21. package/src/server.js +0 -46
  22. package/src/agents/context-manager.js +0 -236
  23. package/src/agents/decomposition/dispatcher.js +0 -185
  24. package/src/agents/decomposition/gate.js +0 -136
  25. package/src/agents/decomposition/index.js +0 -183
  26. package/src/agents/decomposition/model-call.js +0 -75
  27. package/src/agents/decomposition/planner.js +0 -223
  28. package/src/agents/decomposition/synthesizer.js +0 -89
  29. package/src/agents/decomposition/telemetry.js +0 -55
  30. package/src/agents/definitions/loader.js +0 -653
  31. package/src/agents/executor.js +0 -457
  32. package/src/agents/index.js +0 -165
  33. package/src/agents/parallel-coordinator.js +0 -68
  34. package/src/agents/reflector.js +0 -331
  35. package/src/agents/skillbook.js +0 -331
  36. package/src/agents/store.js +0 -259
  37. package/src/edits/index.js +0 -171
  38. package/src/indexer/babel-parser.js +0 -213
  39. package/src/indexer/index.js +0 -1629
  40. package/src/indexer/navigation/index.js +0 -32
  41. package/src/indexer/navigation/providers/treeSitter.js +0 -36
  42. package/src/indexer/parser.js +0 -443
  43. package/src/tasks/store.js +0 -349
  44. package/src/tests/coverage.js +0 -173
  45. package/src/tests/index.js +0 -171
  46. package/src/tests/store.js +0 -213
  47. package/src/tools/agent-task.js +0 -145
  48. package/src/tools/code-mode.js +0 -304
  49. package/src/tools/decompose.js +0 -91
  50. package/src/tools/edits.js +0 -94
  51. package/src/tools/execution.js +0 -171
  52. package/src/tools/git.js +0 -1346
  53. package/src/tools/index.js +0 -306
  54. package/src/tools/indexer.js +0 -360
  55. package/src/tools/lazy-loader.js +0 -366
  56. package/src/tools/mcp-remote.js +0 -88
  57. package/src/tools/mcp.js +0 -116
  58. package/src/tools/process.js +0 -167
  59. package/src/tools/smart-selection.js +0 -180
  60. package/src/tools/stubs.js +0 -55
  61. package/src/tools/tasks.js +0 -260
  62. package/src/tools/tests.js +0 -132
  63. package/src/tools/tinyfish.js +0 -358
  64. package/src/tools/truncate.js +0 -106
  65. package/src/tools/web-client.js +0 -71
  66. package/src/tools/web.js +0 -415
  67. package/src/tools/workspace.js +0 -204
@@ -1,457 +0,0 @@
1
- const logger = require("../logger");
2
- const { executeToolCall, listTools } = require("../tools");
3
- const { invokeModel } = require("../clients/databricks");
4
- const { STANDARD_TOOLS } = require("../clients/standard-tools");
5
- const ContextManager = require("./context-manager");
6
- const Skillbook = require("./skillbook");
7
- const Reflector = require("./reflector");
8
-
9
- const contextManager = new ContextManager();
10
-
11
- class SubagentExecutor {
12
- /**
13
- * @param {Object} [options]
14
- * @param {Object} [options.definitionLoader] - Agent definition loader. When
15
- * provided, newly-learned skills are re-injected into agent prompts live
16
- * (within the running process). Optional so the executor still works
17
- * standalone (e.g. in tests); without it, learning still persists to disk
18
- * and is picked up on the next process start.
19
- */
20
- constructor({ definitionLoader = null } = {}) {
21
- this.definitionLoader = definitionLoader;
22
- }
23
-
24
- /**
25
- * Execute a single subagent
26
- * @param {Object} agentDef - Agent definition
27
- * @param {string} taskPrompt - Task to perform
28
- * @param {Object} options - sessionId, mainContext, etc.
29
- * @returns {Promise<Object>} - { success, result, stats }
30
- */
31
- async execute(agentDef, taskPrompt, options = {}) {
32
- // Create fresh isolated context
33
- const context = contextManager.createSubagentContext(
34
- agentDef,
35
- taskPrompt,
36
- options.mainContext
37
- );
38
-
39
- // Store client CWD in context for tool execution
40
- context.cwd = options.cwd;
41
-
42
- try {
43
- // Set timeout
44
- const timeout = options.timeout || 120000; // 2 minutes
45
- const timeoutPromise = new Promise((_, reject) => {
46
- setTimeout(() => reject(new Error("Subagent timeout")), timeout);
47
- });
48
-
49
- const executionPromise = this._runAgentLoop(context, options.sessionId);
50
-
51
- await Promise.race([executionPromise, timeoutPromise]);
52
-
53
- // Extract final result (summary only, not intermediate steps)
54
- const finalResult = this._extractFinalResult(context);
55
-
56
- contextManager.completeExecution(context, finalResult);
57
-
58
- // Learn from successful execution (async, non-blocking)
59
- this._learnFromExecution(context, true).catch(err => {
60
- logger.warn({
61
- agentType: agentDef.name,
62
- error: err.message
63
- }, "Failed to learn from execution");
64
- });
65
-
66
- return {
67
- success: true,
68
- result: finalResult,
69
- stats: {
70
- agentId: context.agentId,
71
- steps: context.steps,
72
- durationMs: Date.now() - context.startTime,
73
- inputTokens: context.inputTokens,
74
- outputTokens: context.outputTokens
75
- }
76
- };
77
-
78
- } catch (error) {
79
- contextManager.failExecution(context, error);
80
-
81
- // Learn from failed execution (async, non-blocking)
82
- this._learnFromExecution(context, false).catch(err => {
83
- logger.debug({
84
- agentType: agentDef.name,
85
- error: err.message
86
- }, "Failed to learn from failed execution");
87
- });
88
-
89
- return {
90
- success: false,
91
- error: error.message,
92
- stats: {
93
- agentId: context.agentId,
94
- steps: context.steps,
95
- durationMs: Date.now() - context.startTime
96
- }
97
- };
98
- }
99
- }
100
-
101
- /**
102
- * Run agent loop (similar to main orchestrator but isolated)
103
- */
104
- async _runAgentLoop(context, sessionId) {
105
- while (context.steps < context.maxSteps && !context.terminated) {
106
- context.steps++;
107
-
108
- logger.debug({
109
- agentId: context.agentId,
110
- step: context.steps,
111
- messageCount: context.messages.length
112
- }, "Subagent step starting");
113
-
114
- // Call model with filtered tools
115
- const response = await this._callModel(context);
116
-
117
- // Update token usage
118
- context.inputTokens += response.usage?.input_tokens || 0;
119
- context.outputTokens += response.usage?.output_tokens || 0;
120
-
121
- // Check stop reason
122
- if (response.stop_reason === "end_turn" || response.stop_reason === "stop_sequence") {
123
- // Agent finished - extract result
124
- context.result = this._extractTextFromContent(response.content);
125
- context.terminated = true;
126
-
127
- contextManager.addMessage(context, {
128
- role: "assistant",
129
- content: response.content
130
- });
131
-
132
- break;
133
- }
134
-
135
- // Execute tool calls if any
136
- if (response.stop_reason === "tool_use") {
137
- await this._executeTools(context, response.content, sessionId);
138
- } else {
139
- logger.warn({
140
- agentId: context.agentId,
141
- stopReason: response.stop_reason
142
- }, "Unexpected stop reason in subagent");
143
-
144
- context.result = this._extractTextFromContent(response.content);
145
- context.terminated = true;
146
- break;
147
- }
148
- }
149
-
150
- if (context.steps >= context.maxSteps && !context.terminated) {
151
- logger.warn({
152
- agentId: context.agentId,
153
- maxSteps: context.maxSteps
154
- }, "Subagent reached max steps");
155
-
156
- context.result = "Subagent incomplete - reached maximum steps";
157
- }
158
- }
159
-
160
- /**
161
- * Call model with subagent context
162
- */
163
- async _callModel(context) {
164
- const payload = {
165
- model: this._resolveModel(context.model),
166
- messages: context.messages,
167
- max_tokens: 4096,
168
- temperature: 0.3
169
- };
170
-
171
- // Add filtered tools for subagent (based on allowedTools)
172
- const filteredTools = this._getFilteredTools(context.allowedTools);
173
- if (filteredTools.length > 0) {
174
- payload.tools = filteredTools;
175
- }
176
-
177
- // Determine provider based on model family.
178
- // Subagents should use the currently configured MODEL_PROVIDER and avoid
179
- // hard-fallbacks to Azure when Azure is not selected/configured.
180
- let forceProvider = null;
181
- const modelLower = String(payload.model || "").toLowerCase();
182
- const isClaudeFamilyModel =
183
- modelLower.includes("claude") ||
184
- modelLower.includes("sonnet") ||
185
- modelLower.includes("haiku") ||
186
- modelLower.includes("opus");
187
- const isGptFamilyModel = modelLower.includes("gpt");
188
-
189
- if (isClaudeFamilyModel || isGptFamilyModel) {
190
- const config = require('../config');
191
- // `type` is the canonical key; `provider` kept as legacy fallback.
192
- forceProvider = config.modelProvider?.type || config.modelProvider?.provider || null;
193
- }
194
-
195
- logger.debug({
196
- agentId: context.agentId,
197
- model: payload.model,
198
- forceProvider,
199
- messageCount: context.messages.length,
200
- toolCount: filteredTools.length,
201
- toolNames: filteredTools.map(t => t.name)
202
- }, "Calling model for subagent");
203
-
204
- // Use invokeModel with forceProvider to ensure correct model routing
205
- const response = await invokeModel(payload, { forceProvider });
206
-
207
- if (!response.json) {
208
- throw new Error("Invalid model response");
209
- }
210
-
211
- return response.json;
212
- }
213
-
214
- /**
215
- * Execute tools (with restrictions)
216
- */
217
- async _executeTools(context, content, sessionId) {
218
- const toolUseBlocks = content.filter(block => block.type === "tool_use");
219
-
220
- if (toolUseBlocks.length === 0) {
221
- return;
222
- }
223
-
224
- // Add assistant message with tool calls
225
- contextManager.addMessage(context, {
226
- role: "assistant",
227
- content: content
228
- });
229
-
230
- // Execute each tool (sequentially for now, can parallelize later)
231
- const toolResults = [];
232
-
233
- for (const toolUse of toolUseBlocks) {
234
- const toolStart = Date.now();
235
-
236
- try {
237
- // Check if tool is allowed
238
- if (context.allowedTools.length > 0 && !this._isToolAllowed(toolUse.name, context.allowedTools)) {
239
- throw new Error(`Tool ${toolUse.name} not allowed for agent ${context.agentName}`);
240
- }
241
-
242
- // CRITICAL: Block Task tool for subagents (prevents recursion)
243
- if (toolUse.name === "Task") {
244
- throw new Error("Subagents cannot spawn other subagents");
245
- }
246
-
247
- logger.debug({
248
- agentId: context.agentId,
249
- step: context.steps,
250
- toolName: toolUse.name
251
- }, "Subagent executing tool");
252
-
253
- // Execute tool
254
- const result = await executeToolCall({
255
- name: toolUse.name,
256
- arguments: toolUse.input
257
- }, {
258
- sessionId: sessionId,
259
- agentId: context.agentId,
260
- cwd: context.cwd,
261
- isSubagent: true
262
- });
263
-
264
- const toolDuration = Date.now() - toolStart;
265
-
266
- // Record in transcript
267
- contextManager.recordToolCall(
268
- context,
269
- toolUse.name,
270
- toolUse.input,
271
- result.content,
272
- null
273
- );
274
-
275
- toolResults.push({
276
- type: "tool_result",
277
- tool_use_id: toolUse.id,
278
- content: result.content
279
- });
280
-
281
- } catch (error) {
282
- const toolDuration = Date.now() - toolStart;
283
-
284
- logger.warn({
285
- agentId: context.agentId,
286
- toolName: toolUse.name,
287
- error: error.message
288
- }, "Subagent tool execution failed");
289
-
290
- contextManager.recordToolCall(
291
- context,
292
- toolUse.name,
293
- toolUse.input,
294
- null,
295
- error
296
- );
297
-
298
- toolResults.push({
299
- type: "tool_result",
300
- tool_use_id: toolUse.id,
301
- content: `Error: ${error.message}`,
302
- is_error: true
303
- });
304
- }
305
- }
306
-
307
- // Add tool results as user message
308
- contextManager.addMessage(context, {
309
- role: "user",
310
- content: toolResults
311
- });
312
- }
313
-
314
- /**
315
- * Get filtered tools for subagent (based on allowedTools)
316
- * Returns tools in Anthropic format (conversion to OpenAI happens in invokeModel)
317
- */
318
- _getFilteredTools(allowedTools) {
319
- if (!allowedTools || allowedTools.length === 0) {
320
- return [];
321
- }
322
-
323
- // Filter STANDARD_TOOLS based on allowedTools list
324
- // Exclude Task tool (subagents cannot spawn other subagents)
325
- return STANDARD_TOOLS.filter(tool => {
326
- if (tool.name === "Task") {
327
- return false; // Never allow subagents to spawn subagents
328
- }
329
- return this._isToolAllowed(tool.name, allowedTools);
330
- });
331
- }
332
-
333
- /**
334
- * Check if tool is allowed (case-insensitive)
335
- */
336
- _isToolAllowed(toolName, allowedTools) {
337
- const normalized = toolName.toLowerCase();
338
- return allowedTools.some(allowed => allowed.toLowerCase() === normalized);
339
- }
340
-
341
- /**
342
- * Extract text from content blocks
343
- */
344
- _extractTextFromContent(content) {
345
- if (!Array.isArray(content)) {
346
- return String(content);
347
- }
348
-
349
- const textBlocks = content.filter(block => block.type === "text");
350
- return textBlocks.map(block => block.text).join("\n");
351
- }
352
-
353
- /**
354
- * Extract FINAL RESULT only (not intermediate steps)
355
- */
356
- _extractFinalResult(context) {
357
- if (context.result) {
358
- return context.result;
359
- }
360
-
361
- // Look for summary markers in last assistant message
362
- const reversedMessages = [...context.messages].reverse();
363
- const lastMessage = reversedMessages.find(m => m.role === "assistant");
364
-
365
- if (lastMessage && lastMessage.content) {
366
- const text = this._extractTextFromContent(lastMessage.content);
367
-
368
- // Look for summary markers
369
- const markers = [
370
- "EXPLORATION COMPLETE:",
371
- "IMPLEMENTATION PLAN:",
372
- "TASK COMPLETE:",
373
- "SUMMARY:",
374
- "FINDINGS:"
375
- ];
376
-
377
- for (const marker of markers) {
378
- const index = text.indexOf(marker);
379
- if (index !== -1) {
380
- return text.substring(index);
381
- }
382
- }
383
-
384
- return text;
385
- }
386
-
387
- return "Subagent completed with no result";
388
- }
389
-
390
- /**
391
- * Resolve model name to full model identifier
392
- */
393
- _resolveModel(modelName) {
394
- const modelMap = {
395
- "haiku": "claude-3-haiku-20240307",
396
- "sonnet": "claude-3-5-sonnet-20241022",
397
- "opus": "claude-3-opus-20240229",
398
- "gpt-4o-mini": "gpt-4o-mini",
399
- "gpt-4o": "gpt-4o"
400
- };
401
-
402
- return modelMap[modelName] || modelName;
403
- }
404
-
405
- /**
406
- * Learn from execution (async, non-blocking)
407
- * Uses Reflector to extract patterns and updates skillbook
408
- */
409
- async _learnFromExecution(context, successful) {
410
- try {
411
- // Use Reflector to extract sophisticated patterns
412
- const patterns = Reflector.reflect(context, successful);
413
-
414
- if (patterns.length === 0) {
415
- return; // Nothing to learn
416
- }
417
-
418
- // Prefer the loader's live skillbook instance (so use-counts/confidence
419
- // accumulate in one place); fall back to loading from disk when running
420
- // standalone.
421
- const skillbook =
422
- this.definitionLoader?.getSkillbook(context.agentName) ||
423
- (await Skillbook.load(context.agentName));
424
-
425
- // Add each learned pattern
426
- for (const pattern of patterns) {
427
- skillbook.addSkill(pattern);
428
- }
429
-
430
- // Save skillbook (persists learning)
431
- await skillbook.save();
432
-
433
- // Re-inject into the agent's prompt so the next run benefits without a
434
- // restart. No-op when no loader was provided.
435
- if (this.definitionLoader) {
436
- this.definitionLoader.setSkillbook(context.agentName, skillbook);
437
- }
438
-
439
- logger.info({
440
- agentType: context.agentName,
441
- patternsLearned: patterns.length,
442
- totalSkills: skillbook.skills.size,
443
- successful
444
- }, "Agent learned from execution");
445
-
446
- } catch (error) {
447
- // Don't throw - learning failures shouldn't break execution
448
- logger.warn({
449
- agentType: context.agentName,
450
- error: error.message
451
- }, "Learning failed");
452
- }
453
- }
454
-
455
- }
456
-
457
- module.exports = SubagentExecutor;
@@ -1,165 +0,0 @@
1
- const logger = require("../logger");
2
- const config = require("../config");
3
- const AgentDefinitionLoader = require("./definitions/loader");
4
- const ParallelCoordinator = require("./parallel-coordinator");
5
- const agentStore = require("./store");
6
-
7
- const definitionLoader = new AgentDefinitionLoader();
8
- const coordinator = new ParallelCoordinator(config.agents?.maxConcurrent || 10, {
9
- definitionLoader,
10
- });
11
-
12
- // Periodically drop learned skills that have proven unhelpful. Only when the
13
- // agents subsystem is active; the timer is unref'd so it never blocks exit.
14
- if (config.agents?.enabled) {
15
- definitionLoader.startSkillPruning();
16
- }
17
-
18
- /**
19
- * Spawn and execute subagent(s)
20
- * @param {string|Array} agentType - Agent type(s) to spawn
21
- * @param {string|Array} prompt - Task prompt(s)
22
- * @param {Object} options - sessionId, mainContext, etc.
23
- * @returns {Promise<Object>} - Execution result(s)
24
- */
25
- async function spawnAgent(agentType, prompt, options = {}) {
26
- if (!config.agents?.enabled) {
27
- throw new Error("Agents disabled. Set AGENTS_ENABLED=true");
28
- }
29
-
30
- // Handle parallel execution
31
- if (Array.isArray(agentType)) {
32
- return await spawnParallel(agentType, prompt, options);
33
- }
34
-
35
- // Single agent execution
36
- logger.info({
37
- agentType,
38
- prompt: prompt.slice(0, 100),
39
- sessionId: options.sessionId
40
- }, "Spawning subagent");
41
-
42
- // Get agent definition
43
- const agentDef = definitionLoader.getAgent(agentType);
44
-
45
- if (!agentDef) {
46
- throw new Error(`Unknown agent type: ${agentType}`);
47
- }
48
-
49
- // Record in store
50
- const executionId = agentStore.createExecution({
51
- sessionId: options.sessionId,
52
- agentType,
53
- prompt,
54
- model: agentDef.model
55
- });
56
-
57
- // Execute
58
- const result = await coordinator.executeSingle(agentDef, prompt, options);
59
-
60
- // Update store
61
- if (result.success) {
62
- agentStore.completeExecution(executionId, result.result, result.stats);
63
- } else {
64
- agentStore.failExecution(executionId, { message: result.error }, result.stats);
65
- }
66
-
67
- return result;
68
- }
69
-
70
- /**
71
- * Spawn multiple agents in parallel
72
- */
73
- async function spawnParallel(agentTypes, prompts, options = {}) {
74
- if (!Array.isArray(prompts) || agentTypes.length !== prompts.length) {
75
- throw new Error("agentTypes and prompts must be arrays of same length");
76
- }
77
-
78
- logger.info({
79
- count: agentTypes.length,
80
- sessionId: options.sessionId
81
- }, "Spawning parallel subagents");
82
-
83
- // Build tasks
84
- const tasks = agentTypes.map((type, i) => {
85
- const agentDef = definitionLoader.getAgent(type);
86
-
87
- if (!agentDef) {
88
- throw new Error(`Unknown agent type: ${type}`);
89
- }
90
-
91
- const executionId = agentStore.createExecution({
92
- sessionId: options.sessionId,
93
- agentType: type,
94
- prompt: prompts[i],
95
- model: agentDef.model
96
- });
97
-
98
- return {
99
- agentDef,
100
- taskPrompt: prompts[i],
101
- options: { ...options, executionId }
102
- };
103
- });
104
-
105
- // Execute in parallel with batching
106
- const results = await coordinator.executeBatched(tasks);
107
-
108
- // Update store for each result
109
- results.forEach((result, i) => {
110
- const executionId = tasks[i].options.executionId;
111
-
112
- if (result.success) {
113
- agentStore.completeExecution(executionId, result.result, result.stats);
114
- } else {
115
- agentStore.failExecution(executionId, { message: result.error }, result.stats);
116
- }
117
- });
118
-
119
- return results;
120
- }
121
-
122
- /**
123
- * Auto-select agent based on task description
124
- */
125
- function autoSelectAgent(taskDescription) {
126
- return definitionLoader.findAgentForTask(taskDescription);
127
- }
128
-
129
- /**
130
- * Register custom agent programmatically
131
- */
132
- function registerAgent(name, definition) {
133
- definitionLoader.registerAgent(name, definition);
134
- }
135
-
136
- /**
137
- * Get all available agents
138
- */
139
- function listAgents() {
140
- return definitionLoader.getAllAgents();
141
- }
142
-
143
- /**
144
- * Get agent execution stats
145
- */
146
- function getAgentStats() {
147
- return agentStore.getStats();
148
- }
149
-
150
- /**
151
- * Get specific execution details
152
- */
153
- function getAgentExecution(executionId) {
154
- return agentStore.getExecution(executionId);
155
- }
156
-
157
- module.exports = {
158
- spawnAgent,
159
- spawnParallel,
160
- autoSelectAgent,
161
- registerAgent,
162
- listAgents,
163
- getAgentStats,
164
- getAgentExecution
165
- };
@@ -1,68 +0,0 @@
1
- const logger = require("../logger");
2
- const SubagentExecutor = require("./executor");
3
-
4
- class ParallelCoordinator {
5
- constructor(maxConcurrent = 10, { definitionLoader = null } = {}) {
6
- this.maxConcurrent = maxConcurrent;
7
- this.executor = new SubagentExecutor({ definitionLoader });
8
- }
9
-
10
- /**
11
- * Execute multiple subagents in parallel with batching
12
- * @param {Array} tasks - Array of { agentDef, taskPrompt, options }
13
- * @returns {Promise<Array>} - Array of results
14
- */
15
- async executeBatched(tasks) {
16
- if (tasks.length === 0) {
17
- return [];
18
- }
19
-
20
- logger.info({
21
- totalTasks: tasks.length,
22
- maxConcurrent: this.maxConcurrent
23
- }, "Starting batched subagent execution");
24
-
25
- const results = [];
26
- let processed = 0;
27
-
28
- // Process in batches
29
- while (processed < tasks.length) {
30
- const batch = tasks.slice(processed, processed + this.maxConcurrent);
31
-
32
- logger.debug({
33
- batchSize: batch.length,
34
- processed,
35
- remaining: tasks.length - processed - batch.length
36
- }, "Processing subagent batch");
37
-
38
- // Execute batch in parallel
39
- const batchResults = await Promise.all(
40
- batch.map(task => this.executor.execute(
41
- task.agentDef,
42
- task.taskPrompt,
43
- task.options
44
- ))
45
- );
46
-
47
- results.push(...batchResults);
48
- processed += batch.length;
49
-
50
- logger.info({
51
- completedInBatch: batch.length,
52
- totalCompleted: processed,
53
- totalTasks: tasks.length
54
- }, "Completed subagent batch");
55
- }
56
-
57
- return results;
58
- }
59
-
60
- /**
61
- * Execute single subagent (convenience method)
62
- */
63
- async executeSingle(agentDef, taskPrompt, options = {}) {
64
- return this.executor.execute(agentDef, taskPrompt, options);
65
- }
66
- }
67
-
68
- module.exports = ParallelCoordinator;