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.
- package/README.ja.md +25 -1
- package/README.md +30 -1
- package/package.json +1 -1
- package/src/agents/agent-loop.js +532 -0
- package/src/agents/agentic/code-generator.js +767 -0
- package/src/agents/agentic/code-reviewer.js +698 -0
- package/src/agents/agentic/index.js +43 -0
- package/src/agents/function-tool.js +432 -0
- package/src/agents/index.js +45 -0
- package/src/agents/schema-generator.js +514 -0
- package/src/analyzers/ast-extractor.js +863 -0
- package/src/analyzers/context-optimizer.js +674 -0
- package/src/analyzers/repository-map.js +685 -0
- package/src/integrations/index.js +7 -1
- package/src/integrations/mcp/index.js +175 -0
- package/src/integrations/mcp/mcp-context-provider.js +472 -0
- package/src/integrations/mcp/mcp-discovery.js +436 -0
- package/src/integrations/mcp/mcp-tool-registry.js +467 -0
- package/src/integrations/mcp-connector.js +818 -0
- package/src/integrations/tool-discovery.js +589 -0
- package/src/managers/index.js +7 -0
- package/src/managers/skill-tools.js +565 -0
- package/src/monitoring/quality-dashboard.js +483 -0
- package/src/orchestration/agent-skill-binding.js +655 -0
- package/src/orchestration/error-handler.js +827 -0
- package/src/orchestration/index.js +235 -1
- package/src/orchestration/mcp-tool-adapters.js +896 -0
- package/src/orchestration/reasoning/index.js +58 -0
- package/src/orchestration/reasoning/planning-engine.js +831 -0
- package/src/orchestration/reasoning/reasoning-engine.js +710 -0
- package/src/orchestration/reasoning/self-correction.js +751 -0
- package/src/orchestration/skill-executor.js +665 -0
- package/src/orchestration/skill-registry.js +650 -0
- package/src/orchestration/workflow-examples.js +1072 -0
- package/src/orchestration/workflow-executor.js +779 -0
- package/src/phase4-integration.js +248 -0
- package/src/phase5-integration.js +402 -0
- package/src/steering/steering-auto-update.js +572 -0
- package/src/steering/steering-validator.js +547 -0
- package/src/templates/agents/claude-code/CLAUDE.md +9 -0
- package/src/templates/agents/claude-code/skills/constitution-enforcer/SKILL.md +26 -0
- package/src/templates/agents/claude-code/skills/orchestrator/SKILL.md +9 -0
- package/src/templates/agents/claude-code/skills/security-auditor/SKILL.md +34 -0
- package/src/templates/agents/codex/AGENTS.md +9 -0
- package/src/templates/agents/cursor/AGENTS.md +9 -0
- package/src/templates/agents/gemini-cli/GEMINI.md +9 -0
- package/src/templates/agents/github-copilot/AGENTS.md +9 -0
- package/src/templates/agents/qwen-code/QWEN.md +9 -0
- package/src/templates/agents/windsurf/AGENTS.md +9 -0
- package/src/templates/template-constraints.js +646 -0
- 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.
|
|
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
|
|
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
|
+
"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
|
+
};
|