network-ai 3.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 (92) hide show
  1. package/LICENSE +21 -0
  2. package/QUICKSTART.md +260 -0
  3. package/README.md +604 -0
  4. package/SKILL.md +568 -0
  5. package/dist/adapters/adapter-registry.d.ts +94 -0
  6. package/dist/adapters/adapter-registry.d.ts.map +1 -0
  7. package/dist/adapters/adapter-registry.js +355 -0
  8. package/dist/adapters/adapter-registry.js.map +1 -0
  9. package/dist/adapters/agno-adapter.d.ts +112 -0
  10. package/dist/adapters/agno-adapter.d.ts.map +1 -0
  11. package/dist/adapters/agno-adapter.js +140 -0
  12. package/dist/adapters/agno-adapter.js.map +1 -0
  13. package/dist/adapters/autogen-adapter.d.ts +67 -0
  14. package/dist/adapters/autogen-adapter.d.ts.map +1 -0
  15. package/dist/adapters/autogen-adapter.js +141 -0
  16. package/dist/adapters/autogen-adapter.js.map +1 -0
  17. package/dist/adapters/base-adapter.d.ts +51 -0
  18. package/dist/adapters/base-adapter.d.ts.map +1 -0
  19. package/dist/adapters/base-adapter.js +103 -0
  20. package/dist/adapters/base-adapter.js.map +1 -0
  21. package/dist/adapters/crewai-adapter.d.ts +72 -0
  22. package/dist/adapters/crewai-adapter.d.ts.map +1 -0
  23. package/dist/adapters/crewai-adapter.js +148 -0
  24. package/dist/adapters/crewai-adapter.js.map +1 -0
  25. package/dist/adapters/custom-adapter.d.ts +74 -0
  26. package/dist/adapters/custom-adapter.d.ts.map +1 -0
  27. package/dist/adapters/custom-adapter.js +142 -0
  28. package/dist/adapters/custom-adapter.js.map +1 -0
  29. package/dist/adapters/dspy-adapter.d.ts +70 -0
  30. package/dist/adapters/dspy-adapter.d.ts.map +1 -0
  31. package/dist/adapters/dspy-adapter.js +127 -0
  32. package/dist/adapters/dspy-adapter.js.map +1 -0
  33. package/dist/adapters/haystack-adapter.d.ts +83 -0
  34. package/dist/adapters/haystack-adapter.d.ts.map +1 -0
  35. package/dist/adapters/haystack-adapter.js +149 -0
  36. package/dist/adapters/haystack-adapter.js.map +1 -0
  37. package/dist/adapters/index.d.ts +47 -0
  38. package/dist/adapters/index.d.ts.map +1 -0
  39. package/dist/adapters/index.js +56 -0
  40. package/dist/adapters/index.js.map +1 -0
  41. package/dist/adapters/langchain-adapter.d.ts +51 -0
  42. package/dist/adapters/langchain-adapter.d.ts.map +1 -0
  43. package/dist/adapters/langchain-adapter.js +134 -0
  44. package/dist/adapters/langchain-adapter.js.map +1 -0
  45. package/dist/adapters/llamaindex-adapter.d.ts +89 -0
  46. package/dist/adapters/llamaindex-adapter.d.ts.map +1 -0
  47. package/dist/adapters/llamaindex-adapter.js +135 -0
  48. package/dist/adapters/llamaindex-adapter.js.map +1 -0
  49. package/dist/adapters/mcp-adapter.d.ts +90 -0
  50. package/dist/adapters/mcp-adapter.d.ts.map +1 -0
  51. package/dist/adapters/mcp-adapter.js +200 -0
  52. package/dist/adapters/mcp-adapter.js.map +1 -0
  53. package/dist/adapters/openai-assistants-adapter.d.ts +94 -0
  54. package/dist/adapters/openai-assistants-adapter.d.ts.map +1 -0
  55. package/dist/adapters/openai-assistants-adapter.js +130 -0
  56. package/dist/adapters/openai-assistants-adapter.js.map +1 -0
  57. package/dist/adapters/openclaw-adapter.d.ts +21 -0
  58. package/dist/adapters/openclaw-adapter.d.ts.map +1 -0
  59. package/dist/adapters/openclaw-adapter.js +140 -0
  60. package/dist/adapters/openclaw-adapter.js.map +1 -0
  61. package/dist/adapters/semantic-kernel-adapter.d.ts +73 -0
  62. package/dist/adapters/semantic-kernel-adapter.d.ts.map +1 -0
  63. package/dist/adapters/semantic-kernel-adapter.js +123 -0
  64. package/dist/adapters/semantic-kernel-adapter.js.map +1 -0
  65. package/dist/index.d.ts +379 -0
  66. package/dist/index.d.ts.map +1 -0
  67. package/dist/index.js +1428 -0
  68. package/dist/index.js.map +1 -0
  69. package/dist/lib/blackboard-validator.d.ts +205 -0
  70. package/dist/lib/blackboard-validator.d.ts.map +1 -0
  71. package/dist/lib/blackboard-validator.js +756 -0
  72. package/dist/lib/blackboard-validator.js.map +1 -0
  73. package/dist/lib/locked-blackboard.d.ts +174 -0
  74. package/dist/lib/locked-blackboard.d.ts.map +1 -0
  75. package/dist/lib/locked-blackboard.js +654 -0
  76. package/dist/lib/locked-blackboard.js.map +1 -0
  77. package/dist/lib/swarm-utils.d.ts +136 -0
  78. package/dist/lib/swarm-utils.d.ts.map +1 -0
  79. package/dist/lib/swarm-utils.js +510 -0
  80. package/dist/lib/swarm-utils.js.map +1 -0
  81. package/dist/security.d.ts +269 -0
  82. package/dist/security.d.ts.map +1 -0
  83. package/dist/security.js +713 -0
  84. package/dist/security.js.map +1 -0
  85. package/package.json +84 -0
  86. package/scripts/blackboard.py +819 -0
  87. package/scripts/check_permission.py +331 -0
  88. package/scripts/revoke_token.py +243 -0
  89. package/scripts/swarm_guard.py +1140 -0
  90. package/scripts/validate_token.py +97 -0
  91. package/types/agent-adapter.d.ts +244 -0
  92. package/types/openclaw-core.d.ts +52 -0
package/dist/index.js ADDED
@@ -0,0 +1,1428 @@
1
+ "use strict";
2
+ /**
3
+ * SwarmOrchestrator - Multi-Agent Swarm Orchestration Skill
4
+ *
5
+ * This module implements the core logic for agent-to-agent communication,
6
+ * task decomposition, permission management, and shared blackboard coordination.
7
+ *
8
+ * @module SwarmOrchestrator
9
+ * @version 3.0.0
10
+ * @license MIT
11
+ */
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;
14
+ exports.createSwarmOrchestrator = createSwarmOrchestrator;
15
+ const fs_1 = require("fs");
16
+ const path_1 = require("path");
17
+ const crypto_1 = require("crypto");
18
+ const adapter_registry_1 = require("./adapters/adapter-registry");
19
+ const security_1 = require("./security");
20
+ const locked_blackboard_1 = require("./lib/locked-blackboard");
21
+ const blackboard_validator_1 = require("./lib/blackboard-validator");
22
+ // ============================================================================
23
+ // CONFIGURATION
24
+ // ============================================================================
25
+ const CONFIG = {
26
+ blackboardPath: './swarm-blackboard.md',
27
+ maxParallelAgents: 3,
28
+ defaultTimeout: 30000,
29
+ enableTracing: true,
30
+ grantTokenTTL: 300000, // 5 minutes in milliseconds
31
+ maxBlackboardValueSize: 1024 * 1024, // 1 MB max per entry
32
+ auditLogPath: './data/audit_log.jsonl',
33
+ trustConfigPath: './data/trust_levels.json',
34
+ };
35
+ // ============================================================================
36
+ // DEFAULT RESOURCE PROFILES -- Universal, domain-agnostic
37
+ // Users can override/extend these for any domain (coding, finance, devops, etc.)
38
+ // ============================================================================
39
+ const DEFAULT_RESOURCE_PROFILES = {
40
+ // --- Financial / Enterprise ---
41
+ SAP_API: { baseRisk: 0.5, defaultRestrictions: ['read_only', 'max_records:100'], description: 'SAP enterprise API' },
42
+ FINANCIAL_API: { baseRisk: 0.7, defaultRestrictions: ['read_only', 'no_pii_fields', 'audit_required'], description: 'Financial data API' },
43
+ DATA_EXPORT: { baseRisk: 0.6, defaultRestrictions: ['anonymize_pii', 'local_only'], description: 'Data export operations' },
44
+ // --- Coding / Development ---
45
+ FILE_SYSTEM: { baseRisk: 0.5, defaultRestrictions: ['workspace_only', 'no_system_dirs', 'max_file_size:10mb'], description: 'Read/write files in workspace' },
46
+ SHELL_EXEC: { baseRisk: 0.8, defaultRestrictions: ['sandbox_only', 'no_sudo', 'timeout:30s', 'audit_required'], description: 'Execute shell commands' },
47
+ GIT: { baseRisk: 0.4, defaultRestrictions: ['local_repo_only', 'no_force_push'], description: 'Git operations' },
48
+ PACKAGE_MANAGER: { baseRisk: 0.6, defaultRestrictions: ['audit_required', 'no_global_install', 'lockfile_required'], description: 'npm/pip/cargo package management' },
49
+ BUILD_TOOL: { baseRisk: 0.5, defaultRestrictions: ['workspace_only', 'timeout:120s'], description: 'Build and compilation' },
50
+ // --- Infrastructure / DevOps ---
51
+ DOCKER: { baseRisk: 0.7, defaultRestrictions: ['no_privileged', 'no_host_network', 'audit_required'], description: 'Container operations' },
52
+ CLOUD_DEPLOY: { baseRisk: 0.9, defaultRestrictions: ['staging_only', 'approval_required', 'rollback_ready'], description: 'Cloud deployment' },
53
+ DATABASE: { baseRisk: 0.6, defaultRestrictions: ['read_only', 'max_records:1000', 'no_schema_changes'], description: 'Database access' },
54
+ // --- Communication / External ---
55
+ EXTERNAL_SERVICE: { baseRisk: 0.4, defaultRestrictions: ['rate_limit:10_per_minute'], description: 'External API calls' },
56
+ EMAIL: { baseRisk: 0.5, defaultRestrictions: ['rate_limit:5_per_minute', 'no_attachments'], description: 'Email sending' },
57
+ WEBHOOK: { baseRisk: 0.4, defaultRestrictions: ['allowed_domains_only', 'no_credentials'], description: 'Webhook dispatch' },
58
+ };
59
+ const DEFAULT_AGENT_TRUST = [
60
+ { agentId: 'orchestrator', trustLevel: 0.9, allowedNamespaces: ['*'], allowedResources: ['*'] },
61
+ { agentId: 'data_analyst', trustLevel: 0.8, allowedNamespaces: ['task:', 'analytics:', 'agent:'], allowedResources: ['SAP_API', 'DATABASE', 'DATA_EXPORT', 'EXTERNAL_SERVICE'] },
62
+ { agentId: 'strategy_advisor', trustLevel: 0.7, allowedNamespaces: ['task:', 'strategy:'], allowedResources: ['EXTERNAL_SERVICE', 'DATA_EXPORT'] },
63
+ { agentId: 'risk_assessor', trustLevel: 0.85, allowedNamespaces: ['task:', 'risk:', 'analytics:'], allowedResources: ['EXTERNAL_SERVICE', 'DATABASE'] },
64
+ // Coding agents
65
+ { agentId: 'code_writer', trustLevel: 0.75, allowedNamespaces: ['task:', 'code:', 'build:'], allowedResources: ['FILE_SYSTEM', 'GIT', 'BUILD_TOOL', 'PACKAGE_MANAGER'] },
66
+ { agentId: 'code_reviewer', trustLevel: 0.8, allowedNamespaces: ['task:', 'code:', 'review:'], allowedResources: ['FILE_SYSTEM', 'GIT'] },
67
+ { agentId: 'test_runner', trustLevel: 0.75, allowedNamespaces: ['task:', 'test:', 'build:'], allowedResources: ['FILE_SYSTEM', 'SHELL_EXEC', 'BUILD_TOOL'] },
68
+ { agentId: 'devops_agent', trustLevel: 0.7, allowedNamespaces: ['task:', 'deploy:', 'infra:'], allowedResources: ['DOCKER', 'SHELL_EXEC', 'CLOUD_DEPLOY', 'GIT'] },
69
+ ];
70
+ // ============================================================================
71
+ // BLACKBOARD MANAGEMENT -- Secured with LockedBlackboard, identity verification,
72
+ // namespace scoping, value validation, and input sanitization
73
+ // ============================================================================
74
+ class SharedBlackboard {
75
+ backend;
76
+ agentTokens = new Map(); // agentId -> verified token
77
+ agentNamespaces = new Map(); // agentId -> allowed prefixes
78
+ constructor(basePath) {
79
+ this.backend = new locked_blackboard_1.LockedBlackboard(basePath);
80
+ }
81
+ /**
82
+ * Register a verified agent identity. Only agents with registered tokens
83
+ * can write to the blackboard. The orchestrator registers agents after
84
+ * verifying their identity through the AuthGuardian.
85
+ */
86
+ registerAgent(agentId, verificationToken, allowedNamespaces = ['*']) {
87
+ this.agentTokens.set(agentId, verificationToken);
88
+ this.agentNamespaces.set(agentId, allowedNamespaces);
89
+ }
90
+ /**
91
+ * Check if an agent is allowed to access a key based on namespace rules.
92
+ */
93
+ canAccessKey(agentId, key) {
94
+ const namespaces = this.agentNamespaces.get(agentId);
95
+ if (!namespaces)
96
+ return false;
97
+ if (namespaces.includes('*'))
98
+ return true;
99
+ return namespaces.some(ns => key.startsWith(ns));
100
+ }
101
+ /**
102
+ * Verify that the calling agent is who they claim to be.
103
+ */
104
+ verifyAgent(agentId, token) {
105
+ const registeredToken = this.agentTokens.get(agentId);
106
+ // If no token system is configured for this agent, allow (backward compat)
107
+ if (!registeredToken)
108
+ return true;
109
+ return token === registeredToken;
110
+ }
111
+ /**
112
+ * Validate value size and structure before writing.
113
+ * Prevents DoS via oversized writes and circular data.
114
+ */
115
+ validateValue(value) {
116
+ try {
117
+ const serialized = JSON.stringify(value);
118
+ if (serialized.length > CONFIG.maxBlackboardValueSize) {
119
+ return { valid: false, reason: `Value exceeds max size (${serialized.length} > ${CONFIG.maxBlackboardValueSize} bytes)` };
120
+ }
121
+ return { valid: true };
122
+ }
123
+ catch {
124
+ return { valid: false, reason: 'Value cannot be serialized (circular reference or invalid structure)' };
125
+ }
126
+ }
127
+ /**
128
+ * Sanitize a key to prevent markdown injection.
129
+ */
130
+ sanitizeKey(key) {
131
+ // Keys must be safe for markdown headings -- no #, newlines, or markdown syntax
132
+ return key.replace(/[#\n\r|`]/g, '_').slice(0, 256);
133
+ }
134
+ read(key) {
135
+ const entry = this.backend.read(key);
136
+ if (!entry)
137
+ return null;
138
+ // Normalize field name for backward compatibility
139
+ return {
140
+ key: entry.key,
141
+ value: entry.value,
142
+ sourceAgent: entry.source_agent ?? entry.sourceAgent ?? 'unknown',
143
+ timestamp: entry.timestamp,
144
+ ttl: entry.ttl,
145
+ };
146
+ }
147
+ /**
148
+ * Write to the blackboard with identity verification, namespace checks,
149
+ * value validation, and input sanitization. Uses LockedBlackboard for
150
+ * atomic file-system writes.
151
+ *
152
+ * @param key - The key to write
153
+ * @param value - The value (will be sanitized and size-checked)
154
+ * @param sourceAgent - Agent claiming to write (verified against registered token)
155
+ * @param ttl - Optional TTL in seconds
156
+ * @param agentToken - Optional verification token for identity check
157
+ */
158
+ write(key, value, sourceAgent, ttl, agentToken) {
159
+ // 1. Verify agent identity
160
+ if (!this.verifyAgent(sourceAgent, agentToken)) {
161
+ throw new Error(`[Blackboard] Identity verification failed for agent '${sourceAgent}'`);
162
+ }
163
+ // 2. Namespace check
164
+ if (!this.canAccessKey(sourceAgent, key)) {
165
+ throw new Error(`[Blackboard] Agent '${sourceAgent}' not allowed to write to key '${key}'`);
166
+ }
167
+ // 3. Sanitize key
168
+ const safeKey = this.sanitizeKey(key);
169
+ // 4. Validate value size/structure
170
+ const validation = this.validateValue(value);
171
+ if (!validation.valid) {
172
+ throw new Error(`[Blackboard] Value validation failed: ${validation.reason}`);
173
+ }
174
+ // 5. Sanitize value -- strip injection payloads from string content
175
+ let sanitizedValue;
176
+ try {
177
+ sanitizedValue = security_1.InputSanitizer.sanitizeObject(value);
178
+ }
179
+ catch {
180
+ sanitizedValue = value; // Fall back to raw if sanitization can't handle it
181
+ }
182
+ // 6. Write through LockedBlackboard (atomic, file-locked)
183
+ const entry = this.backend.write(safeKey, sanitizedValue, sourceAgent, ttl);
184
+ // Normalize for backward compat
185
+ return {
186
+ key: entry.key,
187
+ value: entry.value,
188
+ sourceAgent: entry.source_agent ?? sourceAgent,
189
+ timestamp: entry.timestamp,
190
+ ttl: entry.ttl,
191
+ };
192
+ }
193
+ exists(key) {
194
+ return this.read(key) !== null;
195
+ }
196
+ /**
197
+ * Get a full snapshot of all blackboard entries.
198
+ */
199
+ getSnapshot() {
200
+ const raw = this.backend.getSnapshot();
201
+ const normalized = {};
202
+ for (const [key, entry] of Object.entries(raw)) {
203
+ normalized[key] = {
204
+ key: entry.key,
205
+ value: entry.value,
206
+ sourceAgent: entry.source_agent ?? entry.sourceAgent ?? 'unknown',
207
+ timestamp: entry.timestamp,
208
+ ttl: entry.ttl,
209
+ };
210
+ }
211
+ return normalized;
212
+ }
213
+ /**
214
+ * Get a namespace-scoped snapshot -- only returns keys an agent is allowed to see.
215
+ * Prevents data leakage between agents.
216
+ */
217
+ getScopedSnapshot(agentId) {
218
+ const full = this.getSnapshot();
219
+ const scoped = {};
220
+ for (const [key, entry] of Object.entries(full)) {
221
+ if (this.canAccessKey(agentId, key)) {
222
+ scoped[key] = entry;
223
+ }
224
+ }
225
+ return scoped;
226
+ }
227
+ /**
228
+ * Clear all entries (for testing).
229
+ */
230
+ clear() {
231
+ // Write an empty state through locked backend
232
+ const keys = this.backend.listKeys();
233
+ for (const key of keys) {
234
+ this.backend.delete(key);
235
+ }
236
+ }
237
+ }
238
+ exports.SharedBlackboard = SharedBlackboard;
239
+ // ============================================================================
240
+ // AUTH GUARDIAN - UNIVERSAL PERMISSION WALL IMPLEMENTATION
241
+ // Now domain-agnostic: resource types, risk profiles, trust levels, and
242
+ // restrictions are all configurable. Works for coding, finance, devops, etc.
243
+ // Integrates with SecureSwarmGateway for HMAC tokens, rate limiting,
244
+ // input sanitization, and cryptographic audit logs.
245
+ // ============================================================================
246
+ class AuthGuardian {
247
+ activeGrants = new Map();
248
+ agentTrustLevels = new Map();
249
+ agentTrustConfigs = new Map();
250
+ resourceProfiles = new Map();
251
+ auditLog = [];
252
+ auditLogPath;
253
+ trustConfigPath;
254
+ constructor(options) {
255
+ this.auditLogPath = options?.auditLogPath ?? CONFIG.auditLogPath;
256
+ this.trustConfigPath = options?.trustConfigPath ?? CONFIG.trustConfigPath;
257
+ // Load resource profiles (user-provided + defaults)
258
+ const profiles = { ...DEFAULT_RESOURCE_PROFILES, ...(options?.resourceProfiles ?? {}) };
259
+ for (const [name, profile] of Object.entries(profiles)) {
260
+ this.resourceProfiles.set(name, profile);
261
+ }
262
+ // Load trust levels (try disk first, then user-provided, then defaults)
263
+ const trustConfigs = options?.trustLevels ?? this.loadTrustFromDisk() ?? DEFAULT_AGENT_TRUST;
264
+ for (const config of trustConfigs) {
265
+ this.agentTrustLevels.set(config.agentId, config.trustLevel);
266
+ this.agentTrustConfigs.set(config.agentId, config);
267
+ }
268
+ // Load existing audit log from disk
269
+ this.loadAuditFromDisk();
270
+ }
271
+ /**
272
+ * Register a new resource type at runtime.
273
+ * Makes the system extensible for any domain.
274
+ */
275
+ registerResourceType(name, profile) {
276
+ this.resourceProfiles.set(name, profile);
277
+ }
278
+ /**
279
+ * Register or update an agent's trust configuration at runtime.
280
+ */
281
+ registerAgentTrust(config) {
282
+ this.agentTrustLevels.set(config.agentId, config.trustLevel);
283
+ this.agentTrustConfigs.set(config.agentId, config);
284
+ this.persistTrustToDisk();
285
+ }
286
+ /**
287
+ * Request permission to access a resource.
288
+ * resourceType is now a free string -- validated against registered profiles.
289
+ */
290
+ async requestPermission(agentId, resourceType, justification, scope) {
291
+ // Sanitize inputs
292
+ let safeAgentId;
293
+ let safeJustification;
294
+ try {
295
+ safeAgentId = security_1.InputSanitizer.sanitizeAgentId(agentId);
296
+ safeJustification = security_1.InputSanitizer.sanitizeString(justification, 2000);
297
+ }
298
+ catch {
299
+ safeAgentId = agentId.replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 64) || 'unknown';
300
+ safeJustification = justification.slice(0, 2000);
301
+ }
302
+ this.log('permission_request', { agentId: safeAgentId, resourceType, justification: safeJustification, scope });
303
+ // Check if agent is allowed to access this resource type
304
+ const agentConfig = this.agentTrustConfigs.get(safeAgentId);
305
+ if (agentConfig && agentConfig.allowedResources && !agentConfig.allowedResources.includes('*')) {
306
+ if (!agentConfig.allowedResources.includes(resourceType)) {
307
+ this.log('permission_denied', { agentId: safeAgentId, resourceType, reason: 'resource_not_in_allowlist' });
308
+ return {
309
+ granted: false,
310
+ grantToken: null,
311
+ expiresAt: null,
312
+ restrictions: [],
313
+ reason: `Agent '${safeAgentId}' is not authorized to access '${resourceType}'. Allowed: ${agentConfig.allowedResources.join(', ')}`,
314
+ };
315
+ }
316
+ }
317
+ // Evaluate the permission request
318
+ const evaluation = this.evaluateRequest(safeAgentId, resourceType, safeJustification, scope);
319
+ if (!evaluation.approved) {
320
+ this.log('permission_denied', { agentId: safeAgentId, resourceType, reason: evaluation.reason });
321
+ return {
322
+ granted: false,
323
+ grantToken: null,
324
+ expiresAt: null,
325
+ restrictions: [],
326
+ reason: evaluation.reason,
327
+ };
328
+ }
329
+ // Generate grant token
330
+ const grantToken = this.generateGrantToken();
331
+ const expiresAt = new Date(Date.now() + CONFIG.grantTokenTTL).toISOString();
332
+ const grant = {
333
+ grantToken,
334
+ resourceType,
335
+ agentId: safeAgentId,
336
+ expiresAt,
337
+ restrictions: evaluation.restrictions,
338
+ scope,
339
+ };
340
+ this.activeGrants.set(grantToken, grant);
341
+ this.log('permission_granted', { grantToken, agentId: safeAgentId, resourceType, expiresAt, restrictions: evaluation.restrictions });
342
+ return {
343
+ granted: true,
344
+ grantToken,
345
+ expiresAt,
346
+ restrictions: evaluation.restrictions,
347
+ };
348
+ }
349
+ validateToken(token) {
350
+ const grant = this.activeGrants.get(token);
351
+ if (!grant)
352
+ return false;
353
+ if (new Date(grant.expiresAt) < new Date()) {
354
+ this.activeGrants.delete(token);
355
+ return false;
356
+ }
357
+ return true;
358
+ }
359
+ /**
360
+ * Validate a token and return the bound restrictions and scope.
361
+ * Used to enforce restrictions at the point of use.
362
+ */
363
+ validateTokenWithGrant(token) {
364
+ const grant = this.activeGrants.get(token);
365
+ if (!grant)
366
+ return null;
367
+ if (new Date(grant.expiresAt) < new Date()) {
368
+ this.activeGrants.delete(token);
369
+ return null;
370
+ }
371
+ return grant;
372
+ }
373
+ /**
374
+ * Enforce restrictions on an operation. Returns an error string if
375
+ * the operation violates any restriction, or null if allowed.
376
+ */
377
+ enforceRestrictions(grantToken, operation) {
378
+ const grant = this.validateTokenWithGrant(grantToken);
379
+ if (!grant)
380
+ return 'Invalid or expired grant token';
381
+ for (const restriction of grant.restrictions) {
382
+ // Enforce read_only
383
+ if (restriction === 'read_only' && operation.type && operation.type !== 'read') {
384
+ return `Restriction 'read_only' violated: attempted '${operation.type}'`;
385
+ }
386
+ // Enforce max_records
387
+ const maxRecordsMatch = restriction.match(/^max_records:(\d+)$/);
388
+ if (maxRecordsMatch && operation.recordCount) {
389
+ const max = parseInt(maxRecordsMatch[1], 10);
390
+ if (operation.recordCount > max) {
391
+ return `Restriction '${restriction}' violated: requested ${operation.recordCount} records`;
392
+ }
393
+ }
394
+ // Enforce sandbox_only
395
+ if (restriction === 'sandbox_only' && operation.targetPath) {
396
+ if (/^\/|^[A-Z]:\\(?:Windows|Program)/i.test(operation.targetPath)) {
397
+ return `Restriction 'sandbox_only' violated: path '${operation.targetPath}' is outside sandbox`;
398
+ }
399
+ }
400
+ // Enforce no_sudo
401
+ if (restriction === 'no_sudo' && operation.command) {
402
+ if (/\bsudo\b/i.test(operation.command)) {
403
+ return `Restriction 'no_sudo' violated: command contains sudo`;
404
+ }
405
+ }
406
+ // Enforce workspace_only
407
+ if (restriction === 'workspace_only' && operation.targetPath) {
408
+ if (/\.\.[/\\]/.test(operation.targetPath)) {
409
+ return `Restriction 'workspace_only' violated: path traversal detected`;
410
+ }
411
+ }
412
+ // Enforce no_system_dirs
413
+ if (restriction === 'no_system_dirs' && operation.targetPath) {
414
+ if (/(?:\/etc|\/usr|\/var|\\Windows|\\System32)/i.test(operation.targetPath)) {
415
+ return `Restriction 'no_system_dirs' violated: system directory access`;
416
+ }
417
+ }
418
+ // Enforce no_attachments
419
+ if (restriction === 'no_attachments' && operation.hasAttachments) {
420
+ return `Restriction 'no_attachments' violated`;
421
+ }
422
+ }
423
+ return null; // All restrictions passed
424
+ }
425
+ revokeToken(token) {
426
+ this.activeGrants.delete(token);
427
+ this.log('permission_revoked', { token });
428
+ }
429
+ evaluateRequest(agentId, resourceType, justification, scope) {
430
+ // 1. Justification Quality (40% weight) -- now includes resource-relevance
431
+ const justificationScore = this.scoreJustification(justification, resourceType);
432
+ if (justificationScore < 0.3) {
433
+ return {
434
+ approved: false,
435
+ reason: 'Justification is insufficient. Please provide specific task context.',
436
+ restrictions: [],
437
+ };
438
+ }
439
+ // 2. Agent Trust Level (30% weight)
440
+ const trustLevel = this.agentTrustLevels.get(agentId) ?? 0.5;
441
+ if (trustLevel < 0.4) {
442
+ return {
443
+ approved: false,
444
+ reason: 'Agent trust level is below threshold. Escalate to human operator.',
445
+ restrictions: [],
446
+ };
447
+ }
448
+ // 3. Risk Assessment (30% weight)
449
+ const riskScore = this.assessRisk(resourceType, scope);
450
+ if (riskScore > 0.8) {
451
+ return {
452
+ approved: false,
453
+ reason: 'Risk assessment exceeds acceptable threshold. Narrow the requested scope.',
454
+ restrictions: [],
455
+ };
456
+ }
457
+ // Get restrictions from resource profile (data-driven, not hardcoded)
458
+ const profile = this.resourceProfiles.get(resourceType);
459
+ const restrictions = profile
460
+ ? [...profile.defaultRestrictions]
461
+ : ['audit_required']; // Unknown resources get audited by default
462
+ // Calculate weighted approval
463
+ const weightedScore = (justificationScore * 0.4) + (trustLevel * 0.3) + ((1 - riskScore) * 0.3);
464
+ const approved = weightedScore >= 0.5;
465
+ return {
466
+ approved,
467
+ reason: approved ? undefined : 'Combined evaluation score below threshold.',
468
+ restrictions,
469
+ };
470
+ }
471
+ /**
472
+ * Improved justification scoring with resource-relevance checking.
473
+ * Prevents trivial gaming by verifying the justification mentions
474
+ * concepts relevant to the requested resource.
475
+ */
476
+ scoreJustification(justification, resourceType) {
477
+ let score = 0;
478
+ // Length scoring
479
+ if (justification.length > 20)
480
+ score += 0.15;
481
+ if (justification.length > 50)
482
+ score += 0.15;
483
+ // Intent keywords
484
+ if (/task|purpose|need|require|generate|analyze|process|build|deploy|test|review/i.test(justification))
485
+ score += 0.15;
486
+ // Specificity keywords
487
+ if (/specific|particular|exact|for\s+the|in\s+order\s+to|because|so\s+that/i.test(justification))
488
+ score += 0.15;
489
+ // Penalty for vague/test phrasing
490
+ if (/^test$|^debug$|^try$|^just\s+testing/i.test(justification.trim()))
491
+ score -= 0.3;
492
+ // Resource-relevance check: does the justification mention anything related
493
+ // to the requested resource? (+0.2 bonus for relevant context)
494
+ if (resourceType) {
495
+ const relevancePatterns = {
496
+ SAP_API: /sap|erp|invoice|procurement|purchase|material|vendor/i,
497
+ FINANCIAL_API: /financ|revenue|budget|accounting|payment|ledger|balance/i,
498
+ DATA_EXPORT: /export|report|csv|download|extract|migrate/i,
499
+ FILE_SYSTEM: /file|read|write|save|load|path|directory|workspace/i,
500
+ SHELL_EXEC: /command|script|compile|build|run|execute|terminal/i,
501
+ GIT: /git|commit|branch|merge|pull|push|repository|diff/i,
502
+ PACKAGE_MANAGER: /package|install|dependency|npm|pip|cargo|module/i,
503
+ BUILD_TOOL: /build|compile|webpack|tsc|make|gradle|cargo/i,
504
+ DOCKER: /container|docker|image|deploy|service|compose/i,
505
+ CLOUD_DEPLOY: /deploy|cloud|staging|production|release|infrastructure/i,
506
+ DATABASE: /database|query|sql|table|record|schema|migration/i,
507
+ EXTERNAL_SERVICE: /api|service|endpoint|webhook|request|fetch/i,
508
+ EMAIL: /email|mail|send|notification|alert|message/i,
509
+ WEBHOOK: /webhook|callback|notification|event|dispatch/i,
510
+ };
511
+ const pattern = relevancePatterns[resourceType];
512
+ if (pattern && pattern.test(justification)) {
513
+ score += 0.2;
514
+ }
515
+ else if (pattern && !pattern.test(justification)) {
516
+ // Justification doesn't mention anything relevant -- small penalty
517
+ score -= 0.1;
518
+ }
519
+ }
520
+ // Bonus for mentioning a task/ticket ID
521
+ if (/(?:task|ticket|issue|jira|pr|bug)[_\-#]?\s*\d+/i.test(justification))
522
+ score += 0.1;
523
+ return Math.max(0, Math.min(score, 1));
524
+ }
525
+ assessRisk(resourceType, scope) {
526
+ // Look up base risk from registered profile (not hardcoded)
527
+ const profile = this.resourceProfiles.get(resourceType);
528
+ let risk = profile?.baseRisk ?? 0.5; // Unknown resources get medium risk
529
+ // Broad scopes increase risk
530
+ if (!scope || scope === '*' || scope === 'all') {
531
+ risk += 0.2;
532
+ }
533
+ // Write/delete operations increase risk
534
+ if (scope && /write|delete|update|modify|execute|deploy/i.test(scope)) {
535
+ risk += 0.2;
536
+ }
537
+ return Math.min(risk, 1);
538
+ }
539
+ generateGrantToken() {
540
+ return `grant_${(0, crypto_1.randomUUID)().replace(/-/g, '')}`;
541
+ }
542
+ log(action, details) {
543
+ const entry = {
544
+ timestamp: new Date().toISOString(),
545
+ action,
546
+ details,
547
+ };
548
+ this.auditLog.push(entry);
549
+ // Persist to disk
550
+ try {
551
+ const dir = (0, path_1.join)('.', 'data');
552
+ if (!(0, fs_1.existsSync)(dir)) {
553
+ require('fs').mkdirSync(dir, { recursive: true });
554
+ }
555
+ (0, fs_1.appendFileSync)(this.auditLogPath, JSON.stringify(entry) + '\n');
556
+ }
557
+ catch {
558
+ // Non-fatal -- log is also in memory
559
+ }
560
+ }
561
+ getActiveGrants() {
562
+ // Clean expired grants
563
+ const now = new Date();
564
+ for (const [token, grant] of this.activeGrants.entries()) {
565
+ if (new Date(grant.expiresAt) < now) {
566
+ this.activeGrants.delete(token);
567
+ }
568
+ }
569
+ return Array.from(this.activeGrants.values());
570
+ }
571
+ getAuditLog() {
572
+ return [...this.auditLog];
573
+ }
574
+ /**
575
+ * Get all registered resource profiles.
576
+ */
577
+ getResourceProfiles() {
578
+ return Object.fromEntries(this.resourceProfiles);
579
+ }
580
+ /**
581
+ * Get the allowed namespaces for an agent (used by blackboard scoping).
582
+ */
583
+ getAgentNamespaces(agentId) {
584
+ const config = this.agentTrustConfigs.get(agentId);
585
+ return config?.allowedNamespaces ?? ['task:'];
586
+ }
587
+ // ---- Persistence helpers ----
588
+ loadTrustFromDisk() {
589
+ try {
590
+ if ((0, fs_1.existsSync)(this.trustConfigPath)) {
591
+ const raw = (0, fs_1.readFileSync)(this.trustConfigPath, 'utf-8');
592
+ return JSON.parse(raw);
593
+ }
594
+ }
595
+ catch { /* ignore */ }
596
+ return null;
597
+ }
598
+ persistTrustToDisk() {
599
+ try {
600
+ const dir = (0, path_1.join)('.', 'data');
601
+ if (!(0, fs_1.existsSync)(dir)) {
602
+ require('fs').mkdirSync(dir, { recursive: true });
603
+ }
604
+ const configs = Array.from(this.agentTrustConfigs.values());
605
+ (0, fs_1.writeFileSync)(this.trustConfigPath, JSON.stringify(configs, null, 2));
606
+ }
607
+ catch {
608
+ // Non-fatal
609
+ }
610
+ }
611
+ loadAuditFromDisk() {
612
+ try {
613
+ if ((0, fs_1.existsSync)(this.auditLogPath)) {
614
+ const raw = (0, fs_1.readFileSync)(this.auditLogPath, 'utf-8');
615
+ const lines = raw.trim().split('\n').filter(l => l);
616
+ for (const line of lines) {
617
+ try {
618
+ this.auditLog.push(JSON.parse(line));
619
+ }
620
+ catch { /* skip malformed */ }
621
+ }
622
+ }
623
+ }
624
+ catch { /* ignore */ }
625
+ }
626
+ }
627
+ exports.AuthGuardian = AuthGuardian;
628
+ // ============================================================================
629
+ // TASK DECOMPOSITION ENGINE
630
+ // ============================================================================
631
+ class TaskDecomposer {
632
+ blackboard;
633
+ authGuardian;
634
+ adapterRegistry;
635
+ constructor(blackboard, authGuardian, adapterRegistry) {
636
+ this.blackboard = blackboard;
637
+ this.authGuardian = authGuardian;
638
+ this.adapterRegistry = adapterRegistry;
639
+ }
640
+ /**
641
+ * Decomposes a complex task into parallel sub-agent calls
642
+ * This is the "Wall Breaker" - transforms impossible monolithic tasks
643
+ * into manageable parallel executions
644
+ */
645
+ async executeParallel(tasks, synthesisStrategy = 'merge', context) {
646
+ // Enforce maximum parallel agent limit
647
+ if (tasks.length > CONFIG.maxParallelAgents) {
648
+ throw new Error(`Cannot spawn ${tasks.length} agents. Maximum is ${CONFIG.maxParallelAgents}. ` +
649
+ `Decompose further or use 'chain' strategy.`);
650
+ }
651
+ const startTime = Date.now();
652
+ const individualResults = [];
653
+ // Check blackboard for cached results first
654
+ const cachedTasks = [];
655
+ const uncachedTasks = [];
656
+ for (const task of tasks) {
657
+ const cacheKey = `task:${task.agentType}:${this.hashPayload(task.taskPayload)}`;
658
+ const cached = this.blackboard.read(cacheKey);
659
+ if (cached) {
660
+ individualResults.push({
661
+ agentType: task.agentType,
662
+ success: true,
663
+ result: cached.value,
664
+ executionTime: 0, // From cache
665
+ });
666
+ cachedTasks.push(task);
667
+ }
668
+ else {
669
+ uncachedTasks.push(task);
670
+ }
671
+ }
672
+ // Execute uncached tasks in parallel using Promise.all
673
+ if (uncachedTasks.length > 0) {
674
+ const parallelPromises = uncachedTasks.map(task => this.executeSingleTask(task, context));
675
+ const results = await Promise.all(parallelPromises);
676
+ for (let i = 0; i < results.length; i++) {
677
+ const task = uncachedTasks[i];
678
+ const result = results[i];
679
+ individualResults.push(result);
680
+ // Cache successful results
681
+ if (result.success) {
682
+ const cacheKey = `task:${task.agentType}:${this.hashPayload(task.taskPayload)}`;
683
+ this.blackboard.write(cacheKey, result.result, context.agentId, 3600); // 1 hour TTL
684
+ }
685
+ }
686
+ }
687
+ // Synthesize results based on strategy
688
+ const synthesizedResult = this.synthesize(individualResults, synthesisStrategy);
689
+ const totalTime = Date.now() - startTime;
690
+ const successCount = individualResults.filter(r => r.success).length;
691
+ return {
692
+ synthesizedResult,
693
+ individualResults,
694
+ executionMetrics: {
695
+ totalTime,
696
+ successRate: successCount / individualResults.length,
697
+ synthesisStrategy,
698
+ },
699
+ };
700
+ }
701
+ async executeSingleTask(task, context) {
702
+ const taskStart = Date.now();
703
+ try {
704
+ // Build the handoff message
705
+ const handoff = {
706
+ handoffId: (0, crypto_1.randomUUID)(),
707
+ sourceAgent: context.agentId,
708
+ targetAgent: task.agentType,
709
+ taskType: 'delegate',
710
+ payload: task.taskPayload,
711
+ metadata: {
712
+ priority: 1,
713
+ deadline: Date.now() + CONFIG.defaultTimeout,
714
+ parentTaskId: context.taskId ?? null,
715
+ },
716
+ };
717
+ // Sanitize the instruction before sending to adapter
718
+ let sanitizedInstruction = task.taskPayload.instruction;
719
+ try {
720
+ sanitizedInstruction = security_1.InputSanitizer.sanitizeString(task.taskPayload.instruction, 10000);
721
+ }
722
+ catch { /* use original if sanitization fails */ }
723
+ // Use namespace-scoped snapshot -- target agent only sees keys it's allowed to see
724
+ const scopedSnapshot = this.blackboard.getScopedSnapshot(task.agentType);
725
+ // Route through the adapter registry (framework-agnostic)
726
+ const agentPayload = {
727
+ action: 'execute',
728
+ params: {},
729
+ handoff: {
730
+ handoffId: handoff.handoffId,
731
+ sourceAgent: handoff.sourceAgent,
732
+ targetAgent: handoff.targetAgent,
733
+ taskType: handoff.taskType,
734
+ instruction: sanitizedInstruction,
735
+ context: handoff.payload.context,
736
+ constraints: handoff.payload.constraints,
737
+ expectedOutput: handoff.payload.expectedOutput,
738
+ metadata: handoff.metadata,
739
+ },
740
+ blackboardSnapshot: scopedSnapshot,
741
+ };
742
+ const agentContext = {
743
+ agentId: context.agentId,
744
+ taskId: context.taskId,
745
+ sessionId: context.sessionId,
746
+ };
747
+ const result = await this.adapterRegistry.executeAgent(task.agentType, agentPayload, agentContext);
748
+ // Sanitize adapter output before returning/caching
749
+ let sanitizedData = result.data;
750
+ try {
751
+ sanitizedData = security_1.InputSanitizer.sanitizeObject(result.data);
752
+ }
753
+ catch { /* use raw if sanitization fails */ }
754
+ return {
755
+ agentType: task.agentType,
756
+ success: true,
757
+ result: sanitizedData,
758
+ executionTime: Date.now() - taskStart,
759
+ };
760
+ }
761
+ catch (error) {
762
+ return {
763
+ agentType: task.agentType,
764
+ success: false,
765
+ result: {
766
+ error: error instanceof Error ? error.message : 'Unknown error',
767
+ recoverable: true,
768
+ },
769
+ executionTime: Date.now() - taskStart,
770
+ };
771
+ }
772
+ }
773
+ synthesize(results, strategy) {
774
+ const successfulResults = results.filter(r => r.success);
775
+ if (successfulResults.length === 0) {
776
+ return {
777
+ error: 'All parallel tasks failed',
778
+ individualErrors: results.map(r => ({
779
+ agent: r.agentType,
780
+ error: r.result,
781
+ })),
782
+ };
783
+ }
784
+ switch (strategy) {
785
+ case 'merge':
786
+ // Combine all results into a unified object
787
+ return {
788
+ merged: true,
789
+ contributions: successfulResults.map(r => ({
790
+ source: r.agentType,
791
+ data: r.result,
792
+ })),
793
+ summary: this.generateMergeSummary(successfulResults),
794
+ };
795
+ case 'vote':
796
+ // Return the result with highest "confidence" (simplified: most data)
797
+ const scored = successfulResults.map(r => ({
798
+ result: r,
799
+ score: JSON.stringify(r.result).length,
800
+ }));
801
+ scored.sort((a, b) => b.score - a.score);
802
+ return {
803
+ voted: true,
804
+ winner: scored[0].result.agentType,
805
+ result: scored[0].result.result,
806
+ };
807
+ case 'chain':
808
+ // Results should already be ordered; return the final one
809
+ return {
810
+ chained: true,
811
+ finalResult: successfulResults[successfulResults.length - 1].result,
812
+ chainLength: successfulResults.length,
813
+ };
814
+ case 'first-success':
815
+ // Return the first successful result
816
+ return {
817
+ firstSuccess: true,
818
+ source: successfulResults[0].agentType,
819
+ result: successfulResults[0].result,
820
+ };
821
+ default:
822
+ return successfulResults.map(r => r.result);
823
+ }
824
+ }
825
+ generateMergeSummary(results) {
826
+ const agents = results.map(r => r.agentType).join(', ');
827
+ return `Synthesized from ${results.length} agents: ${agents}`;
828
+ }
829
+ hashPayload(payload) {
830
+ // Simple hash for cache key generation
831
+ const str = JSON.stringify(payload);
832
+ let hash = 0;
833
+ for (let i = 0; i < str.length; i++) {
834
+ const char = str.charCodeAt(i);
835
+ hash = ((hash << 5) - hash) + char;
836
+ hash = hash & hash;
837
+ }
838
+ return Math.abs(hash).toString(16);
839
+ }
840
+ }
841
+ exports.TaskDecomposer = TaskDecomposer;
842
+ // ============================================================================
843
+ // SWARM ORCHESTRATOR - MAIN SKILL IMPLEMENTATION
844
+ // ============================================================================
845
+ class SwarmOrchestrator {
846
+ name = 'SwarmOrchestrator';
847
+ version = '3.0.0';
848
+ blackboard;
849
+ authGuardian;
850
+ taskDecomposer;
851
+ agentRegistry = new Map();
852
+ gateway;
853
+ qualityGate;
854
+ /** The adapter registry -- routes requests to the right agent framework */
855
+ adapters;
856
+ constructor(workspacePath = process.cwd(), adapterRegistry, options) {
857
+ this.blackboard = new SharedBlackboard(workspacePath);
858
+ this.authGuardian = new AuthGuardian({
859
+ trustLevels: options?.trustLevels,
860
+ resourceProfiles: options?.resourceProfiles,
861
+ });
862
+ this.adapters = adapterRegistry ?? new adapter_registry_1.AdapterRegistry();
863
+ this.taskDecomposer = new TaskDecomposer(this.blackboard, this.authGuardian, this.adapters);
864
+ this.gateway = new security_1.SecureSwarmGateway();
865
+ this.qualityGate = new blackboard_validator_1.QualityGateAgent({
866
+ validationConfig: options?.validationConfig,
867
+ qualityThreshold: options?.qualityThreshold,
868
+ aiReviewCallback: options?.aiReviewCallback,
869
+ });
870
+ // Register the orchestrator agent on the blackboard with full access
871
+ this.blackboard.registerAgent('orchestrator', 'system-orchestrator-token', ['*']);
872
+ }
873
+ /**
874
+ * Add an agent framework adapter (LangChain, AutoGen, CrewAI, MCP, custom, etc.)
875
+ * This is the plug-and-play entry point.
876
+ */
877
+ async addAdapter(adapter, config = {}) {
878
+ await this.adapters.addAdapter(adapter, config);
879
+ }
880
+ /**
881
+ * Main entry point for the skill.
882
+ * Now integrates SecureSwarmGateway: every request flows through
883
+ * input sanitization, rate limiting, and agent ID validation.
884
+ */
885
+ async execute(action, params, context) {
886
+ const traceId = (0, crypto_1.randomUUID)();
887
+ // P0: Route through SecureSwarmGateway -- sanitization + rate limiting
888
+ const gatewayResult = await this.gateway.handleSecureRequest(context.agentId, action, params);
889
+ if (!gatewayResult.allowed) {
890
+ return {
891
+ success: false,
892
+ error: {
893
+ code: 'GATEWAY_DENIED',
894
+ message: `Security gateway denied request: ${gatewayResult.reason}`,
895
+ recoverable: true,
896
+ suggestedAction: 'Check agent ID, rate limits, or input format',
897
+ },
898
+ };
899
+ }
900
+ // Use sanitized params from gateway
901
+ const safeParams = gatewayResult.sanitizedParams ?? params;
902
+ if (CONFIG.enableTracing) {
903
+ try {
904
+ this.blackboard.write(`trace:${traceId}`, {
905
+ action,
906
+ startTime: new Date().toISOString(),
907
+ }, context.agentId);
908
+ }
909
+ catch {
910
+ // Non-fatal -- tracing failure shouldn't block execution
911
+ }
912
+ }
913
+ try {
914
+ switch (action) {
915
+ case 'delegate_task':
916
+ return await this.delegateTask(safeParams, context);
917
+ case 'query_swarm_state':
918
+ return await this.querySwarmState(safeParams, context);
919
+ case 'spawn_parallel_agents':
920
+ return await this.spawnParallelAgents(safeParams, context);
921
+ case 'request_permission':
922
+ return await this.handlePermissionRequest(safeParams, context);
923
+ case 'update_blackboard':
924
+ return await this.handleBlackboardUpdate(safeParams, context);
925
+ case 'quality_gate_status':
926
+ return this.handleQualityGateStatus();
927
+ case 'review_quarantine':
928
+ return this.handleQuarantineReview(safeParams);
929
+ default:
930
+ return {
931
+ success: false,
932
+ error: {
933
+ code: 'UNKNOWN_ACTION',
934
+ message: `Unknown action: ${action}`,
935
+ recoverable: false,
936
+ },
937
+ };
938
+ }
939
+ }
940
+ catch (error) {
941
+ return {
942
+ success: false,
943
+ error: {
944
+ code: 'EXECUTION_ERROR',
945
+ message: error instanceof Error ? error.message : 'Unknown error',
946
+ recoverable: true,
947
+ trace: { traceId, action },
948
+ },
949
+ };
950
+ }
951
+ }
952
+ // -------------------------------------------------------------------------
953
+ // CAPABILITY: delegate_task
954
+ // -------------------------------------------------------------------------
955
+ async delegateTask(params, context) {
956
+ const targetAgent = params.targetAgent;
957
+ const taskPayload = params.taskPayload;
958
+ const priority = params.priority ?? 'normal';
959
+ const timeout = params.timeout ?? CONFIG.defaultTimeout;
960
+ const requiresAuth = params.requiresAuth ?? false;
961
+ const resourceType = params.resourceType ?? 'EXTERNAL_SERVICE';
962
+ // Check permission wall if required -- now returns bound restrictions
963
+ let grantToken = null;
964
+ if (requiresAuth) {
965
+ const authResult = await this.authGuardian.requestPermission(context.agentId, resourceType, `Delegating task to ${targetAgent}: ${taskPayload.instruction}`, 'delegate');
966
+ if (!authResult.granted) {
967
+ return {
968
+ success: false,
969
+ error: {
970
+ code: 'AUTH_DENIED',
971
+ message: `Permission denied: ${authResult.reason}`,
972
+ recoverable: true,
973
+ suggestedAction: 'Provide more specific justification or narrow scope',
974
+ },
975
+ };
976
+ }
977
+ grantToken = authResult.grantToken;
978
+ // Enforce restrictions at point of use
979
+ if (grantToken) {
980
+ const restrictionViolation = this.authGuardian.enforceRestrictions(grantToken, {
981
+ type: 'execute',
982
+ });
983
+ if (restrictionViolation) {
984
+ return {
985
+ success: false,
986
+ error: {
987
+ code: 'RESTRICTION_VIOLATED',
988
+ message: restrictionViolation,
989
+ recoverable: true,
990
+ suggestedAction: 'Request a grant with broader scope',
991
+ },
992
+ };
993
+ }
994
+ }
995
+ }
996
+ // Check blackboard for existing work
997
+ const cacheKey = `task:${targetAgent}:${JSON.stringify(taskPayload).slice(0, 50)}`;
998
+ const existingWork = this.blackboard.read(cacheKey);
999
+ if (existingWork) {
1000
+ return {
1001
+ success: true,
1002
+ data: {
1003
+ taskId: 'cached',
1004
+ status: 'completed',
1005
+ result: existingWork.value,
1006
+ agentTrace: ['blackboard-cache'],
1007
+ fromCache: true,
1008
+ },
1009
+ };
1010
+ }
1011
+ // Build handoff message
1012
+ const handoff = {
1013
+ handoffId: (0, crypto_1.randomUUID)(),
1014
+ sourceAgent: context.agentId,
1015
+ targetAgent,
1016
+ taskType: 'delegate',
1017
+ payload: taskPayload,
1018
+ metadata: {
1019
+ priority: this.priorityToNumber(priority),
1020
+ deadline: Date.now() + timeout,
1021
+ parentTaskId: context.taskId ?? null,
1022
+ },
1023
+ };
1024
+ // Execute via adapter registry (routes to the right framework)
1025
+ try {
1026
+ // Sanitize instruction before sending to adapter
1027
+ let sanitizedInstruction = taskPayload.instruction;
1028
+ try {
1029
+ sanitizedInstruction = security_1.InputSanitizer.sanitizeString(taskPayload.instruction, 10000);
1030
+ }
1031
+ catch { /* use original if sanitization fails */ }
1032
+ // P1: Namespace-scoped snapshot -- target agent only sees keys it's allowed to see
1033
+ const scopedSnapshot = this.blackboard.getScopedSnapshot(targetAgent);
1034
+ const agentPayload = {
1035
+ action: 'execute',
1036
+ params: {},
1037
+ handoff: {
1038
+ handoffId: handoff.handoffId,
1039
+ sourceAgent: handoff.sourceAgent,
1040
+ targetAgent: handoff.targetAgent,
1041
+ taskType: handoff.taskType,
1042
+ instruction: sanitizedInstruction,
1043
+ context: taskPayload.context,
1044
+ constraints: taskPayload.constraints,
1045
+ expectedOutput: taskPayload.expectedOutput,
1046
+ metadata: handoff.metadata,
1047
+ },
1048
+ blackboardSnapshot: scopedSnapshot,
1049
+ };
1050
+ const agentContext = {
1051
+ agentId: context.agentId,
1052
+ taskId: context.taskId,
1053
+ sessionId: context.sessionId,
1054
+ };
1055
+ const result = await Promise.race([
1056
+ this.adapters.executeAgent(targetAgent, agentPayload, agentContext),
1057
+ this.timeoutPromise(timeout),
1058
+ ]);
1059
+ // P1: Sanitize adapter output before caching
1060
+ let sanitizedResult = result;
1061
+ try {
1062
+ sanitizedResult = security_1.InputSanitizer.sanitizeObject(result);
1063
+ }
1064
+ catch { /* use raw if sanitization fails */ }
1065
+ // Quality gate: validate result before committing to blackboard
1066
+ const gateResult = await this.qualityGate.gate(cacheKey, sanitizedResult, targetAgent, {
1067
+ taskInstruction: taskPayload.instruction,
1068
+ expectedOutput: taskPayload.expectedOutput,
1069
+ });
1070
+ if (gateResult.decision === 'reject') {
1071
+ return {
1072
+ success: false,
1073
+ error: {
1074
+ code: 'QUALITY_REJECTED',
1075
+ message: `Result from ${targetAgent} failed quality validation: ${gateResult.validation.issues.filter(i => i.severity === 'error').map(i => i.message).join('; ')}`,
1076
+ recoverable: gateResult.validation.recoverable,
1077
+ suggestedAction: gateResult.validation.issues.find(i => i.suggestion)?.suggestion,
1078
+ },
1079
+ };
1080
+ }
1081
+ if (gateResult.decision === 'quarantine') {
1082
+ // Still return the result but flag it
1083
+ return {
1084
+ success: true,
1085
+ data: {
1086
+ taskId: handoff.handoffId,
1087
+ status: 'quarantined',
1088
+ result: sanitizedResult,
1089
+ agentTrace: [context.agentId, targetAgent],
1090
+ qualityGate: {
1091
+ decision: 'quarantine',
1092
+ quarantineKey: gateResult.quarantineKey,
1093
+ score: gateResult.validation.score,
1094
+ issues: gateResult.validation.issues,
1095
+ reviewNotes: gateResult.reviewNotes,
1096
+ },
1097
+ },
1098
+ };
1099
+ }
1100
+ // Approved -- cache result
1101
+ this.blackboard.write(cacheKey, sanitizedResult, context.agentId, 1800); // 30 min TTL
1102
+ return {
1103
+ success: true,
1104
+ data: {
1105
+ taskId: handoff.handoffId,
1106
+ status: 'completed',
1107
+ result: sanitizedResult,
1108
+ agentTrace: [context.agentId, targetAgent],
1109
+ qualityGate: {
1110
+ decision: 'approve',
1111
+ score: gateResult.validation.score,
1112
+ },
1113
+ },
1114
+ };
1115
+ }
1116
+ catch (error) {
1117
+ return {
1118
+ success: false,
1119
+ error: {
1120
+ code: 'DELEGATION_FAILED',
1121
+ message: error instanceof Error ? error.message : 'Task delegation failed',
1122
+ recoverable: true,
1123
+ },
1124
+ };
1125
+ }
1126
+ }
1127
+ // -------------------------------------------------------------------------
1128
+ // CAPABILITY: query_swarm_state
1129
+ // -------------------------------------------------------------------------
1130
+ async querySwarmState(params, context) {
1131
+ const scope = params.scope ?? 'all';
1132
+ const agentFilter = params.agentFilter;
1133
+ const includeHistory = params.includeHistory ?? false;
1134
+ const state = {
1135
+ timestamp: new Date().toISOString(),
1136
+ };
1137
+ if (scope === 'all' || scope === 'agents') {
1138
+ let agents = Array.from(this.agentRegistry.values());
1139
+ if (agentFilter) {
1140
+ agents = agents.filter(a => agentFilter.includes(a.agentId));
1141
+ }
1142
+ state.activeAgents = agents;
1143
+ }
1144
+ if (scope === 'all' || scope === 'blackboard') {
1145
+ // P1: Namespace-scoped -- agent only sees keys it's allowed to access
1146
+ state.blackboardSnapshot = this.blackboard.getScopedSnapshot(context.agentId);
1147
+ }
1148
+ if (scope === 'all' || scope === 'permissions') {
1149
+ state.permissionGrants = this.authGuardian.getActiveGrants();
1150
+ }
1151
+ if (scope === 'all' || scope === 'tasks') {
1152
+ // Extract tasks from scoped blackboard
1153
+ const snapshot = this.blackboard.getScopedSnapshot(context.agentId);
1154
+ state.pendingTasks = Object.entries(snapshot)
1155
+ .filter(([key]) => key.startsWith('task:'))
1156
+ .map(([, entry]) => ({
1157
+ taskId: entry.key,
1158
+ agentId: entry.sourceAgent,
1159
+ status: 'in_progress',
1160
+ startedAt: entry.timestamp,
1161
+ description: String(entry.value),
1162
+ }));
1163
+ }
1164
+ return {
1165
+ success: true,
1166
+ data: state,
1167
+ };
1168
+ }
1169
+ // -------------------------------------------------------------------------
1170
+ // CAPABILITY: spawn_parallel_agents
1171
+ // -------------------------------------------------------------------------
1172
+ async spawnParallelAgents(params, context) {
1173
+ const tasks = params.tasks;
1174
+ const synthesisStrategy = params.synthesisStrategy ?? 'merge';
1175
+ if (!tasks || !Array.isArray(tasks) || tasks.length === 0) {
1176
+ return {
1177
+ success: false,
1178
+ error: {
1179
+ code: 'INVALID_PARAMS',
1180
+ message: 'Tasks array is required and must not be empty',
1181
+ recoverable: false,
1182
+ },
1183
+ };
1184
+ }
1185
+ try {
1186
+ const result = await this.taskDecomposer.executeParallel(tasks, synthesisStrategy, context);
1187
+ return {
1188
+ success: true,
1189
+ data: result,
1190
+ };
1191
+ }
1192
+ catch (error) {
1193
+ return {
1194
+ success: false,
1195
+ error: {
1196
+ code: 'PARALLEL_EXECUTION_FAILED',
1197
+ message: error instanceof Error ? error.message : 'Parallel execution failed',
1198
+ recoverable: true,
1199
+ },
1200
+ };
1201
+ }
1202
+ }
1203
+ // -------------------------------------------------------------------------
1204
+ // CAPABILITY: request_permission
1205
+ // -------------------------------------------------------------------------
1206
+ async handlePermissionRequest(params, context) {
1207
+ const resourceType = params.resourceType;
1208
+ const justification = params.justification;
1209
+ const scope = params.scope;
1210
+ if (!resourceType || !justification) {
1211
+ return {
1212
+ success: false,
1213
+ error: {
1214
+ code: 'INVALID_PARAMS',
1215
+ message: 'resourceType and justification are required',
1216
+ recoverable: false,
1217
+ },
1218
+ };
1219
+ }
1220
+ const grant = await this.authGuardian.requestPermission(context.agentId, resourceType, justification, scope);
1221
+ return {
1222
+ success: grant.granted,
1223
+ data: grant,
1224
+ };
1225
+ }
1226
+ // -------------------------------------------------------------------------
1227
+ // CAPABILITY: update_blackboard
1228
+ // -------------------------------------------------------------------------
1229
+ async handleBlackboardUpdate(params, context) {
1230
+ const key = params.key;
1231
+ const value = params.value;
1232
+ const ttl = params.ttl;
1233
+ if (!key || value === undefined) {
1234
+ return {
1235
+ success: false,
1236
+ error: {
1237
+ code: 'INVALID_PARAMS',
1238
+ message: 'key and value are required',
1239
+ recoverable: false,
1240
+ },
1241
+ };
1242
+ }
1243
+ const previousValue = this.blackboard.read(key)?.value ?? null;
1244
+ // Quality gate: validate before writing to blackboard
1245
+ const gateResult = await this.qualityGate.gate(key, value, context.agentId);
1246
+ if (gateResult.decision === 'reject') {
1247
+ return {
1248
+ success: false,
1249
+ error: {
1250
+ code: 'QUALITY_REJECTED',
1251
+ message: `Blackboard write rejected: ${gateResult.validation.issues.filter(i => i.severity === 'error').map(i => i.message).join('; ')}`,
1252
+ recoverable: gateResult.validation.recoverable,
1253
+ suggestedAction: gateResult.validation.issues.find(i => i.suggestion)?.suggestion,
1254
+ },
1255
+ };
1256
+ }
1257
+ if (gateResult.decision === 'quarantine') {
1258
+ return {
1259
+ success: true,
1260
+ data: {
1261
+ success: true,
1262
+ quarantined: true,
1263
+ quarantineKey: gateResult.quarantineKey,
1264
+ qualityScore: gateResult.validation.score,
1265
+ issues: gateResult.validation.issues,
1266
+ previousValue,
1267
+ },
1268
+ };
1269
+ }
1270
+ this.blackboard.write(key, value, context.agentId, ttl);
1271
+ return {
1272
+ success: true,
1273
+ data: {
1274
+ success: true,
1275
+ previousValue,
1276
+ qualityScore: gateResult.validation.score,
1277
+ },
1278
+ };
1279
+ }
1280
+ // -------------------------------------------------------------------------
1281
+ // QUALITY GATE MANAGEMENT
1282
+ // -------------------------------------------------------------------------
1283
+ /** Returns quality gate metrics and quarantined entries */
1284
+ handleQualityGateStatus() {
1285
+ return {
1286
+ success: true,
1287
+ data: {
1288
+ metrics: this.qualityGate.getMetrics(),
1289
+ quarantined: this.qualityGate.getQuarantined(),
1290
+ },
1291
+ };
1292
+ }
1293
+ /** Approve or reject a quarantined entry */
1294
+ handleQuarantineReview(params) {
1295
+ const quarantineId = params.quarantineId;
1296
+ const decision = params.decision;
1297
+ if (!quarantineId || !decision) {
1298
+ return {
1299
+ success: false,
1300
+ error: {
1301
+ code: 'INVALID_PARAMS',
1302
+ message: 'quarantineId and decision ("approve" or "reject") are required',
1303
+ recoverable: false,
1304
+ },
1305
+ };
1306
+ }
1307
+ let entry;
1308
+ if (decision === 'approve') {
1309
+ entry = this.qualityGate.approveQuarantined(quarantineId);
1310
+ if (entry) {
1311
+ // Write the approved entry to the blackboard
1312
+ this.blackboard.write(`approved:${quarantineId}`, entry, 'orchestrator');
1313
+ }
1314
+ }
1315
+ else {
1316
+ entry = this.qualityGate.rejectQuarantined(quarantineId);
1317
+ }
1318
+ return {
1319
+ success: !!entry,
1320
+ data: entry ? { quarantineId, decision, resolved: true } : undefined,
1321
+ error: entry ? undefined : {
1322
+ code: 'NOT_FOUND',
1323
+ message: `Quarantine entry ${quarantineId} not found`,
1324
+ recoverable: false,
1325
+ },
1326
+ };
1327
+ }
1328
+ /** Expose the quality gate for external configuration */
1329
+ getQualityGate() {
1330
+ return this.qualityGate;
1331
+ }
1332
+ // -------------------------------------------------------------------------
1333
+ // UTILITY METHODS
1334
+ // -------------------------------------------------------------------------
1335
+ priorityToNumber(priority) {
1336
+ const map = {
1337
+ low: 0,
1338
+ normal: 1,
1339
+ high: 2,
1340
+ critical: 3,
1341
+ };
1342
+ return map[priority] ?? 1;
1343
+ }
1344
+ timeoutPromise(ms) {
1345
+ return new Promise((_, reject) => {
1346
+ setTimeout(() => reject(new Error(`Operation timed out after ${ms}ms`)), ms);
1347
+ });
1348
+ }
1349
+ /**
1350
+ * Register an agent with the swarm
1351
+ */
1352
+ registerAgent(agentId, status = 'available') {
1353
+ this.agentRegistry.set(agentId, {
1354
+ agentId,
1355
+ status,
1356
+ currentTask: null,
1357
+ lastHeartbeat: new Date().toISOString(),
1358
+ });
1359
+ }
1360
+ /**
1361
+ * Update agent status
1362
+ */
1363
+ updateAgentStatus(agentId, status, currentTask) {
1364
+ const existing = this.agentRegistry.get(agentId);
1365
+ if (existing) {
1366
+ existing.status = status;
1367
+ existing.currentTask = currentTask ?? null;
1368
+ existing.lastHeartbeat = new Date().toISOString();
1369
+ }
1370
+ }
1371
+ }
1372
+ exports.SwarmOrchestrator = SwarmOrchestrator;
1373
+ // ============================================================================
1374
+ // EXPORTS & MODULE INITIALIZATION
1375
+ // ============================================================================
1376
+ // Default export for OpenClaw skill loader (backward compatible)
1377
+ exports.default = SwarmOrchestrator;
1378
+ // Quality gate & validation exports
1379
+ var blackboard_validator_2 = require("./lib/blackboard-validator");
1380
+ Object.defineProperty(exports, "BlackboardValidator", { enumerable: true, get: function () { return blackboard_validator_2.BlackboardValidator; } });
1381
+ Object.defineProperty(exports, "QualityGateAgent", { enumerable: true, get: function () { return blackboard_validator_2.QualityGateAgent; } });
1382
+ // Adapter system re-exports for convenience
1383
+ var adapter_registry_2 = require("./adapters/adapter-registry");
1384
+ Object.defineProperty(exports, "AdapterRegistry", { enumerable: true, get: function () { return adapter_registry_2.AdapterRegistry; } });
1385
+ var base_adapter_1 = require("./adapters/base-adapter");
1386
+ Object.defineProperty(exports, "BaseAdapter", { enumerable: true, get: function () { return base_adapter_1.BaseAdapter; } });
1387
+ var openclaw_adapter_1 = require("./adapters/openclaw-adapter");
1388
+ Object.defineProperty(exports, "OpenClawAdapter", { enumerable: true, get: function () { return openclaw_adapter_1.OpenClawAdapter; } });
1389
+ var langchain_adapter_1 = require("./adapters/langchain-adapter");
1390
+ Object.defineProperty(exports, "LangChainAdapter", { enumerable: true, get: function () { return langchain_adapter_1.LangChainAdapter; } });
1391
+ var autogen_adapter_1 = require("./adapters/autogen-adapter");
1392
+ Object.defineProperty(exports, "AutoGenAdapter", { enumerable: true, get: function () { return autogen_adapter_1.AutoGenAdapter; } });
1393
+ var crewai_adapter_1 = require("./adapters/crewai-adapter");
1394
+ Object.defineProperty(exports, "CrewAIAdapter", { enumerable: true, get: function () { return crewai_adapter_1.CrewAIAdapter; } });
1395
+ var mcp_adapter_1 = require("./adapters/mcp-adapter");
1396
+ Object.defineProperty(exports, "MCPAdapter", { enumerable: true, get: function () { return mcp_adapter_1.MCPAdapter; } });
1397
+ var custom_adapter_1 = require("./adapters/custom-adapter");
1398
+ Object.defineProperty(exports, "CustomAdapter", { enumerable: true, get: function () { return custom_adapter_1.CustomAdapter; } });
1399
+ /**
1400
+ * Factory function for creating a configured SwarmOrchestrator instance.
1401
+ *
1402
+ * For plug-and-play with other agent systems, pass adapters:
1403
+ *
1404
+ * const orchestrator = createSwarmOrchestrator({
1405
+ * adapters: [{ adapter: new LangChainAdapter(), config: {} }],
1406
+ * });
1407
+ */
1408
+ function createSwarmOrchestrator(config) {
1409
+ if (config) {
1410
+ const { adapters: adapterList, adapterRegistry, trustLevels, resourceProfiles, validationConfig, qualityThreshold, aiReviewCallback, ...rest } = config;
1411
+ Object.assign(CONFIG, rest);
1412
+ const registry = adapterRegistry ?? new adapter_registry_1.AdapterRegistry();
1413
+ const orchestrator = new SwarmOrchestrator(undefined, registry, {
1414
+ trustLevels,
1415
+ resourceProfiles,
1416
+ validationConfig,
1417
+ qualityThreshold,
1418
+ aiReviewCallback,
1419
+ });
1420
+ // Initialize adapters if provided
1421
+ if (adapterList) {
1422
+ Promise.all(adapterList.map(({ adapter, config: adapterConfig }) => orchestrator.addAdapter(adapter, adapterConfig ?? {}))).catch(err => console.error('[SwarmOrchestrator] Adapter init error:', err));
1423
+ }
1424
+ return orchestrator;
1425
+ }
1426
+ return new SwarmOrchestrator();
1427
+ }
1428
+ //# sourceMappingURL=index.js.map