network-ai 3.0.3 → 3.1.2
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 +34 -15
- package/SKILL.md +10 -3
- package/dist/adapters/adapter-registry.d.ts +21 -0
- package/dist/adapters/adapter-registry.d.ts.map +1 -1
- package/dist/adapters/adapter-registry.js +34 -3
- package/dist/adapters/adapter-registry.js.map +1 -1
- package/dist/adapters/base-adapter.d.ts.map +1 -1
- package/dist/adapters/base-adapter.js +2 -1
- package/dist/adapters/base-adapter.js.map +1 -1
- package/dist/index.d.ts +243 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +297 -10
- package/dist/index.js.map +1 -1
- package/dist/lib/errors.d.ts +97 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +163 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/locked-blackboard.d.ts +18 -2
- package/dist/lib/locked-blackboard.d.ts.map +1 -1
- package/dist/lib/locked-blackboard.js +75 -15
- package/dist/lib/locked-blackboard.js.map +1 -1
- package/dist/lib/logger.d.ts +102 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +150 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/swarm-utils.d.ts.map +1 -1
- package/dist/lib/swarm-utils.js +3 -1
- package/dist/lib/swarm-utils.js.map +1 -1
- package/dist/security.d.ts +100 -0
- package/dist/security.d.ts.map +1 -1
- package/dist/security.js +100 -0
- package/dist/security.js.map +1 -1
- package/package.json +1 -1
- package/scripts/blackboard.py +40 -7
package/dist/index.js
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* task decomposition, permission management, and shared blackboard coordination.
|
|
7
7
|
*
|
|
8
8
|
* @module SwarmOrchestrator
|
|
9
|
-
* @version 3.
|
|
9
|
+
* @version 3.1.0
|
|
10
10
|
* @license MIT
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
exports.CustomAdapter = exports.MCPAdapter = exports.CrewAIAdapter = exports.AutoGenAdapter = exports.LangChainAdapter = exports.OpenClawAdapter = exports.BaseAdapter = exports.AdapterRegistry = exports.QualityGateAgent = exports.BlackboardValidator = exports.TaskDecomposer = exports.AuthGuardian = exports.SharedBlackboard = exports.SwarmOrchestrator = void 0;
|
|
13
|
+
exports.TimeoutError = exports.ParallelLimitError = exports.AdapterNotInitializedError = exports.AdapterNotFoundError = exports.AdapterAlreadyRegisteredError = exports.ConflictError = exports.LockAcquisitionError = exports.ValidationError = exports.NamespaceViolationError = exports.IdentityVerificationError = exports.NetworkAIError = exports.LogLevel = exports.Logger = exports.CustomAdapter = exports.MCPAdapter = exports.CrewAIAdapter = exports.AutoGenAdapter = exports.LangChainAdapter = exports.OpenClawAdapter = exports.BaseAdapter = exports.AdapterRegistry = exports.QualityGateAgent = exports.BlackboardValidator = exports.TaskDecomposer = exports.AuthGuardian = exports.SharedBlackboard = exports.SwarmOrchestrator = void 0;
|
|
14
14
|
exports.createSwarmOrchestrator = createSwarmOrchestrator;
|
|
15
15
|
const fs_1 = require("fs");
|
|
16
16
|
const path_1 = require("path");
|
|
@@ -19,6 +19,9 @@ const adapter_registry_1 = require("./adapters/adapter-registry");
|
|
|
19
19
|
const security_1 = require("./security");
|
|
20
20
|
const locked_blackboard_1 = require("./lib/locked-blackboard");
|
|
21
21
|
const blackboard_validator_1 = require("./lib/blackboard-validator");
|
|
22
|
+
const logger_1 = require("./lib/logger");
|
|
23
|
+
const errors_1 = require("./lib/errors");
|
|
24
|
+
const log = logger_1.Logger.create('SwarmOrchestrator');
|
|
22
25
|
// ============================================================================
|
|
23
26
|
// CONFIGURATION
|
|
24
27
|
// ============================================================================
|
|
@@ -71,11 +74,29 @@ const DEFAULT_AGENT_TRUST = [
|
|
|
71
74
|
// BLACKBOARD MANAGEMENT -- Secured with LockedBlackboard, identity verification,
|
|
72
75
|
// namespace scoping, value validation, and input sanitization
|
|
73
76
|
// ============================================================================
|
|
77
|
+
/**
|
|
78
|
+
* Namespace-scoped, identity-verified shared state for multi-agent coordination.
|
|
79
|
+
*
|
|
80
|
+
* Every write is identity-verified (agent token), namespace-checked,
|
|
81
|
+
* size-validated, input-sanitized, and atomically persisted through
|
|
82
|
+
* {@link LockedBlackboard}.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* const bb = new SharedBlackboard('./workspace');
|
|
87
|
+
* bb.registerAgent('analyst', 'secret-token', ['task:', 'analytics:']);
|
|
88
|
+
* bb.write('task:revenue', { q4: 42_000 }, 'analyst', 3600, 'secret-token');
|
|
89
|
+
* const entry = bb.read('task:revenue');
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
74
92
|
class SharedBlackboard {
|
|
75
93
|
backend;
|
|
76
94
|
agentTokens = new Map(); // agentId -> verified token
|
|
77
95
|
agentNamespaces = new Map(); // agentId -> allowed prefixes
|
|
78
96
|
constructor(basePath) {
|
|
97
|
+
if (!basePath || typeof basePath !== 'string' || basePath.trim() === '') {
|
|
98
|
+
throw new errors_1.ValidationError('basePath must be a non-empty string');
|
|
99
|
+
}
|
|
79
100
|
this.backend = new locked_blackboard_1.LockedBlackboard(basePath);
|
|
80
101
|
}
|
|
81
102
|
/**
|
|
@@ -84,6 +105,15 @@ class SharedBlackboard {
|
|
|
84
105
|
* verifying their identity through the AuthGuardian.
|
|
85
106
|
*/
|
|
86
107
|
registerAgent(agentId, verificationToken, allowedNamespaces = ['*']) {
|
|
108
|
+
if (!agentId || typeof agentId !== 'string' || agentId.trim() === '') {
|
|
109
|
+
throw new errors_1.ValidationError('agentId must be a non-empty string');
|
|
110
|
+
}
|
|
111
|
+
if (!verificationToken || typeof verificationToken !== 'string') {
|
|
112
|
+
throw new errors_1.ValidationError('verificationToken must be a non-empty string');
|
|
113
|
+
}
|
|
114
|
+
if (!Array.isArray(allowedNamespaces)) {
|
|
115
|
+
throw new errors_1.ValidationError('allowedNamespaces must be an array of strings');
|
|
116
|
+
}
|
|
87
117
|
this.agentTokens.set(agentId, verificationToken);
|
|
88
118
|
this.agentNamespaces.set(agentId, allowedNamespaces);
|
|
89
119
|
}
|
|
@@ -131,7 +161,17 @@ class SharedBlackboard {
|
|
|
131
161
|
// Keys must be safe for markdown headings -- no #, newlines, or markdown syntax
|
|
132
162
|
return key.replace(/[#\n\r|`]/g, '_').slice(0, 256);
|
|
133
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Read an entry from the blackboard by key.
|
|
166
|
+
*
|
|
167
|
+
* @param key - The entry key to look up
|
|
168
|
+
* @returns The entry, or `null` if not found or expired
|
|
169
|
+
* @throws {@link ValidationError} if `key` is not a non-empty string
|
|
170
|
+
*/
|
|
134
171
|
read(key) {
|
|
172
|
+
if (!key || typeof key !== 'string') {
|
|
173
|
+
throw new errors_1.ValidationError('key must be a non-empty string');
|
|
174
|
+
}
|
|
135
175
|
const entry = this.backend.read(key);
|
|
136
176
|
if (!entry)
|
|
137
177
|
return null;
|
|
@@ -158,18 +198,18 @@ class SharedBlackboard {
|
|
|
158
198
|
write(key, value, sourceAgent, ttl, agentToken) {
|
|
159
199
|
// 1. Verify agent identity
|
|
160
200
|
if (!this.verifyAgent(sourceAgent, agentToken)) {
|
|
161
|
-
throw new
|
|
201
|
+
throw new errors_1.IdentityVerificationError(sourceAgent);
|
|
162
202
|
}
|
|
163
203
|
// 2. Namespace check
|
|
164
204
|
if (!this.canAccessKey(sourceAgent, key)) {
|
|
165
|
-
throw new
|
|
205
|
+
throw new errors_1.NamespaceViolationError(sourceAgent, key);
|
|
166
206
|
}
|
|
167
207
|
// 3. Sanitize key
|
|
168
208
|
const safeKey = this.sanitizeKey(key);
|
|
169
209
|
// 4. Validate value size/structure
|
|
170
210
|
const validation = this.validateValue(value);
|
|
171
211
|
if (!validation.valid) {
|
|
172
|
-
throw new
|
|
212
|
+
throw new errors_1.ValidationError(validation.reason);
|
|
173
213
|
}
|
|
174
214
|
// 5. Sanitize value -- strip injection payloads from string content
|
|
175
215
|
let sanitizedValue;
|
|
@@ -190,6 +230,10 @@ class SharedBlackboard {
|
|
|
190
230
|
ttl: entry.ttl,
|
|
191
231
|
};
|
|
192
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Check whether a key exists on the blackboard (not expired).
|
|
235
|
+
* @param key - The entry key to check
|
|
236
|
+
*/
|
|
193
237
|
exists(key) {
|
|
194
238
|
return this.read(key) !== null;
|
|
195
239
|
}
|
|
@@ -215,6 +259,9 @@ class SharedBlackboard {
|
|
|
215
259
|
* Prevents data leakage between agents.
|
|
216
260
|
*/
|
|
217
261
|
getScopedSnapshot(agentId) {
|
|
262
|
+
if (!agentId || typeof agentId !== 'string') {
|
|
263
|
+
throw new errors_1.ValidationError('agentId must be a non-empty string');
|
|
264
|
+
}
|
|
218
265
|
const full = this.getSnapshot();
|
|
219
266
|
const scoped = {};
|
|
220
267
|
for (const [key, entry] of Object.entries(full)) {
|
|
@@ -243,6 +290,29 @@ exports.SharedBlackboard = SharedBlackboard;
|
|
|
243
290
|
// Integrates with SecureSwarmGateway for HMAC tokens, rate limiting,
|
|
244
291
|
// input sanitization, and cryptographic audit logs.
|
|
245
292
|
// ============================================================================
|
|
293
|
+
/**
|
|
294
|
+
* Universal permission wall for multi-agent systems.
|
|
295
|
+
*
|
|
296
|
+
* Evaluates permission requests using a weighted formula of justification
|
|
297
|
+
* quality (40%), agent trust level (30%), and risk score (30%).
|
|
298
|
+
* Resource types, risk profiles, trust levels, and restrictions are all
|
|
299
|
+
* configurable — works for coding, finance, DevOps, or any domain.
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```typescript
|
|
303
|
+
* const guardian = new AuthGuardian({
|
|
304
|
+
* trustLevels: [{ agentId: 'analyst', trustLevel: 0.8 }],
|
|
305
|
+
* resourceProfiles: { CUSTOM_API: { baseRisk: 0.5, defaultRestrictions: ['audit_required'] } },
|
|
306
|
+
* });
|
|
307
|
+
*
|
|
308
|
+
* const grant = await guardian.requestPermission(
|
|
309
|
+
* 'analyst', 'CUSTOM_API', 'Need to fetch Q4 revenue data for report', 'read'
|
|
310
|
+
* );
|
|
311
|
+
* if (grant.granted) {
|
|
312
|
+
* // Use grant.grantToken to prove authorization
|
|
313
|
+
* }
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
246
316
|
class AuthGuardian {
|
|
247
317
|
activeGrants = new Map();
|
|
248
318
|
agentTrustLevels = new Map();
|
|
@@ -273,12 +343,33 @@ class AuthGuardian {
|
|
|
273
343
|
* Makes the system extensible for any domain.
|
|
274
344
|
*/
|
|
275
345
|
registerResourceType(name, profile) {
|
|
346
|
+
if (!name || typeof name !== 'string' || name.trim() === '') {
|
|
347
|
+
throw new errors_1.ValidationError('resource name must be a non-empty string');
|
|
348
|
+
}
|
|
349
|
+
if (!profile || typeof profile !== 'object' || typeof profile.baseRisk !== 'number') {
|
|
350
|
+
throw new errors_1.ValidationError('profile must be an object with a numeric baseRisk');
|
|
351
|
+
}
|
|
352
|
+
if (profile.baseRisk < 0 || profile.baseRisk > 1) {
|
|
353
|
+
throw new errors_1.ValidationError('profile.baseRisk must be between 0 and 1');
|
|
354
|
+
}
|
|
355
|
+
if (!Array.isArray(profile.defaultRestrictions)) {
|
|
356
|
+
throw new errors_1.ValidationError('profile.defaultRestrictions must be an array');
|
|
357
|
+
}
|
|
276
358
|
this.resourceProfiles.set(name, profile);
|
|
277
359
|
}
|
|
278
360
|
/**
|
|
279
361
|
* Register or update an agent's trust configuration at runtime.
|
|
280
362
|
*/
|
|
281
363
|
registerAgentTrust(config) {
|
|
364
|
+
if (!config || typeof config !== 'object') {
|
|
365
|
+
throw new errors_1.ValidationError('config must be an object');
|
|
366
|
+
}
|
|
367
|
+
if (!config.agentId || typeof config.agentId !== 'string' || config.agentId.trim() === '') {
|
|
368
|
+
throw new errors_1.ValidationError('config.agentId must be a non-empty string');
|
|
369
|
+
}
|
|
370
|
+
if (typeof config.trustLevel !== 'number' || config.trustLevel < 0 || config.trustLevel > 1) {
|
|
371
|
+
throw new errors_1.ValidationError('config.trustLevel must be a number between 0 and 1');
|
|
372
|
+
}
|
|
282
373
|
this.agentTrustLevels.set(config.agentId, config.trustLevel);
|
|
283
374
|
this.agentTrustConfigs.set(config.agentId, config);
|
|
284
375
|
this.persistTrustToDisk();
|
|
@@ -288,6 +379,15 @@ class AuthGuardian {
|
|
|
288
379
|
* resourceType is now a free string -- validated against registered profiles.
|
|
289
380
|
*/
|
|
290
381
|
async requestPermission(agentId, resourceType, justification, scope) {
|
|
382
|
+
if (!agentId || typeof agentId !== 'string') {
|
|
383
|
+
throw new errors_1.ValidationError('agentId must be a non-empty string');
|
|
384
|
+
}
|
|
385
|
+
if (!resourceType || typeof resourceType !== 'string') {
|
|
386
|
+
throw new errors_1.ValidationError('resourceType must be a non-empty string');
|
|
387
|
+
}
|
|
388
|
+
if (!justification || typeof justification !== 'string') {
|
|
389
|
+
throw new errors_1.ValidationError('justification must be a non-empty string');
|
|
390
|
+
}
|
|
291
391
|
// Sanitize inputs
|
|
292
392
|
let safeAgentId;
|
|
293
393
|
let safeJustification;
|
|
@@ -346,7 +446,15 @@ class AuthGuardian {
|
|
|
346
446
|
restrictions: evaluation.restrictions,
|
|
347
447
|
};
|
|
348
448
|
}
|
|
449
|
+
/**
|
|
450
|
+
* Validate a grant token and return `true` if it is active and not expired.
|
|
451
|
+
*
|
|
452
|
+
* @param token - The grant token to validate
|
|
453
|
+
* @returns `true` if the token is valid, `false` otherwise
|
|
454
|
+
*/
|
|
349
455
|
validateToken(token) {
|
|
456
|
+
if (!token || typeof token !== 'string')
|
|
457
|
+
return false;
|
|
350
458
|
const grant = this.activeGrants.get(token);
|
|
351
459
|
if (!grant)
|
|
352
460
|
return false;
|
|
@@ -360,7 +468,16 @@ class AuthGuardian {
|
|
|
360
468
|
* Validate a token and return the bound restrictions and scope.
|
|
361
469
|
* Used to enforce restrictions at the point of use.
|
|
362
470
|
*/
|
|
471
|
+
/**
|
|
472
|
+
* Validate a token and return the full grant object (including restrictions
|
|
473
|
+
* and scope) for point-of-use enforcement.
|
|
474
|
+
*
|
|
475
|
+
* @param token - The grant token to validate
|
|
476
|
+
* @returns The grant details, or `null` if invalid/expired
|
|
477
|
+
*/
|
|
363
478
|
validateTokenWithGrant(token) {
|
|
479
|
+
if (!token || typeof token !== 'string')
|
|
480
|
+
return null;
|
|
364
481
|
const grant = this.activeGrants.get(token);
|
|
365
482
|
if (!grant)
|
|
366
483
|
return null;
|
|
@@ -374,7 +491,18 @@ class AuthGuardian {
|
|
|
374
491
|
* Enforce restrictions on an operation. Returns an error string if
|
|
375
492
|
* the operation violates any restriction, or null if allowed.
|
|
376
493
|
*/
|
|
494
|
+
/**
|
|
495
|
+
* Enforce restrictions on an operation. Returns an error string if
|
|
496
|
+
* the operation violates any restriction, or `null` if all restrictions pass.
|
|
497
|
+
*
|
|
498
|
+
* @param grantToken - The grant token authorizing the operation
|
|
499
|
+
* @param operation - Description of the operation to check against restrictions
|
|
500
|
+
* @returns Error message string if a restriction is violated, or `null` if allowed
|
|
501
|
+
*/
|
|
377
502
|
enforceRestrictions(grantToken, operation) {
|
|
503
|
+
if (!grantToken || typeof grantToken !== 'string') {
|
|
504
|
+
return 'Invalid or expired grant token';
|
|
505
|
+
}
|
|
378
506
|
const grant = this.validateTokenWithGrant(grantToken);
|
|
379
507
|
if (!grant)
|
|
380
508
|
return 'Invalid or expired grant token';
|
|
@@ -422,6 +550,12 @@ class AuthGuardian {
|
|
|
422
550
|
}
|
|
423
551
|
return null; // All restrictions passed
|
|
424
552
|
}
|
|
553
|
+
/**
|
|
554
|
+
* Revoke a grant token, immediately invalidating it.
|
|
555
|
+
* Silently no-ops if the token doesn't exist.
|
|
556
|
+
*
|
|
557
|
+
* @param token - The grant token to revoke
|
|
558
|
+
*/
|
|
425
559
|
revokeToken(token) {
|
|
426
560
|
this.activeGrants.delete(token);
|
|
427
561
|
this.log('permission_revoked', { token });
|
|
@@ -558,6 +692,10 @@ class AuthGuardian {
|
|
|
558
692
|
// Non-fatal -- log is also in memory
|
|
559
693
|
}
|
|
560
694
|
}
|
|
695
|
+
/**
|
|
696
|
+
* Get all active (non-expired) permission grants.
|
|
697
|
+
* Automatically cleans up expired grants before returning.
|
|
698
|
+
*/
|
|
561
699
|
getActiveGrants() {
|
|
562
700
|
// Clean expired grants
|
|
563
701
|
const now = new Date();
|
|
@@ -568,6 +706,10 @@ class AuthGuardian {
|
|
|
568
706
|
}
|
|
569
707
|
return Array.from(this.activeGrants.values());
|
|
570
708
|
}
|
|
709
|
+
/**
|
|
710
|
+
* Get the full audit log of permission decisions.
|
|
711
|
+
* Returns a defensive copy.
|
|
712
|
+
*/
|
|
571
713
|
getAuditLog() {
|
|
572
714
|
return [...this.auditLog];
|
|
573
715
|
}
|
|
@@ -581,6 +723,8 @@ class AuthGuardian {
|
|
|
581
723
|
* Get the allowed namespaces for an agent (used by blackboard scoping).
|
|
582
724
|
*/
|
|
583
725
|
getAgentNamespaces(agentId) {
|
|
726
|
+
if (!agentId || typeof agentId !== 'string')
|
|
727
|
+
return ['task:'];
|
|
584
728
|
const config = this.agentTrustConfigs.get(agentId);
|
|
585
729
|
return config?.allowedNamespaces ?? ['task:'];
|
|
586
730
|
}
|
|
@@ -628,11 +772,28 @@ exports.AuthGuardian = AuthGuardian;
|
|
|
628
772
|
// ============================================================================
|
|
629
773
|
// TASK DECOMPOSITION ENGINE
|
|
630
774
|
// ============================================================================
|
|
775
|
+
/**
|
|
776
|
+
* Decomposes complex tasks into parallel sub-agent executions.
|
|
777
|
+
*
|
|
778
|
+
* Supports four synthesis strategies (`merge`, `vote`, `chain`, `first-success`)
|
|
779
|
+
* and caches results on the blackboard to avoid redundant work.
|
|
780
|
+
* Routes each sub-task through the {@link AdapterRegistry} so any
|
|
781
|
+
* registered framework can participate.
|
|
782
|
+
*/
|
|
631
783
|
class TaskDecomposer {
|
|
632
784
|
blackboard;
|
|
633
785
|
authGuardian;
|
|
634
786
|
adapterRegistry;
|
|
635
787
|
constructor(blackboard, authGuardian, adapterRegistry) {
|
|
788
|
+
if (!blackboard || !(blackboard instanceof SharedBlackboard)) {
|
|
789
|
+
throw new errors_1.ValidationError('blackboard must be an instance of SharedBlackboard');
|
|
790
|
+
}
|
|
791
|
+
if (!authGuardian || !(authGuardian instanceof AuthGuardian)) {
|
|
792
|
+
throw new errors_1.ValidationError('authGuardian must be an instance of AuthGuardian');
|
|
793
|
+
}
|
|
794
|
+
if (!adapterRegistry || !(adapterRegistry instanceof adapter_registry_1.AdapterRegistry)) {
|
|
795
|
+
throw new errors_1.ValidationError('adapterRegistry must be an instance of AdapterRegistry');
|
|
796
|
+
}
|
|
636
797
|
this.blackboard = blackboard;
|
|
637
798
|
this.authGuardian = authGuardian;
|
|
638
799
|
this.adapterRegistry = adapterRegistry;
|
|
@@ -643,10 +804,18 @@ class TaskDecomposer {
|
|
|
643
804
|
* into manageable parallel executions
|
|
644
805
|
*/
|
|
645
806
|
async executeParallel(tasks, synthesisStrategy = 'merge', context) {
|
|
807
|
+
if (!tasks || !Array.isArray(tasks)) {
|
|
808
|
+
throw new errors_1.ValidationError('tasks must be an array');
|
|
809
|
+
}
|
|
810
|
+
if (tasks.length === 0) {
|
|
811
|
+
throw new errors_1.ValidationError('tasks array must not be empty');
|
|
812
|
+
}
|
|
813
|
+
if (!context || typeof context !== 'object' || !context.agentId) {
|
|
814
|
+
throw new errors_1.ValidationError('context is required and must include agentId');
|
|
815
|
+
}
|
|
646
816
|
// Enforce maximum parallel agent limit
|
|
647
817
|
if (tasks.length > CONFIG.maxParallelAgents) {
|
|
648
|
-
throw new
|
|
649
|
-
`Decompose further or use 'chain' strategy.`);
|
|
818
|
+
throw new errors_1.ParallelLimitError(tasks.length, CONFIG.maxParallelAgents);
|
|
650
819
|
}
|
|
651
820
|
const startTime = Date.now();
|
|
652
821
|
const individualResults = [];
|
|
@@ -842,9 +1011,31 @@ exports.TaskDecomposer = TaskDecomposer;
|
|
|
842
1011
|
// ============================================================================
|
|
843
1012
|
// SWARM ORCHESTRATOR - MAIN SKILL IMPLEMENTATION
|
|
844
1013
|
// ============================================================================
|
|
1014
|
+
/**
|
|
1015
|
+
* The main orchestrator class — coordinates agents, permissions, blackboard,
|
|
1016
|
+
* quality gates, and adapter routing in a single entry point.
|
|
1017
|
+
*
|
|
1018
|
+
* Implements the OpenClaw skill interface for backward compatibility and
|
|
1019
|
+
* can also be used standalone via {@link createSwarmOrchestrator}.
|
|
1020
|
+
*
|
|
1021
|
+
* @example
|
|
1022
|
+
* ```typescript
|
|
1023
|
+
* import { createSwarmOrchestrator, LangChainAdapter } from 'network-ai';
|
|
1024
|
+
*
|
|
1025
|
+
* const orchestrator = createSwarmOrchestrator({
|
|
1026
|
+
* adapters: [{ adapter: new LangChainAdapter() }],
|
|
1027
|
+
* trustLevels: [{ agentId: 'my-agent', trustLevel: 0.8 }],
|
|
1028
|
+
* });
|
|
1029
|
+
*
|
|
1030
|
+
* const result = await orchestrator.execute('delegate_task', {
|
|
1031
|
+
* targetAgent: 'my-agent',
|
|
1032
|
+
* taskPayload: { instruction: 'Summarize the quarterly report' },
|
|
1033
|
+
* }, { agentId: 'orchestrator' });
|
|
1034
|
+
* ```
|
|
1035
|
+
*/
|
|
845
1036
|
class SwarmOrchestrator {
|
|
846
1037
|
name = 'SwarmOrchestrator';
|
|
847
|
-
version = '3.
|
|
1038
|
+
version = '3.1.0';
|
|
848
1039
|
blackboard;
|
|
849
1040
|
authGuardian;
|
|
850
1041
|
taskDecomposer;
|
|
@@ -854,6 +1045,12 @@ class SwarmOrchestrator {
|
|
|
854
1045
|
/** The adapter registry -- routes requests to the right agent framework */
|
|
855
1046
|
adapters;
|
|
856
1047
|
constructor(workspacePath = process.cwd(), adapterRegistry, options) {
|
|
1048
|
+
if (workspacePath !== undefined && typeof workspacePath !== 'string') {
|
|
1049
|
+
throw new errors_1.ValidationError('workspacePath must be a string');
|
|
1050
|
+
}
|
|
1051
|
+
if (workspacePath !== undefined && workspacePath.trim() === '') {
|
|
1052
|
+
throw new errors_1.ValidationError('workspacePath must not be empty');
|
|
1053
|
+
}
|
|
857
1054
|
this.blackboard = new SharedBlackboard(workspacePath);
|
|
858
1055
|
this.authGuardian = new AuthGuardian({
|
|
859
1056
|
trustLevels: options?.trustLevels,
|
|
@@ -875,6 +1072,12 @@ class SwarmOrchestrator {
|
|
|
875
1072
|
* This is the plug-and-play entry point.
|
|
876
1073
|
*/
|
|
877
1074
|
async addAdapter(adapter, config = {}) {
|
|
1075
|
+
if (!adapter || typeof adapter !== 'object') {
|
|
1076
|
+
throw new errors_1.ValidationError('adapter is required and must be an object');
|
|
1077
|
+
}
|
|
1078
|
+
if (typeof adapter.name !== 'string' || adapter.name.trim() === '') {
|
|
1079
|
+
throw new errors_1.ValidationError('adapter.name must be a non-empty string');
|
|
1080
|
+
}
|
|
878
1081
|
await this.adapters.addAdapter(adapter, config);
|
|
879
1082
|
}
|
|
880
1083
|
/**
|
|
@@ -883,6 +1086,36 @@ class SwarmOrchestrator {
|
|
|
883
1086
|
* input sanitization, rate limiting, and agent ID validation.
|
|
884
1087
|
*/
|
|
885
1088
|
async execute(action, params, context) {
|
|
1089
|
+
if (!action || typeof action !== 'string') {
|
|
1090
|
+
return {
|
|
1091
|
+
success: false,
|
|
1092
|
+
error: {
|
|
1093
|
+
code: 'INVALID_PARAMS',
|
|
1094
|
+
message: 'action is required and must be a non-empty string',
|
|
1095
|
+
recoverable: false,
|
|
1096
|
+
},
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
if (!params || typeof params !== 'object' || Array.isArray(params)) {
|
|
1100
|
+
return {
|
|
1101
|
+
success: false,
|
|
1102
|
+
error: {
|
|
1103
|
+
code: 'INVALID_PARAMS',
|
|
1104
|
+
message: 'params is required and must be a plain object',
|
|
1105
|
+
recoverable: false,
|
|
1106
|
+
},
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
if (!context || typeof context !== 'object' || !context.agentId || typeof context.agentId !== 'string') {
|
|
1110
|
+
return {
|
|
1111
|
+
success: false,
|
|
1112
|
+
error: {
|
|
1113
|
+
code: 'INVALID_PARAMS',
|
|
1114
|
+
message: 'context is required and must include a non-empty agentId string',
|
|
1115
|
+
recoverable: false,
|
|
1116
|
+
},
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
886
1119
|
const traceId = (0, crypto_1.randomUUID)();
|
|
887
1120
|
// P0: Route through SecureSwarmGateway -- sanitization + rate limiting
|
|
888
1121
|
const gatewayResult = await this.gateway.handleSecureRequest(context.agentId, action, params);
|
|
@@ -1343,13 +1576,20 @@ class SwarmOrchestrator {
|
|
|
1343
1576
|
}
|
|
1344
1577
|
timeoutPromise(ms) {
|
|
1345
1578
|
return new Promise((_, reject) => {
|
|
1346
|
-
setTimeout(() => reject(new
|
|
1579
|
+
setTimeout(() => reject(new errors_1.TimeoutError(ms)), ms);
|
|
1347
1580
|
});
|
|
1348
1581
|
}
|
|
1349
1582
|
/**
|
|
1350
1583
|
* Register an agent with the swarm
|
|
1351
1584
|
*/
|
|
1352
1585
|
registerAgent(agentId, status = 'available') {
|
|
1586
|
+
if (!agentId || typeof agentId !== 'string' || agentId.trim() === '') {
|
|
1587
|
+
throw new errors_1.ValidationError('agentId must be a non-empty string');
|
|
1588
|
+
}
|
|
1589
|
+
const validStatuses = ['available', 'busy', 'waiting_auth', 'offline'];
|
|
1590
|
+
if (!validStatuses.includes(status)) {
|
|
1591
|
+
throw new errors_1.ValidationError(`status must be one of: ${validStatuses.join(', ')}`);
|
|
1592
|
+
}
|
|
1353
1593
|
this.agentRegistry.set(agentId, {
|
|
1354
1594
|
agentId,
|
|
1355
1595
|
status,
|
|
@@ -1361,6 +1601,9 @@ class SwarmOrchestrator {
|
|
|
1361
1601
|
* Update agent status
|
|
1362
1602
|
*/
|
|
1363
1603
|
updateAgentStatus(agentId, status, currentTask) {
|
|
1604
|
+
if (!agentId || typeof agentId !== 'string' || agentId.trim() === '') {
|
|
1605
|
+
throw new errors_1.ValidationError('agentId must be a non-empty string');
|
|
1606
|
+
}
|
|
1364
1607
|
const existing = this.agentRegistry.get(agentId);
|
|
1365
1608
|
if (existing) {
|
|
1366
1609
|
existing.status = status;
|
|
@@ -1396,6 +1639,23 @@ var mcp_adapter_1 = require("./adapters/mcp-adapter");
|
|
|
1396
1639
|
Object.defineProperty(exports, "MCPAdapter", { enumerable: true, get: function () { return mcp_adapter_1.MCPAdapter; } });
|
|
1397
1640
|
var custom_adapter_1 = require("./adapters/custom-adapter");
|
|
1398
1641
|
Object.defineProperty(exports, "CustomAdapter", { enumerable: true, get: function () { return custom_adapter_1.CustomAdapter; } });
|
|
1642
|
+
// Logger
|
|
1643
|
+
var logger_2 = require("./lib/logger");
|
|
1644
|
+
Object.defineProperty(exports, "Logger", { enumerable: true, get: function () { return logger_2.Logger; } });
|
|
1645
|
+
Object.defineProperty(exports, "LogLevel", { enumerable: true, get: function () { return logger_2.LogLevel; } });
|
|
1646
|
+
// Typed errors
|
|
1647
|
+
var errors_2 = require("./lib/errors");
|
|
1648
|
+
Object.defineProperty(exports, "NetworkAIError", { enumerable: true, get: function () { return errors_2.NetworkAIError; } });
|
|
1649
|
+
Object.defineProperty(exports, "IdentityVerificationError", { enumerable: true, get: function () { return errors_2.IdentityVerificationError; } });
|
|
1650
|
+
Object.defineProperty(exports, "NamespaceViolationError", { enumerable: true, get: function () { return errors_2.NamespaceViolationError; } });
|
|
1651
|
+
Object.defineProperty(exports, "ValidationError", { enumerable: true, get: function () { return errors_2.ValidationError; } });
|
|
1652
|
+
Object.defineProperty(exports, "LockAcquisitionError", { enumerable: true, get: function () { return errors_2.LockAcquisitionError; } });
|
|
1653
|
+
Object.defineProperty(exports, "ConflictError", { enumerable: true, get: function () { return errors_2.ConflictError; } });
|
|
1654
|
+
Object.defineProperty(exports, "AdapterAlreadyRegisteredError", { enumerable: true, get: function () { return errors_2.AdapterAlreadyRegisteredError; } });
|
|
1655
|
+
Object.defineProperty(exports, "AdapterNotFoundError", { enumerable: true, get: function () { return errors_2.AdapterNotFoundError; } });
|
|
1656
|
+
Object.defineProperty(exports, "AdapterNotInitializedError", { enumerable: true, get: function () { return errors_2.AdapterNotInitializedError; } });
|
|
1657
|
+
Object.defineProperty(exports, "ParallelLimitError", { enumerable: true, get: function () { return errors_2.ParallelLimitError; } });
|
|
1658
|
+
Object.defineProperty(exports, "TimeoutError", { enumerable: true, get: function () { return errors_2.TimeoutError; } });
|
|
1399
1659
|
/**
|
|
1400
1660
|
* Factory function for creating a configured SwarmOrchestrator instance.
|
|
1401
1661
|
*
|
|
@@ -1405,7 +1665,34 @@ Object.defineProperty(exports, "CustomAdapter", { enumerable: true, get: functio
|
|
|
1405
1665
|
* adapters: [{ adapter: new LangChainAdapter(), config: {} }],
|
|
1406
1666
|
* });
|
|
1407
1667
|
*/
|
|
1668
|
+
/**
|
|
1669
|
+
* Factory function for creating a fully configured {@link SwarmOrchestrator}.
|
|
1670
|
+
*
|
|
1671
|
+
* Accepts optional configuration for adapters, trust levels, resource profiles,
|
|
1672
|
+
* quality gate settings, and runtime overrides.
|
|
1673
|
+
*
|
|
1674
|
+
* @param config - Optional configuration object. Pass `undefined` for all defaults.
|
|
1675
|
+
* @returns A ready-to-use SwarmOrchestrator instance.
|
|
1676
|
+
*
|
|
1677
|
+
* @example
|
|
1678
|
+
* ```typescript
|
|
1679
|
+
* import { createSwarmOrchestrator, LangChainAdapter } from 'network-ai';
|
|
1680
|
+
*
|
|
1681
|
+
* // Minimal
|
|
1682
|
+
* const orc = createSwarmOrchestrator();
|
|
1683
|
+
*
|
|
1684
|
+
* // With adapters and trust
|
|
1685
|
+
* const orc2 = createSwarmOrchestrator({
|
|
1686
|
+
* adapters: [{ adapter: new LangChainAdapter() }],
|
|
1687
|
+
* trustLevels: [{ agentId: 'analyst', trustLevel: 0.8 }],
|
|
1688
|
+
* qualityThreshold: 0.7,
|
|
1689
|
+
* });
|
|
1690
|
+
* ```
|
|
1691
|
+
*/
|
|
1408
1692
|
function createSwarmOrchestrator(config) {
|
|
1693
|
+
if (config !== undefined && (typeof config !== 'object' || config === null || Array.isArray(config))) {
|
|
1694
|
+
throw new errors_1.ValidationError('config must be a plain object');
|
|
1695
|
+
}
|
|
1409
1696
|
if (config) {
|
|
1410
1697
|
const { adapters: adapterList, adapterRegistry, trustLevels, resourceProfiles, validationConfig, qualityThreshold, aiReviewCallback, ...rest } = config;
|
|
1411
1698
|
Object.assign(CONFIG, rest);
|
|
@@ -1419,7 +1706,7 @@ function createSwarmOrchestrator(config) {
|
|
|
1419
1706
|
});
|
|
1420
1707
|
// Initialize adapters if provided
|
|
1421
1708
|
if (adapterList) {
|
|
1422
|
-
Promise.all(adapterList.map(({ adapter, config: adapterConfig }) => orchestrator.addAdapter(adapter, adapterConfig ?? {}))).catch(err =>
|
|
1709
|
+
Promise.all(adapterList.map(({ adapter, config: adapterConfig }) => orchestrator.addAdapter(adapter, adapterConfig ?? {}))).catch(err => log.error('Adapter init error', { error: err instanceof Error ? err.message : String(err) }));
|
|
1423
1710
|
}
|
|
1424
1711
|
return orchestrator;
|
|
1425
1712
|
}
|