dank-ai 1.0.41 → 1.0.42
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 +429 -1393
- 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 +165 -10
- 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,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PluginBase - Abstract base class for all Dank plugins
|
|
3
|
+
*
|
|
4
|
+
* Provides lifecycle hooks, event handling, tool registration, and state management
|
|
5
|
+
* for plugins that integrate with Dank agents.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const EventEmitter = require('events');
|
|
9
|
+
const { v4: uuidv4 } = require('uuid');
|
|
10
|
+
|
|
11
|
+
class PluginBase extends EventEmitter {
|
|
12
|
+
constructor(name, config = {}) {
|
|
13
|
+
super();
|
|
14
|
+
|
|
15
|
+
if (!name || typeof name !== 'string') {
|
|
16
|
+
throw new Error('Plugin name must be a non-empty string');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
this.name = name;
|
|
20
|
+
this.id = uuidv4();
|
|
21
|
+
this.config = config;
|
|
22
|
+
this.state = new Map();
|
|
23
|
+
this.tools = new Map();
|
|
24
|
+
this.handlers = new Map();
|
|
25
|
+
this.status = 'initialized'; // initialized, starting, running, stopping, stopped, error
|
|
26
|
+
this.agentContext = null;
|
|
27
|
+
this.pluginManager = null;
|
|
28
|
+
this.createdAt = new Date().toISOString();
|
|
29
|
+
this.startedAt = null;
|
|
30
|
+
this.stoppedAt = null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Initialize the plugin
|
|
35
|
+
* Override this method to set up connections, validate config, etc.
|
|
36
|
+
*/
|
|
37
|
+
async init() {
|
|
38
|
+
this.status = 'initialized';
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Start the plugin
|
|
44
|
+
* Override this method to start services, begin listening, etc.
|
|
45
|
+
*/
|
|
46
|
+
async start() {
|
|
47
|
+
if (this.status === 'running') {
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.status = 'starting';
|
|
52
|
+
this.startedAt = new Date().toISOString();
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
await this.onStart();
|
|
56
|
+
this.status = 'running';
|
|
57
|
+
this.emit('started');
|
|
58
|
+
return this;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
this.status = 'error';
|
|
61
|
+
this.emit('error', error);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Stop the plugin
|
|
68
|
+
* Override this method to clean up resources, close connections, etc.
|
|
69
|
+
*/
|
|
70
|
+
async stop() {
|
|
71
|
+
if (this.status === 'stopped' || this.status === 'stopping') {
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.status = 'stopping';
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
await this.onStop();
|
|
79
|
+
this.status = 'stopped';
|
|
80
|
+
this.stoppedAt = new Date().toISOString();
|
|
81
|
+
this.emit('stopped');
|
|
82
|
+
return this;
|
|
83
|
+
} catch (error) {
|
|
84
|
+
this.status = 'error';
|
|
85
|
+
this.emit('error', error);
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Destroy the plugin (cleanup)
|
|
92
|
+
* Override this method for final cleanup
|
|
93
|
+
*/
|
|
94
|
+
async destroy() {
|
|
95
|
+
if (this.status !== 'stopped') {
|
|
96
|
+
await this.stop();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
await this.onDestroy();
|
|
100
|
+
this.removeAllListeners();
|
|
101
|
+
this.state.clear();
|
|
102
|
+
this.tools.clear();
|
|
103
|
+
this.handlers.clear();
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Lifecycle hooks (override these in subclasses)
|
|
109
|
+
*/
|
|
110
|
+
async onStart() {
|
|
111
|
+
// Override in subclass
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async onStop() {
|
|
115
|
+
// Override in subclass
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async onDestroy() {
|
|
119
|
+
// Override in subclass
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Register an event handler
|
|
124
|
+
* Uses the same pattern as agent handlers
|
|
125
|
+
*/
|
|
126
|
+
on(eventType, handler) {
|
|
127
|
+
if (typeof handler !== 'function') {
|
|
128
|
+
throw new Error('Handler must be a function');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!this.handlers.has(eventType)) {
|
|
132
|
+
this.handlers.set(eventType, []);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.handlers.get(eventType).push({
|
|
136
|
+
handler,
|
|
137
|
+
plugin: this.name
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Also register with EventEmitter for compatibility
|
|
141
|
+
super.on(eventType, handler);
|
|
142
|
+
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Remove an event handler
|
|
148
|
+
*/
|
|
149
|
+
off(eventType, handler) {
|
|
150
|
+
if (this.handlers.has(eventType)) {
|
|
151
|
+
const handlers = this.handlers.get(eventType);
|
|
152
|
+
const index = handlers.findIndex(h => h.handler === handler);
|
|
153
|
+
if (index !== -1) {
|
|
154
|
+
handlers.splice(index, 1);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
super.off(eventType, handler);
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Emit an event (delegates to EventEmitter)
|
|
164
|
+
*/
|
|
165
|
+
emit(eventName, ...args) {
|
|
166
|
+
// Prefix plugin events with plugin name
|
|
167
|
+
const prefixedEvent = `plugin:${this.name}:${eventName}`;
|
|
168
|
+
super.emit(prefixedEvent, ...args);
|
|
169
|
+
super.emit(eventName, ...args);
|
|
170
|
+
return this;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Register a tool that agents can use
|
|
175
|
+
*/
|
|
176
|
+
registerTool(name, definition) {
|
|
177
|
+
if (typeof name !== 'string' || name.trim().length === 0) {
|
|
178
|
+
throw new Error('Tool name must be a non-empty string');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Prefix tool name with plugin name
|
|
182
|
+
const prefixedName = `plugin:${this.name}:${name}`;
|
|
183
|
+
|
|
184
|
+
this.tools.set(prefixedName, {
|
|
185
|
+
...definition,
|
|
186
|
+
name: prefixedName,
|
|
187
|
+
plugin: this.name,
|
|
188
|
+
registeredAt: new Date().toISOString()
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Emit event for tool registration
|
|
192
|
+
this.emit('tool:registered', { name: prefixedName, definition });
|
|
193
|
+
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get all registered tools
|
|
199
|
+
*/
|
|
200
|
+
getTools() {
|
|
201
|
+
return Array.from(this.tools.values());
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get a specific tool
|
|
206
|
+
*/
|
|
207
|
+
getTool(name) {
|
|
208
|
+
const prefixedName = name.startsWith(`plugin:${this.name}:`)
|
|
209
|
+
? name
|
|
210
|
+
: `plugin:${this.name}:${name}`;
|
|
211
|
+
return this.tools.get(prefixedName);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Set plugin state (key-value store)
|
|
216
|
+
*/
|
|
217
|
+
setState(key, value) {
|
|
218
|
+
this.state.set(key, value);
|
|
219
|
+
this.emit('state:changed', { key, value });
|
|
220
|
+
return this;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get plugin state
|
|
225
|
+
*/
|
|
226
|
+
getState(key) {
|
|
227
|
+
return this.state.get(key);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Get all plugin state
|
|
232
|
+
*/
|
|
233
|
+
getAllState() {
|
|
234
|
+
return Object.fromEntries(this.state);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Clear plugin state
|
|
239
|
+
*/
|
|
240
|
+
clearState() {
|
|
241
|
+
this.state.clear();
|
|
242
|
+
this.emit('state:cleared');
|
|
243
|
+
return this;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Get agent context (set by PluginManager)
|
|
248
|
+
*/
|
|
249
|
+
getAgentContext() {
|
|
250
|
+
return this.agentContext;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Set agent context (called by PluginManager)
|
|
255
|
+
*/
|
|
256
|
+
setAgentContext(context) {
|
|
257
|
+
this.agentContext = context;
|
|
258
|
+
return this;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Get plugin manager reference
|
|
263
|
+
*/
|
|
264
|
+
getPluginManager() {
|
|
265
|
+
return this.pluginManager;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Set plugin manager reference (called by PluginManager)
|
|
270
|
+
*/
|
|
271
|
+
setPluginManager(manager) {
|
|
272
|
+
this.pluginManager = manager;
|
|
273
|
+
return this;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get another plugin by name
|
|
278
|
+
*/
|
|
279
|
+
getPlugin(name) {
|
|
280
|
+
if (!this.pluginManager) {
|
|
281
|
+
throw new Error('Plugin manager not available');
|
|
282
|
+
}
|
|
283
|
+
return this.pluginManager.getPlugin(name);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Get all other plugins
|
|
288
|
+
*/
|
|
289
|
+
getPlugins() {
|
|
290
|
+
if (!this.pluginManager) {
|
|
291
|
+
throw new Error('Plugin manager not available');
|
|
292
|
+
}
|
|
293
|
+
return this.pluginManager.getAllPlugins();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Get plugin metadata
|
|
298
|
+
*/
|
|
299
|
+
getMetadata() {
|
|
300
|
+
return {
|
|
301
|
+
name: this.name,
|
|
302
|
+
id: this.id,
|
|
303
|
+
status: this.status,
|
|
304
|
+
createdAt: this.createdAt,
|
|
305
|
+
startedAt: this.startedAt,
|
|
306
|
+
stoppedAt: this.stoppedAt,
|
|
307
|
+
toolCount: this.tools.size,
|
|
308
|
+
handlerCount: this.handlers.size,
|
|
309
|
+
stateSize: this.state.size
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Validate plugin configuration
|
|
315
|
+
* Override this method to add custom validation
|
|
316
|
+
*/
|
|
317
|
+
validateConfig(config) {
|
|
318
|
+
// Override in subclass for custom validation
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
module.exports = { PluginBase };
|
|
324
|
+
|
|
@@ -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
|
+
|