loki-mode 5.51.0 → 5.52.1

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 (44) hide show
  1. package/README.md +4 -56
  2. package/SKILL.md +2 -2
  3. package/VERSION +1 -1
  4. package/autonomy/hooks/validate-bash.sh +5 -2
  5. package/dashboard/__init__.py +1 -1
  6. package/dashboard/server.py +1 -1
  7. package/docs/INSTALLATION.md +1 -1
  8. package/docs/alternative-installations.md +3 -3
  9. package/docs/certification/01-core-concepts/lab.md +174 -0
  10. package/docs/certification/01-core-concepts/lesson.md +182 -0
  11. package/docs/certification/01-core-concepts/quiz.md +93 -0
  12. package/docs/certification/02-enterprise-features/lab.md +154 -0
  13. package/docs/certification/02-enterprise-features/lesson.md +202 -0
  14. package/docs/certification/02-enterprise-features/quiz.md +93 -0
  15. package/docs/certification/03-advanced-patterns/lab.md +138 -0
  16. package/docs/certification/03-advanced-patterns/lesson.md +199 -0
  17. package/docs/certification/03-advanced-patterns/quiz.md +93 -0
  18. package/docs/certification/04-production-deployment/lab.md +160 -0
  19. package/docs/certification/04-production-deployment/lesson.md +261 -0
  20. package/docs/certification/04-production-deployment/quiz.md +93 -0
  21. package/docs/certification/05-troubleshooting/lab.md +254 -0
  22. package/docs/certification/05-troubleshooting/lesson.md +266 -0
  23. package/docs/certification/05-troubleshooting/quiz.md +93 -0
  24. package/docs/certification/README.md +80 -0
  25. package/docs/certification/answer-key.md +117 -0
  26. package/docs/certification/certification-exam.md +471 -0
  27. package/docs/certification/sample-prds/microservices-platform.md +100 -0
  28. package/docs/certification/sample-prds/saas-dashboard.md +60 -0
  29. package/docs/certification/sample-prds/todo-app.md +44 -0
  30. package/mcp/__init__.py +1 -1
  31. package/mcp/server.py +230 -0
  32. package/package.json +1 -1
  33. package/src/plugins/agent-plugin.js +123 -0
  34. package/src/plugins/gate-plugin.js +153 -0
  35. package/src/plugins/index.js +116 -0
  36. package/src/plugins/integration-plugin.js +174 -0
  37. package/src/plugins/loader.js +275 -0
  38. package/src/plugins/mcp-plugin.js +190 -0
  39. package/src/plugins/schemas/agent.json +59 -0
  40. package/src/plugins/schemas/integration.json +62 -0
  41. package/src/plugins/schemas/mcp_tool.json +73 -0
  42. package/src/plugins/schemas/quality_gate.json +52 -0
  43. package/src/plugins/validator.js +297 -0
  44. /package/dashboard/{secrets.py → app_secrets.py} +0 -0
@@ -0,0 +1,100 @@
1
+ # Microservices Platform PRD
2
+
3
+ **Complexity Tier:** Complex (10+ files, multiple services, external integrations)
4
+
5
+ ## Overview
6
+
7
+ A multi-service e-commerce platform with independent microservices communicating via message queues. Includes user management, product catalog, order processing, payment handling, and notification delivery. Deployed on Kubernetes with monitoring and observability.
8
+
9
+ ## Services
10
+
11
+ ### User Service
12
+ - Registration and authentication (OAuth2 + JWT)
13
+ - User profile management
14
+ - Role-based access control (admin, seller, buyer)
15
+ - Rate limiting on auth endpoints
16
+
17
+ ### Product Service
18
+ - CRUD operations for products
19
+ - Category and tag management
20
+ - Full-text search with Elasticsearch
21
+ - Image upload to S3-compatible storage
22
+ - Inventory tracking with optimistic locking
23
+
24
+ ### Order Service
25
+ - Shopping cart management (Redis-backed)
26
+ - Order creation with inventory reservation
27
+ - Order status tracking (created, paid, shipped, delivered, cancelled)
28
+ - Saga pattern for distributed transactions
29
+
30
+ ### Payment Service
31
+ - Stripe integration for payment processing
32
+ - Webhook handling for payment events
33
+ - Refund processing
34
+ - Payment status reconciliation
35
+
36
+ ### Notification Service
37
+ - Email notifications (order confirmation, shipping updates)
38
+ - Event-driven via message queue consumption
39
+ - Template-based email rendering
40
+ - Delivery status tracking
41
+
42
+ ## Architecture
43
+
44
+ ### Communication
45
+ - Synchronous: REST APIs between services (via API gateway)
46
+ - Asynchronous: RabbitMQ message queues for event-driven flows
47
+ - Events: OrderCreated, PaymentCompleted, OrderShipped, UserRegistered
48
+
49
+ ### Data Storage
50
+ - PostgreSQL per service (database-per-service pattern)
51
+ - Redis for session storage and shopping cart
52
+ - Elasticsearch for product search
53
+ - S3-compatible object storage for images
54
+
55
+ ### Infrastructure
56
+ - Docker containers for all services
57
+ - Kubernetes manifests (Deployment, Service, Ingress, ConfigMap, Secret)
58
+ - Helm chart for parameterized deployment
59
+ - Health check endpoints per service (`/health`, `/ready`)
60
+ - Horizontal Pod Autoscaler based on CPU/request metrics
61
+
62
+ ## Tech Stack
63
+
64
+ - **Services:** Node.js with TypeScript, Express
65
+ - **Database:** PostgreSQL with Prisma ORM
66
+ - **Cache:** Redis
67
+ - **Search:** Elasticsearch
68
+ - **Queue:** RabbitMQ
69
+ - **Payments:** Stripe SDK
70
+ - **Container:** Docker
71
+ - **Orchestration:** Kubernetes
72
+ - **Monitoring:** Prometheus metrics endpoints, Grafana dashboards
73
+
74
+ ## Non-Functional Requirements
75
+
76
+ - Service response time < 200ms (p99)
77
+ - Message queue processing latency < 500ms
78
+ - 99.9% uptime target
79
+ - Zero-downtime deployments (rolling updates)
80
+ - Unit test coverage > 80% per service
81
+ - Integration tests for cross-service flows
82
+ - E2E test for complete purchase flow
83
+ - Security: OWASP Top 10, secret management via K8s Secrets, network policies
84
+
85
+ ## Success Criteria
86
+
87
+ - All 5 services start and pass health checks
88
+ - User can register, browse products, add to cart, and place an order
89
+ - Payment webhook processes correctly (requires Stripe test keys)
90
+ - Notifications are queued and sent on order events
91
+ - Kubernetes manifests deploy all services
92
+ - Prometheus endpoints expose metrics
93
+ - All unit and integration tests pass
94
+ - No Critical or High security findings
95
+
96
+ ## Notes
97
+
98
+ - Stripe integration requires a test API key (`STRIPE_TEST_KEY`). Mark as "requires provider API key" if not available.
99
+ - Elasticsearch and RabbitMQ require running instances. Docker Compose is provided for local development.
100
+ - Kubernetes deployment requires a cluster (minikube, kind, or cloud provider).
@@ -0,0 +1,60 @@
1
+ # SaaS Analytics Dashboard PRD
2
+
3
+ **Complexity Tier:** Standard (3-10 files, features with auth and database)
4
+
5
+ ## Overview
6
+
7
+ A web-based analytics dashboard for a SaaS product. Users can sign up, log in, view charts of their usage metrics, and manage their account settings.
8
+
9
+ ## Requirements
10
+
11
+ ### Authentication
12
+ - Email/password registration with bcrypt hashing
13
+ - Login with JWT token issuance
14
+ - Protected routes requiring valid JWT
15
+ - Password reset flow (email-based)
16
+
17
+ ### Dashboard
18
+ - Overview page with key metrics (active users, revenue, API calls)
19
+ - Line chart showing daily active users over 30 days
20
+ - Bar chart showing API call volume by endpoint
21
+ - Date range selector for filtering data
22
+
23
+ ### API
24
+ - RESTful API with OpenAPI specification
25
+ - `POST /api/auth/register` -- Create account
26
+ - `POST /api/auth/login` -- Authenticate and receive JWT
27
+ - `GET /api/metrics/overview` -- Key metrics summary
28
+ - `GET /api/metrics/users?range=30d` -- User activity data
29
+ - `GET /api/metrics/api-calls?range=30d` -- API call data
30
+ - `GET /api/account` -- Account details
31
+ - `PUT /api/account` -- Update account settings
32
+
33
+ ### Database
34
+ - PostgreSQL for persistent storage
35
+ - Users table (id, email, password_hash, created_at)
36
+ - Metrics table (id, user_id, metric_type, value, recorded_at)
37
+ - Database migrations for schema management
38
+
39
+ ## Tech Stack
40
+
41
+ - **Frontend:** React with TypeScript, Tailwind CSS, Recharts for charts
42
+ - **Backend:** Node.js with Express, TypeScript
43
+ - **Database:** PostgreSQL with Knex.js for migrations
44
+ - **Auth:** bcrypt + JWT
45
+
46
+ ## Non-Functional Requirements
47
+
48
+ - API response time < 200ms for all endpoints
49
+ - Frontend loads in < 3 seconds on 3G connection
50
+ - Unit test coverage > 80%
51
+ - Integration tests for auth flow and API endpoints
52
+ - OWASP Top 10 compliance (SQL injection prevention, XSS protection, CSRF tokens)
53
+
54
+ ## Success Criteria
55
+
56
+ - User can register, log in, and view dashboard
57
+ - Charts render with sample data
58
+ - All API endpoints match OpenAPI spec
59
+ - Unit and integration tests pass
60
+ - No Critical or High security findings in review
@@ -0,0 +1,44 @@
1
+ # Todo App PRD
2
+
3
+ **Complexity Tier:** Simple (1-2 files, basic CRUD)
4
+
5
+ ## Overview
6
+
7
+ A command-line todo application written in Node.js. No external dependencies, no database, no authentication.
8
+
9
+ ## Requirements
10
+
11
+ ### Functional
12
+ - Add a todo item with a title (string, max 200 characters)
13
+ - List all todo items with their status (pending/complete)
14
+ - Mark a todo item as complete by ID
15
+ - Delete a todo item by ID
16
+ - Each todo has: id (auto-generated UUID), title, status, createdAt timestamp
17
+
18
+ ### Non-Functional
19
+ - Store todos in a local JSON file (`todos.json`)
20
+ - Data persists between runs
21
+ - CLI responds in under 100ms for all operations
22
+ - No external npm dependencies (use built-in `fs`, `crypto`, `path` modules)
23
+
24
+ ## Tech Stack
25
+
26
+ - Node.js (built-in modules only)
27
+ - JSON file storage
28
+
29
+ ## CLI Interface
30
+
31
+ ```bash
32
+ todo add "Buy groceries" # Add a new todo
33
+ todo list # List all todos
34
+ todo done <id> # Mark as complete
35
+ todo delete <id> # Delete a todo
36
+ ```
37
+
38
+ ## Success Criteria
39
+
40
+ - All 4 CRUD operations work correctly
41
+ - Data persists in `todos.json` between runs
42
+ - Unit tests pass with >80% coverage
43
+ - No external dependencies in `package.json`
44
+ - Error handling for: missing title, invalid ID, file read/write failures
package/mcp/__init__.py CHANGED
@@ -21,4 +21,4 @@ try:
21
21
  except ImportError:
22
22
  __all__ = ['mcp']
23
23
 
24
- __version__ = '5.51.0'
24
+ __version__ = '5.52.1'
package/mcp/server.py CHANGED
@@ -979,6 +979,236 @@ async def get_pending_tasks() -> str:
979
979
  return json.dumps({"error": "Access denied", "pending_tasks": [], "count": 0})
980
980
 
981
981
 
982
+ # ============================================================
983
+ # ENTERPRISE TOOLS (P0-1)
984
+ # ============================================================
985
+
986
+ @mcp.tool()
987
+ async def loki_start_project(prd_content: str = "", prd_path: str = "") -> str:
988
+ """
989
+ Start a new Loki Mode project from a PRD.
990
+
991
+ Args:
992
+ prd_content: Inline PRD content (takes priority over prd_path)
993
+ prd_path: Path to a PRD file on disk
994
+
995
+ Returns:
996
+ JSON with project initialization status
997
+ """
998
+ _emit_tool_event_async('loki_start_project', 'start', parameters={'prd_path': prd_path})
999
+ try:
1000
+ content = prd_content
1001
+ if not content and prd_path:
1002
+ resolved = safe_path_join('.', prd_path)
1003
+ if os.path.exists(resolved):
1004
+ with safe_open(resolved, 'r') as f:
1005
+ content = f.read()
1006
+ else:
1007
+ return json.dumps({"error": f"PRD file not found: {prd_path}"})
1008
+
1009
+ if not content:
1010
+ return json.dumps({"error": "No PRD content or path provided"})
1011
+
1012
+ # Initialize project state
1013
+ os.makedirs('.loki/state', exist_ok=True)
1014
+ project = {
1015
+ "status": "initialized",
1016
+ "prd_length": len(content),
1017
+ "prd_path": prd_path or "inline",
1018
+ "created_at": datetime.now(timezone.utc).isoformat(),
1019
+ }
1020
+ state_path = safe_path_join('.loki', 'state', 'project.json')
1021
+ with safe_open(state_path, 'w') as f:
1022
+ json.dump(project, f, indent=2)
1023
+
1024
+ _emit_tool_event_async('loki_start_project', 'complete', result_status='success')
1025
+ return json.dumps({"success": True, **project})
1026
+ except PathTraversalError as e:
1027
+ return json.dumps({"error": f"Access denied: {e}"})
1028
+ except Exception as e:
1029
+ logger.error(f"Start project failed: {e}")
1030
+ _emit_tool_event_async('loki_start_project', 'complete', result_status='error', error=str(e))
1031
+ return json.dumps({"error": str(e)})
1032
+
1033
+
1034
+ @mcp.tool()
1035
+ async def loki_project_status() -> str:
1036
+ """
1037
+ Get the current project status including RARV cycle state, agent activity, and task progress.
1038
+
1039
+ Returns:
1040
+ JSON with project status, phase, iteration, agents, and task counts
1041
+ """
1042
+ _emit_tool_event_async('loki_project_status', 'start', parameters={})
1043
+ try:
1044
+ status = {}
1045
+
1046
+ # Read orchestrator state
1047
+ orch_path = safe_path_join('.loki', 'state', 'orchestrator.json')
1048
+ if os.path.exists(orch_path):
1049
+ with safe_open(orch_path, 'r') as f:
1050
+ status["orchestrator"] = json.load(f)
1051
+
1052
+ # Read project state
1053
+ proj_path = safe_path_join('.loki', 'state', 'project.json')
1054
+ if os.path.exists(proj_path):
1055
+ with safe_open(proj_path, 'r') as f:
1056
+ status["project"] = json.load(f)
1057
+
1058
+ # Read task queue summary
1059
+ queue_path = safe_path_join('.loki', 'state', 'task-queue.json')
1060
+ if os.path.exists(queue_path):
1061
+ with safe_open(queue_path, 'r') as f:
1062
+ queue = json.load(f)
1063
+ tasks = queue.get("tasks", [])
1064
+ status["tasks"] = {
1065
+ "total": len(tasks),
1066
+ "pending": sum(1 for t in tasks if t.get("status") == "pending"),
1067
+ "in_progress": sum(1 for t in tasks if t.get("status") == "in-progress"),
1068
+ "completed": sum(1 for t in tasks if t.get("status") == "completed"),
1069
+ }
1070
+
1071
+ if not status:
1072
+ status = {"status": "no_project", "message": "No active project found"}
1073
+
1074
+ _emit_tool_event_async('loki_project_status', 'complete', result_status='success')
1075
+ return json.dumps(status, default=str)
1076
+ except PathTraversalError:
1077
+ return json.dumps({"error": "Access denied"})
1078
+ except Exception as e:
1079
+ logger.error(f"Project status failed: {e}")
1080
+ _emit_tool_event_async('loki_project_status', 'complete', result_status='error', error=str(e))
1081
+ return json.dumps({"error": str(e)})
1082
+
1083
+
1084
+ @mcp.tool()
1085
+ async def loki_agent_metrics() -> str:
1086
+ """
1087
+ Get agent metrics including token usage, task completion rates, and timing.
1088
+
1089
+ Returns:
1090
+ JSON with per-agent metrics and aggregates
1091
+ """
1092
+ _emit_tool_event_async('loki_agent_metrics', 'start', parameters={})
1093
+ try:
1094
+ metrics = {"agents": [], "aggregate": {}}
1095
+
1096
+ # Read efficiency metrics
1097
+ metrics_dir = safe_path_join('.loki', 'metrics', 'efficiency')
1098
+ if os.path.isdir(metrics_dir):
1099
+ for fname in os.listdir(metrics_dir):
1100
+ if fname.endswith('.json'):
1101
+ fpath = os.path.join(metrics_dir, fname)
1102
+ with open(fpath, 'r') as f:
1103
+ metrics["agents"].append(json.load(f))
1104
+
1105
+ # Read token economics
1106
+ econ_path = safe_path_join('.loki', 'metrics', 'token-economics.json')
1107
+ if os.path.exists(econ_path):
1108
+ with safe_open(econ_path, 'r') as f:
1109
+ metrics["token_economics"] = json.load(f)
1110
+
1111
+ metrics["agent_count"] = len(metrics["agents"])
1112
+ _emit_tool_event_async('loki_agent_metrics', 'complete', result_status='success')
1113
+ return json.dumps(metrics, default=str)
1114
+ except PathTraversalError:
1115
+ return json.dumps({"error": "Access denied"})
1116
+ except Exception as e:
1117
+ logger.error(f"Agent metrics failed: {e}")
1118
+ _emit_tool_event_async('loki_agent_metrics', 'complete', result_status='error', error=str(e))
1119
+ return json.dumps({"error": str(e)})
1120
+
1121
+
1122
+ @mcp.tool()
1123
+ async def loki_checkpoint_restore(checkpoint_id: str = "") -> str:
1124
+ """
1125
+ List available checkpoints or restore project state from a specific checkpoint.
1126
+
1127
+ Args:
1128
+ checkpoint_id: ID of checkpoint to restore (empty = list all)
1129
+
1130
+ Returns:
1131
+ JSON with available checkpoints or restoration result
1132
+ """
1133
+ _emit_tool_event_async('loki_checkpoint_restore', 'start', parameters={'checkpoint_id': checkpoint_id})
1134
+ try:
1135
+ cp_dir = safe_path_join('.loki', 'state', 'checkpoints')
1136
+ if not os.path.isdir(cp_dir):
1137
+ return json.dumps({"checkpoints": [], "message": "No checkpoints directory"})
1138
+
1139
+ checkpoints = []
1140
+ for fname in sorted(os.listdir(cp_dir)):
1141
+ if fname.endswith('.json'):
1142
+ fpath = os.path.join(cp_dir, fname)
1143
+ with open(fpath, 'r') as f:
1144
+ cp = json.load(f)
1145
+ cp["id"] = fname.replace('.json', '')
1146
+ checkpoints.append(cp)
1147
+
1148
+ if not checkpoint_id:
1149
+ _emit_tool_event_async('loki_checkpoint_restore', 'complete', result_status='success')
1150
+ return json.dumps({"checkpoints": checkpoints, "count": len(checkpoints)})
1151
+
1152
+ # Find and restore specific checkpoint
1153
+ target = next((c for c in checkpoints if c["id"] == checkpoint_id), None)
1154
+ if not target:
1155
+ return json.dumps({"error": f"Checkpoint not found: {checkpoint_id}"})
1156
+
1157
+ # Write checkpoint state as current state
1158
+ state_path = safe_path_join('.loki', 'state', 'orchestrator.json')
1159
+ with safe_open(state_path, 'w') as f:
1160
+ json.dump(target, f, indent=2)
1161
+
1162
+ _emit_tool_event_async('loki_checkpoint_restore', 'complete', result_status='success')
1163
+ return json.dumps({"restored": True, "checkpoint_id": checkpoint_id})
1164
+ except PathTraversalError:
1165
+ return json.dumps({"error": "Access denied"})
1166
+ except Exception as e:
1167
+ logger.error(f"Checkpoint restore failed: {e}")
1168
+ _emit_tool_event_async('loki_checkpoint_restore', 'complete', result_status='error', error=str(e))
1169
+ return json.dumps({"error": str(e)})
1170
+
1171
+
1172
+ @mcp.tool()
1173
+ async def loki_quality_report() -> str:
1174
+ """
1175
+ Get quality gate results including blind review scores, council verdicts, and test coverage.
1176
+
1177
+ Returns:
1178
+ JSON with quality gate status, review results, and coverage metrics
1179
+ """
1180
+ _emit_tool_event_async('loki_quality_report', 'start', parameters={})
1181
+ try:
1182
+ report = {"gates": [], "council": None, "coverage": None}
1183
+
1184
+ # Read quality gate results
1185
+ gates_path = safe_path_join('.loki', 'state', 'quality-gates.json')
1186
+ if os.path.exists(gates_path):
1187
+ with safe_open(gates_path, 'r') as f:
1188
+ report["gates"] = json.load(f)
1189
+
1190
+ # Read council results
1191
+ council_path = safe_path_join('.loki', 'state', 'council-results.json')
1192
+ if os.path.exists(council_path):
1193
+ with safe_open(council_path, 'r') as f:
1194
+ report["council"] = json.load(f)
1195
+
1196
+ # Read coverage
1197
+ coverage_path = safe_path_join('.loki', 'metrics', 'coverage.json')
1198
+ if os.path.exists(coverage_path):
1199
+ with safe_open(coverage_path, 'r') as f:
1200
+ report["coverage"] = json.load(f)
1201
+
1202
+ _emit_tool_event_async('loki_quality_report', 'complete', result_status='success')
1203
+ return json.dumps(report, default=str)
1204
+ except PathTraversalError:
1205
+ return json.dumps({"error": "Access denied"})
1206
+ except Exception as e:
1207
+ logger.error(f"Quality report failed: {e}")
1208
+ _emit_tool_event_async('loki_quality_report', 'complete', result_status='error', error=str(e))
1209
+ return json.dumps({"error": str(e)})
1210
+
1211
+
982
1212
  # ============================================================
983
1213
  # PROMPTS - Pre-built prompt templates
984
1214
  # ============================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "5.51.0",
3
+ "version": "5.52.1",
4
4
  "description": "Loki Mode by Autonomi - Multi-agent autonomous startup system for Claude Code, Codex CLI, and Gemini CLI",
5
5
  "keywords": [
6
6
  "autonomi",
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ const { BUILTIN_AGENT_NAMES } = require('./validator');
4
+
5
+ // In-memory registry for custom agent plugins
6
+ const _registeredAgents = new Map();
7
+
8
+ class AgentPlugin {
9
+ /**
10
+ * Register a custom agent plugin.
11
+ * Cannot override built-in agent types (additive only).
12
+ *
13
+ * @param {object} pluginConfig - Validated agent plugin config
14
+ * @param {object} [registry] - Optional external registry to also register into
15
+ * @returns {{ success: boolean, error?: string }}
16
+ */
17
+ static register(pluginConfig, registry) {
18
+ if (!pluginConfig || pluginConfig.type !== 'agent') {
19
+ return { success: false, error: 'Invalid plugin config: type must be "agent"' };
20
+ }
21
+
22
+ const name = pluginConfig.name;
23
+
24
+ // Prevent overriding built-in agents
25
+ if (BUILTIN_AGENT_NAMES.includes(name)) {
26
+ return {
27
+ success: false,
28
+ error: `Cannot override built-in agent type: "${name}"`,
29
+ };
30
+ }
31
+
32
+ // Check for duplicate custom registration
33
+ if (_registeredAgents.has(name)) {
34
+ return {
35
+ success: false,
36
+ error: `Agent plugin "${name}" is already registered`,
37
+ };
38
+ }
39
+
40
+ // Build agent definition compatible with swarm registry format
41
+ const agentDef = {
42
+ name: name,
43
+ type: 'custom',
44
+ category: pluginConfig.category || 'custom',
45
+ description: pluginConfig.description,
46
+ prompt_template: pluginConfig.prompt_template,
47
+ trigger: pluginConfig.trigger || null,
48
+ quality_gate: pluginConfig.quality_gate || false,
49
+ capabilities: pluginConfig.capabilities || [],
50
+ registered_at: new Date().toISOString(),
51
+ };
52
+
53
+ _registeredAgents.set(name, agentDef);
54
+
55
+ // If an external registry object is provided, add to its custom category
56
+ if (registry && typeof registry === 'object') {
57
+ if (registry.customAgents) {
58
+ registry.customAgents[name] = agentDef;
59
+ }
60
+ }
61
+
62
+ return { success: true };
63
+ }
64
+
65
+ /**
66
+ * Unregister a custom agent plugin.
67
+ *
68
+ * @param {string} pluginName - Name of the agent plugin to remove
69
+ * @param {object} [registry] - Optional external registry to also remove from
70
+ * @returns {{ success: boolean, error?: string }}
71
+ */
72
+ static unregister(pluginName, registry) {
73
+ if (!_registeredAgents.has(pluginName)) {
74
+ return { success: false, error: `Agent plugin "${pluginName}" is not registered` };
75
+ }
76
+
77
+ _registeredAgents.delete(pluginName);
78
+
79
+ if (registry && registry.customAgents) {
80
+ delete registry.customAgents[pluginName];
81
+ }
82
+
83
+ return { success: true };
84
+ }
85
+
86
+ /**
87
+ * List all registered custom agent plugins.
88
+ *
89
+ * @returns {object[]} Array of agent definitions
90
+ */
91
+ static listRegistered() {
92
+ return Array.from(_registeredAgents.values());
93
+ }
94
+
95
+ /**
96
+ * Get a specific registered agent plugin by name.
97
+ *
98
+ * @param {string} name - Agent plugin name
99
+ * @returns {object|null} Agent definition or null
100
+ */
101
+ static get(name) {
102
+ return _registeredAgents.get(name) || null;
103
+ }
104
+
105
+ /**
106
+ * Check if a custom agent is registered.
107
+ *
108
+ * @param {string} name - Agent name
109
+ * @returns {boolean}
110
+ */
111
+ static isRegistered(name) {
112
+ return _registeredAgents.has(name);
113
+ }
114
+
115
+ /**
116
+ * Clear all registered custom agents (primarily for testing).
117
+ */
118
+ static _clearAll() {
119
+ _registeredAgents.clear();
120
+ }
121
+ }
122
+
123
+ module.exports = { AgentPlugin };