Auto-deduplicate agent shortname on join request approval
When approving an agent join request with a shortname already in use, append a numeric suffix (e.g. "openclaw-2") instead of returning an error. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { hasAgentShortnameCollision } from "../services/agents.ts";
|
import { hasAgentShortnameCollision, deduplicateAgentName } from "../services/agents.ts";
|
||||||
|
|
||||||
describe("hasAgentShortnameCollision", () => {
|
describe("hasAgentShortnameCollision", () => {
|
||||||
it("detects collisions by normalized shortname", () => {
|
it("detects collisions by normalized shortname", () => {
|
||||||
@@ -35,3 +35,35 @@ describe("hasAgentShortnameCollision", () => {
|
|||||||
expect(collision).toBe(false);
|
expect(collision).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("deduplicateAgentName", () => {
|
||||||
|
it("returns original name when no collision", () => {
|
||||||
|
const name = deduplicateAgentName("OpenClaw", [
|
||||||
|
{ id: "a1", name: "other-agent", status: "idle" },
|
||||||
|
]);
|
||||||
|
expect(name).toBe("OpenClaw");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("appends suffix when name collides", () => {
|
||||||
|
const name = deduplicateAgentName("OpenClaw", [
|
||||||
|
{ id: "a1", name: "openclaw", status: "idle" },
|
||||||
|
]);
|
||||||
|
expect(name).toBe("OpenClaw 2");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("increments suffix until unique", () => {
|
||||||
|
const name = deduplicateAgentName("OpenClaw", [
|
||||||
|
{ id: "a1", name: "openclaw", status: "idle" },
|
||||||
|
{ id: "a2", name: "openclaw-2", status: "idle" },
|
||||||
|
{ id: "a3", name: "openclaw-3", status: "idle" },
|
||||||
|
]);
|
||||||
|
expect(name).toBe("OpenClaw 4");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores terminated agents for collision", () => {
|
||||||
|
const name = deduplicateAgentName("OpenClaw", [
|
||||||
|
{ id: "a1", name: "openclaw", status: "terminated" },
|
||||||
|
]);
|
||||||
|
expect(name).toBe("OpenClaw");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -165,6 +165,22 @@ export function hasAgentShortnameCollision(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deduplicateAgentName(
|
||||||
|
candidateName: string,
|
||||||
|
existingAgents: AgentShortnameRow[],
|
||||||
|
): string {
|
||||||
|
if (!hasAgentShortnameCollision(candidateName, existingAgents)) {
|
||||||
|
return candidateName;
|
||||||
|
}
|
||||||
|
for (let i = 2; i <= 100; i++) {
|
||||||
|
const suffixed = `${candidateName} ${i}`;
|
||||||
|
if (!hasAgentShortnameCollision(suffixed, existingAgents)) {
|
||||||
|
return suffixed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `${candidateName} ${Date.now()}`;
|
||||||
|
}
|
||||||
|
|
||||||
export function agentService(db: Db) {
|
export function agentService(db: Db) {
|
||||||
function withUrlKey<T extends { id: string; name: string }>(row: T) {
|
function withUrlKey<T extends { id: string; name: string }>(row: T) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export { companyService } from "./companies.js";
|
export { companyService } from "./companies.js";
|
||||||
export { agentService } from "./agents.js";
|
export { agentService, deduplicateAgentName } from "./agents.js";
|
||||||
export { assetService } from "./assets.js";
|
export { assetService } from "./assets.js";
|
||||||
export { projectService } from "./projects.js";
|
export { projectService } from "./projects.js";
|
||||||
export { issueService, type IssueFilters } from "./issues.js";
|
export { issueService, type IssueFilters } from "./issues.js";
|
||||||
|
|||||||
Reference in New Issue
Block a user