Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,4 @@ coverage/
*.seed
*.pid.lock

.worktrees
21 changes: 18 additions & 3 deletions src/core/resources/agent/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,22 @@ import {
import type { AgentConfig, AgentConfigApiResponse } from "./schema.js";
import { AgentConfigSchema } from "./schema.js";

/**
* Convert an agent name to a filesystem-safe filename slug.
* Lowercases, replaces non-alphanumeric characters with underscores,
* and collapses consecutive underscores.
*/
function toFileSlug(name: string): string {
return name
.toLowerCase()
.replace(/[^a-z0-9_]/g, "_")
.replace(/_+/g, "_")
.replace(/^_|_$/g, "");
}

export function generateAgentConfigContent(name: string): string {
return `// Base44 Agent Configuration
// Agent name must be lowercase alphanumeric with underscores only
// Agent configuration file
{
"name": "${name}",
// Brief description of what this agent does
Expand Down Expand Up @@ -82,7 +95,8 @@ export async function writeAgents(

const toDelete = existingAgents.filter((a) => !newNames.has(a.name));
for (const agent of toDelete) {
const files = await globby(`${agent.name}.${CONFIG_FILE_EXTENSION_GLOB}`, {
const slug = toFileSlug(agent.name);
const files = await globby(`${slug}.${CONFIG_FILE_EXTENSION_GLOB}`, {
cwd: agentsDir,
absolute: true,
});
Expand All @@ -92,7 +106,8 @@ export async function writeAgents(
}

for (const agent of remoteAgents) {
const filePath = join(agentsDir, `${agent.name}.${CONFIG_FILE_EXTENSION}`);
const slug = toFileSlug(agent.name);
const filePath = join(agentsDir, `${slug}.${CONFIG_FILE_EXTENSION}`);
await writeJsonFile(filePath, agent);
}

Expand Down
9 changes: 1 addition & 8 deletions src/core/resources/agent/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,7 @@ const ToolConfigSchema = z.union([
]);

export const AgentConfigSchema = z.looseObject({
name: z
.string()
.regex(
/^[a-z0-9_]+$/,
"Agent name must be lowercase alphanumeric with underscores"
)
.min(1)
.max(100),
name: z.string().trim().min(1).max(100),
description: z.string().trim().min(1, "Description is required"),
instructions: z.string().trim().min(1, "Instructions are required"),
tool_configs: z.array(ToolConfigSchema).optional().default([]),
Expand Down
3 changes: 1 addition & 2 deletions tests/cli/agents_push.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,12 @@ describe("agents push command", () => {
t.expectResult(result).toContain("Deleted: old_agent");
});

it("fails with helpful error when agent has invalid name format", async () => {
it("fails with helpful error when agent has empty name", async () => {
await t.givenLoggedInWithProject(fixture("invalid-agent"));

const result = await t.run("agents", "push");

t.expectResult(result).toFail();
t.expectResult(result).toContain("name");
});

it("fails when API returns error", async () => {
Expand Down
5 changes: 3 additions & 2 deletions tests/fixtures/invalid-agent/base44/agents/broken.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"name": "INVALID-NAME!",
"description": ""
"name": "",
"description": "A valid description",
"instructions": "Valid instructions"
}
Loading