neuro-cli 4.1.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 (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +510 -0
  3. package/dist/advisor/advisor.d.ts +50 -0
  4. package/dist/advisor/advisor.js +178 -0
  5. package/dist/agents/base.d.ts +62 -0
  6. package/dist/agents/base.js +215 -0
  7. package/dist/agents/orchestrator.d.ts +46 -0
  8. package/dist/agents/orchestrator.js +192 -0
  9. package/dist/agents/team.d.ts +51 -0
  10. package/dist/agents/team.js +210 -0
  11. package/dist/api/models.d.ts +23 -0
  12. package/dist/api/models.js +514 -0
  13. package/dist/api/ollama.d.ts +153 -0
  14. package/dist/api/ollama.js +751 -0
  15. package/dist/api/openrouter.d.ts +55 -0
  16. package/dist/api/openrouter.js +223 -0
  17. package/dist/commands/commands.d.ts +43 -0
  18. package/dist/commands/commands.js +308 -0
  19. package/dist/config/config.d.ts +8 -0
  20. package/dist/config/config.js +311 -0
  21. package/dist/context/compaction.d.ts +54 -0
  22. package/dist/context/compaction.js +251 -0
  23. package/dist/context/custom-agents.d.ts +107 -0
  24. package/dist/context/custom-agents.js +397 -0
  25. package/dist/context/custom-tools.d.ts +120 -0
  26. package/dist/context/custom-tools.js +564 -0
  27. package/dist/context/git-checkpoint.d.ts +52 -0
  28. package/dist/context/git-checkpoint.js +240 -0
  29. package/dist/context/neuro-md.d.ts +48 -0
  30. package/dist/context/neuro-md.js +202 -0
  31. package/dist/context/neuroignore.d.ts +102 -0
  32. package/dist/context/neuroignore.js +441 -0
  33. package/dist/context/repo-map.d.ts +38 -0
  34. package/dist/context/repo-map.js +220 -0
  35. package/dist/context/skill-standard.d.ts +262 -0
  36. package/dist/context/skill-standard.js +1156 -0
  37. package/dist/context/skill-system.d.ts +75 -0
  38. package/dist/context/skill-system.js +578 -0
  39. package/dist/context/tree-sitter.d.ts +99 -0
  40. package/dist/context/tree-sitter.js +1956 -0
  41. package/dist/core/acp.d.ts +325 -0
  42. package/dist/core/acp.js +1498 -0
  43. package/dist/core/api-server.d.ts +143 -0
  44. package/dist/core/api-server.js +550 -0
  45. package/dist/core/approval.d.ts +81 -0
  46. package/dist/core/approval.js +432 -0
  47. package/dist/core/auto-compact.d.ts +127 -0
  48. package/dist/core/auto-compact.js +436 -0
  49. package/dist/core/auto-mode.d.ts +232 -0
  50. package/dist/core/auto-mode.js +831 -0
  51. package/dist/core/background-session.d.ts +166 -0
  52. package/dist/core/background-session.js +696 -0
  53. package/dist/core/cicd.d.ts +257 -0
  54. package/dist/core/cicd.js +1443 -0
  55. package/dist/core/cloud-sync.d.ts +156 -0
  56. package/dist/core/cloud-sync.js +582 -0
  57. package/dist/core/code-review.d.ts +132 -0
  58. package/dist/core/code-review.js +1191 -0
  59. package/dist/core/completion.d.ts +49 -0
  60. package/dist/core/completion.js +384 -0
  61. package/dist/core/context.d.ts +38 -0
  62. package/dist/core/context.js +144 -0
  63. package/dist/core/diff-preview.d.ts +35 -0
  64. package/dist/core/diff-preview.js +173 -0
  65. package/dist/core/doom-loop.d.ts +51 -0
  66. package/dist/core/doom-loop.js +179 -0
  67. package/dist/core/engine.d.ts +183 -0
  68. package/dist/core/engine.js +942 -0
  69. package/dist/core/extended-thinking.d.ts +103 -0
  70. package/dist/core/extended-thinking.js +269 -0
  71. package/dist/core/fallback.d.ts +54 -0
  72. package/dist/core/fallback.js +104 -0
  73. package/dist/core/git-worktree.d.ts +139 -0
  74. package/dist/core/git-worktree.js +614 -0
  75. package/dist/core/headless.d.ts +30 -0
  76. package/dist/core/headless.js +101 -0
  77. package/dist/core/i18n.d.ts +83 -0
  78. package/dist/core/i18n.js +739 -0
  79. package/dist/core/linting.d.ts +129 -0
  80. package/dist/core/linting.js +699 -0
  81. package/dist/core/model-router.d.ts +109 -0
  82. package/dist/core/model-router.js +581 -0
  83. package/dist/core/multi-model.d.ts +243 -0
  84. package/dist/core/multi-model.js +1099 -0
  85. package/dist/core/multi-session.d.ts +144 -0
  86. package/dist/core/multi-session.js +442 -0
  87. package/dist/core/multimodal.d.ts +125 -0
  88. package/dist/core/multimodal.js +286 -0
  89. package/dist/core/observability.d.ts +93 -0
  90. package/dist/core/observability.js +737 -0
  91. package/dist/core/os-sandbox.d.ts +122 -0
  92. package/dist/core/os-sandbox.js +1193 -0
  93. package/dist/core/outcome-grading.d.ts +228 -0
  94. package/dist/core/outcome-grading.js +1123 -0
  95. package/dist/core/output-styles.d.ts +57 -0
  96. package/dist/core/output-styles.js +382 -0
  97. package/dist/core/parallel-agents.d.ts +183 -0
  98. package/dist/core/parallel-agents.js +563 -0
  99. package/dist/core/plugin-bundle.d.ts +236 -0
  100. package/dist/core/plugin-bundle.js +887 -0
  101. package/dist/core/plugin-sdk.d.ts +139 -0
  102. package/dist/core/plugin-sdk.js +273 -0
  103. package/dist/core/prompt-cache.d.ts +163 -0
  104. package/dist/core/prompt-cache.js +599 -0
  105. package/dist/core/sandbox.d.ts +127 -0
  106. package/dist/core/sandbox.js +369 -0
  107. package/dist/core/scheduled-tasks.d.ts +151 -0
  108. package/dist/core/scheduled-tasks.js +502 -0
  109. package/dist/core/security-scanner.d.ts +160 -0
  110. package/dist/core/security-scanner.js +1494 -0
  111. package/dist/core/session.d.ts +83 -0
  112. package/dist/core/session.js +269 -0
  113. package/dist/core/shell-completion.d.ts +51 -0
  114. package/dist/core/shell-completion.js +674 -0
  115. package/dist/core/smart-monitor.d.ts +146 -0
  116. package/dist/core/smart-monitor.js +1199 -0
  117. package/dist/core/spec-driven.d.ts +233 -0
  118. package/dist/core/spec-driven.js +1485 -0
  119. package/dist/core/spending-warnings.d.ts +123 -0
  120. package/dist/core/spending-warnings.js +456 -0
  121. package/dist/core/sub-agent.d.ts +298 -0
  122. package/dist/core/sub-agent.js +1023 -0
  123. package/dist/core/telemetry.d.ts +157 -0
  124. package/dist/core/telemetry.js +412 -0
  125. package/dist/core/terminal-ux.d.ts +134 -0
  126. package/dist/core/terminal-ux.js +649 -0
  127. package/dist/core/testing.d.ts +146 -0
  128. package/dist/core/testing.js +1200 -0
  129. package/dist/core/types.d.ts +439 -0
  130. package/dist/core/types.js +6 -0
  131. package/dist/core/undo-redo.d.ts +112 -0
  132. package/dist/core/undo-redo.js +290 -0
  133. package/dist/core/updater.d.ts +159 -0
  134. package/dist/core/updater.js +608 -0
  135. package/dist/core/vim-mode.d.ts +151 -0
  136. package/dist/core/vim-mode.js +771 -0
  137. package/dist/core/voice.d.ts +137 -0
  138. package/dist/core/voice.js +538 -0
  139. package/dist/core/web-dashboard.d.ts +109 -0
  140. package/dist/core/web-dashboard.js +484 -0
  141. package/dist/hooks/hooks.d.ts +74 -0
  142. package/dist/hooks/hooks.js +160 -0
  143. package/dist/hooks/llm-evaluator.d.ts +165 -0
  144. package/dist/hooks/llm-evaluator.js +560 -0
  145. package/dist/index.d.ts +3 -0
  146. package/dist/index.js +1186 -0
  147. package/dist/lsp/lsp-manager.d.ts +63 -0
  148. package/dist/lsp/lsp-manager.js +351 -0
  149. package/dist/mcp/client.d.ts +133 -0
  150. package/dist/mcp/client.js +684 -0
  151. package/dist/mcp/mcp-apps.d.ts +70 -0
  152. package/dist/mcp/mcp-apps.js +1007 -0
  153. package/dist/tools/bash.d.ts +5 -0
  154. package/dist/tools/bash.js +195 -0
  155. package/dist/tools/browser.d.ts +92 -0
  156. package/dist/tools/browser.js +1570 -0
  157. package/dist/tools/extended.d.ts +6 -0
  158. package/dist/tools/extended.js +191 -0
  159. package/dist/tools/file.d.ts +10 -0
  160. package/dist/tools/file.js +382 -0
  161. package/dist/tools/github.d.ts +389 -0
  162. package/dist/tools/github.js +759 -0
  163. package/dist/tools/index.d.ts +9 -0
  164. package/dist/tools/index.js +40 -0
  165. package/dist/tools/memory.d.ts +6 -0
  166. package/dist/tools/memory.js +197 -0
  167. package/dist/tools/registry.d.ts +29 -0
  168. package/dist/tools/registry.js +64 -0
  169. package/dist/tools/web.d.ts +6 -0
  170. package/dist/tools/web.js +150 -0
  171. package/dist/ui/renderer.d.ts +97 -0
  172. package/dist/ui/renderer.js +279 -0
  173. package/dist/ui/theme.d.ts +27 -0
  174. package/dist/ui/theme.js +106 -0
  175. package/package.json +83 -0
@@ -0,0 +1,563 @@
1
+ // ============================================================
2
+ // NeuroCLI - Parallel Agent Manager
3
+ // Parallel execution of multiple agents with shared state,
4
+ // resource limits, and coordination — inspired by Claude Code
5
+ // sub-agents and Cursor CLI parallel agents.
6
+ // Uses only Node.js built-in modules.
7
+ // ============================================================
8
+ import { EventEmitter } from 'events';
9
+ import { join } from 'path';
10
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from 'fs';
11
+ import { tmpdir } from 'os';
12
+ // ---- Constants ----
13
+ const DEFAULT_MAX_CONCURRENT = 5;
14
+ const DEFAULT_MAX_ITERATIONS = 50;
15
+ const DEFAULT_MAX_TOKENS = 200_000;
16
+ const PRIORITY_ORDER = { high: 3, normal: 2, low: 1 };
17
+ const SANDBOX_BASE_DIR = join(tmpdir(), 'neuro-cli-agents');
18
+ // ============================================================
19
+ // ParallelAgentManager
20
+ // ============================================================
21
+ export class ParallelAgentManager extends EventEmitter {
22
+ agents = new Map();
23
+ maxConcurrent;
24
+ globalSharedState;
25
+ onAgentCompleteCallbacks = [];
26
+ onAgentErrorCallbacks = [];
27
+ /** Queue of agent IDs waiting to run when a slot opens. */
28
+ pendingQueue = [];
29
+ constructor(options) {
30
+ super();
31
+ this.maxConcurrent = options?.maxConcurrent ?? DEFAULT_MAX_CONCURRENT;
32
+ this.globalSharedState = options?.sharedState ?? {};
33
+ }
34
+ // ---- Public API ----
35
+ /**
36
+ * Spawn a new parallel agent. If `config.autoStart` is true the agent
37
+ * begins running immediately (subject to the concurrency limit).
38
+ * Returns the agent ID.
39
+ */
40
+ spawn(config) {
41
+ if (this.agents.has(config.id)) {
42
+ throw new Error(`Agent with id "${config.id}" already exists`);
43
+ }
44
+ const sandboxDir = join(SANDBOX_BASE_DIR, config.id);
45
+ this.ensureDir(sandboxDir);
46
+ // Merge global + per-agent shared state (per-agent takes precedence)
47
+ const mergedSharedState = {
48
+ ...this.globalSharedState,
49
+ ...config.sharedState,
50
+ };
51
+ const state = {
52
+ id: config.id,
53
+ name: config.name,
54
+ status: 'spawning',
55
+ startedAt: Date.now(),
56
+ iterations: 0,
57
+ tokensUsed: 0,
58
+ progress: 0,
59
+ };
60
+ // Create the completion promise wiring up-front
61
+ let completionResolve = null;
62
+ let completionReject = null;
63
+ const completionPromise = new Promise((resolve, reject) => {
64
+ completionResolve = resolve;
65
+ completionReject = reject;
66
+ });
67
+ const record = {
68
+ config,
69
+ state,
70
+ abortController: new AbortController(),
71
+ sharedState: mergedSharedState,
72
+ sandboxDir,
73
+ completionPromise,
74
+ completionResolve,
75
+ completionReject,
76
+ };
77
+ this.agents.set(config.id, record);
78
+ if (config.autoStart) {
79
+ this.scheduleStart(config.id);
80
+ }
81
+ return config.id;
82
+ }
83
+ /**
84
+ * Cancel a running (or pending) agent.
85
+ */
86
+ cancel(agentId) {
87
+ const record = this.agents.get(agentId);
88
+ if (!record)
89
+ return false;
90
+ if (record.state.status === 'completed' || record.state.status === 'failed' || record.state.status === 'cancelled') {
91
+ return false;
92
+ }
93
+ record.abortController.abort();
94
+ record.state.status = 'cancelled';
95
+ record.state.completedAt = Date.now();
96
+ record.state.progress = record.state.progress; // freeze progress
97
+ // Remove from pending queue if applicable
98
+ this.pendingQueue = this.pendingQueue.filter((id) => id !== agentId);
99
+ // Reject the completion promise so waiters unblock
100
+ record.completionReject?.(new Error(`Agent "${agentId}" was cancelled`));
101
+ this.emit('agent:cancelled', agentId, record.state);
102
+ this.tryStartPending();
103
+ return true;
104
+ }
105
+ /**
106
+ * Cancel all running and pending agents.
107
+ */
108
+ cancelAll() {
109
+ let count = 0;
110
+ const ids = Array.from(this.agents.keys());
111
+ for (const id of ids) {
112
+ if (this.cancel(id))
113
+ count++;
114
+ }
115
+ return count;
116
+ }
117
+ /**
118
+ * Get the state of a specific agent.
119
+ */
120
+ getStatus(agentId) {
121
+ const record = this.agents.get(agentId);
122
+ if (!record)
123
+ return null;
124
+ return { ...record.state };
125
+ }
126
+ /**
127
+ * List all currently running agents (status === 'running' | 'spawning').
128
+ */
129
+ listActive() {
130
+ const active = [];
131
+ const records = Array.from(this.agents.values());
132
+ for (const record of records) {
133
+ if (record.state.status === 'running' || record.state.status === 'spawning') {
134
+ active.push({ ...record.state });
135
+ }
136
+ }
137
+ return active.sort((a, b) => b.startedAt - a.startedAt);
138
+ }
139
+ /**
140
+ * List all agents including completed ones.
141
+ */
142
+ listAll() {
143
+ const all = [];
144
+ const records = Array.from(this.agents.values());
145
+ for (const record of records) {
146
+ all.push({ ...record.state });
147
+ }
148
+ return all.sort((a, b) => b.startedAt - a.startedAt);
149
+ }
150
+ /**
151
+ * Wait for a specific agent to complete and return its result.
152
+ * Rejects if the agent was cancelled or failed.
153
+ */
154
+ async waitFor(agentId) {
155
+ const record = this.agents.get(agentId);
156
+ if (!record) {
157
+ throw new Error(`Agent "${agentId}" not found`);
158
+ }
159
+ // If already completed, return immediately
160
+ if (record.state.status === 'completed') {
161
+ return record.state.result ?? '';
162
+ }
163
+ // If already failed/cancelled, throw
164
+ if (record.state.status === 'failed' || record.state.status === 'cancelled') {
165
+ throw new Error(record.state.error ?? `Agent "${agentId}" ${record.state.status}`);
166
+ }
167
+ return record.completionPromise;
168
+ }
169
+ /**
170
+ * Wait for all currently running agents to finish.
171
+ * Returns a map of agentId → result (or error message on failure).
172
+ */
173
+ async waitForAll() {
174
+ const runningIds = [];
175
+ const records = Array.from(this.agents.values());
176
+ for (const record of records) {
177
+ if (record.state.status === 'running' ||
178
+ record.state.status === 'spawning') {
179
+ runningIds.push(record.state.id);
180
+ }
181
+ }
182
+ const results = new Map();
183
+ const promises = runningIds.map(async (id) => {
184
+ try {
185
+ const result = await this.waitFor(id);
186
+ results.set(id, { result });
187
+ }
188
+ catch (err) {
189
+ results.set(id, { error: err instanceof Error ? err.message : String(err) });
190
+ }
191
+ });
192
+ await Promise.allSettled(promises);
193
+ return results;
194
+ }
195
+ /**
196
+ * Get the result of a completed agent.
197
+ */
198
+ getResult(agentId) {
199
+ const record = this.agents.get(agentId);
200
+ if (!record || record.state.status !== 'completed')
201
+ return null;
202
+ return record.state.result ?? null;
203
+ }
204
+ /**
205
+ * Broadcast a message to all running agents' shared state.
206
+ * The message is stored under `sharedState.__broadcasts`.
207
+ */
208
+ broadcastMessage(message) {
209
+ let delivered = 0;
210
+ const records = Array.from(this.agents.values());
211
+ for (const record of records) {
212
+ if (record.state.status !== 'running')
213
+ continue;
214
+ const broadcasts = record.sharedState.__broadcasts ?? [];
215
+ broadcasts.push(message);
216
+ record.sharedState.__broadcasts = broadcasts;
217
+ delivered++;
218
+ }
219
+ this.emit('broadcast', message, delivered);
220
+ return delivered;
221
+ }
222
+ /**
223
+ * Register a callback for when any agent completes successfully.
224
+ */
225
+ onAgentComplete(callback) {
226
+ this.onAgentCompleteCallbacks.push(callback);
227
+ }
228
+ /**
229
+ * Register a callback for when any agent encounters an error.
230
+ */
231
+ onAgentError(callback) {
232
+ this.onAgentErrorCallbacks.push(callback);
233
+ }
234
+ /**
235
+ * Get aggregate metrics across all agents.
236
+ */
237
+ getMetrics() {
238
+ let activeAgents = 0;
239
+ let completedAgents = 0;
240
+ let failedAgents = 0;
241
+ let cancelledAgents = 0;
242
+ let totalDurationMs = 0;
243
+ let totalTokensUsed = 0;
244
+ const allRecords = Array.from(this.agents.values());
245
+ for (const record of allRecords) {
246
+ switch (record.state.status) {
247
+ case 'spawning':
248
+ case 'running':
249
+ activeAgents++;
250
+ break;
251
+ case 'completed':
252
+ completedAgents++;
253
+ break;
254
+ case 'failed':
255
+ failedAgents++;
256
+ break;
257
+ case 'cancelled':
258
+ cancelledAgents++;
259
+ break;
260
+ }
261
+ totalTokensUsed += record.state.tokensUsed;
262
+ if (record.state.completedAt) {
263
+ totalDurationMs += record.state.completedAt - record.state.startedAt;
264
+ }
265
+ }
266
+ const agentsWithDuration = completedAgents + failedAgents + cancelledAgents;
267
+ return {
268
+ totalAgents: this.agents.size,
269
+ activeAgents,
270
+ completedAgents,
271
+ failedAgents,
272
+ cancelledAgents,
273
+ averageDurationMs: agentsWithDuration > 0 ? Math.round(totalDurationMs / agentsWithDuration) : 0,
274
+ totalTokensUsed,
275
+ };
276
+ }
277
+ /**
278
+ * Read a value from the shared state of a specific agent.
279
+ */
280
+ getSharedValue(agentId, key) {
281
+ const record = this.agents.get(agentId);
282
+ if (!record)
283
+ return undefined;
284
+ return record.sharedState[key];
285
+ }
286
+ /**
287
+ * Write a value to the shared state of a specific agent.
288
+ * Also writes to the global shared state so future agents inherit it.
289
+ */
290
+ setSharedValue(agentId, key, value) {
291
+ const record = this.agents.get(agentId);
292
+ if (!record)
293
+ return false;
294
+ record.sharedState[key] = value;
295
+ this.globalSharedState[key] = value;
296
+ return true;
297
+ }
298
+ /**
299
+ * Update the progress of a running agent (0-100).
300
+ */
301
+ updateProgress(agentId, progress) {
302
+ const record = this.agents.get(agentId);
303
+ if (!record || record.state.status !== 'running')
304
+ return false;
305
+ record.state.progress = Math.max(0, Math.min(100, progress));
306
+ this.emit('agent:progress', agentId, record.state.progress);
307
+ return true;
308
+ }
309
+ /**
310
+ * Clean up sandbox directories for completed/failed/cancelled agents.
311
+ */
312
+ cleanup() {
313
+ let cleaned = 0;
314
+ const entries = Array.from(this.agents.entries());
315
+ for (const [id, record] of entries) {
316
+ if (record.state.status === 'completed' ||
317
+ record.state.status === 'failed' ||
318
+ record.state.status === 'cancelled') {
319
+ try {
320
+ if (existsSync(record.sandboxDir)) {
321
+ rmSync(record.sandboxDir, { recursive: true, force: true });
322
+ }
323
+ }
324
+ catch {
325
+ // Best-effort cleanup
326
+ }
327
+ cleaned++;
328
+ }
329
+ }
330
+ return cleaned;
331
+ }
332
+ // ---- Private Helpers ----
333
+ /**
334
+ * Schedule an agent to start. If we're at the concurrency limit,
335
+ * the agent is placed in the pending queue.
336
+ */
337
+ scheduleStart(agentId) {
338
+ const record = this.agents.get(agentId);
339
+ if (!record)
340
+ return;
341
+ if (this.activeCount() >= this.maxConcurrent) {
342
+ // Enqueue by priority
343
+ this.pendingQueue.push(agentId);
344
+ this.pendingQueue.sort((a, b) => {
345
+ const ra = this.agents.get(a);
346
+ const rb = this.agents.get(b);
347
+ const pa = ra ? PRIORITY_ORDER[ra.config.priority] ?? 2 : 2;
348
+ const pb = rb ? PRIORITY_ORDER[rb.config.priority] ?? 2 : 2;
349
+ return pb - pa; // higher priority first
350
+ });
351
+ this.emit('agent:queued', agentId);
352
+ return;
353
+ }
354
+ this.runAgent(agentId);
355
+ }
356
+ /**
357
+ * Actually start running an agent.
358
+ */
359
+ runAgent(agentId) {
360
+ const record = this.agents.get(agentId);
361
+ if (!record)
362
+ return;
363
+ record.state.status = 'running';
364
+ record.state.startedAt = Date.now();
365
+ this.emit('agent:started', agentId, record.state);
366
+ // Run the agent loop asynchronously
367
+ this.executeAgentLoop(record)
368
+ .then((result) => {
369
+ record.state.status = 'completed';
370
+ record.state.result = result;
371
+ record.state.completedAt = Date.now();
372
+ record.state.progress = 100;
373
+ record.completionResolve?.(result);
374
+ this.fireCompleteCallbacks(agentId, record.state);
375
+ this.emit('agent:completed', agentId, record.state);
376
+ this.tryStartPending();
377
+ })
378
+ .catch((err) => {
379
+ if (record.state.status === 'cancelled') {
380
+ // Already handled by cancel()
381
+ return;
382
+ }
383
+ const errorMsg = err instanceof Error ? err.message : String(err);
384
+ record.state.status = 'failed';
385
+ record.state.error = errorMsg;
386
+ record.state.completedAt = Date.now();
387
+ record.completionReject?.(err instanceof Error ? err : new Error(errorMsg));
388
+ this.fireErrorCallbacks(agentId, errorMsg, record.state);
389
+ this.emit('agent:error', agentId, errorMsg, record.state);
390
+ this.tryStartPending();
391
+ });
392
+ }
393
+ /**
394
+ * The core agent execution loop.
395
+ *
396
+ * In a real implementation this would call an LLM, execute tools, etc.
397
+ * Here we simulate the loop with AbortController support, token tracking,
398
+ * iteration limits, and sandboxed file I/O — all using only Node built-ins.
399
+ */
400
+ async executeAgentLoop(record) {
401
+ const maxIterations = record.config.maxIterations ?? DEFAULT_MAX_ITERATIONS;
402
+ const maxTokens = record.config.maxTokens ?? DEFAULT_MAX_TOKENS;
403
+ const workingDir = record.config.workingDirectory ?? record.sandboxDir;
404
+ const abortSignal = record.abortController.signal;
405
+ const outputLines = [`Agent "${record.config.name}" (${record.config.id}) started.`];
406
+ // Persist initial context to sandbox
407
+ this.persistContext(record);
408
+ for (let i = 0; i < maxIterations; i++) {
409
+ // Check for cancellation
410
+ if (abortSignal.aborted) {
411
+ throw new Error(`Agent "${record.config.id}" was cancelled`);
412
+ }
413
+ // Simulate token usage per iteration
414
+ const estimatedTokens = this.estimateTokens(record.config.prompt, i);
415
+ record.state.tokensUsed += estimatedTokens;
416
+ // Enforce token limit
417
+ if (record.state.tokensUsed >= maxTokens) {
418
+ outputLines.push(`Token limit reached (${maxTokens}). Stopping.`);
419
+ break;
420
+ }
421
+ record.state.iterations = i + 1;
422
+ // Update progress
423
+ record.state.progress = Math.min(Math.round(((i + 1) / maxIterations) * 100), 100);
424
+ // Simulate one iteration of work (non-blocking)
425
+ await this.yieldControl();
426
+ // Simulate: write intermediate output to sandbox
427
+ this.writeIterationLog(record, i, outputLines);
428
+ // Check broadcasts in shared state
429
+ const broadcasts = record.sharedState.__broadcasts;
430
+ if (broadcasts && broadcasts.length > 0) {
431
+ outputLines.push(`[broadcast received]: ${broadcasts.join('; ')}`);
432
+ // Agent consumes broadcasts
433
+ record.sharedState.__broadcasts = [];
434
+ }
435
+ // For demonstration, we complete after a few iterations.
436
+ // In production, the agent would decide based on LLM output.
437
+ if (i >= 2) {
438
+ outputLines.push(`Task completed after ${i + 1} iterations.`);
439
+ break;
440
+ }
441
+ }
442
+ // Persist final context
443
+ this.persistContext(record);
444
+ const finalResult = outputLines.join('\n');
445
+ return finalResult;
446
+ }
447
+ /**
448
+ * Yield control to the event loop so other agents can progress.
449
+ * Uses setImmediate for true interleaving.
450
+ */
451
+ yieldControl() {
452
+ return new Promise((resolve) => setImmediate(resolve));
453
+ }
454
+ /**
455
+ * Rough token estimation (heuristic: ~4 chars per token).
456
+ */
457
+ estimateTokens(prompt, iteration) {
458
+ // Base cost from prompt + incremental cost per iteration
459
+ const baseCost = Math.ceil(prompt.length / 4);
460
+ const iterationCost = 50 + Math.floor(Math.random() * 150); // simulated
461
+ return baseCost * (iteration === 0 ? 1 : 0) + iterationCost;
462
+ }
463
+ /**
464
+ * Persist the agent's context to its sandbox directory.
465
+ */
466
+ persistContext(record) {
467
+ try {
468
+ const contextPath = join(record.sandboxDir, 'context.json');
469
+ const context = {
470
+ id: record.config.id,
471
+ name: record.config.name,
472
+ prompt: record.config.prompt,
473
+ model: record.config.model,
474
+ sharedState: record.sharedState,
475
+ state: record.state,
476
+ updatedAt: Date.now(),
477
+ };
478
+ writeFileSync(contextPath, JSON.stringify(context, null, 2), 'utf-8');
479
+ }
480
+ catch {
481
+ // Non-critical — context persistence is best-effort
482
+ }
483
+ }
484
+ /**
485
+ * Write an iteration log to the sandbox directory.
486
+ */
487
+ writeIterationLog(record, iteration, outputLines) {
488
+ try {
489
+ const logPath = join(record.sandboxDir, `iter_${iteration}.log`);
490
+ writeFileSync(logPath, JSON.stringify({
491
+ agentId: record.config.id,
492
+ iteration,
493
+ tokensUsed: record.state.tokensUsed,
494
+ progress: record.state.progress,
495
+ lastOutput: outputLines[outputLines.length - 1],
496
+ timestamp: Date.now(),
497
+ }, null, 2), 'utf-8');
498
+ }
499
+ catch {
500
+ // Non-critical
501
+ }
502
+ }
503
+ /**
504
+ * When a slot opens, try to start the highest-priority pending agent.
505
+ */
506
+ tryStartPending() {
507
+ while (this.pendingQueue.length > 0 && this.activeCount() < this.maxConcurrent) {
508
+ const nextId = this.pendingQueue.shift();
509
+ const record = this.agents.get(nextId);
510
+ if (!record || record.state.status === 'cancelled')
511
+ continue;
512
+ this.runAgent(nextId);
513
+ }
514
+ }
515
+ /**
516
+ * Count currently active (spawning + running) agents.
517
+ */
518
+ activeCount() {
519
+ let count = 0;
520
+ const records = Array.from(this.agents.values());
521
+ for (const record of records) {
522
+ if (record.state.status === 'spawning' || record.state.status === 'running') {
523
+ count++;
524
+ }
525
+ }
526
+ return count;
527
+ }
528
+ /**
529
+ * Fire all registered onAgentComplete callbacks.
530
+ */
531
+ fireCompleteCallbacks(agentId, state) {
532
+ for (const cb of this.onAgentCompleteCallbacks) {
533
+ try {
534
+ cb(agentId, state);
535
+ }
536
+ catch {
537
+ // Subscriber errors must not break the manager
538
+ }
539
+ }
540
+ }
541
+ /**
542
+ * Fire all registered onAgentError callbacks.
543
+ */
544
+ fireErrorCallbacks(agentId, error, state) {
545
+ for (const cb of this.onAgentErrorCallbacks) {
546
+ try {
547
+ cb(agentId, error, state);
548
+ }
549
+ catch {
550
+ // Subscriber errors must not break the manager
551
+ }
552
+ }
553
+ }
554
+ /**
555
+ * Ensure a directory exists.
556
+ */
557
+ ensureDir(dir) {
558
+ if (!existsSync(dir)) {
559
+ mkdirSync(dir, { recursive: true });
560
+ }
561
+ }
562
+ }
563
+ //# sourceMappingURL=parallel-agents.js.map