From 00aa70cee606a60ba45e7553e981388c4dc8c45a Mon Sep 17 00:00:00 2001 From: Pavel Tarnopolsky Date: Wed, 11 Feb 2026 14:59:25 +0200 Subject: [PATCH 1/3] Remove agent name pattern validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `^[a-z0-9_]+$` regex constraint was too restrictive — agents created via the AI builder can have names with uppercase, dashes, etc. This aligns the CLI with the apper-side change (base44-dev/apper#3610) so pull works for all existing agents. Co-Authored-By: Claude Opus 4.6 --- src/core/resources/agent/config.ts | 2 +- src/core/resources/agent/schema.ts | 9 +-------- tests/cli/agents_push.spec.ts | 3 +-- tests/fixtures/invalid-agent/base44/agents/broken.json | 5 +++-- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/core/resources/agent/config.ts b/src/core/resources/agent/config.ts index c29a5a3b..f2d03f63 100644 --- a/src/core/resources/agent/config.ts +++ b/src/core/resources/agent/config.ts @@ -16,7 +16,7 @@ import { AgentConfigSchema } from "./schema.js"; 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 diff --git a/src/core/resources/agent/schema.ts b/src/core/resources/agent/schema.ts index 9ea14472..be07055c 100644 --- a/src/core/resources/agent/schema.ts +++ b/src/core/resources/agent/schema.ts @@ -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([]), diff --git a/tests/cli/agents_push.spec.ts b/tests/cli/agents_push.spec.ts index 5e8f639b..8b8906b6 100644 --- a/tests/cli/agents_push.spec.ts +++ b/tests/cli/agents_push.spec.ts @@ -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 () => { diff --git a/tests/fixtures/invalid-agent/base44/agents/broken.json b/tests/fixtures/invalid-agent/base44/agents/broken.json index bd75d157..4b220d8f 100644 --- a/tests/fixtures/invalid-agent/base44/agents/broken.json +++ b/tests/fixtures/invalid-agent/base44/agents/broken.json @@ -1,4 +1,5 @@ { - "name": "INVALID-NAME!", - "description": "" + "name": "", + "description": "A valid description", + "instructions": "Valid instructions" } From 5dc1932c3bdcd558b5faaaeed8cac7753dfbafb3 Mon Sep 17 00:00:00 2001 From: Pavel Tarnopolsky Date: Wed, 11 Feb 2026 15:01:12 +0200 Subject: [PATCH 2/3] chore: add .worktrees to .gitignore Co-Authored-By: Claude Opus 4.6 --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 82e41184..fdc480a7 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,4 @@ coverage/ *.seed *.pid.lock +.worktrees From 3ffc978a8d0357acfcadc6e6547d88635574628d Mon Sep 17 00:00:00 2001 From: Pavel Tarnopolsky Date: Wed, 11 Feb 2026 15:03:47 +0200 Subject: [PATCH 3/3] Add toFileSlug for filesystem-safe agent filenames Agent names are no longer restricted to [a-z0-9_], so we need to slugify them when creating/deleting local files on pull. The name inside the JSON remains the source of truth. Co-Authored-By: Claude Opus 4.6 --- src/core/resources/agent/config.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/core/resources/agent/config.ts b/src/core/resources/agent/config.ts index f2d03f63..e4a77636 100644 --- a/src/core/resources/agent/config.ts +++ b/src/core/resources/agent/config.ts @@ -14,6 +14,19 @@ 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 configuration file @@ -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, }); @@ -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); }