musubi-sdd 3.9.0 → 5.0.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 (51) hide show
  1. package/README.ja.md +25 -1
  2. package/README.md +30 -1
  3. package/package.json +1 -1
  4. package/src/agents/agent-loop.js +532 -0
  5. package/src/agents/agentic/code-generator.js +767 -0
  6. package/src/agents/agentic/code-reviewer.js +698 -0
  7. package/src/agents/agentic/index.js +43 -0
  8. package/src/agents/function-tool.js +432 -0
  9. package/src/agents/index.js +45 -0
  10. package/src/agents/schema-generator.js +514 -0
  11. package/src/analyzers/ast-extractor.js +863 -0
  12. package/src/analyzers/context-optimizer.js +674 -0
  13. package/src/analyzers/repository-map.js +685 -0
  14. package/src/integrations/index.js +7 -1
  15. package/src/integrations/mcp/index.js +175 -0
  16. package/src/integrations/mcp/mcp-context-provider.js +472 -0
  17. package/src/integrations/mcp/mcp-discovery.js +436 -0
  18. package/src/integrations/mcp/mcp-tool-registry.js +467 -0
  19. package/src/integrations/mcp-connector.js +818 -0
  20. package/src/integrations/tool-discovery.js +589 -0
  21. package/src/managers/index.js +7 -0
  22. package/src/managers/skill-tools.js +565 -0
  23. package/src/monitoring/quality-dashboard.js +483 -0
  24. package/src/orchestration/agent-skill-binding.js +655 -0
  25. package/src/orchestration/error-handler.js +827 -0
  26. package/src/orchestration/index.js +235 -1
  27. package/src/orchestration/mcp-tool-adapters.js +896 -0
  28. package/src/orchestration/reasoning/index.js +58 -0
  29. package/src/orchestration/reasoning/planning-engine.js +831 -0
  30. package/src/orchestration/reasoning/reasoning-engine.js +710 -0
  31. package/src/orchestration/reasoning/self-correction.js +751 -0
  32. package/src/orchestration/skill-executor.js +665 -0
  33. package/src/orchestration/skill-registry.js +650 -0
  34. package/src/orchestration/workflow-examples.js +1072 -0
  35. package/src/orchestration/workflow-executor.js +779 -0
  36. package/src/phase4-integration.js +248 -0
  37. package/src/phase5-integration.js +402 -0
  38. package/src/steering/steering-auto-update.js +572 -0
  39. package/src/steering/steering-validator.js +547 -0
  40. package/src/templates/agents/claude-code/CLAUDE.md +9 -0
  41. package/src/templates/agents/claude-code/skills/constitution-enforcer/SKILL.md +26 -0
  42. package/src/templates/agents/claude-code/skills/orchestrator/SKILL.md +9 -0
  43. package/src/templates/agents/claude-code/skills/security-auditor/SKILL.md +34 -0
  44. package/src/templates/agents/codex/AGENTS.md +9 -0
  45. package/src/templates/agents/cursor/AGENTS.md +9 -0
  46. package/src/templates/agents/gemini-cli/GEMINI.md +9 -0
  47. package/src/templates/agents/github-copilot/AGENTS.md +9 -0
  48. package/src/templates/agents/qwen-code/QWEN.md +9 -0
  49. package/src/templates/agents/windsurf/AGENTS.md +9 -0
  50. package/src/templates/template-constraints.js +646 -0
  51. package/src/validators/advanced-validation.js +580 -0
package/README.ja.md CHANGED
@@ -71,7 +71,31 @@ musubi init --windsurf # Windsurf IDE
71
71
 
72
72
  ---
73
73
 
74
- ## 📊 v3.7.1 の新機能
74
+ ## 📊 v3.9.0 の新機能
75
+
76
+ - 🛡️ **Guardrailsシステム** - OpenAI Agents SDK inspired 入出力検証とセーフティチェック
77
+ - ✅ **InputGuardrail** - 入力検証、PII検出、インジェクション攻撃防止
78
+ - ✅ **OutputGuardrail** - 出力サニタイズ、機密データ墨消し、コンテンツポリシー適用
79
+ - ⚖️ **SafetyCheckGuardrail** - 憲法条項準拠、コンテンツ安全性分析
80
+ - 🔧 **GuardrailRules DSL** - RuleBuilderによる検証ルール構築のFluent API
81
+ - 🔗 **GuardrailChain** - 複数Guardrailの順次/並列実行
82
+ - 🖥️ **CLIコマンド** - `musubi-validate guardrails` と `guardrails-chain` コマンド
83
+
84
+ ```bash
85
+ # セキュリティプリセットで入力検証
86
+ npx musubi-validate guardrails "user input" --type input --preset security
87
+
88
+ # PII墨消しで出力検証
89
+ npx musubi-validate guardrails "output" --type output --redact
90
+
91
+ # 憲法準拠でセーフティチェック
92
+ npx musubi-validate guardrails "code" --type safety --constitutional --level high
93
+
94
+ # Guardrailチェーンを並列実行
95
+ npx musubi-validate guardrails-chain "content" --parallel
96
+ ```
97
+
98
+ ### 以前のバージョン (v3.7.1)
75
99
 
76
100
  - 🌐 **WebSocketリアルタイムGUI** - `musubi-browser`ダッシュボードでライブ更新
77
101
  - 📋 **GUIクイックアクション** - 新規要件モーダル、プロジェクト検証、レポートエクスポート
package/README.md CHANGED
@@ -71,7 +71,36 @@ musubi init --windsurf # Windsurf IDE
71
71
 
72
72
  ---
73
73
 
74
- ## 📊 What's New in v3.7.1
74
+ ## 📊 What's New in v5.0.0
75
+
76
+ ### Phase 5: Advanced Features 🚀
77
+ - 🔄 **Steering Auto-Update** - Automatic project memory synchronization with 5 trigger types
78
+ - ✅ **Steering Validator** - Comprehensive validation for steering documents
79
+ - 📋 **Template Constraints** - Enforce structure requirements with ThinkingChecklist
80
+ - 📊 **Quality Dashboard** - A-F grade metrics with health status monitoring
81
+ - 🔍 **Advanced Validation** - Cross-artifact consistency, gap detection, traceability validation
82
+ - 🎯 **Phase 5 Integration** - Unified access to all advanced features
83
+
84
+ ### Phase 4: Agent Loop & Agentic Features 🤖
85
+ - 🧠 **Codebase Intelligence** - RepositoryMap, ASTExtractor, ContextOptimizer
86
+ - 💭 **Agentic Reasoning** - ReasoningEngine, PlanningEngine, SelfCorrection
87
+ - ⚡ **Code Generation** - CodeGenerator, CodeReviewer with multiple modes
88
+ - 🔗 **Integrated Agent** - Unified agent with all Phase 4 capabilities
89
+
90
+ ```bash
91
+ # Use advanced features via orchestrator
92
+ const { Phase5Integration, createIntegratedAgent } = require('musubi-sdd');
93
+
94
+ # Quality dashboard
95
+ const dashboard = new QualityDashboard();
96
+ const metrics = await dashboard.collectMetrics(projectPath);
97
+
98
+ # Advanced validation
99
+ const validator = new AdvancedValidation();
100
+ const result = await validator.validateAll(projectPath);
101
+ ```
102
+
103
+ ### Previous Highlights (v3.11.0)
75
104
 
76
105
  - 🌐 **WebSocket Real-time GUI** - Live replanning updates with `musubi-browser` dashboard
77
106
  - 📋 **GUI Quick Actions** - Modal dialog for New Requirement, Validate Project, Export Report
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musubi-sdd",
3
- "version": "3.9.0",
3
+ "version": "5.0.0",
4
4
  "description": "Ultimate Specification Driven Development Tool with 27 Agents for 7 AI Coding Platforms + MCP Integration (Claude Code, GitHub Copilot, Cursor, Gemini CLI, Windsurf, Codex, Qwen Code)",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -0,0 +1,532 @@
1
+ /**
2
+ * MUSUBI Agent Loop
3
+ *
4
+ * Implements agentic tool-calling loop inspired by OpenAI Agents SDK
5
+ * and AutoGen patterns. Executes tool calls, processes results, and
6
+ * continues until completion or limit is reached.
7
+ *
8
+ * @module agents/agent-loop
9
+ */
10
+
11
+ const EventEmitter = require('events');
12
+
13
+ /**
14
+ * @typedef {Object} AgentLoopConfig
15
+ * @property {number} [maxIterations=10] - Maximum tool calling iterations
16
+ * @property {number} [timeout=60000] - Total timeout in milliseconds
17
+ * @property {number} [iterationTimeout=30000] - Per-iteration timeout
18
+ * @property {boolean} [continueOnError=false] - Continue on tool errors
19
+ * @property {Function} [completionCheck] - Custom completion checker
20
+ * @property {Object} [guardrails] - Input/output guardrails
21
+ */
22
+
23
+ /**
24
+ * @typedef {Object} ToolDefinition
25
+ * @property {string} name - Tool name
26
+ * @property {string} description - Tool description
27
+ * @property {Object} parameters - JSON Schema for parameters
28
+ * @property {Function} handler - Async function to execute
29
+ */
30
+
31
+ /**
32
+ * @typedef {Object} ToolCall
33
+ * @property {string} id - Unique call ID
34
+ * @property {string} tool - Tool name
35
+ * @property {Object} arguments - Tool arguments
36
+ */
37
+
38
+ /**
39
+ * @typedef {Object} LoopResult
40
+ * @property {string} status - 'completed' | 'max_iterations' | 'timeout' | 'error'
41
+ * @property {Array} messages - Conversation history
42
+ * @property {Array} toolCalls - All tool calls made
43
+ * @property {*} finalOutput - Final result
44
+ * @property {Object} metrics - Execution metrics
45
+ */
46
+
47
+ /**
48
+ * AgentLoop class for managing tool-calling agentic workflows
49
+ */
50
+ class AgentLoop extends EventEmitter {
51
+ /**
52
+ * @param {AgentLoopConfig} config
53
+ */
54
+ constructor(config = {}) {
55
+ super();
56
+
57
+ this.maxIterations = config.maxIterations ?? 10;
58
+ this.timeout = config.timeout ?? 60000;
59
+ this.iterationTimeout = config.iterationTimeout ?? 30000;
60
+ this.continueOnError = config.continueOnError ?? false;
61
+ this.completionCheck = config.completionCheck ?? this.defaultCompletionCheck.bind(this);
62
+ this.guardrails = config.guardrails ?? null;
63
+
64
+ /** @type {Map<string, ToolDefinition>} */
65
+ this.tools = new Map();
66
+
67
+ /** @type {Array} */
68
+ this.messages = [];
69
+
70
+ /** @type {Array<ToolCall>} */
71
+ this.toolCallHistory = [];
72
+
73
+ /** @type {boolean} */
74
+ this.isRunning = false;
75
+
76
+ /** @type {AbortController|null} */
77
+ this.abortController = null;
78
+ }
79
+
80
+ /**
81
+ * Register a tool for the agent to use
82
+ * @param {ToolDefinition} tool
83
+ */
84
+ registerTool(tool) {
85
+ if (!tool.name || typeof tool.handler !== 'function') {
86
+ throw new Error('Tool must have name and handler function');
87
+ }
88
+
89
+ this.tools.set(tool.name, {
90
+ name: tool.name,
91
+ description: tool.description || '',
92
+ parameters: tool.parameters || { type: 'object', properties: {} },
93
+ handler: tool.handler
94
+ });
95
+
96
+ this.emit('tool:registered', { name: tool.name });
97
+ return this;
98
+ }
99
+
100
+ /**
101
+ * Register multiple tools at once
102
+ * @param {ToolDefinition[]} tools
103
+ */
104
+ registerTools(tools) {
105
+ for (const tool of tools) {
106
+ this.registerTool(tool);
107
+ }
108
+ return this;
109
+ }
110
+
111
+ /**
112
+ * Unregister a tool
113
+ * @param {string} name
114
+ */
115
+ unregisterTool(name) {
116
+ const deleted = this.tools.delete(name);
117
+ if (deleted) {
118
+ this.emit('tool:unregistered', { name });
119
+ }
120
+ return deleted;
121
+ }
122
+
123
+ /**
124
+ * Get all registered tools in OpenAI function format
125
+ * @returns {Array}
126
+ */
127
+ getToolSchemas() {
128
+ return Array.from(this.tools.values()).map(tool => ({
129
+ type: 'function',
130
+ function: {
131
+ name: tool.name,
132
+ description: tool.description,
133
+ parameters: tool.parameters
134
+ }
135
+ }));
136
+ }
137
+
138
+ /**
139
+ * Run the agent loop with an LLM provider
140
+ * @param {Object} options
141
+ * @param {Object} options.llmProvider - LLM provider with chat() method
142
+ * @param {string} options.systemPrompt - System prompt for the agent
143
+ * @param {string} options.userMessage - Initial user message
144
+ * @param {Object} [options.context] - Additional context
145
+ * @returns {Promise<LoopResult>}
146
+ */
147
+ async run({ llmProvider, systemPrompt, userMessage, context = {} }) {
148
+ if (this.isRunning) {
149
+ throw new Error('Agent loop is already running');
150
+ }
151
+
152
+ this.isRunning = true;
153
+ this.abortController = new AbortController();
154
+ this.messages = [];
155
+ this.toolCallHistory = [];
156
+
157
+ const startTime = Date.now();
158
+ const timeoutId = setTimeout(() => this.abort('timeout'), this.timeout);
159
+
160
+ const metrics = {
161
+ iterations: 0,
162
+ toolCalls: 0,
163
+ startTime,
164
+ endTime: null,
165
+ duration: null,
166
+ tokensUsed: 0
167
+ };
168
+
169
+ try {
170
+ // Initialize messages
171
+ this.messages.push({ role: 'system', content: systemPrompt });
172
+
173
+ // Apply input guardrails if configured
174
+ const processedInput = await this.applyInputGuardrails(userMessage, context);
175
+ this.messages.push({ role: 'user', content: processedInput });
176
+
177
+ let iteration = 0;
178
+ let completed = false;
179
+ let finalOutput = null;
180
+
181
+ while (iteration < this.maxIterations && !completed) {
182
+ if (this.abortController.signal.aborted) {
183
+ return this.createResult('aborted', finalOutput, metrics);
184
+ }
185
+
186
+ iteration++;
187
+ metrics.iterations = iteration;
188
+ this.emit('iteration:start', { iteration, maxIterations: this.maxIterations });
189
+
190
+ // Call LLM
191
+ const response = await this.callLLMWithTimeout(llmProvider, {
192
+ messages: this.messages,
193
+ tools: this.getToolSchemas(),
194
+ context
195
+ });
196
+
197
+ if (response.usage) {
198
+ metrics.tokensUsed += response.usage.total_tokens || 0;
199
+ }
200
+
201
+ // Check for tool calls
202
+ if (response.toolCalls && response.toolCalls.length > 0) {
203
+ // Execute tool calls
204
+ const toolResults = await this.executeToolCalls(response.toolCalls);
205
+ metrics.toolCalls += response.toolCalls.length;
206
+
207
+ // Add assistant message with tool calls
208
+ this.messages.push({
209
+ role: 'assistant',
210
+ content: response.content || null,
211
+ tool_calls: response.toolCalls.map(tc => ({
212
+ id: tc.id,
213
+ type: 'function',
214
+ function: {
215
+ name: tc.tool || tc.name,
216
+ arguments: JSON.stringify(tc.arguments)
217
+ }
218
+ }))
219
+ });
220
+
221
+ // Add tool results
222
+ for (const result of toolResults) {
223
+ this.messages.push({
224
+ role: 'tool',
225
+ tool_call_id: result.callId,
226
+ content: JSON.stringify(result.output)
227
+ });
228
+ }
229
+
230
+ this.emit('iteration:complete', {
231
+ iteration,
232
+ toolCalls: response.toolCalls.length,
233
+ hasMore: true
234
+ });
235
+ } else {
236
+ // No tool calls - LLM response is final
237
+ finalOutput = response.content;
238
+
239
+ // Apply output guardrails
240
+ finalOutput = await this.applyOutputGuardrails(finalOutput, context);
241
+
242
+ // Add final assistant message
243
+ this.messages.push({
244
+ role: 'assistant',
245
+ content: finalOutput
246
+ });
247
+
248
+ // Check completion
249
+ completed = await this.completionCheck(finalOutput, this.messages, context);
250
+
251
+ this.emit('iteration:complete', {
252
+ iteration,
253
+ toolCalls: 0,
254
+ hasMore: !completed
255
+ });
256
+ }
257
+ }
258
+
259
+ metrics.endTime = Date.now();
260
+ metrics.duration = metrics.endTime - startTime;
261
+
262
+ if (iteration >= this.maxIterations && !completed) {
263
+ return this.createResult('max_iterations', finalOutput, metrics);
264
+ }
265
+
266
+ return this.createResult('completed', finalOutput, metrics);
267
+
268
+ } catch (error) {
269
+ metrics.endTime = Date.now();
270
+ metrics.duration = metrics.endTime - startTime;
271
+
272
+ this.emit('error', error);
273
+
274
+ if (error.message === 'timeout') {
275
+ return this.createResult('timeout', null, metrics);
276
+ }
277
+
278
+ return this.createResult('error', null, metrics, error);
279
+ } finally {
280
+ clearTimeout(timeoutId);
281
+ this.isRunning = false;
282
+ this.abortController = null;
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Call LLM with per-iteration timeout
288
+ * @param {Object} llmProvider
289
+ * @param {Object} options
290
+ * @returns {Promise<Object>}
291
+ */
292
+ async callLLMWithTimeout(llmProvider, options) {
293
+ return new Promise((resolve, reject) => {
294
+ const timeoutId = setTimeout(() => {
295
+ reject(new Error('Iteration timeout'));
296
+ }, this.iterationTimeout);
297
+
298
+ llmProvider.chat(options)
299
+ .then(response => {
300
+ clearTimeout(timeoutId);
301
+ resolve(response);
302
+ })
303
+ .catch(error => {
304
+ clearTimeout(timeoutId);
305
+ reject(error);
306
+ });
307
+ });
308
+ }
309
+
310
+ /**
311
+ * Execute tool calls and collect results
312
+ * @param {ToolCall[]} toolCalls
313
+ * @returns {Promise<Array>}
314
+ */
315
+ async executeToolCalls(toolCalls) {
316
+ const results = [];
317
+
318
+ for (const call of toolCalls) {
319
+ const toolName = call.tool || call.name;
320
+ const callId = call.id || `call_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
321
+
322
+ this.emit('tool:call', { name: toolName, arguments: call.arguments, id: callId });
323
+
324
+ try {
325
+ const tool = this.tools.get(toolName);
326
+
327
+ if (!tool) {
328
+ throw new Error(`Unknown tool: ${toolName}`);
329
+ }
330
+
331
+ const output = await tool.handler(call.arguments);
332
+
333
+ this.toolCallHistory.push({
334
+ id: callId,
335
+ tool: toolName,
336
+ arguments: call.arguments,
337
+ output,
338
+ status: 'success'
339
+ });
340
+
341
+ results.push({
342
+ callId,
343
+ tool: toolName,
344
+ output,
345
+ status: 'success'
346
+ });
347
+
348
+ this.emit('tool:result', { name: toolName, output, id: callId, status: 'success' });
349
+
350
+ } catch (error) {
351
+ const errorResult = {
352
+ callId,
353
+ tool: toolName,
354
+ error: error.message,
355
+ status: 'error'
356
+ };
357
+
358
+ this.toolCallHistory.push({
359
+ id: callId,
360
+ tool: toolName,
361
+ arguments: call.arguments,
362
+ error: error.message,
363
+ status: 'error'
364
+ });
365
+
366
+ this.emit('tool:error', { name: toolName, error: error.message, id: callId });
367
+
368
+ if (this.continueOnError) {
369
+ results.push({
370
+ callId,
371
+ tool: toolName,
372
+ output: { error: error.message },
373
+ status: 'error'
374
+ });
375
+ } else {
376
+ throw error;
377
+ }
378
+ }
379
+ }
380
+
381
+ return results;
382
+ }
383
+
384
+ /**
385
+ * Default completion check - returns true when LLM provides final response
386
+ * @param {*} output
387
+ * @param {Array} messages
388
+ * @param {Object} context
389
+ * @returns {Promise<boolean>}
390
+ */
391
+ async defaultCompletionCheck(output, messages, context) {
392
+ // By default, if we reach here (no tool calls), we're done
393
+ return true;
394
+ }
395
+
396
+ /**
397
+ * Apply input guardrails if configured
398
+ * @param {string} input
399
+ * @param {Object} context
400
+ * @returns {Promise<string>}
401
+ */
402
+ async applyInputGuardrails(input, context) {
403
+ if (!this.guardrails?.input) {
404
+ return input;
405
+ }
406
+
407
+ try {
408
+ const result = await this.guardrails.input.validate(input, context);
409
+ if (!result.valid) {
410
+ throw new Error(`Input guardrail violation: ${result.reason}`);
411
+ }
412
+ return result.transformed || input;
413
+ } catch (error) {
414
+ this.emit('guardrail:input:error', error);
415
+ throw error;
416
+ }
417
+ }
418
+
419
+ /**
420
+ * Apply output guardrails if configured
421
+ * @param {string} output
422
+ * @param {Object} context
423
+ * @returns {Promise<string>}
424
+ */
425
+ async applyOutputGuardrails(output, context) {
426
+ if (!this.guardrails?.output) {
427
+ return output;
428
+ }
429
+
430
+ try {
431
+ const result = await this.guardrails.output.validate(output, context);
432
+ if (!result.valid) {
433
+ this.emit('guardrail:output:violation', { output, reason: result.reason });
434
+ return result.fallback || '[Output blocked by guardrails]';
435
+ }
436
+ return result.transformed || output;
437
+ } catch (error) {
438
+ this.emit('guardrail:output:error', error);
439
+ return output;
440
+ }
441
+ }
442
+
443
+ /**
444
+ * Create standardized result object
445
+ * @param {string} status
446
+ * @param {*} finalOutput
447
+ * @param {Object} metrics
448
+ * @param {Error} [error]
449
+ * @returns {LoopResult}
450
+ */
451
+ createResult(status, finalOutput, metrics, error = null) {
452
+ return {
453
+ status,
454
+ messages: this.messages,
455
+ toolCalls: this.toolCallHistory,
456
+ finalOutput,
457
+ metrics: {
458
+ ...metrics,
459
+ toolCallCount: this.toolCallHistory.length,
460
+ successfulCalls: this.toolCallHistory.filter(tc => tc.status === 'success').length,
461
+ failedCalls: this.toolCallHistory.filter(tc => tc.status === 'error').length
462
+ },
463
+ ...(error && { error: error.message })
464
+ };
465
+ }
466
+
467
+ /**
468
+ * Abort the running loop
469
+ * @param {string} [reason='aborted']
470
+ */
471
+ abort(reason = 'aborted') {
472
+ if (this.abortController) {
473
+ this.abortController.abort();
474
+ this.emit('abort', { reason });
475
+ }
476
+ }
477
+
478
+ /**
479
+ * Get current loop state
480
+ * @returns {Object}
481
+ */
482
+ getState() {
483
+ return {
484
+ isRunning: this.isRunning,
485
+ messageCount: this.messages.length,
486
+ toolCallCount: this.toolCallHistory.length,
487
+ registeredTools: Array.from(this.tools.keys())
488
+ };
489
+ }
490
+
491
+ /**
492
+ * Reset the loop state
493
+ */
494
+ reset() {
495
+ if (this.isRunning) {
496
+ throw new Error('Cannot reset while loop is running');
497
+ }
498
+
499
+ this.messages = [];
500
+ this.toolCallHistory = [];
501
+ this.emit('reset');
502
+ }
503
+ }
504
+
505
+ /**
506
+ * Create a simple mock LLM provider for testing
507
+ * @param {Array} responses - Pre-defined responses
508
+ * @returns {Object}
509
+ */
510
+ function createMockLLMProvider(responses) {
511
+ let callIndex = 0;
512
+
513
+ return {
514
+ async chat(options) {
515
+ if (callIndex >= responses.length) {
516
+ return { content: 'Done', toolCalls: [] };
517
+ }
518
+
519
+ const response = responses[callIndex++];
520
+ return {
521
+ content: response.content || null,
522
+ toolCalls: response.toolCalls || [],
523
+ usage: response.usage || { total_tokens: 100 }
524
+ };
525
+ }
526
+ };
527
+ }
528
+
529
+ module.exports = {
530
+ AgentLoop,
531
+ createMockLLMProvider
532
+ };