elsabro 2.3.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +668 -20
- package/bin/install.js +0 -0
- package/flows/development-flow.json +452 -0
- package/flows/quick-flow.json +118 -0
- package/package.json +3 -2
- package/references/SYSTEM_INDEX.md +379 -5
- package/references/agent-marketplace.md +2274 -0
- package/references/agent-protocol.md +1126 -0
- package/references/ai-code-suggestions.md +2413 -0
- package/references/checkpointing.md +595 -0
- package/references/collaboration-patterns.md +851 -0
- package/references/collaborative-sessions.md +1081 -0
- package/references/configuration-management.md +1810 -0
- package/references/cost-tracking.md +1095 -0
- package/references/enterprise-sso.md +2001 -0
- package/references/error-contracts-v2.md +968 -0
- package/references/event-driven.md +1031 -0
- package/references/flow-orchestration.md +940 -0
- package/references/flow-visualization.md +1557 -0
- package/references/ide-integrations.md +3513 -0
- package/references/interrupt-system.md +681 -0
- package/references/kubernetes-deployment.md +3099 -0
- package/references/memory-system.md +683 -0
- package/references/mobile-companion.md +3236 -0
- package/references/multi-llm-providers.md +2494 -0
- package/references/multi-project-memory.md +1182 -0
- package/references/observability.md +793 -0
- package/references/output-schemas.md +858 -0
- package/references/performance-profiler.md +955 -0
- package/references/plugin-system.md +1526 -0
- package/references/prompt-management.md +292 -0
- package/references/sandbox-execution.md +303 -0
- package/references/security-system.md +1253 -0
- package/references/streaming.md +696 -0
- package/references/testing-framework.md +1151 -0
- package/references/time-travel.md +802 -0
- package/references/tool-registry.md +886 -0
- package/references/voice-commands.md +3296 -0
- package/templates/agent-marketplace-config.json +220 -0
- package/templates/agent-protocol-config.json +136 -0
- package/templates/ai-suggestions-config.json +100 -0
- package/templates/checkpoint-state.json +61 -0
- package/templates/collaboration-config.json +157 -0
- package/templates/collaborative-sessions-config.json +153 -0
- package/templates/configuration-config.json +245 -0
- package/templates/cost-tracking-config.json +148 -0
- package/templates/enterprise-sso-config.json +438 -0
- package/templates/events-config.json +148 -0
- package/templates/flow-visualization-config.json +196 -0
- package/templates/ide-integrations-config.json +442 -0
- package/templates/kubernetes-config.json +764 -0
- package/templates/memory-state.json +84 -0
- package/templates/mobile-companion-config.json +600 -0
- package/templates/multi-llm-config.json +544 -0
- package/templates/multi-project-memory-config.json +145 -0
- package/templates/observability-config.json +109 -0
- package/templates/performance-profiler-config.json +125 -0
- package/templates/plugin-config.json +170 -0
- package/templates/prompt-management-config.json +86 -0
- package/templates/sandbox-config.json +185 -0
- package/templates/schemas-config.json +65 -0
- package/templates/security-config.json +120 -0
- package/templates/streaming-config.json +72 -0
- package/templates/testing-config.json +81 -0
- package/templates/timetravel-config.json +62 -0
- package/templates/tool-registry-config.json +109 -0
- package/templates/voice-commands-config.json +658 -0
|
@@ -0,0 +1,2274 @@
|
|
|
1
|
+
# ELSABRO Agent Marketplace
|
|
2
|
+
|
|
3
|
+
> Sistema de marketplace para publicar, descubrir e instalar agentes de la comunidad con verificacion, reviews y sandboxing.
|
|
4
|
+
|
|
5
|
+
## Arquitectura General
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
+-----------------------------------------------------------------------------+
|
|
9
|
+
| Agent Marketplace System |
|
|
10
|
+
+-----------------------------------------------------------------------------+
|
|
11
|
+
| +-------------------+ +-------------------+ +-------------------+ |
|
|
12
|
+
| | MarketplaceRegistry| | AgentPackager | |PublishingPipeline| |
|
|
13
|
+
| | ----------------- | | --------------- | | --------------- | |
|
|
14
|
+
| | * Agent catalog | | * Package agents | | * Submit flow | |
|
|
15
|
+
| | * Search/filter | | * Validate code | | * Auto review | |
|
|
16
|
+
| | * Versioning | | * Sign packages | | * Approval | |
|
|
17
|
+
| +-------------------+ +-------------------+ +-------------------+ |
|
|
18
|
+
| | |
|
|
19
|
+
| +---------------------------+-------------------------------+ |
|
|
20
|
+
| | ReviewSystem | |
|
|
21
|
+
| | * Ratings (1-5) * Text reviews * Usage metrics | |
|
|
22
|
+
| +------------------------------------------------------------+ |
|
|
23
|
+
| | |
|
|
24
|
+
| +---------------------------+-------------------------------+ |
|
|
25
|
+
| | InstallationManager | |
|
|
26
|
+
| | * Install/Update * Dependencies * Sandboxed execution | |
|
|
27
|
+
| +------------------------------------------------------------+ |
|
|
28
|
+
+-----------------------------------------------------------------------------+
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 1. MarketplaceRegistry
|
|
34
|
+
|
|
35
|
+
### Proposito
|
|
36
|
+
|
|
37
|
+
Registro central de agentes publicados con busqueda, filtrado y versionado semantico.
|
|
38
|
+
|
|
39
|
+
### Interfaces
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
interface MarketplaceRegistry {
|
|
43
|
+
// Catalog operations
|
|
44
|
+
list(options?: ListOptions): Promise<AgentListing[]>;
|
|
45
|
+
get(name: string, version?: string): Promise<AgentDetails | null>;
|
|
46
|
+
search(query: SearchQuery): Promise<SearchResult>;
|
|
47
|
+
|
|
48
|
+
// Categories
|
|
49
|
+
getCategories(): Promise<Category[]>;
|
|
50
|
+
getByCategory(categoryId: AgentCategory): Promise<AgentListing[]>;
|
|
51
|
+
|
|
52
|
+
// Versions
|
|
53
|
+
getVersions(name: string): Promise<VersionInfo[]>;
|
|
54
|
+
getLatestVersion(name: string): Promise<string>;
|
|
55
|
+
checkCompatibility(name: string, version: string): Promise<CompatibilityResult>;
|
|
56
|
+
|
|
57
|
+
// Statistics
|
|
58
|
+
getStats(name: string): Promise<AgentStats>;
|
|
59
|
+
getTrending(period: TrendingPeriod): Promise<AgentListing[]>;
|
|
60
|
+
|
|
61
|
+
// Events
|
|
62
|
+
onUpdate(callback: (agent: AgentListing) => void): Disposable;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type AgentCategory =
|
|
66
|
+
| 'exploration'
|
|
67
|
+
| 'implementation'
|
|
68
|
+
| 'review'
|
|
69
|
+
| 'testing'
|
|
70
|
+
| 'specialized';
|
|
71
|
+
|
|
72
|
+
interface AgentListing {
|
|
73
|
+
// Identity
|
|
74
|
+
name: string;
|
|
75
|
+
displayName: string;
|
|
76
|
+
description: string;
|
|
77
|
+
version: string;
|
|
78
|
+
author: AuthorInfo;
|
|
79
|
+
|
|
80
|
+
// Classification
|
|
81
|
+
category: AgentCategory;
|
|
82
|
+
tags: string[];
|
|
83
|
+
|
|
84
|
+
// Stats
|
|
85
|
+
downloads: number;
|
|
86
|
+
rating: number;
|
|
87
|
+
reviewCount: number;
|
|
88
|
+
|
|
89
|
+
// Metadata
|
|
90
|
+
createdAt: Date;
|
|
91
|
+
updatedAt: Date;
|
|
92
|
+
verified: boolean;
|
|
93
|
+
featured: boolean;
|
|
94
|
+
|
|
95
|
+
// Links
|
|
96
|
+
repository?: string;
|
|
97
|
+
homepage?: string;
|
|
98
|
+
documentation?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface AuthorInfo {
|
|
102
|
+
name: string;
|
|
103
|
+
email?: string;
|
|
104
|
+
url?: string;
|
|
105
|
+
verified: boolean;
|
|
106
|
+
publisherSince: Date;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface SearchQuery {
|
|
110
|
+
text?: string;
|
|
111
|
+
category?: AgentCategory;
|
|
112
|
+
tags?: string[];
|
|
113
|
+
minRating?: number;
|
|
114
|
+
minDownloads?: number;
|
|
115
|
+
verified?: boolean;
|
|
116
|
+
compatible?: string; // ELSABRO version
|
|
117
|
+
sortBy?: 'relevance' | 'downloads' | 'rating' | 'updated' | 'name';
|
|
118
|
+
sortOrder?: 'asc' | 'desc';
|
|
119
|
+
page?: number;
|
|
120
|
+
limit?: number;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface SearchResult {
|
|
124
|
+
items: AgentListing[];
|
|
125
|
+
total: number;
|
|
126
|
+
page: number;
|
|
127
|
+
totalPages: number;
|
|
128
|
+
facets: {
|
|
129
|
+
categories: Record<AgentCategory, number>;
|
|
130
|
+
tags: Record<string, number>;
|
|
131
|
+
ratings: Record<number, number>;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface VersionInfo {
|
|
136
|
+
version: string;
|
|
137
|
+
publishedAt: Date;
|
|
138
|
+
downloads: number;
|
|
139
|
+
changelog?: string;
|
|
140
|
+
compatibility: {
|
|
141
|
+
elsabro: string;
|
|
142
|
+
node?: string;
|
|
143
|
+
};
|
|
144
|
+
deprecated?: boolean;
|
|
145
|
+
deprecationMessage?: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
interface CompatibilityResult {
|
|
149
|
+
compatible: boolean;
|
|
150
|
+
currentVersion: string;
|
|
151
|
+
requiredVersion: string;
|
|
152
|
+
issues: CompatibilityIssue[];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
interface CompatibilityIssue {
|
|
156
|
+
type: 'version' | 'dependency' | 'permission' | 'platform';
|
|
157
|
+
severity: 'error' | 'warning';
|
|
158
|
+
message: string;
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Implementacion
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
class MarketplaceRegistryImpl implements MarketplaceRegistry {
|
|
166
|
+
private cache: Map<string, CachedEntry> = new Map();
|
|
167
|
+
private updateCallbacks: Set<Function> = new Set();
|
|
168
|
+
|
|
169
|
+
constructor(
|
|
170
|
+
private config: MarketplaceConfig,
|
|
171
|
+
private httpClient: HttpClient,
|
|
172
|
+
private cacheManager: CacheManager
|
|
173
|
+
) {}
|
|
174
|
+
|
|
175
|
+
async list(options: ListOptions = {}): Promise<AgentListing[]> {
|
|
176
|
+
const cacheKey = `list:${JSON.stringify(options)}`;
|
|
177
|
+
const cached = await this.cacheManager.get<AgentListing[]>(cacheKey);
|
|
178
|
+
|
|
179
|
+
if (cached && !options.forceRefresh) {
|
|
180
|
+
return cached;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const response = await this.httpClient.get<ApiResponse<AgentListing[]>>(
|
|
184
|
+
`${this.config.registry.url}/agents`,
|
|
185
|
+
{ params: this.buildListParams(options) }
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
await this.cacheManager.set(cacheKey, response.data, this.config.registry.cacheTTL);
|
|
189
|
+
return response.data;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async get(name: string, version?: string): Promise<AgentDetails | null> {
|
|
193
|
+
const cacheKey = `agent:${name}:${version || 'latest'}`;
|
|
194
|
+
const cached = await this.cacheManager.get<AgentDetails>(cacheKey);
|
|
195
|
+
|
|
196
|
+
if (cached) return cached;
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const url = version
|
|
200
|
+
? `${this.config.registry.url}/agents/${name}/versions/${version}`
|
|
201
|
+
: `${this.config.registry.url}/agents/${name}`;
|
|
202
|
+
|
|
203
|
+
const response = await this.httpClient.get<ApiResponse<AgentDetails>>(url);
|
|
204
|
+
await this.cacheManager.set(cacheKey, response.data, this.config.registry.cacheTTL);
|
|
205
|
+
return response.data;
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (error.status === 404) return null;
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async search(query: SearchQuery): Promise<SearchResult> {
|
|
213
|
+
const response = await this.httpClient.post<ApiResponse<SearchResult>>(
|
|
214
|
+
`${this.config.registry.url}/search`,
|
|
215
|
+
{ body: query }
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
return response.data;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async getCategories(): Promise<Category[]> {
|
|
222
|
+
return this.config.categories.map(cat => ({
|
|
223
|
+
id: cat.id,
|
|
224
|
+
name: cat.name,
|
|
225
|
+
description: cat.description,
|
|
226
|
+
icon: cat.icon,
|
|
227
|
+
agentCount: 0 // Populated from API
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async getByCategory(categoryId: AgentCategory): Promise<AgentListing[]> {
|
|
232
|
+
return this.search({ category: categoryId, limit: 100 }).then(r => r.items);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async getVersions(name: string): Promise<VersionInfo[]> {
|
|
236
|
+
const response = await this.httpClient.get<ApiResponse<VersionInfo[]>>(
|
|
237
|
+
`${this.config.registry.url}/agents/${name}/versions`
|
|
238
|
+
);
|
|
239
|
+
return response.data;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async getLatestVersion(name: string): Promise<string> {
|
|
243
|
+
const agent = await this.get(name);
|
|
244
|
+
if (!agent) throw new Error(`Agent not found: ${name}`);
|
|
245
|
+
return agent.version;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async checkCompatibility(name: string, version: string): Promise<CompatibilityResult> {
|
|
249
|
+
const agent = await this.get(name, version);
|
|
250
|
+
if (!agent) {
|
|
251
|
+
return {
|
|
252
|
+
compatible: false,
|
|
253
|
+
currentVersion: this.config.elsabroVersion,
|
|
254
|
+
requiredVersion: 'unknown',
|
|
255
|
+
issues: [{ type: 'version', severity: 'error', message: 'Agent not found' }]
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const issues: CompatibilityIssue[] = [];
|
|
260
|
+
const requiredVersion = agent.manifest.engines?.elsabro || '*';
|
|
261
|
+
|
|
262
|
+
if (!semver.satisfies(this.config.elsabroVersion, requiredVersion)) {
|
|
263
|
+
issues.push({
|
|
264
|
+
type: 'version',
|
|
265
|
+
severity: 'error',
|
|
266
|
+
message: `Requires ELSABRO ${requiredVersion}, current: ${this.config.elsabroVersion}`
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Check dependencies
|
|
271
|
+
for (const [dep, ver] of Object.entries(agent.manifest.dependencies || {})) {
|
|
272
|
+
const installed = await this.checkInstalledAgent(dep);
|
|
273
|
+
if (!installed) {
|
|
274
|
+
issues.push({
|
|
275
|
+
type: 'dependency',
|
|
276
|
+
severity: 'error',
|
|
277
|
+
message: `Missing dependency: ${dep}@${ver}`
|
|
278
|
+
});
|
|
279
|
+
} else if (!semver.satisfies(installed.version, ver)) {
|
|
280
|
+
issues.push({
|
|
281
|
+
type: 'dependency',
|
|
282
|
+
severity: 'warning',
|
|
283
|
+
message: `Dependency version mismatch: ${dep} requires ${ver}, installed: ${installed.version}`
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
compatible: issues.filter(i => i.severity === 'error').length === 0,
|
|
290
|
+
currentVersion: this.config.elsabroVersion,
|
|
291
|
+
requiredVersion,
|
|
292
|
+
issues
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async getStats(name: string): Promise<AgentStats> {
|
|
297
|
+
const response = await this.httpClient.get<ApiResponse<AgentStats>>(
|
|
298
|
+
`${this.config.registry.url}/agents/${name}/stats`
|
|
299
|
+
);
|
|
300
|
+
return response.data;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async getTrending(period: TrendingPeriod = 'week'): Promise<AgentListing[]> {
|
|
304
|
+
const response = await this.httpClient.get<ApiResponse<AgentListing[]>>(
|
|
305
|
+
`${this.config.registry.url}/trending`,
|
|
306
|
+
{ params: { period } }
|
|
307
|
+
);
|
|
308
|
+
return response.data;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
onUpdate(callback: (agent: AgentListing) => void): Disposable {
|
|
312
|
+
this.updateCallbacks.add(callback);
|
|
313
|
+
return { dispose: () => this.updateCallbacks.delete(callback) };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private buildListParams(options: ListOptions): Record<string, string> {
|
|
317
|
+
const params: Record<string, string> = {};
|
|
318
|
+
if (options.page) params.page = String(options.page);
|
|
319
|
+
if (options.limit) params.limit = String(options.limit);
|
|
320
|
+
if (options.sortBy) params.sort = options.sortBy;
|
|
321
|
+
if (options.category) params.category = options.category;
|
|
322
|
+
return params;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## 2. AgentPackager
|
|
330
|
+
|
|
331
|
+
### Proposito
|
|
332
|
+
|
|
333
|
+
Empaquetado de agentes para distribucion con validacion, manifest y firma digital.
|
|
334
|
+
|
|
335
|
+
### Interfaces
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
interface AgentPackager {
|
|
339
|
+
// Packaging
|
|
340
|
+
pack(sourcePath: string, options?: PackOptions): Promise<PackageResult>;
|
|
341
|
+
validate(sourcePath: string): Promise<ValidationResult>;
|
|
342
|
+
|
|
343
|
+
// Manifest
|
|
344
|
+
generateManifest(sourcePath: string): Promise<AgentManifest>;
|
|
345
|
+
validateManifest(manifest: AgentManifest): ValidationResult;
|
|
346
|
+
|
|
347
|
+
// Signing
|
|
348
|
+
sign(packagePath: string, privateKey: string): Promise<SignedPackage>;
|
|
349
|
+
verify(packagePath: string): Promise<VerificationResult>;
|
|
350
|
+
|
|
351
|
+
// Unpacking
|
|
352
|
+
unpack(packagePath: string, destPath: string): Promise<UnpackResult>;
|
|
353
|
+
inspect(packagePath: string): Promise<PackageInfo>;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
interface AgentManifest {
|
|
357
|
+
// Required fields
|
|
358
|
+
name: string;
|
|
359
|
+
version: string;
|
|
360
|
+
displayName: string;
|
|
361
|
+
description: string;
|
|
362
|
+
author: string | AuthorInfo;
|
|
363
|
+
main: string;
|
|
364
|
+
|
|
365
|
+
// Classification
|
|
366
|
+
category: AgentCategory;
|
|
367
|
+
tags?: string[];
|
|
368
|
+
keywords?: string[];
|
|
369
|
+
|
|
370
|
+
// Compatibility
|
|
371
|
+
engines: {
|
|
372
|
+
elsabro: string;
|
|
373
|
+
node?: string;
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// Dependencies
|
|
377
|
+
dependencies?: Record<string, string>;
|
|
378
|
+
peerDependencies?: Record<string, string>;
|
|
379
|
+
|
|
380
|
+
// Permissions
|
|
381
|
+
permissions?: AgentPermission[];
|
|
382
|
+
|
|
383
|
+
// Contributions
|
|
384
|
+
contributes?: {
|
|
385
|
+
tools?: ToolContribution[];
|
|
386
|
+
commands?: CommandContribution[];
|
|
387
|
+
hooks?: HookContribution[];
|
|
388
|
+
config?: ConfigContribution;
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// Metadata
|
|
392
|
+
license?: string;
|
|
393
|
+
repository?: string;
|
|
394
|
+
homepage?: string;
|
|
395
|
+
bugs?: string;
|
|
396
|
+
changelog?: string;
|
|
397
|
+
|
|
398
|
+
// Files
|
|
399
|
+
files?: string[];
|
|
400
|
+
exclude?: string[];
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
type AgentPermission =
|
|
404
|
+
| 'filesystem:read'
|
|
405
|
+
| 'filesystem:write'
|
|
406
|
+
| 'network'
|
|
407
|
+
| 'network:external'
|
|
408
|
+
| 'shell'
|
|
409
|
+
| 'shell:restricted'
|
|
410
|
+
| 'env'
|
|
411
|
+
| 'secrets'
|
|
412
|
+
| 'config:read'
|
|
413
|
+
| 'config:write'
|
|
414
|
+
| 'agents:spawn'
|
|
415
|
+
| 'tools:register';
|
|
416
|
+
|
|
417
|
+
interface PackOptions {
|
|
418
|
+
outputPath?: string;
|
|
419
|
+
sign?: boolean;
|
|
420
|
+
privateKeyPath?: string;
|
|
421
|
+
includeSourceMaps?: boolean;
|
|
422
|
+
minify?: boolean;
|
|
423
|
+
compressionLevel?: number;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
interface PackageResult {
|
|
427
|
+
success: boolean;
|
|
428
|
+
packagePath: string;
|
|
429
|
+
size: number;
|
|
430
|
+
checksum: string;
|
|
431
|
+
manifest: AgentManifest;
|
|
432
|
+
files: PackagedFile[];
|
|
433
|
+
warnings: string[];
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
interface PackagedFile {
|
|
437
|
+
path: string;
|
|
438
|
+
size: number;
|
|
439
|
+
checksum: string;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
interface SignedPackage {
|
|
443
|
+
packagePath: string;
|
|
444
|
+
signature: string;
|
|
445
|
+
signedAt: Date;
|
|
446
|
+
publicKey: string;
|
|
447
|
+
algorithm: string;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
interface VerificationResult {
|
|
451
|
+
valid: boolean;
|
|
452
|
+
signed: boolean;
|
|
453
|
+
signature?: {
|
|
454
|
+
algorithm: string;
|
|
455
|
+
publicKey: string;
|
|
456
|
+
signedAt: Date;
|
|
457
|
+
publisher?: string;
|
|
458
|
+
verified: boolean;
|
|
459
|
+
};
|
|
460
|
+
checksum: {
|
|
461
|
+
expected: string;
|
|
462
|
+
actual: string;
|
|
463
|
+
valid: boolean;
|
|
464
|
+
};
|
|
465
|
+
issues: string[];
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### Implementacion
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
class AgentPackagerImpl implements AgentPackager {
|
|
473
|
+
constructor(
|
|
474
|
+
private config: MarketplaceConfig,
|
|
475
|
+
private crypto: CryptoService,
|
|
476
|
+
private validator: ManifestValidator
|
|
477
|
+
) {}
|
|
478
|
+
|
|
479
|
+
async pack(sourcePath: string, options: PackOptions = {}): Promise<PackageResult> {
|
|
480
|
+
// Validate first
|
|
481
|
+
const validation = await this.validate(sourcePath);
|
|
482
|
+
if (!validation.valid) {
|
|
483
|
+
throw new PackagingError('Validation failed', validation.errors);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Read manifest
|
|
487
|
+
const manifestPath = path.join(sourcePath, this.config.packaging.manifestFile);
|
|
488
|
+
const manifest = await this.readManifest(manifestPath);
|
|
489
|
+
|
|
490
|
+
// Determine files to include
|
|
491
|
+
const files = await this.collectFiles(sourcePath, manifest);
|
|
492
|
+
|
|
493
|
+
// Create package
|
|
494
|
+
const outputPath = options.outputPath ||
|
|
495
|
+
path.join(sourcePath, `${manifest.name}-${manifest.version}.elsabro`);
|
|
496
|
+
|
|
497
|
+
const archive = archiver('zip', {
|
|
498
|
+
zlib: { level: options.compressionLevel ?? this.config.packaging.compressionLevel }
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
const output = fs.createWriteStream(outputPath);
|
|
502
|
+
archive.pipe(output);
|
|
503
|
+
|
|
504
|
+
// Add manifest
|
|
505
|
+
archive.append(JSON.stringify(manifest, null, 2), { name: 'agent.manifest.json' });
|
|
506
|
+
|
|
507
|
+
// Add files
|
|
508
|
+
const packagedFiles: PackagedFile[] = [];
|
|
509
|
+
for (const file of files) {
|
|
510
|
+
const content = await fs.readFile(path.join(sourcePath, file));
|
|
511
|
+
const checksum = this.crypto.hash(content, 'sha256');
|
|
512
|
+
|
|
513
|
+
archive.append(content, { name: file });
|
|
514
|
+
packagedFiles.push({
|
|
515
|
+
path: file,
|
|
516
|
+
size: content.length,
|
|
517
|
+
checksum
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
await archive.finalize();
|
|
522
|
+
|
|
523
|
+
// Calculate package checksum
|
|
524
|
+
const packageContent = await fs.readFile(outputPath);
|
|
525
|
+
const checksum = this.crypto.hash(packageContent, 'sha256');
|
|
526
|
+
|
|
527
|
+
// Sign if requested
|
|
528
|
+
if (options.sign && options.privateKeyPath) {
|
|
529
|
+
const privateKey = await fs.readFile(options.privateKeyPath, 'utf-8');
|
|
530
|
+
await this.sign(outputPath, privateKey);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return {
|
|
534
|
+
success: true,
|
|
535
|
+
packagePath: outputPath,
|
|
536
|
+
size: packageContent.length,
|
|
537
|
+
checksum,
|
|
538
|
+
manifest,
|
|
539
|
+
files: packagedFiles,
|
|
540
|
+
warnings: validation.warnings
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async validate(sourcePath: string): Promise<ValidationResult> {
|
|
545
|
+
const errors: string[] = [];
|
|
546
|
+
const warnings: string[] = [];
|
|
547
|
+
|
|
548
|
+
// Check manifest exists
|
|
549
|
+
const manifestPath = path.join(sourcePath, this.config.packaging.manifestFile);
|
|
550
|
+
if (!await this.fileExists(manifestPath)) {
|
|
551
|
+
errors.push(`Missing manifest file: ${this.config.packaging.manifestFile}`);
|
|
552
|
+
return { valid: false, errors, warnings };
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Validate manifest
|
|
556
|
+
const manifest = await this.readManifest(manifestPath);
|
|
557
|
+
const manifestValidation = this.validateManifest(manifest);
|
|
558
|
+
errors.push(...manifestValidation.errors);
|
|
559
|
+
warnings.push(...manifestValidation.warnings);
|
|
560
|
+
|
|
561
|
+
// Check main entry exists
|
|
562
|
+
if (manifest.main) {
|
|
563
|
+
const mainPath = path.join(sourcePath, manifest.main);
|
|
564
|
+
if (!await this.fileExists(mainPath)) {
|
|
565
|
+
errors.push(`Main entry file not found: ${manifest.main}`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Check package size
|
|
570
|
+
const totalSize = await this.calculateDirectorySize(sourcePath);
|
|
571
|
+
if (totalSize > this.config.packaging.maxPackageSize) {
|
|
572
|
+
errors.push(`Package exceeds maximum size: ${totalSize} > ${this.config.packaging.maxPackageSize}`);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Validate code structure
|
|
576
|
+
const codeValidation = await this.validateCode(sourcePath, manifest);
|
|
577
|
+
errors.push(...codeValidation.errors);
|
|
578
|
+
warnings.push(...codeValidation.warnings);
|
|
579
|
+
|
|
580
|
+
return {
|
|
581
|
+
valid: errors.length === 0,
|
|
582
|
+
errors,
|
|
583
|
+
warnings
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
validateManifest(manifest: AgentManifest): ValidationResult {
|
|
588
|
+
const errors: string[] = [];
|
|
589
|
+
const warnings: string[] = [];
|
|
590
|
+
|
|
591
|
+
// Required fields
|
|
592
|
+
for (const field of this.config.packaging.requiredFields) {
|
|
593
|
+
if (!manifest[field]) {
|
|
594
|
+
errors.push(`Missing required field: ${field}`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Version format
|
|
599
|
+
if (manifest.version && !semver.valid(manifest.version)) {
|
|
600
|
+
errors.push(`Invalid version format: ${manifest.version}`);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Name format
|
|
604
|
+
if (manifest.name && !/^[a-z0-9-]+$/.test(manifest.name)) {
|
|
605
|
+
errors.push(`Invalid name format: ${manifest.name}. Use lowercase letters, numbers, and hyphens only.`);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Category validation
|
|
609
|
+
const validCategories = ['exploration', 'implementation', 'review', 'testing', 'specialized'];
|
|
610
|
+
if (manifest.category && !validCategories.includes(manifest.category)) {
|
|
611
|
+
errors.push(`Invalid category: ${manifest.category}`);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Engine compatibility
|
|
615
|
+
if (!manifest.engines?.elsabro) {
|
|
616
|
+
warnings.push('No ELSABRO engine version specified');
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Permission validation
|
|
620
|
+
const validPermissions = [
|
|
621
|
+
'filesystem:read', 'filesystem:write', 'network', 'network:external',
|
|
622
|
+
'shell', 'shell:restricted', 'env', 'secrets', 'config:read',
|
|
623
|
+
'config:write', 'agents:spawn', 'tools:register'
|
|
624
|
+
];
|
|
625
|
+
|
|
626
|
+
for (const perm of manifest.permissions || []) {
|
|
627
|
+
if (!validPermissions.includes(perm)) {
|
|
628
|
+
warnings.push(`Unknown permission: ${perm}`);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// High-risk permissions warning
|
|
633
|
+
const highRiskPerms = ['shell', 'secrets', 'filesystem:write'];
|
|
634
|
+
const hasHighRisk = (manifest.permissions || []).some(p => highRiskPerms.includes(p));
|
|
635
|
+
if (hasHighRisk) {
|
|
636
|
+
warnings.push('Agent requests high-risk permissions. Manual review may be required.');
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
async sign(packagePath: string, privateKey: string): Promise<SignedPackage> {
|
|
643
|
+
const content = await fs.readFile(packagePath);
|
|
644
|
+
const signature = this.crypto.sign(content, privateKey, this.config.signing.algorithm);
|
|
645
|
+
const publicKey = this.crypto.derivePublicKey(privateKey);
|
|
646
|
+
|
|
647
|
+
// Append signature to package
|
|
648
|
+
const signatureData = {
|
|
649
|
+
signature,
|
|
650
|
+
algorithm: this.config.signing.algorithm,
|
|
651
|
+
publicKey,
|
|
652
|
+
signedAt: new Date().toISOString()
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
const signaturePath = packagePath + '.sig';
|
|
656
|
+
await fs.writeFile(signaturePath, JSON.stringify(signatureData, null, 2));
|
|
657
|
+
|
|
658
|
+
return {
|
|
659
|
+
packagePath,
|
|
660
|
+
signature,
|
|
661
|
+
signedAt: new Date(),
|
|
662
|
+
publicKey,
|
|
663
|
+
algorithm: this.config.signing.algorithm
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
async verify(packagePath: string): Promise<VerificationResult> {
|
|
668
|
+
const issues: string[] = [];
|
|
669
|
+
const content = await fs.readFile(packagePath);
|
|
670
|
+
|
|
671
|
+
// Check checksum
|
|
672
|
+
const actualChecksum = this.crypto.hash(content, 'sha256');
|
|
673
|
+
const info = await this.inspect(packagePath);
|
|
674
|
+
const expectedChecksum = info.checksum;
|
|
675
|
+
const checksumValid = actualChecksum === expectedChecksum;
|
|
676
|
+
|
|
677
|
+
if (!checksumValid) {
|
|
678
|
+
issues.push('Checksum mismatch - package may be corrupted or tampered');
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Check signature
|
|
682
|
+
const signaturePath = packagePath + '.sig';
|
|
683
|
+
let signatureInfo = undefined;
|
|
684
|
+
let signed = false;
|
|
685
|
+
|
|
686
|
+
if (await this.fileExists(signaturePath)) {
|
|
687
|
+
signed = true;
|
|
688
|
+
const sigData = JSON.parse(await fs.readFile(signaturePath, 'utf-8'));
|
|
689
|
+
const verified = this.crypto.verify(content, sigData.signature, sigData.publicKey, sigData.algorithm);
|
|
690
|
+
|
|
691
|
+
signatureInfo = {
|
|
692
|
+
algorithm: sigData.algorithm,
|
|
693
|
+
publicKey: sigData.publicKey,
|
|
694
|
+
signedAt: new Date(sigData.signedAt),
|
|
695
|
+
verified
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
if (!verified) {
|
|
699
|
+
issues.push('Signature verification failed');
|
|
700
|
+
}
|
|
701
|
+
} else if (this.config.signing.required) {
|
|
702
|
+
issues.push('Package is not signed but signing is required');
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return {
|
|
706
|
+
valid: issues.length === 0,
|
|
707
|
+
signed,
|
|
708
|
+
signature: signatureInfo,
|
|
709
|
+
checksum: {
|
|
710
|
+
expected: expectedChecksum,
|
|
711
|
+
actual: actualChecksum,
|
|
712
|
+
valid: checksumValid
|
|
713
|
+
},
|
|
714
|
+
issues
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
async unpack(packagePath: string, destPath: string): Promise<UnpackResult> {
|
|
719
|
+
// Verify before unpacking
|
|
720
|
+
const verification = await this.verify(packagePath);
|
|
721
|
+
if (!verification.valid && !this.config.signing.allowUnsigned) {
|
|
722
|
+
throw new PackagingError('Package verification failed', verification.issues);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
await fs.mkdir(destPath, { recursive: true });
|
|
726
|
+
|
|
727
|
+
const zip = new AdmZip(packagePath);
|
|
728
|
+
zip.extractAllTo(destPath, true);
|
|
729
|
+
|
|
730
|
+
const manifest = await this.readManifest(path.join(destPath, 'agent.manifest.json'));
|
|
731
|
+
|
|
732
|
+
return {
|
|
733
|
+
success: true,
|
|
734
|
+
destPath,
|
|
735
|
+
manifest,
|
|
736
|
+
verification
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
async inspect(packagePath: string): Promise<PackageInfo> {
|
|
741
|
+
const zip = new AdmZip(packagePath);
|
|
742
|
+
const manifestEntry = zip.getEntry('agent.manifest.json');
|
|
743
|
+
|
|
744
|
+
if (!manifestEntry) {
|
|
745
|
+
throw new PackagingError('Invalid package: missing manifest');
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const manifest = JSON.parse(manifestEntry.getData().toString('utf-8'));
|
|
749
|
+
const entries = zip.getEntries();
|
|
750
|
+
|
|
751
|
+
const content = await fs.readFile(packagePath);
|
|
752
|
+
|
|
753
|
+
return {
|
|
754
|
+
manifest,
|
|
755
|
+
size: content.length,
|
|
756
|
+
checksum: this.crypto.hash(content, 'sha256'),
|
|
757
|
+
files: entries.map(e => ({
|
|
758
|
+
path: e.entryName,
|
|
759
|
+
size: e.header.size,
|
|
760
|
+
compressed: e.header.compressedSize
|
|
761
|
+
})),
|
|
762
|
+
createdAt: new Date(entries[0]?.header.time || Date.now())
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
private async collectFiles(sourcePath: string, manifest: AgentManifest): Promise<string[]> {
|
|
767
|
+
const includePatterns = manifest.files || ['**/*'];
|
|
768
|
+
const excludePatterns = [
|
|
769
|
+
...this.config.packaging.excludePatterns,
|
|
770
|
+
...(manifest.exclude || [])
|
|
771
|
+
];
|
|
772
|
+
|
|
773
|
+
const files = await glob(includePatterns, {
|
|
774
|
+
cwd: sourcePath,
|
|
775
|
+
ignore: excludePatterns,
|
|
776
|
+
nodir: true
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
// Filter by allowed file types
|
|
780
|
+
return files.filter(f => {
|
|
781
|
+
const ext = path.extname(f);
|
|
782
|
+
return this.config.packaging.allowedFileTypes.includes(ext) || ext === '';
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
private async validateCode(sourcePath: string, manifest: AgentManifest): Promise<ValidationResult> {
|
|
787
|
+
const errors: string[] = [];
|
|
788
|
+
const warnings: string[] = [];
|
|
789
|
+
|
|
790
|
+
// Check for potentially dangerous patterns
|
|
791
|
+
const mainPath = path.join(sourcePath, manifest.main);
|
|
792
|
+
if (await this.fileExists(mainPath)) {
|
|
793
|
+
const content = await fs.readFile(mainPath, 'utf-8');
|
|
794
|
+
|
|
795
|
+
// Check for dynamic code execution patterns (security validation)
|
|
796
|
+
if (/\bFunction\s*\(/.test(content)) {
|
|
797
|
+
warnings.push('Code contains Function constructor which may pose security risks');
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// Check for dynamic requires without permissions
|
|
801
|
+
if (/require\s*\([^'"]+\)/.test(content) && !manifest.permissions?.includes('shell')) {
|
|
802
|
+
warnings.push('Code contains dynamic requires');
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
private async fileExists(filePath: string): Promise<boolean> {
|
|
810
|
+
try {
|
|
811
|
+
await fs.access(filePath);
|
|
812
|
+
return true;
|
|
813
|
+
} catch {
|
|
814
|
+
return false;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
private async readManifest(manifestPath: string): Promise<AgentManifest> {
|
|
819
|
+
const content = await fs.readFile(manifestPath, 'utf-8');
|
|
820
|
+
return JSON.parse(content);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
private async calculateDirectorySize(dirPath: string): Promise<number> {
|
|
824
|
+
let size = 0;
|
|
825
|
+
const files = await glob('**/*', { cwd: dirPath, nodir: true });
|
|
826
|
+
|
|
827
|
+
for (const file of files) {
|
|
828
|
+
const stat = await fs.stat(path.join(dirPath, file));
|
|
829
|
+
size += stat.size;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
return size;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
---
|
|
838
|
+
|
|
839
|
+
## 3. PublishingPipeline
|
|
840
|
+
|
|
841
|
+
### Proposito
|
|
842
|
+
|
|
843
|
+
Flujo de publicacion con validacion automatica, review y rollback.
|
|
844
|
+
|
|
845
|
+
### Interfaces
|
|
846
|
+
|
|
847
|
+
```typescript
|
|
848
|
+
interface PublishingPipeline {
|
|
849
|
+
// Submission
|
|
850
|
+
submit(packagePath: string, options?: SubmitOptions): Promise<Submission>;
|
|
851
|
+
getSubmission(id: string): Promise<Submission | null>;
|
|
852
|
+
cancelSubmission(id: string): Promise<void>;
|
|
853
|
+
|
|
854
|
+
// Review process
|
|
855
|
+
startReview(submissionId: string): Promise<ReviewProcess>;
|
|
856
|
+
getReviewStatus(submissionId: string): Promise<ReviewStatus>;
|
|
857
|
+
|
|
858
|
+
// Publishing
|
|
859
|
+
approve(submissionId: string, reviewerId: string): Promise<void>;
|
|
860
|
+
reject(submissionId: string, reason: string, reviewerId: string): Promise<void>;
|
|
861
|
+
publish(submissionId: string): Promise<PublishResult>;
|
|
862
|
+
|
|
863
|
+
// Rollback
|
|
864
|
+
unpublish(name: string, version: string, reason: string): Promise<void>;
|
|
865
|
+
rollback(name: string, toVersion: string): Promise<RollbackResult>;
|
|
866
|
+
|
|
867
|
+
// Webhooks
|
|
868
|
+
onStageChange(callback: (submission: Submission, stage: PublishStage) => void): Disposable;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
type PublishStage = 'submit' | 'validate' | 'review' | 'approve' | 'publish' | 'rejected';
|
|
872
|
+
|
|
873
|
+
interface Submission {
|
|
874
|
+
id: string;
|
|
875
|
+
packagePath: string;
|
|
876
|
+
manifest: AgentManifest;
|
|
877
|
+
submittedBy: string;
|
|
878
|
+
submittedAt: Date;
|
|
879
|
+
stage: PublishStage;
|
|
880
|
+
stageHistory: StageHistoryEntry[];
|
|
881
|
+
validation?: ValidationResult;
|
|
882
|
+
review?: ReviewProcess;
|
|
883
|
+
publishedAt?: Date;
|
|
884
|
+
rejectionReason?: string;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
interface StageHistoryEntry {
|
|
888
|
+
stage: PublishStage;
|
|
889
|
+
timestamp: Date;
|
|
890
|
+
actor?: string;
|
|
891
|
+
notes?: string;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
interface SubmitOptions {
|
|
895
|
+
releaseNotes?: string;
|
|
896
|
+
prerelease?: boolean;
|
|
897
|
+
tags?: string[];
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
interface ReviewProcess {
|
|
901
|
+
id: string;
|
|
902
|
+
submissionId: string;
|
|
903
|
+
startedAt: Date;
|
|
904
|
+
completedAt?: Date;
|
|
905
|
+
status: 'pending' | 'in_progress' | 'passed' | 'failed';
|
|
906
|
+
|
|
907
|
+
// Automated checks
|
|
908
|
+
automatedChecks: AutomatedCheck[];
|
|
909
|
+
automatedScore: number;
|
|
910
|
+
|
|
911
|
+
// Manual review (if needed)
|
|
912
|
+
requiresManualReview: boolean;
|
|
913
|
+
manualReviewer?: string;
|
|
914
|
+
manualReviewNotes?: string;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
interface AutomatedCheck {
|
|
918
|
+
name: string;
|
|
919
|
+
description: string;
|
|
920
|
+
status: 'pending' | 'running' | 'passed' | 'failed' | 'skipped';
|
|
921
|
+
result?: {
|
|
922
|
+
passed: boolean;
|
|
923
|
+
score: number;
|
|
924
|
+
details: string;
|
|
925
|
+
duration: number;
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
interface ReviewStatus {
|
|
930
|
+
stage: PublishStage;
|
|
931
|
+
automatedChecks: {
|
|
932
|
+
total: number;
|
|
933
|
+
passed: number;
|
|
934
|
+
failed: number;
|
|
935
|
+
pending: number;
|
|
936
|
+
};
|
|
937
|
+
score: number;
|
|
938
|
+
requiresManualReview: boolean;
|
|
939
|
+
estimatedCompletion?: Date;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
interface PublishResult {
|
|
943
|
+
success: boolean;
|
|
944
|
+
name: string;
|
|
945
|
+
version: string;
|
|
946
|
+
publishedAt: Date;
|
|
947
|
+
url: string;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
interface RollbackResult {
|
|
951
|
+
success: boolean;
|
|
952
|
+
previousVersion: string;
|
|
953
|
+
newVersion: string;
|
|
954
|
+
affectedInstalls: number;
|
|
955
|
+
}
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
### Implementacion
|
|
959
|
+
|
|
960
|
+
```typescript
|
|
961
|
+
class PublishingPipelineImpl implements PublishingPipeline {
|
|
962
|
+
private submissions: Map<string, Submission> = new Map();
|
|
963
|
+
private stageCallbacks: Set<Function> = new Set();
|
|
964
|
+
|
|
965
|
+
constructor(
|
|
966
|
+
private config: MarketplaceConfig,
|
|
967
|
+
private packager: AgentPackager,
|
|
968
|
+
private registry: MarketplaceRegistry,
|
|
969
|
+
private testRunner: TestRunner,
|
|
970
|
+
private notifier: NotificationService
|
|
971
|
+
) {}
|
|
972
|
+
|
|
973
|
+
async submit(packagePath: string, options: SubmitOptions = {}): Promise<Submission> {
|
|
974
|
+
// Inspect package
|
|
975
|
+
const info = await this.packager.inspect(packagePath);
|
|
976
|
+
|
|
977
|
+
// Check if version already exists
|
|
978
|
+
const existing = await this.registry.get(info.manifest.name, info.manifest.version);
|
|
979
|
+
if (existing) {
|
|
980
|
+
throw new PublishError(`Version ${info.manifest.version} already exists for ${info.manifest.name}`);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
const submission: Submission = {
|
|
984
|
+
id: this.generateId(),
|
|
985
|
+
packagePath,
|
|
986
|
+
manifest: info.manifest,
|
|
987
|
+
submittedBy: this.getCurrentUser(),
|
|
988
|
+
submittedAt: new Date(),
|
|
989
|
+
stage: 'submit',
|
|
990
|
+
stageHistory: [{
|
|
991
|
+
stage: 'submit',
|
|
992
|
+
timestamp: new Date()
|
|
993
|
+
}]
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
this.submissions.set(submission.id, submission);
|
|
997
|
+
|
|
998
|
+
// Start validation automatically
|
|
999
|
+
this.processSubmission(submission);
|
|
1000
|
+
|
|
1001
|
+
return submission;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
private async processSubmission(submission: Submission): Promise<void> {
|
|
1005
|
+
try {
|
|
1006
|
+
// Stage: Validate
|
|
1007
|
+
await this.transitionStage(submission, 'validate');
|
|
1008
|
+
submission.validation = await this.packager.validate(
|
|
1009
|
+
path.dirname(submission.packagePath)
|
|
1010
|
+
);
|
|
1011
|
+
|
|
1012
|
+
if (!submission.validation.valid) {
|
|
1013
|
+
await this.transitionStage(submission, 'rejected', {
|
|
1014
|
+
notes: `Validation failed: ${submission.validation.errors.join(', ')}`
|
|
1015
|
+
});
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// Stage: Review
|
|
1020
|
+
await this.transitionStage(submission, 'review');
|
|
1021
|
+
const review = await this.startReview(submission.id);
|
|
1022
|
+
|
|
1023
|
+
// Wait for automated checks
|
|
1024
|
+
await this.waitForAutomatedChecks(review);
|
|
1025
|
+
|
|
1026
|
+
// Check if manual review is needed
|
|
1027
|
+
if (review.requiresManualReview) {
|
|
1028
|
+
this.notifier.notifyReviewers(submission);
|
|
1029
|
+
return; // Wait for manual approval
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// Auto-approve if score is high enough
|
|
1033
|
+
if (review.automatedScore >= this.config.publishing.manualReviewThreshold) {
|
|
1034
|
+
await this.approve(submission.id, 'system');
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
await this.transitionStage(submission, 'rejected', {
|
|
1039
|
+
notes: `Processing error: ${error.message}`
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
async startReview(submissionId: string): Promise<ReviewProcess> {
|
|
1045
|
+
const submission = this.submissions.get(submissionId);
|
|
1046
|
+
if (!submission) throw new Error('Submission not found');
|
|
1047
|
+
|
|
1048
|
+
const review: ReviewProcess = {
|
|
1049
|
+
id: this.generateId(),
|
|
1050
|
+
submissionId,
|
|
1051
|
+
startedAt: new Date(),
|
|
1052
|
+
status: 'in_progress',
|
|
1053
|
+
automatedChecks: this.createAutomatedChecks(),
|
|
1054
|
+
automatedScore: 0,
|
|
1055
|
+
requiresManualReview: false
|
|
1056
|
+
};
|
|
1057
|
+
|
|
1058
|
+
submission.review = review;
|
|
1059
|
+
|
|
1060
|
+
// Run automated checks
|
|
1061
|
+
await this.runAutomatedChecks(submission, review);
|
|
1062
|
+
|
|
1063
|
+
return review;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
private createAutomatedChecks(): AutomatedCheck[] {
|
|
1067
|
+
return [
|
|
1068
|
+
{ name: 'manifest_validation', description: 'Validate manifest structure', status: 'pending' },
|
|
1069
|
+
{ name: 'code_security', description: 'Security analysis of code', status: 'pending' },
|
|
1070
|
+
{ name: 'dependency_check', description: 'Check dependencies for vulnerabilities', status: 'pending' },
|
|
1071
|
+
{ name: 'unit_tests', description: 'Run unit tests', status: 'pending' },
|
|
1072
|
+
{ name: 'integration_tests', description: 'Run integration tests', status: 'pending' },
|
|
1073
|
+
{ name: 'compatibility_tests', description: 'Test ELSABRO version compatibility', status: 'pending' },
|
|
1074
|
+
{ name: 'performance_check', description: 'Basic performance benchmarks', status: 'pending' },
|
|
1075
|
+
{ name: 'documentation_check', description: 'Verify documentation exists', status: 'pending' }
|
|
1076
|
+
];
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
private async runAutomatedChecks(submission: Submission, review: ReviewProcess): Promise<void> {
|
|
1080
|
+
let totalScore = 0;
|
|
1081
|
+
let checkCount = 0;
|
|
1082
|
+
|
|
1083
|
+
for (const check of review.automatedChecks) {
|
|
1084
|
+
check.status = 'running';
|
|
1085
|
+
const startTime = Date.now();
|
|
1086
|
+
|
|
1087
|
+
try {
|
|
1088
|
+
const result = await this.runCheck(check.name, submission);
|
|
1089
|
+
check.status = result.passed ? 'passed' : 'failed';
|
|
1090
|
+
check.result = {
|
|
1091
|
+
...result,
|
|
1092
|
+
duration: Date.now() - startTime
|
|
1093
|
+
};
|
|
1094
|
+
|
|
1095
|
+
totalScore += result.score;
|
|
1096
|
+
checkCount++;
|
|
1097
|
+
|
|
1098
|
+
// If critical check fails, require manual review
|
|
1099
|
+
if (!result.passed && ['code_security', 'dependency_check'].includes(check.name)) {
|
|
1100
|
+
review.requiresManualReview = true;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
} catch (error) {
|
|
1104
|
+
check.status = 'failed';
|
|
1105
|
+
check.result = {
|
|
1106
|
+
passed: false,
|
|
1107
|
+
score: 0,
|
|
1108
|
+
details: error.message,
|
|
1109
|
+
duration: Date.now() - startTime
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
review.automatedScore = checkCount > 0 ? totalScore / checkCount : 0;
|
|
1115
|
+
review.status = review.automatedChecks.every(c => c.status === 'passed') ? 'passed' : 'failed';
|
|
1116
|
+
|
|
1117
|
+
// High-risk permissions always require manual review
|
|
1118
|
+
const highRiskPerms = ['shell', 'secrets'];
|
|
1119
|
+
if (submission.manifest.permissions?.some(p => highRiskPerms.includes(p))) {
|
|
1120
|
+
review.requiresManualReview = true;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
private async runCheck(checkName: string, submission: Submission): Promise<{passed: boolean; score: number; details: string}> {
|
|
1125
|
+
switch (checkName) {
|
|
1126
|
+
case 'manifest_validation':
|
|
1127
|
+
const validation = this.packager.validateManifest(submission.manifest);
|
|
1128
|
+
return {
|
|
1129
|
+
passed: validation.valid,
|
|
1130
|
+
score: validation.valid ? 1 : 0,
|
|
1131
|
+
details: validation.errors.join('; ') || 'Manifest is valid'
|
|
1132
|
+
};
|
|
1133
|
+
|
|
1134
|
+
case 'unit_tests':
|
|
1135
|
+
const testResult = await this.testRunner.runTests(submission.packagePath, 'unit');
|
|
1136
|
+
return {
|
|
1137
|
+
passed: testResult.passed && testResult.coverage >= this.config.testing.minCoverage,
|
|
1138
|
+
score: testResult.coverage / 100,
|
|
1139
|
+
details: `${testResult.passed}/${testResult.total} tests passed, ${testResult.coverage}% coverage`
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
case 'code_security':
|
|
1143
|
+
const securityResult = await this.runSecurityScan(submission.packagePath);
|
|
1144
|
+
return {
|
|
1145
|
+
passed: securityResult.vulnerabilities.high === 0,
|
|
1146
|
+
score: securityResult.score,
|
|
1147
|
+
details: `Found ${securityResult.vulnerabilities.total} vulnerabilities`
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
default:
|
|
1151
|
+
return { passed: true, score: 1, details: 'Check passed' };
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
async approve(submissionId: string, reviewerId: string): Promise<void> {
|
|
1156
|
+
const submission = this.submissions.get(submissionId);
|
|
1157
|
+
if (!submission) throw new Error('Submission not found');
|
|
1158
|
+
|
|
1159
|
+
if (submission.stage !== 'review') {
|
|
1160
|
+
throw new Error(`Cannot approve submission in stage: ${submission.stage}`);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
await this.transitionStage(submission, 'approve', { actor: reviewerId });
|
|
1164
|
+
|
|
1165
|
+
// Auto-publish after approval
|
|
1166
|
+
await this.publish(submissionId);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
async reject(submissionId: string, reason: string, reviewerId: string): Promise<void> {
|
|
1170
|
+
const submission = this.submissions.get(submissionId);
|
|
1171
|
+
if (!submission) throw new Error('Submission not found');
|
|
1172
|
+
|
|
1173
|
+
submission.rejectionReason = reason;
|
|
1174
|
+
await this.transitionStage(submission, 'rejected', {
|
|
1175
|
+
actor: reviewerId,
|
|
1176
|
+
notes: reason
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
// Notify submitter
|
|
1180
|
+
this.notifier.notifyRejection(submission, reason);
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
async publish(submissionId: string): Promise<PublishResult> {
|
|
1184
|
+
const submission = this.submissions.get(submissionId);
|
|
1185
|
+
if (!submission) throw new Error('Submission not found');
|
|
1186
|
+
|
|
1187
|
+
if (submission.stage !== 'approve') {
|
|
1188
|
+
throw new Error(`Cannot publish submission in stage: ${submission.stage}`);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
try {
|
|
1192
|
+
// Upload to registry
|
|
1193
|
+
const result = await this.uploadToRegistry(submission);
|
|
1194
|
+
|
|
1195
|
+
submission.publishedAt = new Date();
|
|
1196
|
+
await this.transitionStage(submission, 'publish');
|
|
1197
|
+
|
|
1198
|
+
// Trigger webhooks
|
|
1199
|
+
await this.triggerWebhook('onPublish', submission);
|
|
1200
|
+
|
|
1201
|
+
return result;
|
|
1202
|
+
|
|
1203
|
+
} catch (error) {
|
|
1204
|
+
// Automatic rollback on failure
|
|
1205
|
+
await this.transitionStage(submission, 'rejected', {
|
|
1206
|
+
notes: `Publish failed: ${error.message}`
|
|
1207
|
+
});
|
|
1208
|
+
throw error;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
async unpublish(name: string, version: string, reason: string): Promise<void> {
|
|
1213
|
+
await this.registry.removeVersion(name, version);
|
|
1214
|
+
this.notifier.notifyUnpublish(name, version, reason);
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
async rollback(name: string, toVersion: string): Promise<RollbackResult> {
|
|
1218
|
+
const versions = await this.registry.getVersions(name);
|
|
1219
|
+
const currentVersion = versions[0]?.version;
|
|
1220
|
+
|
|
1221
|
+
if (!currentVersion) {
|
|
1222
|
+
throw new Error('No published versions found');
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// Mark newer versions as deprecated
|
|
1226
|
+
for (const ver of versions) {
|
|
1227
|
+
if (semver.gt(ver.version, toVersion)) {
|
|
1228
|
+
await this.registry.deprecateVersion(name, ver.version, `Rolled back to ${toVersion}`);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
return {
|
|
1233
|
+
success: true,
|
|
1234
|
+
previousVersion: currentVersion,
|
|
1235
|
+
newVersion: toVersion,
|
|
1236
|
+
affectedInstalls: await this.countAffectedInstalls(name, currentVersion)
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
private async transitionStage(
|
|
1241
|
+
submission: Submission,
|
|
1242
|
+
stage: PublishStage,
|
|
1243
|
+
options: { actor?: string; notes?: string } = {}
|
|
1244
|
+
): Promise<void> {
|
|
1245
|
+
submission.stage = stage;
|
|
1246
|
+
submission.stageHistory.push({
|
|
1247
|
+
stage,
|
|
1248
|
+
timestamp: new Date(),
|
|
1249
|
+
actor: options.actor,
|
|
1250
|
+
notes: options.notes
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
this.stageCallbacks.forEach(cb => cb(submission, stage));
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
onStageChange(callback: (submission: Submission, stage: PublishStage) => void): Disposable {
|
|
1257
|
+
this.stageCallbacks.add(callback);
|
|
1258
|
+
return { dispose: () => this.stageCallbacks.delete(callback) };
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
private generateId(): string {
|
|
1262
|
+
return `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
```
|
|
1266
|
+
|
|
1267
|
+
---
|
|
1268
|
+
|
|
1269
|
+
## 4. ReviewSystem
|
|
1270
|
+
|
|
1271
|
+
### Proposito
|
|
1272
|
+
|
|
1273
|
+
Sistema de ratings, reviews de texto y metricas de uso.
|
|
1274
|
+
|
|
1275
|
+
### Interfaces
|
|
1276
|
+
|
|
1277
|
+
```typescript
|
|
1278
|
+
interface ReviewSystem {
|
|
1279
|
+
// Reviews
|
|
1280
|
+
submitReview(agentName: string, review: ReviewInput): Promise<Review>;
|
|
1281
|
+
updateReview(reviewId: string, updates: Partial<ReviewInput>): Promise<Review>;
|
|
1282
|
+
deleteReview(reviewId: string): Promise<void>;
|
|
1283
|
+
getReviews(agentName: string, options?: ReviewQueryOptions): Promise<ReviewResult>;
|
|
1284
|
+
|
|
1285
|
+
// Ratings
|
|
1286
|
+
getRating(agentName: string): Promise<RatingSummary>;
|
|
1287
|
+
getUserRating(agentName: string, userId: string): Promise<number | null>;
|
|
1288
|
+
|
|
1289
|
+
// Metrics
|
|
1290
|
+
getMetrics(agentName: string): Promise<AgentMetrics>;
|
|
1291
|
+
trackUsage(agentName: string, event: UsageEvent): Promise<void>;
|
|
1292
|
+
|
|
1293
|
+
// Reporting
|
|
1294
|
+
reportIssue(agentName: string, report: IssueReport): Promise<ReportResult>;
|
|
1295
|
+
getReports(agentName: string): Promise<IssueReport[]>;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
interface ReviewInput {
|
|
1299
|
+
rating: number; // 1-5
|
|
1300
|
+
title?: string;
|
|
1301
|
+
body?: string;
|
|
1302
|
+
pros?: string[];
|
|
1303
|
+
cons?: string[];
|
|
1304
|
+
version: string;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
interface Review {
|
|
1308
|
+
id: string;
|
|
1309
|
+
agentName: string;
|
|
1310
|
+
userId: string;
|
|
1311
|
+
userName: string;
|
|
1312
|
+
rating: number;
|
|
1313
|
+
title?: string;
|
|
1314
|
+
body?: string;
|
|
1315
|
+
pros?: string[];
|
|
1316
|
+
cons?: string[];
|
|
1317
|
+
version: string;
|
|
1318
|
+
createdAt: Date;
|
|
1319
|
+
updatedAt?: Date;
|
|
1320
|
+
helpful: number;
|
|
1321
|
+
verified: boolean; // User actually installed the agent
|
|
1322
|
+
|
|
1323
|
+
// Publisher response
|
|
1324
|
+
response?: {
|
|
1325
|
+
body: string;
|
|
1326
|
+
respondedAt: Date;
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
interface ReviewQueryOptions {
|
|
1331
|
+
sortBy?: 'recent' | 'helpful' | 'rating_high' | 'rating_low';
|
|
1332
|
+
filterRating?: number;
|
|
1333
|
+
filterVersion?: string;
|
|
1334
|
+
verified?: boolean;
|
|
1335
|
+
page?: number;
|
|
1336
|
+
limit?: number;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
interface ReviewResult {
|
|
1340
|
+
items: Review[];
|
|
1341
|
+
total: number;
|
|
1342
|
+
page: number;
|
|
1343
|
+
summary: RatingSummary;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
interface RatingSummary {
|
|
1347
|
+
average: number;
|
|
1348
|
+
total: number;
|
|
1349
|
+
distribution: {
|
|
1350
|
+
1: number;
|
|
1351
|
+
2: number;
|
|
1352
|
+
3: number;
|
|
1353
|
+
4: number;
|
|
1354
|
+
5: number;
|
|
1355
|
+
};
|
|
1356
|
+
trend: 'up' | 'down' | 'stable';
|
|
1357
|
+
trendValue: number;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
interface AgentMetrics {
|
|
1361
|
+
// Downloads
|
|
1362
|
+
downloads: {
|
|
1363
|
+
total: number;
|
|
1364
|
+
lastWeek: number;
|
|
1365
|
+
lastMonth: number;
|
|
1366
|
+
trend: number; // percentage change
|
|
1367
|
+
};
|
|
1368
|
+
|
|
1369
|
+
// Active users
|
|
1370
|
+
activeUsers: {
|
|
1371
|
+
daily: number;
|
|
1372
|
+
weekly: number;
|
|
1373
|
+
monthly: number;
|
|
1374
|
+
};
|
|
1375
|
+
|
|
1376
|
+
// Success rate
|
|
1377
|
+
successRate: {
|
|
1378
|
+
overall: number;
|
|
1379
|
+
lastWeek: number;
|
|
1380
|
+
byVersion: Record<string, number>;
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
// Performance
|
|
1384
|
+
performance: {
|
|
1385
|
+
avgExecutionTime: number;
|
|
1386
|
+
p95ExecutionTime: number;
|
|
1387
|
+
errorRate: number;
|
|
1388
|
+
};
|
|
1389
|
+
|
|
1390
|
+
// Retention
|
|
1391
|
+
retention: {
|
|
1392
|
+
day1: number;
|
|
1393
|
+
day7: number;
|
|
1394
|
+
day30: number;
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
interface UsageEvent {
|
|
1399
|
+
type: 'install' | 'uninstall' | 'update' | 'execute' | 'error';
|
|
1400
|
+
version: string;
|
|
1401
|
+
duration?: number;
|
|
1402
|
+
success?: boolean;
|
|
1403
|
+
errorCode?: string;
|
|
1404
|
+
metadata?: Record<string, unknown>;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
interface IssueReport {
|
|
1408
|
+
id: string;
|
|
1409
|
+
agentName: string;
|
|
1410
|
+
type: 'bug' | 'security' | 'inappropriate' | 'license';
|
|
1411
|
+
title: string;
|
|
1412
|
+
description: string;
|
|
1413
|
+
reportedBy: string;
|
|
1414
|
+
reportedAt: Date;
|
|
1415
|
+
status: 'open' | 'investigating' | 'resolved' | 'dismissed';
|
|
1416
|
+
severity: 'low' | 'medium' | 'high' | 'critical';
|
|
1417
|
+
version?: string;
|
|
1418
|
+
evidence?: string[];
|
|
1419
|
+
}
|
|
1420
|
+
```
|
|
1421
|
+
|
|
1422
|
+
### Implementacion
|
|
1423
|
+
|
|
1424
|
+
```typescript
|
|
1425
|
+
class ReviewSystemImpl implements ReviewSystem {
|
|
1426
|
+
constructor(
|
|
1427
|
+
private config: MarketplaceConfig,
|
|
1428
|
+
private db: Database,
|
|
1429
|
+
private installationManager: InstallationManager,
|
|
1430
|
+
private moderator: ContentModerator
|
|
1431
|
+
) {}
|
|
1432
|
+
|
|
1433
|
+
async submitReview(agentName: string, input: ReviewInput): Promise<Review> {
|
|
1434
|
+
const userId = this.getCurrentUserId();
|
|
1435
|
+
|
|
1436
|
+
// Validate rating
|
|
1437
|
+
if (input.rating < 1 || input.rating > 5) {
|
|
1438
|
+
throw new ValidationError('Rating must be between 1 and 5');
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
// Check if user has installed the agent
|
|
1442
|
+
const hasInstalled = await this.installationManager.hasInstalled(agentName, userId);
|
|
1443
|
+
|
|
1444
|
+
// Check for existing review
|
|
1445
|
+
const existing = await this.db.reviews.findOne({ agentName, userId });
|
|
1446
|
+
if (existing) {
|
|
1447
|
+
throw new ValidationError('You have already reviewed this agent. Use updateReview instead.');
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// Moderate content if enabled
|
|
1451
|
+
if (this.config.reviews.moderationEnabled && input.body) {
|
|
1452
|
+
const moderation = await this.moderator.check(input.body);
|
|
1453
|
+
if (!moderation.approved) {
|
|
1454
|
+
throw new ModerationError('Review content violates community guidelines');
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
const review: Review = {
|
|
1459
|
+
id: this.generateId(),
|
|
1460
|
+
agentName,
|
|
1461
|
+
userId,
|
|
1462
|
+
userName: await this.getUserName(userId),
|
|
1463
|
+
rating: input.rating,
|
|
1464
|
+
title: input.title,
|
|
1465
|
+
body: input.body,
|
|
1466
|
+
pros: input.pros,
|
|
1467
|
+
cons: input.cons,
|
|
1468
|
+
version: input.version,
|
|
1469
|
+
createdAt: new Date(),
|
|
1470
|
+
helpful: 0,
|
|
1471
|
+
verified: hasInstalled
|
|
1472
|
+
};
|
|
1473
|
+
|
|
1474
|
+
await this.db.reviews.insert(review);
|
|
1475
|
+
await this.updateRatingCache(agentName);
|
|
1476
|
+
|
|
1477
|
+
return review;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
async getRating(agentName: string): Promise<RatingSummary> {
|
|
1481
|
+
const reviews = await this.db.reviews.find({ agentName });
|
|
1482
|
+
|
|
1483
|
+
if (reviews.length === 0) {
|
|
1484
|
+
return {
|
|
1485
|
+
average: 0,
|
|
1486
|
+
total: 0,
|
|
1487
|
+
distribution: { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 },
|
|
1488
|
+
trend: 'stable',
|
|
1489
|
+
trendValue: 0
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
const distribution = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
|
|
1494
|
+
let sum = 0;
|
|
1495
|
+
|
|
1496
|
+
for (const review of reviews) {
|
|
1497
|
+
distribution[review.rating as 1|2|3|4|5]++;
|
|
1498
|
+
sum += review.rating;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
const average = sum / reviews.length;
|
|
1502
|
+
|
|
1503
|
+
// Calculate trend (last 30 days vs previous 30 days)
|
|
1504
|
+
const now = new Date();
|
|
1505
|
+
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
|
1506
|
+
const sixtyDaysAgo = new Date(now.getTime() - 60 * 24 * 60 * 60 * 1000);
|
|
1507
|
+
|
|
1508
|
+
const recentReviews = reviews.filter(r => r.createdAt > thirtyDaysAgo);
|
|
1509
|
+
const previousReviews = reviews.filter(r => r.createdAt > sixtyDaysAgo && r.createdAt <= thirtyDaysAgo);
|
|
1510
|
+
|
|
1511
|
+
const recentAvg = recentReviews.length > 0
|
|
1512
|
+
? recentReviews.reduce((s, r) => s + r.rating, 0) / recentReviews.length
|
|
1513
|
+
: average;
|
|
1514
|
+
const previousAvg = previousReviews.length > 0
|
|
1515
|
+
? previousReviews.reduce((s, r) => s + r.rating, 0) / previousReviews.length
|
|
1516
|
+
: average;
|
|
1517
|
+
|
|
1518
|
+
const trendValue = recentAvg - previousAvg;
|
|
1519
|
+
const trend = trendValue > 0.1 ? 'up' : trendValue < -0.1 ? 'down' : 'stable';
|
|
1520
|
+
|
|
1521
|
+
return {
|
|
1522
|
+
average: Math.round(average * 10) / 10,
|
|
1523
|
+
total: reviews.length,
|
|
1524
|
+
distribution,
|
|
1525
|
+
trend,
|
|
1526
|
+
trendValue: Math.round(trendValue * 100) / 100
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
async getMetrics(agentName: string): Promise<AgentMetrics> {
|
|
1531
|
+
const now = new Date();
|
|
1532
|
+
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
1533
|
+
const monthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
|
1534
|
+
|
|
1535
|
+
// Get usage events
|
|
1536
|
+
const events = await this.db.usageEvents.find({ agentName });
|
|
1537
|
+
|
|
1538
|
+
// Calculate downloads
|
|
1539
|
+
const installs = events.filter(e => e.type === 'install');
|
|
1540
|
+
const lastWeekInstalls = installs.filter(e => e.timestamp > weekAgo);
|
|
1541
|
+
const lastMonthInstalls = installs.filter(e => e.timestamp > monthAgo);
|
|
1542
|
+
|
|
1543
|
+
// Calculate success rate
|
|
1544
|
+
const executions = events.filter(e => e.type === 'execute');
|
|
1545
|
+
const successfulExecs = executions.filter(e => e.success);
|
|
1546
|
+
|
|
1547
|
+
// Calculate performance
|
|
1548
|
+
const durations = executions.filter(e => e.duration).map(e => e.duration!);
|
|
1549
|
+
durations.sort((a, b) => a - b);
|
|
1550
|
+
|
|
1551
|
+
const errors = events.filter(e => e.type === 'error');
|
|
1552
|
+
|
|
1553
|
+
return {
|
|
1554
|
+
downloads: {
|
|
1555
|
+
total: installs.length,
|
|
1556
|
+
lastWeek: lastWeekInstalls.length,
|
|
1557
|
+
lastMonth: lastMonthInstalls.length,
|
|
1558
|
+
trend: 0
|
|
1559
|
+
},
|
|
1560
|
+
activeUsers: {
|
|
1561
|
+
daily: await this.countActiveUsers(agentName, 1),
|
|
1562
|
+
weekly: await this.countActiveUsers(agentName, 7),
|
|
1563
|
+
monthly: await this.countActiveUsers(agentName, 30)
|
|
1564
|
+
},
|
|
1565
|
+
successRate: {
|
|
1566
|
+
overall: executions.length > 0 ? (successfulExecs.length / executions.length) * 100 : 100,
|
|
1567
|
+
lastWeek: this.calculateSuccessRate(executions.filter(e => e.timestamp > weekAgo)),
|
|
1568
|
+
byVersion: this.calculateSuccessRateByVersion(executions)
|
|
1569
|
+
},
|
|
1570
|
+
performance: {
|
|
1571
|
+
avgExecutionTime: durations.length > 0 ? durations.reduce((a, b) => a + b, 0) / durations.length : 0,
|
|
1572
|
+
p95ExecutionTime: durations.length > 0 ? durations[Math.floor(durations.length * 0.95)] : 0,
|
|
1573
|
+
errorRate: executions.length > 0 ? (errors.length / executions.length) * 100 : 0
|
|
1574
|
+
},
|
|
1575
|
+
retention: await this.calculateRetention(agentName)
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
async trackUsage(agentName: string, event: UsageEvent): Promise<void> {
|
|
1580
|
+
await this.db.usageEvents.insert({
|
|
1581
|
+
agentName,
|
|
1582
|
+
userId: this.getCurrentUserId(),
|
|
1583
|
+
timestamp: new Date(),
|
|
1584
|
+
...event
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
async reportIssue(agentName: string, report: Omit<IssueReport, 'id' | 'reportedBy' | 'reportedAt' | 'status'>): Promise<ReportResult> {
|
|
1589
|
+
// Validate description length
|
|
1590
|
+
if (report.description.length < this.config.reporting.minDescriptionLength) {
|
|
1591
|
+
throw new ValidationError(`Description must be at least ${this.config.reporting.minDescriptionLength} characters`);
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
const issueReport: IssueReport = {
|
|
1595
|
+
id: this.generateId(),
|
|
1596
|
+
agentName,
|
|
1597
|
+
...report,
|
|
1598
|
+
reportedBy: this.getCurrentUserId(),
|
|
1599
|
+
reportedAt: new Date(),
|
|
1600
|
+
status: 'open'
|
|
1601
|
+
};
|
|
1602
|
+
|
|
1603
|
+
await this.db.reports.insert(issueReport);
|
|
1604
|
+
|
|
1605
|
+
// Notify publisher if configured
|
|
1606
|
+
if (this.config.reporting.notifyPublisher) {
|
|
1607
|
+
await this.notifyPublisher(agentName, issueReport);
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
// Auto-escalate security issues
|
|
1611
|
+
if (report.type === 'security' && report.severity === 'critical') {
|
|
1612
|
+
await this.escalateToSecurityTeam(issueReport);
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
return {
|
|
1616
|
+
id: issueReport.id,
|
|
1617
|
+
status: 'submitted',
|
|
1618
|
+
message: 'Your report has been submitted and will be reviewed.'
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
private generateId(): string {
|
|
1623
|
+
return `rev_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
```
|
|
1627
|
+
|
|
1628
|
+
---
|
|
1629
|
+
|
|
1630
|
+
## 5. InstallationManager
|
|
1631
|
+
|
|
1632
|
+
### Proposito
|
|
1633
|
+
|
|
1634
|
+
Gestion de instalacion, actualizacion y ejecucion sandboxed de agentes.
|
|
1635
|
+
|
|
1636
|
+
### Interfaces
|
|
1637
|
+
|
|
1638
|
+
```typescript
|
|
1639
|
+
interface InstallationManager {
|
|
1640
|
+
// Installation
|
|
1641
|
+
install(name: string, version?: string, options?: InstallOptions): Promise<InstalledAgent>;
|
|
1642
|
+
installFromPath(packagePath: string, options?: InstallOptions): Promise<InstalledAgent>;
|
|
1643
|
+
uninstall(name: string): Promise<void>;
|
|
1644
|
+
|
|
1645
|
+
// Updates
|
|
1646
|
+
update(name: string, version?: string): Promise<InstalledAgent>;
|
|
1647
|
+
checkUpdates(): Promise<UpdateInfo[]>;
|
|
1648
|
+
updateAll(): Promise<UpdateResult[]>;
|
|
1649
|
+
|
|
1650
|
+
// Queries
|
|
1651
|
+
list(): Promise<InstalledAgent[]>;
|
|
1652
|
+
get(name: string): Promise<InstalledAgent | null>;
|
|
1653
|
+
isInstalled(name: string): Promise<boolean>;
|
|
1654
|
+
|
|
1655
|
+
// Dependencies
|
|
1656
|
+
resolveDependencies(name: string, version: string): Promise<DependencyTree>;
|
|
1657
|
+
|
|
1658
|
+
// Execution
|
|
1659
|
+
load(name: string): Promise<LoadedAgent>;
|
|
1660
|
+
execute(name: string, input: AgentInput): Promise<AgentOutput>;
|
|
1661
|
+
|
|
1662
|
+
// Sandbox
|
|
1663
|
+
createSandbox(agent: InstalledAgent): AgentSandbox;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
interface InstalledAgent {
|
|
1667
|
+
name: string;
|
|
1668
|
+
version: string;
|
|
1669
|
+
displayName: string;
|
|
1670
|
+
description: string;
|
|
1671
|
+
category: AgentCategory;
|
|
1672
|
+
|
|
1673
|
+
// Paths
|
|
1674
|
+
installPath: string;
|
|
1675
|
+
mainPath: string;
|
|
1676
|
+
|
|
1677
|
+
// Metadata
|
|
1678
|
+
installedAt: Date;
|
|
1679
|
+
updatedAt?: Date;
|
|
1680
|
+
installedBy: string;
|
|
1681
|
+
|
|
1682
|
+
// State
|
|
1683
|
+
enabled: boolean;
|
|
1684
|
+
loaded: boolean;
|
|
1685
|
+
|
|
1686
|
+
// Manifest
|
|
1687
|
+
manifest: AgentManifest;
|
|
1688
|
+
|
|
1689
|
+
// Dependencies
|
|
1690
|
+
dependencies: InstalledDependency[];
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
interface InstalledDependency {
|
|
1694
|
+
name: string;
|
|
1695
|
+
version: string;
|
|
1696
|
+
required: string;
|
|
1697
|
+
satisfied: boolean;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
interface InstallOptions {
|
|
1701
|
+
global?: boolean;
|
|
1702
|
+
force?: boolean;
|
|
1703
|
+
skipDependencies?: boolean;
|
|
1704
|
+
skipVerification?: boolean;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
interface UpdateInfo {
|
|
1708
|
+
name: string;
|
|
1709
|
+
currentVersion: string;
|
|
1710
|
+
latestVersion: string;
|
|
1711
|
+
updateType: 'major' | 'minor' | 'patch';
|
|
1712
|
+
breaking: boolean;
|
|
1713
|
+
changelog?: string;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
interface DependencyTree {
|
|
1717
|
+
name: string;
|
|
1718
|
+
version: string;
|
|
1719
|
+
dependencies: DependencyTree[];
|
|
1720
|
+
conflicts: DependencyConflict[];
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
interface DependencyConflict {
|
|
1724
|
+
package: string;
|
|
1725
|
+
required: string[];
|
|
1726
|
+
installed?: string;
|
|
1727
|
+
resolution: 'upgrade' | 'downgrade' | 'unresolvable';
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
interface AgentSandbox {
|
|
1731
|
+
// Permissions
|
|
1732
|
+
permissions: Set<AgentPermission>;
|
|
1733
|
+
|
|
1734
|
+
// Resource limits
|
|
1735
|
+
limits: {
|
|
1736
|
+
memory: number;
|
|
1737
|
+
cpu: number;
|
|
1738
|
+
timeout: number;
|
|
1739
|
+
};
|
|
1740
|
+
|
|
1741
|
+
// Execution
|
|
1742
|
+
execute<T>(fn: () => Promise<T>): Promise<T>;
|
|
1743
|
+
|
|
1744
|
+
// Cleanup
|
|
1745
|
+
dispose(): void;
|
|
1746
|
+
}
|
|
1747
|
+
```
|
|
1748
|
+
|
|
1749
|
+
### Implementacion
|
|
1750
|
+
|
|
1751
|
+
```typescript
|
|
1752
|
+
class InstallationManagerImpl implements InstallationManager {
|
|
1753
|
+
private installed: Map<string, InstalledAgent> = new Map();
|
|
1754
|
+
private loaded: Map<string, LoadedAgent> = new Map();
|
|
1755
|
+
private lockfile: Lockfile;
|
|
1756
|
+
|
|
1757
|
+
constructor(
|
|
1758
|
+
private config: MarketplaceConfig,
|
|
1759
|
+
private registry: MarketplaceRegistry,
|
|
1760
|
+
private packager: AgentPackager,
|
|
1761
|
+
private reviewSystem: ReviewSystem
|
|
1762
|
+
) {
|
|
1763
|
+
this.lockfile = new Lockfile(this.config.installation.lockFile);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
async install(name: string, version?: string, options: InstallOptions = {}): Promise<InstalledAgent> {
|
|
1767
|
+
// Get agent info from registry
|
|
1768
|
+
const targetVersion = version || await this.registry.getLatestVersion(name);
|
|
1769
|
+
const agentInfo = await this.registry.get(name, targetVersion);
|
|
1770
|
+
|
|
1771
|
+
if (!agentInfo) {
|
|
1772
|
+
throw new InstallError(`Agent not found: ${name}@${targetVersion}`);
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// Check compatibility
|
|
1776
|
+
const compatibility = await this.registry.checkCompatibility(name, targetVersion);
|
|
1777
|
+
if (!compatibility.compatible && !options.force) {
|
|
1778
|
+
throw new InstallError(`Incompatible agent: ${compatibility.issues.map(i => i.message).join(', ')}`);
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
// Resolve dependencies
|
|
1782
|
+
if (!options.skipDependencies) {
|
|
1783
|
+
const depTree = await this.resolveDependencies(name, targetVersion);
|
|
1784
|
+
|
|
1785
|
+
if (depTree.conflicts.some(c => c.resolution === 'unresolvable')) {
|
|
1786
|
+
throw new DependencyError('Unresolvable dependency conflicts', depTree.conflicts);
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
// Install dependencies first
|
|
1790
|
+
await this.installDependencies(depTree);
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
// Download package
|
|
1794
|
+
const packagePath = await this.downloadPackage(name, targetVersion);
|
|
1795
|
+
|
|
1796
|
+
// Verify package
|
|
1797
|
+
if (!options.skipVerification) {
|
|
1798
|
+
const verification = await this.packager.verify(packagePath);
|
|
1799
|
+
if (!verification.valid) {
|
|
1800
|
+
throw new VerificationError('Package verification failed', verification.issues);
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
if (!verification.signed && this.config.signing.warnUnsigned) {
|
|
1804
|
+
console.warn(`Warning: Installing unsigned package ${name}@${targetVersion}`);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
// Determine install path
|
|
1809
|
+
const installPath = options.global
|
|
1810
|
+
? path.join(this.config.installation.globalPath, name)
|
|
1811
|
+
: path.join(this.config.installation.defaultPath, name);
|
|
1812
|
+
|
|
1813
|
+
// Backup existing if updating
|
|
1814
|
+
const existing = await this.get(name);
|
|
1815
|
+
if (existing && this.config.installation.backupBeforeUpdate) {
|
|
1816
|
+
await this.backupAgent(existing);
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
// Unpack
|
|
1820
|
+
const unpackResult = await this.packager.unpack(packagePath, installPath);
|
|
1821
|
+
|
|
1822
|
+
// Create installed agent record
|
|
1823
|
+
const installedAgent: InstalledAgent = {
|
|
1824
|
+
name,
|
|
1825
|
+
version: targetVersion,
|
|
1826
|
+
displayName: unpackResult.manifest.displayName,
|
|
1827
|
+
description: unpackResult.manifest.description,
|
|
1828
|
+
category: unpackResult.manifest.category,
|
|
1829
|
+
installPath,
|
|
1830
|
+
mainPath: path.join(installPath, unpackResult.manifest.main),
|
|
1831
|
+
installedAt: new Date(),
|
|
1832
|
+
installedBy: this.getCurrentUserId(),
|
|
1833
|
+
enabled: true,
|
|
1834
|
+
loaded: false,
|
|
1835
|
+
manifest: unpackResult.manifest,
|
|
1836
|
+
dependencies: await this.getInstalledDependencies(unpackResult.manifest)
|
|
1837
|
+
};
|
|
1838
|
+
|
|
1839
|
+
// Save to lockfile
|
|
1840
|
+
this.installed.set(name, installedAgent);
|
|
1841
|
+
await this.lockfile.add(installedAgent);
|
|
1842
|
+
|
|
1843
|
+
// Track installation
|
|
1844
|
+
await this.reviewSystem.trackUsage(name, {
|
|
1845
|
+
type: 'install',
|
|
1846
|
+
version: targetVersion
|
|
1847
|
+
});
|
|
1848
|
+
|
|
1849
|
+
return installedAgent;
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
async uninstall(name: string): Promise<void> {
|
|
1853
|
+
const agent = await this.get(name);
|
|
1854
|
+
if (!agent) {
|
|
1855
|
+
throw new Error(`Agent not installed: ${name}`);
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
// Check if other agents depend on this one
|
|
1859
|
+
const dependents = await this.findDependents(name);
|
|
1860
|
+
if (dependents.length > 0) {
|
|
1861
|
+
throw new DependencyError(
|
|
1862
|
+
`Cannot uninstall: other agents depend on ${name}`,
|
|
1863
|
+
dependents.map(d => ({ name: d.name, required: d.manifest.dependencies?.[name] || '*' }))
|
|
1864
|
+
);
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
// Unload if loaded
|
|
1868
|
+
if (this.loaded.has(name)) {
|
|
1869
|
+
await this.unload(name);
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
// Remove files
|
|
1873
|
+
await fs.rm(agent.installPath, { recursive: true, force: true });
|
|
1874
|
+
|
|
1875
|
+
// Update records
|
|
1876
|
+
this.installed.delete(name);
|
|
1877
|
+
await this.lockfile.remove(name);
|
|
1878
|
+
|
|
1879
|
+
// Track uninstall
|
|
1880
|
+
await this.reviewSystem.trackUsage(name, {
|
|
1881
|
+
type: 'uninstall',
|
|
1882
|
+
version: agent.version
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
async checkUpdates(): Promise<UpdateInfo[]> {
|
|
1887
|
+
const updates: UpdateInfo[] = [];
|
|
1888
|
+
|
|
1889
|
+
for (const [name, agent] of this.installed) {
|
|
1890
|
+
try {
|
|
1891
|
+
const latestVersion = await this.registry.getLatestVersion(name);
|
|
1892
|
+
|
|
1893
|
+
if (semver.gt(latestVersion, agent.version)) {
|
|
1894
|
+
const diff = semver.diff(agent.version, latestVersion);
|
|
1895
|
+
const agentInfo = await this.registry.get(name, latestVersion);
|
|
1896
|
+
|
|
1897
|
+
updates.push({
|
|
1898
|
+
name,
|
|
1899
|
+
currentVersion: agent.version,
|
|
1900
|
+
latestVersion,
|
|
1901
|
+
updateType: diff as 'major' | 'minor' | 'patch',
|
|
1902
|
+
breaking: diff === 'major',
|
|
1903
|
+
changelog: agentInfo?.changelog
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1906
|
+
} catch (error) {
|
|
1907
|
+
// Skip agents that can't be checked
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
return updates;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
createSandbox(agent: InstalledAgent): AgentSandbox {
|
|
1915
|
+
const permissions = new Set<AgentPermission>(agent.manifest.permissions || []);
|
|
1916
|
+
|
|
1917
|
+
return new AgentSandboxImpl({
|
|
1918
|
+
permissions,
|
|
1919
|
+
limits: {
|
|
1920
|
+
memory: this.config.sandbox.resourceLimits.memory,
|
|
1921
|
+
cpu: this.config.sandbox.resourceLimits.cpu,
|
|
1922
|
+
timeout: this.config.sandbox.resourceLimits.timeout
|
|
1923
|
+
},
|
|
1924
|
+
allowedHosts: this.config.sandbox.allowedHosts,
|
|
1925
|
+
blockedHosts: this.config.sandbox.blockedHosts,
|
|
1926
|
+
isolationLevel: this.config.sandbox.isolationLevel
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
async list(): Promise<InstalledAgent[]> {
|
|
1931
|
+
return Array.from(this.installed.values());
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
async get(name: string): Promise<InstalledAgent | null> {
|
|
1935
|
+
return this.installed.get(name) || null;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
async isInstalled(name: string): Promise<boolean> {
|
|
1939
|
+
return this.installed.has(name);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
```
|
|
1943
|
+
|
|
1944
|
+
---
|
|
1945
|
+
|
|
1946
|
+
## 6. CLI Commands
|
|
1947
|
+
|
|
1948
|
+
### Comandos Disponibles
|
|
1949
|
+
|
|
1950
|
+
```
|
|
1951
|
+
/elsabro:marketplace list [options]
|
|
1952
|
+
Lista agentes del marketplace
|
|
1953
|
+
|
|
1954
|
+
Opciones:
|
|
1955
|
+
--category <cat> Filtrar por categoria
|
|
1956
|
+
--sort <field> Ordenar por: downloads, rating, updated, name
|
|
1957
|
+
--limit <n> Numero maximo de resultados
|
|
1958
|
+
--installed Mostrar solo agentes instalados
|
|
1959
|
+
--updates Mostrar agentes con actualizaciones disponibles
|
|
1960
|
+
|
|
1961
|
+
Ejemplos:
|
|
1962
|
+
/elsabro:marketplace list
|
|
1963
|
+
/elsabro:marketplace list --category testing --sort rating
|
|
1964
|
+
/elsabro:marketplace list --installed --updates
|
|
1965
|
+
```
|
|
1966
|
+
|
|
1967
|
+
```
|
|
1968
|
+
/elsabro:marketplace search <query> [options]
|
|
1969
|
+
Busca agentes en el marketplace
|
|
1970
|
+
|
|
1971
|
+
Opciones:
|
|
1972
|
+
--category <cat> Filtrar por categoria
|
|
1973
|
+
--tags <t1,t2> Filtrar por tags
|
|
1974
|
+
--min-rating <n> Rating minimo (1-5)
|
|
1975
|
+
--verified Solo agentes verificados
|
|
1976
|
+
|
|
1977
|
+
Ejemplos:
|
|
1978
|
+
/elsabro:marketplace search "code review"
|
|
1979
|
+
/elsabro:marketplace search testing --category testing --min-rating 4
|
|
1980
|
+
```
|
|
1981
|
+
|
|
1982
|
+
```
|
|
1983
|
+
/elsabro:marketplace install <name>[@version] [options]
|
|
1984
|
+
Instala un agente del marketplace
|
|
1985
|
+
|
|
1986
|
+
Opciones:
|
|
1987
|
+
--global Instalar globalmente
|
|
1988
|
+
--force Forzar instalacion
|
|
1989
|
+
--skip-deps No instalar dependencias
|
|
1990
|
+
|
|
1991
|
+
Ejemplos:
|
|
1992
|
+
/elsabro:marketplace install elsabro-reviewer
|
|
1993
|
+
/elsabro:marketplace install elsabro-tester@2.1.0 --global
|
|
1994
|
+
```
|
|
1995
|
+
|
|
1996
|
+
```
|
|
1997
|
+
/elsabro:marketplace publish <path> [options]
|
|
1998
|
+
Publica un agente al marketplace
|
|
1999
|
+
|
|
2000
|
+
Opciones:
|
|
2001
|
+
--prerelease Marcar como prerelease
|
|
2002
|
+
--notes <text> Notas de release
|
|
2003
|
+
--sign <keyfile> Firmar con clave privada
|
|
2004
|
+
|
|
2005
|
+
Ejemplos:
|
|
2006
|
+
/elsabro:marketplace publish ./my-agent
|
|
2007
|
+
/elsabro:marketplace publish ./my-agent --sign ~/.keys/private.pem
|
|
2008
|
+
```
|
|
2009
|
+
|
|
2010
|
+
```
|
|
2011
|
+
/elsabro:marketplace info <name>
|
|
2012
|
+
Muestra informacion detallada de un agente
|
|
2013
|
+
|
|
2014
|
+
Ejemplos:
|
|
2015
|
+
/elsabro:marketplace info elsabro-reviewer
|
|
2016
|
+
```
|
|
2017
|
+
|
|
2018
|
+
```
|
|
2019
|
+
/elsabro:marketplace update [name]
|
|
2020
|
+
Actualiza agentes instalados
|
|
2021
|
+
|
|
2022
|
+
Opciones:
|
|
2023
|
+
--all Actualizar todos los agentes
|
|
2024
|
+
--check Solo verificar actualizaciones
|
|
2025
|
+
|
|
2026
|
+
Ejemplos:
|
|
2027
|
+
/elsabro:marketplace update elsabro-reviewer
|
|
2028
|
+
/elsabro:marketplace update --all
|
|
2029
|
+
/elsabro:marketplace update --check
|
|
2030
|
+
```
|
|
2031
|
+
|
|
2032
|
+
```
|
|
2033
|
+
/elsabro:marketplace uninstall <name>
|
|
2034
|
+
Desinstala un agente
|
|
2035
|
+
|
|
2036
|
+
Ejemplos:
|
|
2037
|
+
/elsabro:marketplace uninstall elsabro-reviewer
|
|
2038
|
+
```
|
|
2039
|
+
|
|
2040
|
+
```
|
|
2041
|
+
/elsabro:marketplace review <name>
|
|
2042
|
+
Escribe una review para un agente
|
|
2043
|
+
|
|
2044
|
+
Opciones:
|
|
2045
|
+
--rating <1-5> Rating (requerido)
|
|
2046
|
+
--title <text> Titulo de la review
|
|
2047
|
+
--body <text> Cuerpo de la review
|
|
2048
|
+
|
|
2049
|
+
Ejemplos:
|
|
2050
|
+
/elsabro:marketplace review elsabro-reviewer --rating 5 --title "Excelente"
|
|
2051
|
+
```
|
|
2052
|
+
|
|
2053
|
+
---
|
|
2054
|
+
|
|
2055
|
+
## 7. Marketplace Dashboard
|
|
2056
|
+
|
|
2057
|
+
```
|
|
2058
|
+
+-----------------------------------------------------------------------------+
|
|
2059
|
+
| ELSABRO Agent Marketplace |
|
|
2060
|
+
+-----------------------------------------------------------------------------+
|
|
2061
|
+
| Catalog: 1,247 agents | Installed: 12 | Updates: 3 |
|
|
2062
|
+
| Categories: exploration(342) implementation(456) review(189) |
|
|
2063
|
+
| testing(198) specialized(62) |
|
|
2064
|
+
+-----------------------------------------------------------------------------+
|
|
2065
|
+
|
|
2066
|
+
TRENDING THIS WEEK
|
|
2067
|
+
+------------------------+--------+--------+----------+-----------------------+
|
|
2068
|
+
| Agent | Rating | DLs/wk | Category | Description |
|
|
2069
|
+
+------------------------+--------+--------+----------+-----------------------+
|
|
2070
|
+
| elsabro-code-reviewer | 4.8 | 2,341 | review | AI-powered code review|
|
|
2071
|
+
| elsabro-test-generator | 4.6 | 1,892 | testing | Auto test generation |
|
|
2072
|
+
| elsabro-doc-writer | 4.7 | 1,654 | impl | Documentation gen |
|
|
2073
|
+
| elsabro-security-scan | 4.9 | 1,423 | review | Security analysis |
|
|
2074
|
+
| elsabro-refactorer | 4.5 | 1,201 | impl | Code refactoring |
|
|
2075
|
+
+------------------------+--------+--------+----------+-----------------------+
|
|
2076
|
+
|
|
2077
|
+
INSTALLED AGENTS
|
|
2078
|
+
+------------------------+---------+--------+------------+---------------------+
|
|
2079
|
+
| Agent | Version | Status | Updated | Updates |
|
|
2080
|
+
+------------------------+---------+--------+------------+---------------------+
|
|
2081
|
+
| elsabro-code-reviewer | 2.3.1 | Active | 2024-01-15 | 2.4.0 available |
|
|
2082
|
+
| elsabro-test-generator | 1.8.0 | Active | 2024-01-10 | Up to date |
|
|
2083
|
+
| elsabro-linter | 3.1.2 | Active | 2024-01-08 | 3.2.0 available |
|
|
2084
|
+
| elsabro-formatter | 1.2.0 | Idle | 2024-01-05 | Up to date |
|
|
2085
|
+
| my-custom-agent | 0.1.0 | Dev | 2024-01-18 | Local |
|
|
2086
|
+
+------------------------+---------+--------+------------+---------------------+
|
|
2087
|
+
|
|
2088
|
+
RECENT ACTIVITY
|
|
2089
|
+
+-----------------------------------------------------------------------------+
|
|
2090
|
+
| [15:32] Installed elsabro-doc-writer@2.1.0 |
|
|
2091
|
+
| [14:15] Updated elsabro-test-generator 1.7.0 -> 1.8.0 |
|
|
2092
|
+
| [12:45] Published my-custom-agent@0.1.0 to marketplace |
|
|
2093
|
+
| [11:20] Review submitted for elsabro-code-reviewer (5 stars) |
|
|
2094
|
+
+-----------------------------------------------------------------------------+
|
|
2095
|
+
|
|
2096
|
+
Commands: list | search | install | publish | update | info | review
|
|
2097
|
+
```
|
|
2098
|
+
|
|
2099
|
+
---
|
|
2100
|
+
|
|
2101
|
+
## 8. Agent Package Structure
|
|
2102
|
+
|
|
2103
|
+
```
|
|
2104
|
+
my-agent/
|
|
2105
|
+
+-- agent.manifest.json # Manifest del agente
|
|
2106
|
+
+-- src/
|
|
2107
|
+
| +-- index.ts # Entry point principal
|
|
2108
|
+
| +-- agent.ts # Implementacion del agente
|
|
2109
|
+
| +-- tools/ # Herramientas custom
|
|
2110
|
+
| | +-- my-tool.ts
|
|
2111
|
+
| +-- prompts/ # System prompts
|
|
2112
|
+
| +-- main.md
|
|
2113
|
+
+-- tests/
|
|
2114
|
+
| +-- agent.test.ts # Tests unitarios
|
|
2115
|
+
| +-- integration.test.ts # Tests de integracion
|
|
2116
|
+
+-- docs/
|
|
2117
|
+
| +-- README.md # Documentacion
|
|
2118
|
+
| +-- CHANGELOG.md # Historial de cambios
|
|
2119
|
+
+-- LICENSE
|
|
2120
|
+
+-- package.json # Dependencias npm
|
|
2121
|
+
```
|
|
2122
|
+
|
|
2123
|
+
### Manifest de Ejemplo
|
|
2124
|
+
|
|
2125
|
+
```json
|
|
2126
|
+
{
|
|
2127
|
+
"name": "elsabro-code-reviewer",
|
|
2128
|
+
"version": "2.4.0",
|
|
2129
|
+
"displayName": "Code Reviewer",
|
|
2130
|
+
"description": "AI-powered code review with best practices analysis",
|
|
2131
|
+
"author": {
|
|
2132
|
+
"name": "ELSABRO Team",
|
|
2133
|
+
"email": "team@elsabro.dev",
|
|
2134
|
+
"url": "https://elsabro.dev"
|
|
2135
|
+
},
|
|
2136
|
+
"main": "dist/index.js",
|
|
2137
|
+
"category": "review",
|
|
2138
|
+
"tags": ["code-review", "best-practices", "quality"],
|
|
2139
|
+
"keywords": ["review", "lint", "quality", "analysis"],
|
|
2140
|
+
|
|
2141
|
+
"engines": {
|
|
2142
|
+
"elsabro": ">=3.4.0",
|
|
2143
|
+
"node": ">=18.0.0"
|
|
2144
|
+
},
|
|
2145
|
+
|
|
2146
|
+
"dependencies": {
|
|
2147
|
+
"elsabro-linter": "^3.0.0"
|
|
2148
|
+
},
|
|
2149
|
+
|
|
2150
|
+
"permissions": [
|
|
2151
|
+
"filesystem:read",
|
|
2152
|
+
"config:read"
|
|
2153
|
+
],
|
|
2154
|
+
|
|
2155
|
+
"contributes": {
|
|
2156
|
+
"tools": [
|
|
2157
|
+
{
|
|
2158
|
+
"name": "analyze-code",
|
|
2159
|
+
"displayName": "Analyze Code",
|
|
2160
|
+
"description": "Perform deep code analysis",
|
|
2161
|
+
"inputSchema": {
|
|
2162
|
+
"type": "object",
|
|
2163
|
+
"properties": {
|
|
2164
|
+
"path": { "type": "string" },
|
|
2165
|
+
"depth": { "type": "string", "enum": ["shallow", "deep"] }
|
|
2166
|
+
},
|
|
2167
|
+
"required": ["path"]
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
],
|
|
2171
|
+
"commands": [
|
|
2172
|
+
{
|
|
2173
|
+
"command": "review.start",
|
|
2174
|
+
"title": "Start Code Review",
|
|
2175
|
+
"category": "Review"
|
|
2176
|
+
}
|
|
2177
|
+
]
|
|
2178
|
+
},
|
|
2179
|
+
|
|
2180
|
+
"license": "MIT",
|
|
2181
|
+
"repository": "https://github.com/elsabro/code-reviewer",
|
|
2182
|
+
"homepage": "https://elsabro.dev/agents/code-reviewer",
|
|
2183
|
+
"bugs": "https://github.com/elsabro/code-reviewer/issues"
|
|
2184
|
+
}
|
|
2185
|
+
```
|
|
2186
|
+
|
|
2187
|
+
---
|
|
2188
|
+
|
|
2189
|
+
## 9. Security Model
|
|
2190
|
+
|
|
2191
|
+
### Niveles de Aislamiento
|
|
2192
|
+
|
|
2193
|
+
```
|
|
2194
|
+
+-----------------------------------------------------------------------------+
|
|
2195
|
+
| Sandbox Isolation Levels |
|
|
2196
|
+
+-----------------------------------------------------------------------------+
|
|
2197
|
+
|
|
2198
|
+
STRICT (Default para agentes de terceros)
|
|
2199
|
+
+----------------------------------+
|
|
2200
|
+
| * VM aislada |
|
|
2201
|
+
| * Sin acceso a filesystem |
|
|
2202
|
+
| * Network solo whitelist |
|
|
2203
|
+
| * Sin shell |
|
|
2204
|
+
| * Memory/CPU limitados |
|
|
2205
|
+
+----------------------------------+
|
|
2206
|
+
|
|
2207
|
+
MODERATE (Agentes verificados)
|
|
2208
|
+
+----------------------------------+
|
|
2209
|
+
| * Proceso separado |
|
|
2210
|
+
| * Filesystem read-only |
|
|
2211
|
+
| * Network con restricciones |
|
|
2212
|
+
| * Shell denegado |
|
|
2213
|
+
| * Limits menos estrictos |
|
|
2214
|
+
+----------------------------------+
|
|
2215
|
+
|
|
2216
|
+
TRUSTED (Agentes oficiales)
|
|
2217
|
+
+----------------------------------+
|
|
2218
|
+
| * Mismo proceso |
|
|
2219
|
+
| * Filesystem segun permisos |
|
|
2220
|
+
| * Network permitido |
|
|
2221
|
+
| * Shell con restricciones |
|
|
2222
|
+
| * Sin limits de recursos |
|
|
2223
|
+
+----------------------------------+
|
|
2224
|
+
```
|
|
2225
|
+
|
|
2226
|
+
### Permission Flow
|
|
2227
|
+
|
|
2228
|
+
```
|
|
2229
|
+
+-------------------+
|
|
2230
|
+
| Agent requests |
|
|
2231
|
+
| permission |
|
|
2232
|
+
+---------+---------+
|
|
2233
|
+
|
|
|
2234
|
+
+---------v---------+
|
|
2235
|
+
| Check manifest |
|
|
2236
|
+
| declarations |
|
|
2237
|
+
+---------+---------+
|
|
2238
|
+
|
|
|
2239
|
+
+---------------+---------------+
|
|
2240
|
+
| |
|
|
2241
|
+
+---------v---------+ +---------v---------+
|
|
2242
|
+
| Declared in | | Not declared |
|
|
2243
|
+
| manifest | | |
|
|
2244
|
+
+---------+---------+ +---------+---------+
|
|
2245
|
+
| |
|
|
2246
|
+
+---------v---------+ |
|
|
2247
|
+
| User approved? | |
|
|
2248
|
+
+---------+---------+ |
|
|
2249
|
+
| |
|
|
2250
|
+
+-------+-------+ |
|
|
2251
|
+
| | |
|
|
2252
|
+
+-----v-----+ +-----v-----+ +-----v-----+
|
|
2253
|
+
| GRANTED | | DENIED | | DENIED |
|
|
2254
|
+
+-----------+ +-----------+ +-----------+
|
|
2255
|
+
```
|
|
2256
|
+
|
|
2257
|
+
---
|
|
2258
|
+
|
|
2259
|
+
## Referencias
|
|
2260
|
+
|
|
2261
|
+
- **REF-024**: Plugin System (base architecture)
|
|
2262
|
+
- **REF-022**: Security System (sandbox implementation)
|
|
2263
|
+
- **REF-023**: Configuration Management
|
|
2264
|
+
- **REF-025**: Esta referencia (Agent Marketplace)
|
|
2265
|
+
|
|
2266
|
+
## Changelog
|
|
2267
|
+
|
|
2268
|
+
### v3.6.0
|
|
2269
|
+
- Initial marketplace implementation
|
|
2270
|
+
- Registry with search and filtering
|
|
2271
|
+
- Package signing and verification
|
|
2272
|
+
- Publishing pipeline with automated review
|
|
2273
|
+
- Rating and review system
|
|
2274
|
+
- Sandboxed execution for third-party agents
|