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.
@@ -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
+