musubi-sdd 3.10.0 → 5.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.
- package/README.md +24 -19
- 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 +870 -0
- package/src/analyzers/context-optimizer.js +681 -0
- package/src/analyzers/repository-map.js +692 -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/cost-tracker.js +7 -0
- package/src/monitoring/incident-manager.js +10 -0
- package/src/monitoring/observability.js +10 -0
- package/src/monitoring/quality-dashboard.js +491 -0
- package/src/monitoring/release-manager.js +10 -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/template-constraints.js +646 -0
- package/src/validators/advanced-validation.js +580 -0
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-Skill Binding - Dynamic capability-based skill assignment
|
|
3
|
+
* Sprint 3.3: Skill System Architecture
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Agent capability scoring
|
|
7
|
+
* - Dynamic skill-agent matching
|
|
8
|
+
* - Permission-based access control
|
|
9
|
+
* - Skill affinity management
|
|
10
|
+
* - Load balancing
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const EventEmitter = require('events');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Agent status
|
|
17
|
+
*/
|
|
18
|
+
const AgentStatus = {
|
|
19
|
+
AVAILABLE: 'available',
|
|
20
|
+
BUSY: 'busy',
|
|
21
|
+
OFFLINE: 'offline',
|
|
22
|
+
MAINTENANCE: 'maintenance'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Agent definition
|
|
27
|
+
*/
|
|
28
|
+
class AgentDefinition {
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
this.id = options.id || '';
|
|
31
|
+
this.name = options.name || '';
|
|
32
|
+
this.description = options.description || '';
|
|
33
|
+
this.capabilities = options.capabilities || [];
|
|
34
|
+
this.permissions = options.permissions || [];
|
|
35
|
+
this.maxConcurrentTasks = options.maxConcurrentTasks || 5;
|
|
36
|
+
this.priority = options.priority || 'normal';
|
|
37
|
+
this.tags = options.tags || [];
|
|
38
|
+
this.metadata = options.metadata || {};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
validate() {
|
|
42
|
+
const errors = [];
|
|
43
|
+
|
|
44
|
+
if (!this.id) {
|
|
45
|
+
errors.push('Agent ID is required');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!this.name) {
|
|
49
|
+
errors.push('Agent name is required');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!Array.isArray(this.capabilities)) {
|
|
53
|
+
errors.push('Capabilities must be an array');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
valid: errors.length === 0,
|
|
58
|
+
errors
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
toJSON() {
|
|
63
|
+
return {
|
|
64
|
+
id: this.id,
|
|
65
|
+
name: this.name,
|
|
66
|
+
description: this.description,
|
|
67
|
+
capabilities: this.capabilities,
|
|
68
|
+
permissions: this.permissions,
|
|
69
|
+
maxConcurrentTasks: this.maxConcurrentTasks,
|
|
70
|
+
priority: this.priority,
|
|
71
|
+
tags: this.tags,
|
|
72
|
+
metadata: this.metadata
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Binding record
|
|
79
|
+
*/
|
|
80
|
+
class BindingRecord {
|
|
81
|
+
constructor(agentId, skillId, options = {}) {
|
|
82
|
+
this.agentId = agentId;
|
|
83
|
+
this.skillId = skillId;
|
|
84
|
+
this.score = options.score || 0;
|
|
85
|
+
this.affinity = options.affinity || 0;
|
|
86
|
+
this.executionCount = options.executionCount || 0;
|
|
87
|
+
this.successRate = options.successRate || 1.0;
|
|
88
|
+
this.averageExecutionTime = options.averageExecutionTime || 0;
|
|
89
|
+
this.lastExecutedAt = options.lastExecutedAt || null;
|
|
90
|
+
this.createdAt = new Date().toISOString();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
updateStats(success, executionTime) {
|
|
94
|
+
this.executionCount++;
|
|
95
|
+
const successWeight = success ? 1 : 0;
|
|
96
|
+
this.successRate = (
|
|
97
|
+
(this.successRate * (this.executionCount - 1) + successWeight) /
|
|
98
|
+
this.executionCount
|
|
99
|
+
);
|
|
100
|
+
this.averageExecutionTime = (
|
|
101
|
+
(this.averageExecutionTime * (this.executionCount - 1) + executionTime) /
|
|
102
|
+
this.executionCount
|
|
103
|
+
);
|
|
104
|
+
this.lastExecutedAt = new Date().toISOString();
|
|
105
|
+
|
|
106
|
+
// Update affinity based on performance
|
|
107
|
+
this.affinity = this._calculateAffinity();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_calculateAffinity() {
|
|
111
|
+
// Affinity increases with success rate and execution count
|
|
112
|
+
const successFactor = this.successRate * 50;
|
|
113
|
+
const experienceFactor = Math.min(this.executionCount / 10, 30);
|
|
114
|
+
const recencyFactor = this.lastExecutedAt
|
|
115
|
+
? Math.max(0, 20 - (Date.now() - new Date(this.lastExecutedAt).getTime()) / 86400000)
|
|
116
|
+
: 0;
|
|
117
|
+
|
|
118
|
+
return Math.round(successFactor + experienceFactor + recencyFactor);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
toJSON() {
|
|
122
|
+
return {
|
|
123
|
+
agentId: this.agentId,
|
|
124
|
+
skillId: this.skillId,
|
|
125
|
+
score: this.score,
|
|
126
|
+
affinity: this.affinity,
|
|
127
|
+
executionCount: this.executionCount,
|
|
128
|
+
successRate: this.successRate,
|
|
129
|
+
averageExecutionTime: this.averageExecutionTime,
|
|
130
|
+
lastExecutedAt: this.lastExecutedAt
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Capability matcher
|
|
137
|
+
*/
|
|
138
|
+
class CapabilityMatcher {
|
|
139
|
+
constructor() {
|
|
140
|
+
this.capabilityWeights = new Map();
|
|
141
|
+
this.synonyms = new Map();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Set weight for a capability (higher = more important)
|
|
146
|
+
*/
|
|
147
|
+
setWeight(capability, weight) {
|
|
148
|
+
this.capabilityWeights.set(capability, weight);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Add synonym mapping
|
|
153
|
+
*/
|
|
154
|
+
addSynonym(capability, synonym) {
|
|
155
|
+
if (!this.synonyms.has(capability)) {
|
|
156
|
+
this.synonyms.set(capability, new Set());
|
|
157
|
+
}
|
|
158
|
+
this.synonyms.get(capability).add(synonym);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Calculate match score between agent capabilities and skill requirements
|
|
163
|
+
*/
|
|
164
|
+
calculateScore(agentCapabilities, skillRequirements) {
|
|
165
|
+
if (!skillRequirements || skillRequirements.length === 0) {
|
|
166
|
+
return 100; // No requirements = full match
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let totalWeight = 0;
|
|
170
|
+
let matchedWeight = 0;
|
|
171
|
+
|
|
172
|
+
for (const req of skillRequirements) {
|
|
173
|
+
const weight = this.capabilityWeights.get(req) || 1;
|
|
174
|
+
totalWeight += weight;
|
|
175
|
+
|
|
176
|
+
if (this._hasCapability(agentCapabilities, req)) {
|
|
177
|
+
matchedWeight += weight;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return totalWeight > 0 ? Math.round((matchedWeight / totalWeight) * 100) : 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Check if agent has capability (including synonyms)
|
|
186
|
+
*/
|
|
187
|
+
_hasCapability(agentCapabilities, required) {
|
|
188
|
+
if (agentCapabilities.includes(required)) {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Check synonyms
|
|
193
|
+
for (const [cap, syns] of this.synonyms) {
|
|
194
|
+
if (cap === required && agentCapabilities.some(ac => syns.has(ac))) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
if (syns.has(required) && agentCapabilities.includes(cap)) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Main Agent-Skill Binding class
|
|
208
|
+
*/
|
|
209
|
+
class AgentSkillBinding extends EventEmitter {
|
|
210
|
+
constructor(skillRegistry, options = {}) {
|
|
211
|
+
super();
|
|
212
|
+
this.skillRegistry = skillRegistry;
|
|
213
|
+
this.agents = new Map();
|
|
214
|
+
this.agentStatus = new Map();
|
|
215
|
+
this.agentLoad = new Map();
|
|
216
|
+
this.bindings = new Map(); // Map<agentId, Map<skillId, BindingRecord>>
|
|
217
|
+
this.matcher = new CapabilityMatcher();
|
|
218
|
+
|
|
219
|
+
// Options
|
|
220
|
+
this.options = {
|
|
221
|
+
autoBinding: options.autoBinding !== false,
|
|
222
|
+
minMatchScore: options.minMatchScore || 50,
|
|
223
|
+
enableLoadBalancing: options.enableLoadBalancing !== false,
|
|
224
|
+
affinityWeight: options.affinityWeight || 0.3
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// Listen to skill registry events
|
|
228
|
+
if (this.skillRegistry) {
|
|
229
|
+
this.skillRegistry.on('skill-registered', ({ skillId }) => {
|
|
230
|
+
if (this.options.autoBinding) {
|
|
231
|
+
this._autoBindSkill(skillId);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Register an agent
|
|
239
|
+
*/
|
|
240
|
+
registerAgent(agentDef) {
|
|
241
|
+
const agent = agentDef instanceof AgentDefinition
|
|
242
|
+
? agentDef
|
|
243
|
+
: new AgentDefinition(agentDef);
|
|
244
|
+
|
|
245
|
+
const validation = agent.validate();
|
|
246
|
+
if (!validation.valid) {
|
|
247
|
+
throw new Error(`Invalid agent: ${validation.errors.join(', ')}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (this.agents.has(agent.id)) {
|
|
251
|
+
throw new Error(`Agent '${agent.id}' already exists`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
this.agents.set(agent.id, agent);
|
|
255
|
+
this.agentStatus.set(agent.id, AgentStatus.AVAILABLE);
|
|
256
|
+
this.agentLoad.set(agent.id, 0);
|
|
257
|
+
this.bindings.set(agent.id, new Map());
|
|
258
|
+
|
|
259
|
+
// Auto-bind existing skills
|
|
260
|
+
if (this.options.autoBinding && this.skillRegistry) {
|
|
261
|
+
this._autoBindAgent(agent.id);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
this.emit('agent-registered', { agentId: agent.id, agent });
|
|
265
|
+
|
|
266
|
+
return agent;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Unregister an agent
|
|
271
|
+
*/
|
|
272
|
+
unregisterAgent(agentId) {
|
|
273
|
+
if (!this.agents.has(agentId)) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
this.agents.delete(agentId);
|
|
278
|
+
this.agentStatus.delete(agentId);
|
|
279
|
+
this.agentLoad.delete(agentId);
|
|
280
|
+
this.bindings.delete(agentId);
|
|
281
|
+
|
|
282
|
+
this.emit('agent-unregistered', { agentId });
|
|
283
|
+
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Get agent by ID
|
|
289
|
+
*/
|
|
290
|
+
getAgent(agentId) {
|
|
291
|
+
return this.agents.get(agentId) || null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Get all agents
|
|
296
|
+
*/
|
|
297
|
+
getAllAgents() {
|
|
298
|
+
return Array.from(this.agents.values());
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Update agent status
|
|
303
|
+
*/
|
|
304
|
+
setAgentStatus(agentId, status) {
|
|
305
|
+
if (!this.agents.has(agentId)) {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const previousStatus = this.agentStatus.get(agentId);
|
|
310
|
+
this.agentStatus.set(agentId, status);
|
|
311
|
+
|
|
312
|
+
if (previousStatus !== status) {
|
|
313
|
+
this.emit('agent-status-changed', { agentId, previousStatus, newStatus: status });
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Get agent status
|
|
321
|
+
*/
|
|
322
|
+
getAgentStatus(agentId) {
|
|
323
|
+
return this.agentStatus.get(agentId) || null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Get available agents
|
|
328
|
+
*/
|
|
329
|
+
getAvailableAgents() {
|
|
330
|
+
return this.getAllAgents().filter(
|
|
331
|
+
agent => this.agentStatus.get(agent.id) === AgentStatus.AVAILABLE
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Create manual binding
|
|
337
|
+
*/
|
|
338
|
+
bind(agentId, skillId, options = {}) {
|
|
339
|
+
const agent = this.agents.get(agentId);
|
|
340
|
+
if (!agent) {
|
|
341
|
+
throw new Error(`Agent '${agentId}' not found`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const skill = this.skillRegistry?.getSkill(skillId);
|
|
345
|
+
if (!skill) {
|
|
346
|
+
throw new Error(`Skill '${skillId}' not found`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Check permissions
|
|
350
|
+
if (skill.permissions && skill.permissions.length > 0) {
|
|
351
|
+
const hasPermission = skill.permissions.every(
|
|
352
|
+
p => agent.permissions.includes(p)
|
|
353
|
+
);
|
|
354
|
+
if (!hasPermission) {
|
|
355
|
+
throw new Error(`Agent '${agentId}' lacks required permissions for skill '${skillId}'`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Calculate score
|
|
360
|
+
const score = options.score || this.matcher.calculateScore(
|
|
361
|
+
agent.capabilities,
|
|
362
|
+
skill.tags // Use tags as capability requirements
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
const record = new BindingRecord(agentId, skillId, { score });
|
|
366
|
+
this.bindings.get(agentId).set(skillId, record);
|
|
367
|
+
|
|
368
|
+
this.emit('skill-bound', { agentId, skillId, score });
|
|
369
|
+
|
|
370
|
+
return record;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Remove binding
|
|
375
|
+
*/
|
|
376
|
+
unbind(agentId, skillId) {
|
|
377
|
+
const agentBindings = this.bindings.get(agentId);
|
|
378
|
+
if (!agentBindings) {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const removed = agentBindings.delete(skillId);
|
|
383
|
+
if (removed) {
|
|
384
|
+
this.emit('skill-unbound', { agentId, skillId });
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return removed;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Get binding
|
|
392
|
+
*/
|
|
393
|
+
getBinding(agentId, skillId) {
|
|
394
|
+
return this.bindings.get(agentId)?.get(skillId) || null;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Get all bindings for an agent
|
|
399
|
+
*/
|
|
400
|
+
getAgentBindings(agentId) {
|
|
401
|
+
const bindings = this.bindings.get(agentId);
|
|
402
|
+
return bindings ? Array.from(bindings.values()) : [];
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Find best agent for a skill
|
|
407
|
+
*/
|
|
408
|
+
findBestAgentForSkill(skillId, options = {}) {
|
|
409
|
+
const skill = this.skillRegistry?.getSkill(skillId);
|
|
410
|
+
if (!skill) {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const candidates = [];
|
|
415
|
+
|
|
416
|
+
for (const [agentId, agent] of this.agents) {
|
|
417
|
+
// Check availability
|
|
418
|
+
if (this.agentStatus.get(agentId) !== AgentStatus.AVAILABLE) {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Check load
|
|
423
|
+
const load = this.agentLoad.get(agentId) || 0;
|
|
424
|
+
if (load >= agent.maxConcurrentTasks) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Check permissions
|
|
429
|
+
if (skill.permissions && skill.permissions.length > 0) {
|
|
430
|
+
const hasPermission = skill.permissions.every(
|
|
431
|
+
p => agent.permissions.includes(p)
|
|
432
|
+
);
|
|
433
|
+
if (!hasPermission) {
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Get or calculate binding score
|
|
439
|
+
let binding = this.bindings.get(agentId)?.get(skillId);
|
|
440
|
+
if (!binding) {
|
|
441
|
+
const score = this.matcher.calculateScore(agent.capabilities, skill.tags);
|
|
442
|
+
if (score >= this.options.minMatchScore) {
|
|
443
|
+
binding = new BindingRecord(agentId, skillId, { score });
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (binding && binding.score >= this.options.minMatchScore) {
|
|
448
|
+
// Calculate final score with affinity and load
|
|
449
|
+
const loadPenalty = (load / agent.maxConcurrentTasks) * 20;
|
|
450
|
+
const affinityBonus = binding.affinity * this.options.affinityWeight;
|
|
451
|
+
const finalScore = binding.score + affinityBonus - loadPenalty;
|
|
452
|
+
|
|
453
|
+
candidates.push({
|
|
454
|
+
agent,
|
|
455
|
+
binding,
|
|
456
|
+
finalScore,
|
|
457
|
+
load
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (candidates.length === 0) {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Sort by final score (descending)
|
|
467
|
+
candidates.sort((a, b) => b.finalScore - a.finalScore);
|
|
468
|
+
|
|
469
|
+
return candidates[0];
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Find all capable agents for a skill
|
|
474
|
+
*/
|
|
475
|
+
findCapableAgents(skillId) {
|
|
476
|
+
const skill = this.skillRegistry?.getSkill(skillId);
|
|
477
|
+
if (!skill) {
|
|
478
|
+
return [];
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const capable = [];
|
|
482
|
+
|
|
483
|
+
for (const [agentId, agent] of this.agents) {
|
|
484
|
+
const score = this.matcher.calculateScore(agent.capabilities, skill.tags);
|
|
485
|
+
if (score >= this.options.minMatchScore) {
|
|
486
|
+
capable.push({
|
|
487
|
+
agent,
|
|
488
|
+
score,
|
|
489
|
+
status: this.agentStatus.get(agentId),
|
|
490
|
+
load: this.agentLoad.get(agentId) || 0
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return capable.sort((a, b) => b.score - a.score);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Acquire agent for task
|
|
500
|
+
*/
|
|
501
|
+
acquireAgent(agentId) {
|
|
502
|
+
const agent = this.agents.get(agentId);
|
|
503
|
+
if (!agent) {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const load = this.agentLoad.get(agentId) || 0;
|
|
508
|
+
if (load >= agent.maxConcurrentTasks) {
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
this.agentLoad.set(agentId, load + 1);
|
|
513
|
+
|
|
514
|
+
if (load + 1 >= agent.maxConcurrentTasks) {
|
|
515
|
+
this.setAgentStatus(agentId, AgentStatus.BUSY);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
this.emit('agent-acquired', { agentId, currentLoad: load + 1 });
|
|
519
|
+
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Release agent from task
|
|
525
|
+
*/
|
|
526
|
+
releaseAgent(agentId) {
|
|
527
|
+
const agent = this.agents.get(agentId);
|
|
528
|
+
if (!agent) {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const load = this.agentLoad.get(agentId) || 0;
|
|
533
|
+
if (load <= 0) {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
this.agentLoad.set(agentId, load - 1);
|
|
538
|
+
|
|
539
|
+
if (this.agentStatus.get(agentId) === AgentStatus.BUSY && load - 1 < agent.maxConcurrentTasks) {
|
|
540
|
+
this.setAgentStatus(agentId, AgentStatus.AVAILABLE);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
this.emit('agent-released', { agentId, currentLoad: load - 1 });
|
|
544
|
+
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Record execution result
|
|
550
|
+
*/
|
|
551
|
+
recordExecution(agentId, skillId, success, executionTime) {
|
|
552
|
+
const binding = this.getBinding(agentId, skillId);
|
|
553
|
+
if (binding) {
|
|
554
|
+
binding.updateStats(success, executionTime);
|
|
555
|
+
this.emit('execution-recorded', { agentId, skillId, success, executionTime });
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Get agent load
|
|
561
|
+
*/
|
|
562
|
+
getAgentLoad(agentId) {
|
|
563
|
+
return this.agentLoad.get(agentId) || 0;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Get capability matcher
|
|
568
|
+
*/
|
|
569
|
+
getMatcher() {
|
|
570
|
+
return this.matcher;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Get summary
|
|
575
|
+
*/
|
|
576
|
+
getSummary() {
|
|
577
|
+
const statusCounts = {
|
|
578
|
+
[AgentStatus.AVAILABLE]: 0,
|
|
579
|
+
[AgentStatus.BUSY]: 0,
|
|
580
|
+
[AgentStatus.OFFLINE]: 0,
|
|
581
|
+
[AgentStatus.MAINTENANCE]: 0
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
let totalBindings = 0;
|
|
585
|
+
let totalLoad = 0;
|
|
586
|
+
|
|
587
|
+
for (const [agentId, status] of this.agentStatus) {
|
|
588
|
+
statusCounts[status]++;
|
|
589
|
+
totalBindings += (this.bindings.get(agentId)?.size || 0);
|
|
590
|
+
totalLoad += (this.agentLoad.get(agentId) || 0);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return {
|
|
594
|
+
totalAgents: this.agents.size,
|
|
595
|
+
statusCounts,
|
|
596
|
+
totalBindings,
|
|
597
|
+
totalLoad,
|
|
598
|
+
averageLoad: this.agents.size > 0 ? totalLoad / this.agents.size : 0
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Clear all
|
|
604
|
+
*/
|
|
605
|
+
clear() {
|
|
606
|
+
this.agents.clear();
|
|
607
|
+
this.agentStatus.clear();
|
|
608
|
+
this.agentLoad.clear();
|
|
609
|
+
this.bindings.clear();
|
|
610
|
+
this.emit('cleared');
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Private methods
|
|
614
|
+
|
|
615
|
+
_autoBindAgent(agentId) {
|
|
616
|
+
const agent = this.agents.get(agentId);
|
|
617
|
+
if (!agent || !this.skillRegistry) return;
|
|
618
|
+
|
|
619
|
+
const skills = this.skillRegistry.getAllSkills();
|
|
620
|
+
for (const skill of skills) {
|
|
621
|
+
try {
|
|
622
|
+
const score = this.matcher.calculateScore(agent.capabilities, skill.tags);
|
|
623
|
+
if (score >= this.options.minMatchScore) {
|
|
624
|
+
this.bind(agentId, skill.id, { score });
|
|
625
|
+
}
|
|
626
|
+
} catch (e) {
|
|
627
|
+
// Ignore binding errors during auto-bind
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
_autoBindSkill(skillId) {
|
|
633
|
+
const skill = this.skillRegistry?.getSkill(skillId);
|
|
634
|
+
if (!skill) return;
|
|
635
|
+
|
|
636
|
+
for (const [agentId, agent] of this.agents) {
|
|
637
|
+
try {
|
|
638
|
+
const score = this.matcher.calculateScore(agent.capabilities, skill.tags);
|
|
639
|
+
if (score >= this.options.minMatchScore) {
|
|
640
|
+
this.bind(agentId, skillId, { score });
|
|
641
|
+
}
|
|
642
|
+
} catch (e) {
|
|
643
|
+
// Ignore binding errors during auto-bind
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
module.exports = {
|
|
650
|
+
AgentSkillBinding,
|
|
651
|
+
AgentDefinition,
|
|
652
|
+
BindingRecord,
|
|
653
|
+
CapabilityMatcher,
|
|
654
|
+
AgentStatus
|
|
655
|
+
};
|