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,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
|
-
|
|
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
|
-
|
|
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');
|