dank-ai 1.0.39 → 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.
@@ -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
+
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Plugin Registry - Discovery, loading, and lifecycle management
3
+ *
4
+ * Manages plugin discovery, loading from npm packages or local paths,
5
+ * validates plugin schemas, and tracks plugin dependencies.
6
+ */
7
+
8
+ const fs = require('fs-extra');
9
+ const path = require('path');
10
+ const { PluginBase } = require('./base');
11
+ const { PluginConfig } = require('./config');
12
+
13
+ class PluginRegistry {
14
+ constructor() {
15
+ this.plugins = new Map(); // name -> plugin instance
16
+ this.pluginClasses = new Map(); // name -> Plugin class
17
+ this.loadedPaths = new Map(); // name -> path
18
+ this.dependencies = new Map(); // name -> [dependencies]
19
+ }
20
+
21
+ /**
22
+ * Register a plugin class
23
+ */
24
+ register(name, PluginClass, options = {}) {
25
+ if (!name || typeof name !== 'string') {
26
+ throw new Error('Plugin name must be a non-empty string');
27
+ }
28
+
29
+ if (!PluginClass || !(PluginClass.prototype instanceof PluginBase)) {
30
+ throw new Error(`Plugin '${name}' must extend PluginBase`);
31
+ }
32
+
33
+ this.pluginClasses.set(name, PluginClass);
34
+
35
+ if (options.dependencies) {
36
+ this.dependencies.set(name, options.dependencies);
37
+ }
38
+
39
+ return this;
40
+ }
41
+
42
+ /**
43
+ * Load plugin from npm package
44
+ */
45
+ async loadFromNpm(packageName, config = {}) {
46
+ try {
47
+ // Try to require the package
48
+ let PluginClass;
49
+ try {
50
+ const pluginModule = require(packageName);
51
+ PluginClass = pluginModule.default || pluginModule;
52
+ } catch (error) {
53
+ // If not installed, try to install it
54
+ if (error.code === 'MODULE_NOT_FOUND') {
55
+ throw new Error(
56
+ `Plugin package '${packageName}' not found. ` +
57
+ `Install it with: npm install ${packageName}`
58
+ );
59
+ }
60
+ throw error;
61
+ }
62
+
63
+ // Validate it's a PluginBase subclass
64
+ if (!PluginClass || !(PluginClass.prototype instanceof PluginBase)) {
65
+ throw new Error(
66
+ `Package '${packageName}' does not export a PluginBase subclass`
67
+ );
68
+ }
69
+
70
+ // Extract plugin name from package or use package name
71
+ const pluginName = PluginClass.name || packageName.replace(/^dank-plugin-/, '');
72
+
73
+ this.pluginClasses.set(pluginName, PluginClass);
74
+ this.loadedPaths.set(pluginName, `npm:${packageName}`);
75
+
76
+ return { name: pluginName, PluginClass };
77
+ } catch (error) {
78
+ throw new Error(`Failed to load plugin from npm package '${packageName}': ${error.message}`);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Load plugin from local file path
84
+ */
85
+ async loadFromPath(filePath, config = {}) {
86
+ try {
87
+ const resolvedPath = path.resolve(filePath);
88
+
89
+ if (!(await fs.pathExists(resolvedPath))) {
90
+ throw new Error(`Plugin file not found: ${resolvedPath}`);
91
+ }
92
+
93
+ // Clear require cache for this file
94
+ delete require.cache[require.resolve(resolvedPath)];
95
+
96
+ const pluginModule = require(resolvedPath);
97
+ const PluginClass = pluginModule.default || pluginModule;
98
+
99
+ if (!PluginClass || !(PluginClass.prototype instanceof PluginBase)) {
100
+ throw new Error(
101
+ `File '${resolvedPath}' does not export a PluginBase subclass`
102
+ );
103
+ }
104
+
105
+ const pluginName = config.name || PluginClass.name || path.basename(resolvedPath, '.js');
106
+
107
+ this.pluginClasses.set(pluginName, PluginClass);
108
+ this.loadedPaths.set(pluginName, resolvedPath);
109
+
110
+ return { name: pluginName, PluginClass };
111
+ } catch (error) {
112
+ throw new Error(`Failed to load plugin from path '${filePath}': ${error.message}`);
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Create plugin instance
118
+ */
119
+ async create(name, config = {}) {
120
+ const PluginClass = this.pluginClasses.get(name);
121
+
122
+ if (!PluginClass) {
123
+ throw new Error(`Plugin '${name}' not registered. Load it first with loadFromNpm() or loadFromPath()`);
124
+ }
125
+
126
+ // Inject environment variables into config
127
+ const injectedConfig = PluginConfig.injectEnvVars(config);
128
+
129
+ // Create plugin instance
130
+ const plugin = new PluginClass(injectedConfig);
131
+
132
+ // Validate plugin config if plugin has validateConfig method
133
+ if (typeof plugin.validateConfig === 'function') {
134
+ plugin.validateConfig(injectedConfig);
135
+ }
136
+
137
+ // Initialize plugin
138
+ await plugin.init();
139
+
140
+ this.plugins.set(name, plugin);
141
+
142
+ return plugin;
143
+ }
144
+
145
+ /**
146
+ * Get plugin instance
147
+ */
148
+ get(name) {
149
+ return this.plugins.get(name);
150
+ }
151
+
152
+ /**
153
+ * Get all plugin instances
154
+ */
155
+ getAll() {
156
+ return Array.from(this.plugins.values());
157
+ }
158
+
159
+ /**
160
+ * Check if plugin is registered
161
+ */
162
+ has(name) {
163
+ return this.pluginClasses.has(name);
164
+ }
165
+
166
+ /**
167
+ * Check if plugin is loaded (has instance)
168
+ */
169
+ isLoaded(name) {
170
+ return this.plugins.has(name);
171
+ }
172
+
173
+ /**
174
+ * Get plugin class
175
+ */
176
+ getClass(name) {
177
+ return this.pluginClasses.get(name);
178
+ }
179
+
180
+ /**
181
+ * Get plugin dependencies
182
+ */
183
+ getDependencies(name) {
184
+ return this.dependencies.get(name) || [];
185
+ }
186
+
187
+ /**
188
+ * Resolve plugin dependencies (topological sort)
189
+ */
190
+ resolveDependencies(pluginNames) {
191
+ const resolved = [];
192
+ const visited = new Set();
193
+ const visiting = new Set();
194
+
195
+ const visit = (name) => {
196
+ if (visiting.has(name)) {
197
+ throw new Error(`Circular dependency detected involving plugin '${name}'`);
198
+ }
199
+
200
+ if (visited.has(name)) {
201
+ return;
202
+ }
203
+
204
+ visiting.add(name);
205
+
206
+ const deps = this.getDependencies(name);
207
+ for (const dep of deps) {
208
+ if (!this.has(dep)) {
209
+ throw new Error(`Plugin '${name}' depends on '${dep}' which is not registered`);
210
+ }
211
+ visit(dep);
212
+ }
213
+
214
+ visiting.delete(name);
215
+ visited.add(name);
216
+ resolved.push(name);
217
+ };
218
+
219
+ for (const name of pluginNames) {
220
+ visit(name);
221
+ }
222
+
223
+ return resolved;
224
+ }
225
+
226
+ /**
227
+ * Unload plugin
228
+ */
229
+ async unload(name) {
230
+ const plugin = this.plugins.get(name);
231
+
232
+ if (plugin) {
233
+ await plugin.destroy();
234
+ this.plugins.delete(name);
235
+ }
236
+
237
+ return this;
238
+ }
239
+
240
+ /**
241
+ * Unload all plugins
242
+ */
243
+ async unloadAll() {
244
+ const names = Array.from(this.plugins.keys());
245
+ for (const name of names) {
246
+ await this.unload(name);
247
+ }
248
+ return this;
249
+ }
250
+
251
+ /**
252
+ * Get registry metadata
253
+ */
254
+ getMetadata() {
255
+ return {
256
+ registered: Array.from(this.pluginClasses.keys()),
257
+ loaded: Array.from(this.plugins.keys()).map(name => ({
258
+ name,
259
+ status: this.plugins.get(name).status,
260
+ path: this.loadedPaths.get(name)
261
+ })),
262
+ dependencies: Object.fromEntries(this.dependencies)
263
+ };
264
+ }
265
+ }
266
+
267
+ module.exports = { PluginRegistry };
268
+
package/lib/project.js CHANGED
@@ -66,6 +66,9 @@ class DankProject {
66
66
  * This file defines your AI agents and their configurations.
67
67
  * Run 'dank run' to start all defined agents.
68
68
  *
69
+ * NPM PACKAGES: You can import any npm package at the top of this file
70
+ * and use it in your handlers. Just make sure packages are in your package.json.
71
+ *
69
72
  * IMPORTANT: Agent IDs (UUIDv4)
70
73
  * ==============================
71
74
  * Each agent has a unique UUIDv4 identifier that is generated when you initialize
@@ -76,6 +79,10 @@ class DankProject {
76
79
  * locked in and owned by your account
77
80
  */
78
81
 
82
+ // Import npm packages - these will be available in your handlers
83
+ const axios = require('axios');
84
+ const { format } = require('date-fns');
85
+
79
86
  const { createAgent } = require('${requirePath}');
80
87
 
81
88
  // Agent IDs - Generated UUIDv4 identifiers for each agent
@@ -119,17 +126,17 @@ module.exports = {
119
126
  prompt: enhancedPrompt
120
127
  };
121
128
  })
122
- .addHandler('request_output', (data) => {
123
- console.log('[Prompt Agent] LLM Response:', {
129
+ .addHandler('request_output', async (data) => {
130
+ // Example: Using imported packages in handlers
131
+ const timestamp = format(new Date(), 'yyyy-MM-dd HH:mm:ss');
132
+ console.log(\`[\${timestamp}] LLM Response:\`, {
124
133
  prompt: data.prompt,
125
- finalPrompt: data.finalPrompt,
126
- promptModified: data.promptModified,
127
134
  response: data.response,
128
- conversationId: data.conversationId,
129
- processingTime: data.processingTime,
130
- usage: data.usage,
131
- model: data.model
135
+ processingTime: data.processingTime
132
136
  });
137
+
138
+ // Example: Make HTTP requests with axios (uncomment to use)
139
+ // await axios.post('https://your-api.com/log', { response: data.response });
133
140
  })
134
141
  .addHandler('request_output:end', (data) => {
135
142
  console.log('[Prompt Agent] Completed in:', data.processingTime + 'ms');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dank-ai",
3
- "version": "1.0.39",
3
+ "version": "1.0.42",
4
4
  "description": "Dank Agent Service - Docker-based AI agent orchestration platform",
5
5
  "main": "lib/index.js",
6
6
  "exports": {