dank-ai 1.0.30 → 1.0.32
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/lib/docker/manager.js +63 -77
- package/package.json +1 -1
package/lib/docker/manager.js
CHANGED
|
@@ -1140,23 +1140,48 @@ class DockerManager {
|
|
|
1140
1140
|
}
|
|
1141
1141
|
}
|
|
1142
1142
|
|
|
1143
|
+
/**
|
|
1144
|
+
* Normalize Docker image name/tag components
|
|
1145
|
+
* Docker allows: lowercase letters, digits, underscores, periods, and hyphens
|
|
1146
|
+
* Cannot start or end with period or hyphen, max 128 chars for tags
|
|
1147
|
+
*/
|
|
1148
|
+
normalizeDockerName(name) {
|
|
1149
|
+
if (!name || typeof name !== 'string') return 'invalid';
|
|
1150
|
+
const lower = String(name).toLowerCase();
|
|
1151
|
+
let sanitized = lower.replace(/[^a-z0-9_.-]/g, '-');
|
|
1152
|
+
sanitized = sanitized.replace(/\.{2,}/g, '.'); // Replace multiple periods with single
|
|
1153
|
+
sanitized = sanitized.replace(/-{2,}/g, '-'); // Replace multiple hyphens with single
|
|
1154
|
+
sanitized = sanitized.replace(/^[.-]+/, '').replace(/[.-]+$/, ''); // Remove leading/trailing . or -
|
|
1155
|
+
if (!sanitized || !/^[a-z0-9]/.test(sanitized)) sanitized = `a${sanitized || ''}`;
|
|
1156
|
+
if (sanitized.length > 128) sanitized = sanitized.slice(0, 128);
|
|
1157
|
+
return sanitized;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1143
1160
|
/**
|
|
1144
1161
|
* Build agent-specific image
|
|
1145
1162
|
*/
|
|
1146
1163
|
async buildAgentImage(agent, options = {}) {
|
|
1147
|
-
const
|
|
1164
|
+
const normalizedName = this.normalizeDockerName(agent.name);
|
|
1165
|
+
const imageName = `dank-agent-${normalizedName}`;
|
|
1148
1166
|
this.logger.info(`Building image for agent: ${agent.name}`);
|
|
1149
1167
|
|
|
1150
1168
|
try {
|
|
1151
1169
|
const buildContext = await this.createAgentBuildContext(agent);
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1170
|
+
const dockerCmd = await this.resolveDockerCommand();
|
|
1171
|
+
|
|
1172
|
+
const buildCommand = [
|
|
1173
|
+
dockerCmd,
|
|
1174
|
+
'buildx',
|
|
1175
|
+
'build',
|
|
1176
|
+
'--platform', 'linux/amd64',
|
|
1177
|
+
'--tag', imageName,
|
|
1178
|
+
'--file', path.join(buildContext, 'Dockerfile'),
|
|
1179
|
+
'--load',
|
|
1180
|
+
...(options.rebuild || options.noCache ? ['--no-cache'] : []),
|
|
1181
|
+
buildContext
|
|
1182
|
+
].join(' ');
|
|
1183
|
+
|
|
1184
|
+
await this.runCommand(buildCommand, `Agent ${agent.name} build`);
|
|
1160
1185
|
|
|
1161
1186
|
this.logger.info(`Agent image '${imageName}' built successfully`);
|
|
1162
1187
|
|
|
@@ -1182,30 +1207,24 @@ class DockerManager {
|
|
|
1182
1207
|
push = false,
|
|
1183
1208
|
} = options;
|
|
1184
1209
|
|
|
1185
|
-
// Normalize
|
|
1186
|
-
const
|
|
1187
|
-
|
|
1188
|
-
let sanitized = lower.replace(/[^a-z0-9_.-]/g, '-');
|
|
1189
|
-
sanitized = sanitized.replace(/^-+/, '').replace(/-+$/, '');
|
|
1190
|
-
if (!sanitized || !/^[a-z0-9]/.test(sanitized)) sanitized = `a${sanitized || ''}`;
|
|
1191
|
-
if (sanitized.length > 128) sanitized = sanitized.slice(0, 128);
|
|
1192
|
-
return sanitized;
|
|
1193
|
-
};
|
|
1210
|
+
// Normalize all components
|
|
1211
|
+
const normalizedAgentName = this.normalizeDockerName(agent.name);
|
|
1212
|
+
const normalizedTag = this.normalizeDockerName(tag);
|
|
1194
1213
|
|
|
1195
1214
|
//construct full repo name
|
|
1196
1215
|
let repoName;
|
|
1197
1216
|
if(!tagByAgent){
|
|
1198
|
-
|
|
1199
|
-
repoName = `${registry?`${registry}/`:''}${namespace?`${namespace}/`:''}${
|
|
1217
|
+
// Per-agent repository: {registry}/{namespace}/{agent-name}
|
|
1218
|
+
repoName = `${registry?`${registry}/`:''}${namespace?`${namespace}/`:''}${normalizedAgentName}`;
|
|
1200
1219
|
}else{
|
|
1220
|
+
// Common repository: {registry}/{namespace}
|
|
1201
1221
|
repoName = `${registry?`${registry}/`:''}${namespace?`${namespace}/`:''}`;
|
|
1202
1222
|
}
|
|
1203
1223
|
|
|
1204
1224
|
repoName = repoName.replace(/\/+$/, '');
|
|
1205
1225
|
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
const finalTag = tagByAgent ? normalizeTag(agent.name) : tag;
|
|
1226
|
+
// Final tag selection - normalize both agent name tags and user-provided tags
|
|
1227
|
+
const finalTag = tagByAgent ? normalizedAgentName : normalizedTag;
|
|
1209
1228
|
const imageName = `${repoName}:${finalTag}`;
|
|
1210
1229
|
|
|
1211
1230
|
this.logger.info(
|
|
@@ -1214,54 +1233,33 @@ class DockerManager {
|
|
|
1214
1233
|
|
|
1215
1234
|
try {
|
|
1216
1235
|
const buildContext = await this.createAgentBuildContext(agent);
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1236
|
+
const dockerCmd = await this.resolveDockerCommand();
|
|
1237
|
+
|
|
1238
|
+
const buildCommand = [
|
|
1239
|
+
dockerCmd,
|
|
1240
|
+
'buildx',
|
|
1241
|
+
'build',
|
|
1242
|
+
'--platform', 'linux/amd64',
|
|
1243
|
+
'--tag', imageName,
|
|
1244
|
+
'--file', path.join(buildContext, 'Dockerfile'),
|
|
1245
|
+
...(push ? ['--push'] : ['--load']),
|
|
1246
|
+
...(force ? ['--no-cache'] : []),
|
|
1247
|
+
buildContext
|
|
1248
|
+
].join(' ');
|
|
1249
|
+
|
|
1250
|
+
await this.runCommand(buildCommand, `Production build for ${agent.name}`);
|
|
1228
1251
|
|
|
1229
1252
|
this.logger.info(`Production image '${imageName}' built successfully`);
|
|
1253
|
+
if (push) {
|
|
1254
|
+
this.logger.info(`Successfully pushed image: ${imageName}`);
|
|
1255
|
+
}
|
|
1230
1256
|
|
|
1231
1257
|
// Clean up build context
|
|
1232
1258
|
await fs.remove(buildContext);
|
|
1233
1259
|
|
|
1234
|
-
let pushed = false;
|
|
1235
|
-
|
|
1236
|
-
// Push to registry if requested (use docker CLI for reliability)
|
|
1237
|
-
if (push) {
|
|
1238
|
-
const dockerCmd = await this.resolveDockerCommand();
|
|
1239
|
-
const maxAttempts = 3;
|
|
1240
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1241
|
-
try {
|
|
1242
|
-
this.logger.info(`Pushing image to registry (attempt ${attempt}/${maxAttempts}): ${imageName}`);
|
|
1243
|
-
await this.runCommand(`${dockerCmd} push "${imageName}"`, `Docker push ${imageName}`);
|
|
1244
|
-
this.logger.info(`Successfully pushed image: ${imageName}`);
|
|
1245
|
-
pushed = true;
|
|
1246
|
-
break;
|
|
1247
|
-
} catch (pushError) {
|
|
1248
|
-
const msg = pushError?.message || '';
|
|
1249
|
-
this.logger.warn(`Push attempt ${attempt} failed: ${msg}`);
|
|
1250
|
-
if (msg.match(/denied|unauthorized|authentication required/i)) {
|
|
1251
|
-
this.logger.warn("Authentication issue detected. Please ensure you're logged in: docker login <registry>");
|
|
1252
|
-
break; // don't retry auth failures automatically
|
|
1253
|
-
}
|
|
1254
|
-
if (attempt < maxAttempts) {
|
|
1255
|
-
await this.sleep(2000 * attempt);
|
|
1256
|
-
continue;
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
1260
|
return {
|
|
1263
1261
|
imageName,
|
|
1264
|
-
pushed,
|
|
1262
|
+
pushed: push,
|
|
1265
1263
|
};
|
|
1266
1264
|
} catch (error) {
|
|
1267
1265
|
throw new Error(`Failed to build production image: ${error.message}`);
|
|
@@ -1623,20 +1621,8 @@ module.exports = {
|
|
|
1623
1621
|
|
|
1624
1622
|
await fs.writeFile(path.join(agentCodeDir, "index.js"), agentCode);
|
|
1625
1623
|
|
|
1626
|
-
//
|
|
1627
|
-
|
|
1628
|
-
__dirname,
|
|
1629
|
-
`../../.agent-${agent.name}-context.tar`
|
|
1630
|
-
);
|
|
1631
|
-
await tar.create(
|
|
1632
|
-
{
|
|
1633
|
-
file: tarPath,
|
|
1634
|
-
cwd: contextDir,
|
|
1635
|
-
},
|
|
1636
|
-
["."]
|
|
1637
|
-
);
|
|
1638
|
-
|
|
1639
|
-
return tarPath;
|
|
1624
|
+
// Return directory path (CLI build commands work with directories directly)
|
|
1625
|
+
return contextDir;
|
|
1640
1626
|
}
|
|
1641
1627
|
|
|
1642
1628
|
/**
|