dank-ai 1.0.41 → 1.0.45
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 +490 -1365
- package/docker/entrypoint.js +48 -32
- package/lib/agent.js +80 -0
- package/lib/cli/init.js +25 -1
- package/lib/cli/production-build.js +3 -1
- package/lib/cli/run.js +3 -1
- package/lib/docker/manager.js +193 -11
- package/lib/index.js +8 -0
- package/lib/plugins/base.js +324 -0
- package/lib/plugins/config.js +171 -0
- package/lib/plugins/events.js +186 -0
- package/lib/plugins/index.js +29 -0
- package/lib/plugins/manager.js +258 -0
- package/lib/plugins/registry.js +268 -0
- package/lib/project.js +15 -8
- package/package.json +1 -1
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Configuration - Schema validation and management
|
|
3
|
+
*
|
|
4
|
+
* Provides Joi-based schema validation for plugin configurations,
|
|
5
|
+
* environment variable injection, and secret management.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const Joi = require('joi');
|
|
9
|
+
|
|
10
|
+
class PluginConfig {
|
|
11
|
+
/**
|
|
12
|
+
* Validate plugin configuration against schema
|
|
13
|
+
*/
|
|
14
|
+
static validate(name, config, schema) {
|
|
15
|
+
if (!schema) {
|
|
16
|
+
// No schema provided, just return config as-is
|
|
17
|
+
return config;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { error, value } = schema.validate(config, {
|
|
21
|
+
stripUnknown: true,
|
|
22
|
+
abortEarly: false
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (error) {
|
|
26
|
+
const details = error.details.map(d => d.message).join(', ');
|
|
27
|
+
throw new Error(`Invalid configuration for plugin '${name}': ${details}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Inject environment variables into config
|
|
35
|
+
* Replaces ${ENV_VAR} or ${ENV_VAR:default} patterns
|
|
36
|
+
*/
|
|
37
|
+
static injectEnvVars(config) {
|
|
38
|
+
if (typeof config === 'string') {
|
|
39
|
+
return this._replaceEnvVars(config);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (Array.isArray(config)) {
|
|
43
|
+
return config.map(item => this.injectEnvVars(item));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (config && typeof config === 'object') {
|
|
47
|
+
const result = {};
|
|
48
|
+
for (const [key, value] of Object.entries(config)) {
|
|
49
|
+
result[key] = this.injectEnvVars(value);
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return config;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Replace environment variable patterns in string
|
|
59
|
+
*/
|
|
60
|
+
static _replaceEnvVars(str) {
|
|
61
|
+
if (typeof str !== 'string') {
|
|
62
|
+
return str;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Match ${ENV_VAR} or ${ENV_VAR:default}
|
|
66
|
+
return str.replace(/\$\{([^}:]+)(?::([^}]+))?\}/g, (match, envVar, defaultValue) => {
|
|
67
|
+
const value = process.env[envVar];
|
|
68
|
+
if (value !== undefined) {
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
if (defaultValue !== undefined) {
|
|
72
|
+
return defaultValue;
|
|
73
|
+
}
|
|
74
|
+
return match; // Keep original if no replacement found
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Common configuration schemas for plugin types
|
|
80
|
+
*/
|
|
81
|
+
static schemas = {
|
|
82
|
+
/**
|
|
83
|
+
* Database connection schema
|
|
84
|
+
*/
|
|
85
|
+
database: Joi.object({
|
|
86
|
+
connectionString: Joi.string().optional(),
|
|
87
|
+
host: Joi.string().optional(),
|
|
88
|
+
port: Joi.number().optional(),
|
|
89
|
+
database: Joi.string().optional(),
|
|
90
|
+
username: Joi.string().optional(),
|
|
91
|
+
password: Joi.string().optional(),
|
|
92
|
+
poolSize: Joi.number().min(1).max(100).default(10),
|
|
93
|
+
timeout: Joi.number().min(1000).default(30000),
|
|
94
|
+
ssl: Joi.boolean().default(false)
|
|
95
|
+
}).or('connectionString', 'host'),
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* API key schema
|
|
99
|
+
*/
|
|
100
|
+
apiKey: Joi.object({
|
|
101
|
+
apiKey: Joi.string().required(),
|
|
102
|
+
baseURL: Joi.string().uri().optional(),
|
|
103
|
+
timeout: Joi.number().min(1000).default(30000),
|
|
104
|
+
retries: Joi.number().min(0).max(5).default(2)
|
|
105
|
+
}),
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Vector database schema
|
|
109
|
+
*/
|
|
110
|
+
vectorDB: Joi.object({
|
|
111
|
+
apiKey: Joi.string().required(),
|
|
112
|
+
environment: Joi.string().optional(),
|
|
113
|
+
index: Joi.string().optional(),
|
|
114
|
+
dimension: Joi.number().optional(),
|
|
115
|
+
timeout: Joi.number().min(1000).default(30000)
|
|
116
|
+
}),
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* File storage schema
|
|
120
|
+
*/
|
|
121
|
+
fileStorage: Joi.object({
|
|
122
|
+
path: Joi.string().required(),
|
|
123
|
+
maxSize: Joi.number().min(0).optional(),
|
|
124
|
+
allowedExtensions: Joi.array().items(Joi.string()).optional()
|
|
125
|
+
}),
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Redis schema
|
|
129
|
+
*/
|
|
130
|
+
redis: Joi.object({
|
|
131
|
+
host: Joi.string().default('localhost'),
|
|
132
|
+
port: Joi.number().min(1).max(65535).default(6379),
|
|
133
|
+
password: Joi.string().optional(),
|
|
134
|
+
db: Joi.number().min(0).default(0),
|
|
135
|
+
keyPrefix: Joi.string().optional()
|
|
136
|
+
})
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get a schema by name
|
|
141
|
+
*/
|
|
142
|
+
static getSchema(name) {
|
|
143
|
+
return this.schemas[name];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Merge multiple schemas
|
|
148
|
+
*/
|
|
149
|
+
static mergeSchemas(...schemas) {
|
|
150
|
+
return schemas.reduce((merged, schema) => {
|
|
151
|
+
return merged ? merged.concat(schema) : schema;
|
|
152
|
+
}, null);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Create a schema with environment variable support
|
|
157
|
+
*/
|
|
158
|
+
static createEnvSchema(baseSchema) {
|
|
159
|
+
return baseSchema.custom((value, helpers) => {
|
|
160
|
+
const injected = this.injectEnvVars(value);
|
|
161
|
+
const { error } = baseSchema.validate(injected);
|
|
162
|
+
if (error) {
|
|
163
|
+
return helpers.error('any.custom', { message: error.message });
|
|
164
|
+
}
|
|
165
|
+
return injected;
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = { PluginConfig };
|
|
171
|
+
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Event System Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for plugin event handling, pattern matching,
|
|
5
|
+
* and event routing between plugins and agents.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class PluginEventSystem {
|
|
9
|
+
/**
|
|
10
|
+
* Check if an event name matches a handler pattern
|
|
11
|
+
* Supports wildcards and specific patterns (same as agent handlers)
|
|
12
|
+
*/
|
|
13
|
+
static matchesEventPattern(eventName, pattern) {
|
|
14
|
+
// Exact match
|
|
15
|
+
if (eventName === pattern) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Wildcard patterns
|
|
20
|
+
if (pattern.includes('*')) {
|
|
21
|
+
const regexPattern = pattern.replace(/\*/g, '.*').replace(/:/g, ':');
|
|
22
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
23
|
+
return regex.test(eventName);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Prefix match (e.g., 'plugin:postgres:*' matches 'plugin:postgres:query')
|
|
27
|
+
if (pattern.endsWith(':*')) {
|
|
28
|
+
const prefix = pattern.slice(0, -2);
|
|
29
|
+
return eventName.startsWith(prefix + ':');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Normalize event name (add plugin prefix if needed)
|
|
37
|
+
*/
|
|
38
|
+
static normalizeEventName(eventName, pluginName) {
|
|
39
|
+
if (eventName.startsWith('plugin:')) {
|
|
40
|
+
return eventName;
|
|
41
|
+
}
|
|
42
|
+
return `plugin:${pluginName}:${eventName}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Extract plugin name from event name
|
|
47
|
+
*/
|
|
48
|
+
static extractPluginName(eventName) {
|
|
49
|
+
const match = eventName.match(/^plugin:([^:]+):/);
|
|
50
|
+
return match ? match[1] : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Create event name for plugin
|
|
55
|
+
*/
|
|
56
|
+
static createPluginEvent(pluginName, eventName) {
|
|
57
|
+
return `plugin:${pluginName}:${eventName}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Create tool event name
|
|
62
|
+
*/
|
|
63
|
+
static createToolEvent(toolName, action) {
|
|
64
|
+
return `tool:${toolName}:${action}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Create plugin tool event name
|
|
69
|
+
*/
|
|
70
|
+
static createPluginToolEvent(pluginName, toolName, action) {
|
|
71
|
+
return `plugin:${pluginName}:tool:${toolName}:${action}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Find all matching handlers for an event
|
|
76
|
+
*/
|
|
77
|
+
static findMatchingHandlers(eventName, handlersMap) {
|
|
78
|
+
const matchingHandlers = [];
|
|
79
|
+
|
|
80
|
+
for (const [handlerPattern, handlers] of handlersMap) {
|
|
81
|
+
if (this.matchesEventPattern(eventName, handlerPattern)) {
|
|
82
|
+
matchingHandlers.push(...handlers);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return matchingHandlers;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Execute handlers and collect responses
|
|
91
|
+
*/
|
|
92
|
+
static async executeHandlers(handlers, data) {
|
|
93
|
+
let modifiedData = { ...data };
|
|
94
|
+
|
|
95
|
+
for (const handlerObj of handlers) {
|
|
96
|
+
try {
|
|
97
|
+
const handler = typeof handlerObj === 'function'
|
|
98
|
+
? handlerObj
|
|
99
|
+
: handlerObj.handler;
|
|
100
|
+
|
|
101
|
+
if (typeof handler !== 'function') {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const result = await handler(modifiedData);
|
|
106
|
+
|
|
107
|
+
// If handler returns an object, merge it with the current data
|
|
108
|
+
if (result && typeof result === 'object' && !Array.isArray(result)) {
|
|
109
|
+
modifiedData = { ...modifiedData, ...result };
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(`Error in event handler:`, error);
|
|
113
|
+
// Continue executing other handlers even if one fails
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return modifiedData;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Emit event to multiple targets
|
|
122
|
+
*/
|
|
123
|
+
static emitToTargets(targets, eventName, data) {
|
|
124
|
+
targets.forEach(target => {
|
|
125
|
+
if (target && typeof target.emit === 'function') {
|
|
126
|
+
target.emit(eventName, data);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Create event router for plugin-to-plugin communication
|
|
133
|
+
*/
|
|
134
|
+
static createEventRouter() {
|
|
135
|
+
const routes = new Map();
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
/**
|
|
139
|
+
* Register a route from event pattern to target
|
|
140
|
+
*/
|
|
141
|
+
route(pattern, target) {
|
|
142
|
+
if (!routes.has(pattern)) {
|
|
143
|
+
routes.set(pattern, []);
|
|
144
|
+
}
|
|
145
|
+
routes.get(pattern).push(target);
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Remove a route
|
|
150
|
+
*/
|
|
151
|
+
unroute(pattern, target) {
|
|
152
|
+
if (routes.has(pattern)) {
|
|
153
|
+
const targets = routes.get(pattern);
|
|
154
|
+
const index = targets.indexOf(target);
|
|
155
|
+
if (index !== -1) {
|
|
156
|
+
targets.splice(index, 1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Route an event
|
|
163
|
+
*/
|
|
164
|
+
emit(eventName, data) {
|
|
165
|
+
for (const [pattern, targets] of routes) {
|
|
166
|
+
if (this.matchesEventPattern(eventName, pattern)) {
|
|
167
|
+
this.emitToTargets(targets, eventName, data);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get all routes
|
|
174
|
+
*/
|
|
175
|
+
getRoutes() {
|
|
176
|
+
return Array.from(routes.entries()).map(([pattern, targets]) => ({
|
|
177
|
+
pattern,
|
|
178
|
+
targets: targets.length
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
module.exports = { PluginEventSystem };
|
|
186
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dank Plugin System - Main Exports
|
|
3
|
+
*
|
|
4
|
+
* Provides the plugin system infrastructure for extending Dank agents
|
|
5
|
+
* with third-party plugins for databases, vector stores, and other services.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { PluginBase } = require('./base');
|
|
9
|
+
const { PluginRegistry } = require('./registry');
|
|
10
|
+
const { PluginManager } = require('./manager');
|
|
11
|
+
const { PluginConfig } = require('./config');
|
|
12
|
+
const { PluginEventSystem } = require('./events');
|
|
13
|
+
|
|
14
|
+
module.exports = {
|
|
15
|
+
// Core classes
|
|
16
|
+
PluginBase,
|
|
17
|
+
PluginRegistry,
|
|
18
|
+
PluginManager,
|
|
19
|
+
PluginConfig,
|
|
20
|
+
PluginEventSystem,
|
|
21
|
+
|
|
22
|
+
// Convenience exports
|
|
23
|
+
createPlugin: (name, config) => {
|
|
24
|
+
// This is a helper for creating plugins, but typically plugins
|
|
25
|
+
// should extend PluginBase directly
|
|
26
|
+
throw new Error('Plugins must extend PluginBase. Use: class MyPlugin extends PluginBase { ... }');
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Manager - Integrates plugins with DankAgent
|
|
3
|
+
*
|
|
4
|
+
* Manages plugin lifecycle, integrates plugin handlers and tools with agents,
|
|
5
|
+
* handles plugin-to-plugin communication, and manages plugin state persistence.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { PluginRegistry } = require('./registry');
|
|
9
|
+
const { PluginEventSystem } = require('./events');
|
|
10
|
+
|
|
11
|
+
class PluginManager {
|
|
12
|
+
constructor(agent) {
|
|
13
|
+
this.agent = agent;
|
|
14
|
+
this.registry = new PluginRegistry();
|
|
15
|
+
this.plugins = new Map(); // name -> plugin instance
|
|
16
|
+
this.eventRouter = PluginEventSystem.createEventRouter();
|
|
17
|
+
this.isInitialized = false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Add a plugin to the agent
|
|
22
|
+
*/
|
|
23
|
+
async addPlugin(name, config = {}) {
|
|
24
|
+
// If name is a string, try to load from npm or local path
|
|
25
|
+
if (typeof name === 'string') {
|
|
26
|
+
// Check if it's already loaded
|
|
27
|
+
if (this.plugins.has(name)) {
|
|
28
|
+
throw new Error(`Plugin '${name}' is already loaded`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Try to load from npm first (if it looks like a package name)
|
|
32
|
+
if (name.startsWith('dank-plugin-') || name.includes('/')) {
|
|
33
|
+
try {
|
|
34
|
+
const { name: pluginName, PluginClass } = await this.registry.loadFromNpm(name);
|
|
35
|
+
const plugin = await this.registry.create(pluginName, config);
|
|
36
|
+
return await this._registerPlugin(pluginName, plugin);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
// If npm load fails, try as local path
|
|
39
|
+
if (name.includes('/') || name.endsWith('.js')) {
|
|
40
|
+
const { name: pluginName, PluginClass } = await this.registry.loadFromPath(name, { name });
|
|
41
|
+
const plugin = await this.registry.create(pluginName, config);
|
|
42
|
+
return await this._registerPlugin(pluginName, plugin);
|
|
43
|
+
}
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
// Assume it's a registered plugin name
|
|
48
|
+
const plugin = await this.registry.create(name, config);
|
|
49
|
+
return await this._registerPlugin(name, plugin);
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
// name is a PluginBase instance
|
|
53
|
+
const plugin = name;
|
|
54
|
+
const pluginName = plugin.name;
|
|
55
|
+
|
|
56
|
+
if (this.plugins.has(pluginName)) {
|
|
57
|
+
throw new Error(`Plugin '${pluginName}' is already loaded`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return await this._registerPlugin(pluginName, plugin);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Register a plugin instance
|
|
66
|
+
*/
|
|
67
|
+
async _registerPlugin(name, plugin) {
|
|
68
|
+
// Set agent context
|
|
69
|
+
plugin.setAgentContext({
|
|
70
|
+
agent: this.agent,
|
|
71
|
+
agentId: this.agent.id,
|
|
72
|
+
agentName: this.agent.name,
|
|
73
|
+
toolRegistry: this.agent.toolRegistry,
|
|
74
|
+
toolExecutor: this.agent.toolExecutor
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Set plugin manager reference
|
|
78
|
+
plugin.setPluginManager(this);
|
|
79
|
+
|
|
80
|
+
// Register plugin tools with agent
|
|
81
|
+
const tools = plugin.getTools();
|
|
82
|
+
for (const tool of tools) {
|
|
83
|
+
this.agent.toolRegistry.register(tool.name, {
|
|
84
|
+
description: tool.description || `Tool from plugin '${name}'`,
|
|
85
|
+
parameters: tool.parameters || {},
|
|
86
|
+
handler: tool.handler,
|
|
87
|
+
category: tool.category || 'plugin',
|
|
88
|
+
version: tool.version || '1.0.0',
|
|
89
|
+
timeout: tool.timeout || 30000,
|
|
90
|
+
retries: tool.retries || 1,
|
|
91
|
+
async: tool.async !== false,
|
|
92
|
+
cacheable: tool.cacheable || false,
|
|
93
|
+
cacheTime: tool.cacheTime || 0,
|
|
94
|
+
metadata: {
|
|
95
|
+
...tool.metadata,
|
|
96
|
+
plugin: name
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Register plugin handlers with agent
|
|
102
|
+
for (const [eventType, handlers] of plugin.handlers) {
|
|
103
|
+
for (const handlerObj of handlers) {
|
|
104
|
+
this.agent.addHandler(eventType, handlerObj.handler);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Set up event routing
|
|
109
|
+
this.eventRouter.route(`plugin:${name}:*`, this.agent);
|
|
110
|
+
this.eventRouter.route(`plugin:${name}:*`, plugin);
|
|
111
|
+
|
|
112
|
+
// Store plugin
|
|
113
|
+
this.plugins.set(name, plugin);
|
|
114
|
+
|
|
115
|
+
// Don't start plugin here - it will be started at runtime
|
|
116
|
+
// Starting plugins during build would try to connect to databases/services
|
|
117
|
+
// which is not available during Docker build
|
|
118
|
+
// Plugins will be started when the agent container starts
|
|
119
|
+
|
|
120
|
+
return plugin;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Add multiple plugins
|
|
125
|
+
*/
|
|
126
|
+
async addPlugins(plugins) {
|
|
127
|
+
// Resolve dependencies first
|
|
128
|
+
const pluginNames = Object.keys(plugins);
|
|
129
|
+
const resolved = this.registry.resolveDependencies(pluginNames);
|
|
130
|
+
|
|
131
|
+
// Load plugins in dependency order
|
|
132
|
+
for (const name of resolved) {
|
|
133
|
+
await this.addPlugin(name, plugins[name]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get a plugin by name
|
|
141
|
+
*/
|
|
142
|
+
getPlugin(name) {
|
|
143
|
+
return this.plugins.get(name);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get all plugins
|
|
148
|
+
*/
|
|
149
|
+
getAllPlugins() {
|
|
150
|
+
return Array.from(this.plugins.values());
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Check if plugin is loaded
|
|
155
|
+
*/
|
|
156
|
+
hasPlugin(name) {
|
|
157
|
+
return this.plugins.has(name);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Remove a plugin
|
|
162
|
+
*/
|
|
163
|
+
async removePlugin(name) {
|
|
164
|
+
const plugin = this.plugins.get(name);
|
|
165
|
+
|
|
166
|
+
if (!plugin) {
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Stop plugin
|
|
171
|
+
await plugin.stop();
|
|
172
|
+
|
|
173
|
+
// Remove plugin tools from agent
|
|
174
|
+
const tools = plugin.getTools();
|
|
175
|
+
for (const tool of tools) {
|
|
176
|
+
// Note: ToolRegistry doesn't have an unregister method yet
|
|
177
|
+
// This would need to be added if we want to fully remove tools
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Remove event routes
|
|
181
|
+
this.eventRouter.unroute(`plugin:${name}:*`, this.agent);
|
|
182
|
+
this.eventRouter.unroute(`plugin:${name}:*`, plugin);
|
|
183
|
+
|
|
184
|
+
// Remove from registry
|
|
185
|
+
await this.registry.unload(name);
|
|
186
|
+
|
|
187
|
+
// Remove from plugins map
|
|
188
|
+
this.plugins.delete(name);
|
|
189
|
+
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Remove all plugins
|
|
195
|
+
*/
|
|
196
|
+
async removeAllPlugins() {
|
|
197
|
+
const names = Array.from(this.plugins.keys());
|
|
198
|
+
for (const name of names) {
|
|
199
|
+
await this.removePlugin(name);
|
|
200
|
+
}
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Start all plugins
|
|
206
|
+
* This should be called at runtime when the agent container starts
|
|
207
|
+
*/
|
|
208
|
+
async startAll() {
|
|
209
|
+
for (const plugin of this.plugins.values()) {
|
|
210
|
+
if (plugin.status === 'initialized' || plugin.status === 'stopped') {
|
|
211
|
+
await plugin.start();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return this;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Stop all plugins
|
|
219
|
+
*/
|
|
220
|
+
async stopAll() {
|
|
221
|
+
for (const plugin of this.plugins.values()) {
|
|
222
|
+
if (plugin.status !== 'stopped') {
|
|
223
|
+
await plugin.stop();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return this;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Get plugin manager metadata
|
|
231
|
+
*/
|
|
232
|
+
getMetadata() {
|
|
233
|
+
return {
|
|
234
|
+
plugins: Array.from(this.plugins.values()).map(p => p.getMetadata()),
|
|
235
|
+
registry: this.registry.getMetadata(),
|
|
236
|
+
eventRoutes: this.eventRouter.getRoutes()
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Emit event to all plugins
|
|
242
|
+
*/
|
|
243
|
+
emitToPlugins(eventName, data) {
|
|
244
|
+
for (const plugin of this.plugins.values()) {
|
|
245
|
+
plugin.emit(eventName, data);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Route event through event router
|
|
251
|
+
*/
|
|
252
|
+
routeEvent(eventName, data) {
|
|
253
|
+
this.eventRouter.emit(eventName, data);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
module.exports = { PluginManager };
|
|
258
|
+
|