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
package/lib/agent.js
CHANGED
|
@@ -10,6 +10,7 @@ const { validate: validateUUID, v4: uuidv4 } = require('uuid');
|
|
|
10
10
|
const { DEFAULT_CONFIG, SUPPORTED_LLMS, DOCKER_CONFIG, INSTANCE_TYPES } = require('./constants');
|
|
11
11
|
const { ToolRegistry, ToolExecutor } = require('./tools');
|
|
12
12
|
const builtinTools = require('./tools/builtin');
|
|
13
|
+
const { PluginManager } = require('./plugins');
|
|
13
14
|
|
|
14
15
|
class DankAgent {
|
|
15
16
|
constructor(name, config = {}) {
|
|
@@ -26,6 +27,10 @@ class DankAgent {
|
|
|
26
27
|
this.toolRegistry = new ToolRegistry();
|
|
27
28
|
this.toolExecutor = new ToolExecutor(this.toolRegistry);
|
|
28
29
|
|
|
30
|
+
// Initialize plugin system
|
|
31
|
+
this.pluginManager = new PluginManager(this);
|
|
32
|
+
this.pendingPlugins = new Map(); // Store plugins to be loaded during finalization
|
|
33
|
+
|
|
29
34
|
// Register built-in tools if enabled
|
|
30
35
|
if (config.enableBuiltinTools !== false) {
|
|
31
36
|
this.registerBuiltinTools();
|
|
@@ -420,6 +425,68 @@ class DankAgent {
|
|
|
420
425
|
return this.toolRegistry.toOpenAISchema();
|
|
421
426
|
}
|
|
422
427
|
|
|
428
|
+
/**
|
|
429
|
+
* Add a plugin to this agent
|
|
430
|
+
* Plugin will be loaded and initialized during finalization
|
|
431
|
+
* @param {string|PluginBase} name - Plugin name, npm package, local path, or PluginBase instance
|
|
432
|
+
* @param {object} config - Plugin configuration
|
|
433
|
+
*/
|
|
434
|
+
addPlugin(name, config = {}) {
|
|
435
|
+
this.pendingPlugins.set(name, config);
|
|
436
|
+
return this;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Add multiple plugins at once
|
|
441
|
+
* Plugins will be loaded and initialized during finalization
|
|
442
|
+
* @param {object} plugins - Object mapping plugin names to configs
|
|
443
|
+
*/
|
|
444
|
+
addPlugins(plugins) {
|
|
445
|
+
Object.entries(plugins).forEach(([name, config]) => {
|
|
446
|
+
this.pendingPlugins.set(name, config);
|
|
447
|
+
});
|
|
448
|
+
return this;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Get a plugin by name
|
|
453
|
+
* @param {string} name - Plugin name
|
|
454
|
+
*/
|
|
455
|
+
getPlugin(name) {
|
|
456
|
+
return this.pluginManager.getPlugin(name);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get all plugins
|
|
461
|
+
*/
|
|
462
|
+
getPlugins() {
|
|
463
|
+
return this.pluginManager.getAllPlugins();
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Check if a plugin is loaded
|
|
468
|
+
* @param {string} name - Plugin name
|
|
469
|
+
*/
|
|
470
|
+
hasPlugin(name) {
|
|
471
|
+
return this.pluginManager.hasPlugin(name);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Remove a plugin
|
|
476
|
+
* @param {string} name - Plugin name
|
|
477
|
+
*/
|
|
478
|
+
async removePlugin(name) {
|
|
479
|
+
await this.pluginManager.removePlugin(name);
|
|
480
|
+
return this;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Get plugin manager metadata
|
|
485
|
+
*/
|
|
486
|
+
getPluginMetadata() {
|
|
487
|
+
return this.pluginManager.getMetadata();
|
|
488
|
+
}
|
|
489
|
+
|
|
423
490
|
/**
|
|
424
491
|
* Register built-in tools
|
|
425
492
|
*/
|
|
@@ -590,6 +657,7 @@ class DankAgent {
|
|
|
590
657
|
/**
|
|
591
658
|
* Finalize agent configuration by auto-detecting features
|
|
592
659
|
* This should be called before the agent is deployed
|
|
660
|
+
* Plugins will be loaded when the agent is started
|
|
593
661
|
*/
|
|
594
662
|
finalize() {
|
|
595
663
|
// Validate that ID is set before finalization (REQUIRED)
|
|
@@ -599,6 +667,18 @@ class DankAgent {
|
|
|
599
667
|
return this;
|
|
600
668
|
}
|
|
601
669
|
|
|
670
|
+
/**
|
|
671
|
+
* Initialize plugins (called internally when agent starts)
|
|
672
|
+
* @private
|
|
673
|
+
*/
|
|
674
|
+
async _initializePlugins() {
|
|
675
|
+
if (this.pendingPlugins.size > 0) {
|
|
676
|
+
const plugins = Object.fromEntries(this.pendingPlugins);
|
|
677
|
+
await this.pluginManager.addPlugins(plugins);
|
|
678
|
+
this.pendingPlugins.clear();
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
602
682
|
/**
|
|
603
683
|
* Set custom configuration
|
|
604
684
|
*/
|
package/lib/cli/init.js
CHANGED
|
@@ -126,7 +126,9 @@ async function createPackageJson(npmProjectName, projectPath) {
|
|
|
126
126
|
clean: 'dank clean'
|
|
127
127
|
},
|
|
128
128
|
dependencies: {
|
|
129
|
-
'dank-ai': '^1.0.0'
|
|
129
|
+
'dank-ai': '^1.0.0',
|
|
130
|
+
'axios': '^1.6.0',
|
|
131
|
+
'date-fns': '^3.0.0'
|
|
130
132
|
},
|
|
131
133
|
keywords: ['dank', 'ai', 'agents', 'automation', 'llm'],
|
|
132
134
|
author: '',
|
|
@@ -270,6 +272,7 @@ A Dank AI agent project with modern event handling and Docker orchestration.
|
|
|
270
272
|
|
|
271
273
|
- 🤖 **AI Agents**: Powered by multiple LLM providers (OpenAI, Anthropic, Google AI)
|
|
272
274
|
- 🐳 **Docker Integration**: Containerized agents with automatic management
|
|
275
|
+
- 📦 **NPM Packages**: Use any npm package in your handlers with top-level imports
|
|
273
276
|
- 📡 **Event System**: Real-time event handling for prompts, responses, and tools
|
|
274
277
|
- 🔧 **Auto-Detection**: Automatically enables features based on usage
|
|
275
278
|
- 📊 **Monitoring**: Built-in logging and status monitoring
|
|
@@ -349,6 +352,27 @@ Each agent in your project has a unique UUIDv4 identifier that is automatically
|
|
|
349
352
|
- Once agents register with Dank Cloud services, these IDs become locked to your account
|
|
350
353
|
- You can generate new UUIDs if needed: \`require('uuid').v4()\`
|
|
351
354
|
|
|
355
|
+
## Using NPM Packages
|
|
356
|
+
|
|
357
|
+
You can use any npm package in your handlers by importing them at the top of \`dank.config.js\`:
|
|
358
|
+
|
|
359
|
+
\`\`\`javascript
|
|
360
|
+
// Import packages at the top
|
|
361
|
+
const axios = require('axios');
|
|
362
|
+
const { format } = require('date-fns');
|
|
363
|
+
|
|
364
|
+
// Use them in your handlers
|
|
365
|
+
.addHandler('request_output', async (data) => {
|
|
366
|
+
const timestamp = format(new Date(), 'yyyy-MM-dd HH:mm');
|
|
367
|
+
await axios.post('https://api.example.com/log', {
|
|
368
|
+
response: data.response,
|
|
369
|
+
timestamp
|
|
370
|
+
});
|
|
371
|
+
})
|
|
372
|
+
\`\`\`
|
|
373
|
+
|
|
374
|
+
Just add any packages you need to your \`package.json\` and run \`npm install\`.
|
|
375
|
+
|
|
352
376
|
## Configuration
|
|
353
377
|
|
|
354
378
|
Edit \`dank.config.js\` to:
|
|
@@ -173,7 +173,9 @@ async function productionBuildCommand(options) {
|
|
|
173
173
|
force: options.force || false,
|
|
174
174
|
push: options.push || false,
|
|
175
175
|
baseImageOverride: options.baseImageOverride || null,
|
|
176
|
-
projectDir: projectDir // Pass project directory so external files can be copied
|
|
176
|
+
projectDir: projectDir, // Pass project directory so external files can be copied
|
|
177
|
+
configPath: configPath, // Pass config path for extracting top-level requires
|
|
178
|
+
projectRoot: process.cwd() // Pass project root for package.json location
|
|
177
179
|
};
|
|
178
180
|
|
|
179
181
|
const result = await dockerManager.buildProductionImage(agent, buildOptions);
|
package/lib/cli/run.js
CHANGED
|
@@ -71,7 +71,9 @@ async function runCommand(options) {
|
|
|
71
71
|
|
|
72
72
|
const container = await dockerManager.startAgent(agent, {
|
|
73
73
|
rebuild: !options.noBuild, // Rebuild by default unless --no-build is specified
|
|
74
|
-
projectDir: projectDir // Pass project directory so external files can be copied
|
|
74
|
+
projectDir: projectDir, // Pass project directory so external files can be copied
|
|
75
|
+
configPath: configPath, // Pass config path for extracting top-level requires
|
|
76
|
+
projectRoot: process.cwd() // Pass project root for package.json location
|
|
75
77
|
});
|
|
76
78
|
|
|
77
79
|
console.log(chalk.green(` ✅ ${agent.name} started (${container.id.substring(0, 12)})`));
|
package/lib/docker/manager.js
CHANGED
|
@@ -1056,12 +1056,15 @@ class DockerManager {
|
|
|
1056
1056
|
*/
|
|
1057
1057
|
async pullBaseImage(baseImageName = null, options = {}) {
|
|
1058
1058
|
const imageName = baseImageName || this.defaultBaseImageName;
|
|
1059
|
-
this.logger.info(`Pulling base Docker image: ${imageName}`);
|
|
1059
|
+
this.logger.info(`Pulling base Docker image: ${imageName} (platform: linux/amd64)`);
|
|
1060
1060
|
|
|
1061
1061
|
try {
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
await this.
|
|
1062
|
+
// Use docker CLI with --platform to ensure we pull the AMD64 version
|
|
1063
|
+
// This is necessary because dockerode's pull doesn't support platform directly
|
|
1064
|
+
const dockerCmd = await this.resolveDockerCommand();
|
|
1065
|
+
const pullCommand = `${dockerCmd} pull --platform linux/amd64 ${imageName}`;
|
|
1066
|
+
|
|
1067
|
+
await this.runCommand(pullCommand, `Pull base image ${imageName}`);
|
|
1065
1068
|
|
|
1066
1069
|
// Verify the image was pulled
|
|
1067
1070
|
const hasImage = await this.hasImage(imageName);
|
|
@@ -1165,13 +1168,21 @@ class DockerManager {
|
|
|
1165
1168
|
const imageName = `dank-agent-${normalizedName}`;
|
|
1166
1169
|
this.logger.info(`Building image for agent: ${agent.name}`);
|
|
1167
1170
|
|
|
1171
|
+
// Initialize plugins before finalizing
|
|
1172
|
+
// This ensures plugin handlers and tools are available during code generation
|
|
1173
|
+
if (agent._initializePlugins) {
|
|
1174
|
+
await agent._initializePlugins();
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1168
1177
|
// Finalize agent configuration before building
|
|
1169
1178
|
// This ensures ports and other configs are properly set
|
|
1170
1179
|
agent.finalize();
|
|
1171
1180
|
|
|
1172
1181
|
try {
|
|
1173
1182
|
const buildContext = await this.createAgentBuildContext(agent, {
|
|
1174
|
-
projectDir: options.projectDir
|
|
1183
|
+
projectDir: options.projectDir,
|
|
1184
|
+
configPath: options.configPath,
|
|
1185
|
+
projectRoot: options.projectRoot
|
|
1175
1186
|
});
|
|
1176
1187
|
const dockerCmd = await this.resolveDockerCommand();
|
|
1177
1188
|
|
|
@@ -1224,6 +1235,8 @@ class DockerManager {
|
|
|
1224
1235
|
push = false,
|
|
1225
1236
|
baseImageOverride = null, // Production-only: override base image for all agents
|
|
1226
1237
|
projectDir = null, // Project directory to copy files from
|
|
1238
|
+
configPath = null, // Config path for extracting top-level requires
|
|
1239
|
+
projectRoot = null, // Project root for package.json location
|
|
1227
1240
|
} = options;
|
|
1228
1241
|
|
|
1229
1242
|
// Normalize all components
|
|
@@ -1250,6 +1263,12 @@ class DockerManager {
|
|
|
1250
1263
|
`Building production image for agent: ${agent.name} -> ${imageName}`
|
|
1251
1264
|
);
|
|
1252
1265
|
|
|
1266
|
+
// Initialize plugins before finalizing
|
|
1267
|
+
// This ensures plugin handlers and tools are available during code generation
|
|
1268
|
+
if (agent._initializePlugins) {
|
|
1269
|
+
await agent._initializePlugins();
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1253
1272
|
// Finalize agent configuration before building
|
|
1254
1273
|
// This ensures ports and other configs are properly set
|
|
1255
1274
|
agent.finalize();
|
|
@@ -1263,7 +1282,9 @@ class DockerManager {
|
|
|
1263
1282
|
const buildContext = await this.createAgentBuildContext(agent, {
|
|
1264
1283
|
isProductionBuild: true,
|
|
1265
1284
|
baseImageOverride: baseImageOverride,
|
|
1266
|
-
projectDir: projectDir
|
|
1285
|
+
projectDir: projectDir,
|
|
1286
|
+
configPath: configPath,
|
|
1287
|
+
projectRoot: projectRoot
|
|
1267
1288
|
});
|
|
1268
1289
|
const dockerCmd = await this.resolveDockerCommand();
|
|
1269
1290
|
|
|
@@ -1298,6 +1319,65 @@ class DockerManager {
|
|
|
1298
1319
|
}
|
|
1299
1320
|
}
|
|
1300
1321
|
|
|
1322
|
+
/**
|
|
1323
|
+
* Extract top-level require statements from a config file
|
|
1324
|
+
* This allows handlers to reference modules imported at the top of the config
|
|
1325
|
+
* @param {string} configPath - Path to dank.config.js
|
|
1326
|
+
* @returns {string[]} - Array of require statement strings
|
|
1327
|
+
*/
|
|
1328
|
+
extractTopLevelRequires(configPath) {
|
|
1329
|
+
try {
|
|
1330
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
1331
|
+
const imports = [];
|
|
1332
|
+
|
|
1333
|
+
// Regex 1: Match CommonJS require() patterns
|
|
1334
|
+
// - const/let/var declaration
|
|
1335
|
+
// - Simple name (axios) or destructured ({ get, post }) or destructured with rename ({ get: httpGet })
|
|
1336
|
+
// - Single or double quotes for module path
|
|
1337
|
+
// - Optional semicolon at end
|
|
1338
|
+
// - Handles multi-line destructured imports via [\s\S]*?
|
|
1339
|
+
const requireRegex = /^(const|let|var)\s+(\{[\s\S]*?\}|\w+)\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\);?/gm;
|
|
1340
|
+
|
|
1341
|
+
let match;
|
|
1342
|
+
while ((match = requireRegex.exec(content)) !== null) {
|
|
1343
|
+
let statement = match[0];
|
|
1344
|
+
if (!statement.endsWith(';')) {
|
|
1345
|
+
statement += ';';
|
|
1346
|
+
}
|
|
1347
|
+
imports.push(statement);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// Regex 2: Match dynamic import() patterns (for ESM-only packages in CommonJS)
|
|
1351
|
+
// Handles:
|
|
1352
|
+
// - const x = import("y");
|
|
1353
|
+
// - const x = import("y").then((m) => m.default);
|
|
1354
|
+
// - const x = import("y").then((m) => { return m.default; });
|
|
1355
|
+
// - Multiline .then() callbacks
|
|
1356
|
+
// - Single or double quotes
|
|
1357
|
+
const dynamicImportRegex = /^(const|let|var)\s+\w+\s*=\s*import\s*\(\s*['"][^'"]+['"]\s*\)[\s\S]*?;/gm;
|
|
1358
|
+
|
|
1359
|
+
while ((match = dynamicImportRegex.exec(content)) !== null) {
|
|
1360
|
+
let statement = match[0];
|
|
1361
|
+
// Clean up extra whitespace in multiline statements
|
|
1362
|
+
statement = statement.replace(/\n\s+/g, '\n ');
|
|
1363
|
+
if (!statement.endsWith(';')) {
|
|
1364
|
+
statement += ';';
|
|
1365
|
+
}
|
|
1366
|
+
imports.push(statement);
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
const requireCount = imports.filter(s => s.includes('require(')).length;
|
|
1370
|
+
const importCount = imports.filter(s => s.includes('import(')).length;
|
|
1371
|
+
|
|
1372
|
+
this.logger.info(`📦 Extracted ${imports.length} top-level imports from config (${requireCount} require, ${importCount} dynamic import)`);
|
|
1373
|
+
|
|
1374
|
+
return imports;
|
|
1375
|
+
} catch (error) {
|
|
1376
|
+
this.logger.warn(`⚠️ Failed to extract imports from config: ${error.message}`);
|
|
1377
|
+
return [];
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1301
1381
|
/**
|
|
1302
1382
|
* Generate handlers code from agent configuration
|
|
1303
1383
|
*/
|
|
@@ -1398,6 +1478,12 @@ class DockerManager {
|
|
|
1398
1478
|
has_docker_config: !!agent.config.docker
|
|
1399
1479
|
});
|
|
1400
1480
|
|
|
1481
|
+
// Initialize plugins before finalizing
|
|
1482
|
+
// This ensures plugin handlers and tools are available during code generation
|
|
1483
|
+
if (agent._initializePlugins) {
|
|
1484
|
+
await agent._initializePlugins();
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1401
1487
|
// Finalize agent configuration (auto-detect features)
|
|
1402
1488
|
// This will validate that agent.id is set (required)
|
|
1403
1489
|
agent.finalize();
|
|
@@ -1433,14 +1519,19 @@ class DockerManager {
|
|
|
1433
1519
|
const hasImage = await this.hasImage(imageName);
|
|
1434
1520
|
if (!hasImage || options.rebuild) {
|
|
1435
1521
|
await this.buildAgentImage(agent, {
|
|
1436
|
-
projectDir: options.projectDir
|
|
1522
|
+
projectDir: options.projectDir,
|
|
1523
|
+
configPath: options.configPath,
|
|
1524
|
+
projectRoot: options.projectRoot
|
|
1437
1525
|
});
|
|
1438
1526
|
}
|
|
1439
1527
|
|
|
1440
1528
|
// Prepare container configuration
|
|
1529
|
+
// Platform is set to linux/amd64 to match the build platform
|
|
1530
|
+
// This ensures consistent behavior and allows ARM machines to run via QEMU emulation
|
|
1441
1531
|
const containerConfig = {
|
|
1442
1532
|
Image: imageName,
|
|
1443
1533
|
name: containerName,
|
|
1534
|
+
platform: 'linux/amd64',
|
|
1444
1535
|
Env: this.prepareEnvironmentVariables(agent),
|
|
1445
1536
|
HostConfig: {
|
|
1446
1537
|
Memory: AgentConfig.parseMemory(AgentConfig.getResourcesFromInstanceType(agent.config.instanceType).memory),
|
|
@@ -1636,11 +1727,35 @@ class DockerManager {
|
|
|
1636
1727
|
|
|
1637
1728
|
/**
|
|
1638
1729
|
* Copy project files to build context (excluding common ignore patterns)
|
|
1730
|
+
* @param {string} projectDir - Directory containing code files to copy
|
|
1731
|
+
* @param {string} contextDir - Build context directory
|
|
1732
|
+
* @param {object} options - Additional options
|
|
1733
|
+
* @param {string} options.projectRoot - Root directory for package.json (defaults to process.cwd())
|
|
1639
1734
|
*/
|
|
1640
|
-
async copyProjectFiles(projectDir, contextDir) {
|
|
1735
|
+
async copyProjectFiles(projectDir, contextDir, options = {}) {
|
|
1641
1736
|
const agentCodeDir = path.join(contextDir, "agent-code");
|
|
1642
1737
|
await fs.ensureDir(agentCodeDir);
|
|
1643
1738
|
|
|
1739
|
+
// Copy package.json and package-lock.json from project root (not projectDir)
|
|
1740
|
+
// This ensures dependencies are installed correctly even for compiled projects
|
|
1741
|
+
// where projectDir might be ./dist but package.json is at root
|
|
1742
|
+
const projectRoot = options.projectRoot || process.cwd();
|
|
1743
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
1744
|
+
const packageLockPath = path.join(projectRoot, 'package-lock.json');
|
|
1745
|
+
|
|
1746
|
+
try {
|
|
1747
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
1748
|
+
await fs.copy(packageJsonPath, path.join(agentCodeDir, 'package.json'));
|
|
1749
|
+
this.logger.info(`📦 Copied package.json from project root`);
|
|
1750
|
+
}
|
|
1751
|
+
if (await fs.pathExists(packageLockPath)) {
|
|
1752
|
+
await fs.copy(packageLockPath, path.join(agentCodeDir, 'package-lock.json'));
|
|
1753
|
+
this.logger.info(`📦 Copied package-lock.json from project root`);
|
|
1754
|
+
}
|
|
1755
|
+
} catch (error) {
|
|
1756
|
+
this.logger.warn(`⚠️ Failed to copy package files: ${error.message}`);
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1644
1759
|
// Patterns to exclude when copying project files
|
|
1645
1760
|
const ignorePatterns = [
|
|
1646
1761
|
'node_modules',
|
|
@@ -1654,7 +1769,9 @@ class DockerManager {
|
|
|
1654
1769
|
'build',
|
|
1655
1770
|
'.dank',
|
|
1656
1771
|
'coverage',
|
|
1657
|
-
'.nyc_output'
|
|
1772
|
+
'.nyc_output',
|
|
1773
|
+
'package.json', // Already copied from project root
|
|
1774
|
+
'package-lock.json' // Already copied from project root
|
|
1658
1775
|
];
|
|
1659
1776
|
|
|
1660
1777
|
try {
|
|
@@ -1707,7 +1824,9 @@ class DockerManager {
|
|
|
1707
1824
|
// Copy project files if project directory is provided
|
|
1708
1825
|
// This allows handlers to reference functions from other files
|
|
1709
1826
|
if (options.projectDir) {
|
|
1710
|
-
await this.copyProjectFiles(options.projectDir, contextDir
|
|
1827
|
+
await this.copyProjectFiles(options.projectDir, contextDir, {
|
|
1828
|
+
projectRoot: options.projectRoot
|
|
1829
|
+
});
|
|
1711
1830
|
}
|
|
1712
1831
|
|
|
1713
1832
|
// Get the base image for this agent
|
|
@@ -1745,14 +1864,39 @@ class DockerManager {
|
|
|
1745
1864
|
this.logger.info(` - DOCKER_PORT: ${env.DOCKER_PORT || 'not set'}`);
|
|
1746
1865
|
}
|
|
1747
1866
|
|
|
1867
|
+
// Check if package.json exists in project root to determine if we need npm install
|
|
1868
|
+
const projectRoot = options.projectRoot || process.cwd();
|
|
1869
|
+
const hasPackageJson = await fs.pathExists(path.join(projectRoot, 'package.json'));
|
|
1870
|
+
|
|
1871
|
+
// Generate npm install step if package.json exists
|
|
1872
|
+
// We copy package files first for Docker layer caching, then install, then copy code
|
|
1873
|
+
const npmInstallStep = hasPackageJson ? `
|
|
1874
|
+
# Copy dependency files first (for Docker layer caching)
|
|
1875
|
+
COPY agent-code/package.json /app/agent-code/package.json
|
|
1876
|
+
COPY agent-code/package-lock.json* /app/agent-code/
|
|
1877
|
+
|
|
1878
|
+
# Install dependencies
|
|
1879
|
+
WORKDIR /app/agent-code
|
|
1880
|
+
RUN npm ci --production 2>/dev/null || npm install --production
|
|
1881
|
+
|
|
1882
|
+
# Reset working directory
|
|
1883
|
+
WORKDIR /app
|
|
1884
|
+
` : '';
|
|
1885
|
+
|
|
1748
1886
|
// Create Dockerfile for agent
|
|
1749
1887
|
const dockerfile = `FROM ${baseImageName}
|
|
1888
|
+
${npmInstallStep}
|
|
1889
|
+
# Copy agent code
|
|
1750
1890
|
COPY agent-code/ /app/agent-code/
|
|
1751
1891
|
${envStatements}
|
|
1752
1892
|
USER dankuser
|
|
1753
1893
|
`;
|
|
1754
1894
|
|
|
1755
1895
|
await fs.writeFile(path.join(contextDir, "Dockerfile"), dockerfile);
|
|
1896
|
+
|
|
1897
|
+
if (hasPackageJson) {
|
|
1898
|
+
this.logger.info(`📦 Dockerfile includes npm ci step for dependency installation`);
|
|
1899
|
+
}
|
|
1756
1900
|
|
|
1757
1901
|
// Copy agent code if it exists
|
|
1758
1902
|
const agentCodeDir = path.join(contextDir, "agent-code");
|
|
@@ -1763,6 +1907,16 @@ USER dankuser
|
|
|
1763
1907
|
const handlersCode = this.generateHandlersCode(agent);
|
|
1764
1908
|
const routesCode = this.generateRoutesCode(agent);
|
|
1765
1909
|
|
|
1910
|
+
// Extract top-level requires from config file for injection into generated code
|
|
1911
|
+
// This solves the closure problem where handlers reference modules imported at top of config
|
|
1912
|
+
const topLevelRequires = options.configPath
|
|
1913
|
+
? this.extractTopLevelRequires(options.configPath)
|
|
1914
|
+
: [];
|
|
1915
|
+
|
|
1916
|
+
const injectedRequires = topLevelRequires.length > 0
|
|
1917
|
+
? `// Injected top-level requires from dank.config.js\n${topLevelRequires.join('\n')}\n`
|
|
1918
|
+
: '';
|
|
1919
|
+
|
|
1766
1920
|
// Check if project files were copied (indicated by presence of files other than index.js)
|
|
1767
1921
|
const hasProjectFiles = options.projectDir ? true : false;
|
|
1768
1922
|
const projectFilesNote = hasProjectFiles
|
|
@@ -1775,6 +1929,7 @@ USER dankuser
|
|
|
1775
1929
|
// Agent: ${agent.name}
|
|
1776
1930
|
// Generated by Dank Agent Service
|
|
1777
1931
|
|
|
1932
|
+
${injectedRequires}
|
|
1778
1933
|
${projectFilesNote}
|
|
1779
1934
|
|
|
1780
1935
|
module.exports = {
|
package/lib/index.js
CHANGED
|
@@ -9,6 +9,7 @@ const { DankAgent } = require('./agent');
|
|
|
9
9
|
const { DankProject } = require('./project');
|
|
10
10
|
const { AgentConfig } = require('./config');
|
|
11
11
|
const { SUPPORTED_LLMS, DEFAULT_CONFIG } = require('./constants');
|
|
12
|
+
const { PluginBase, PluginRegistry, PluginManager, PluginConfig, PluginEventSystem } = require('./plugins');
|
|
12
13
|
|
|
13
14
|
module.exports = {
|
|
14
15
|
// Main classes
|
|
@@ -16,6 +17,13 @@ module.exports = {
|
|
|
16
17
|
DankProject,
|
|
17
18
|
AgentConfig,
|
|
18
19
|
|
|
20
|
+
// Plugin system
|
|
21
|
+
PluginBase,
|
|
22
|
+
PluginRegistry,
|
|
23
|
+
PluginManager,
|
|
24
|
+
PluginConfig,
|
|
25
|
+
PluginEventSystem,
|
|
26
|
+
|
|
19
27
|
// Constants
|
|
20
28
|
SUPPORTED_LLMS,
|
|
21
29
|
DEFAULT_CONFIG,
|