Assign invite-joined agents to company CEO
This commit is contained in:
33
server/src/__tests__/invite-join-manager.test.ts
Normal file
33
server/src/__tests__/invite-join-manager.test.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { resolveJoinRequestAgentManagerId } from "../routes/access.js";
|
||||||
|
|
||||||
|
describe("resolveJoinRequestAgentManagerId", () => {
|
||||||
|
it("returns null when no CEO exists in the company agent list", () => {
|
||||||
|
const managerId = resolveJoinRequestAgentManagerId([
|
||||||
|
{ id: "a1", role: "cto", reportsTo: null },
|
||||||
|
{ id: "a2", role: "engineer", reportsTo: "a1" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(managerId).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("selects the root CEO when available", () => {
|
||||||
|
const managerId = resolveJoinRequestAgentManagerId([
|
||||||
|
{ id: "ceo-child", role: "ceo", reportsTo: "manager-1" },
|
||||||
|
{ id: "manager-1", role: "cto", reportsTo: null },
|
||||||
|
{ id: "ceo-root", role: "ceo", reportsTo: null },
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(managerId).toBe("ceo-root");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to the first CEO when no root CEO is present", () => {
|
||||||
|
const managerId = resolveJoinRequestAgentManagerId([
|
||||||
|
{ id: "ceo-1", role: "ceo", reportsTo: "mgr" },
|
||||||
|
{ id: "ceo-2", role: "ceo", reportsTo: "mgr" },
|
||||||
|
{ id: "mgr", role: "cto", reportsTo: null },
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(managerId).toBe("ceo-1");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -965,6 +965,21 @@ function grantsFromDefaults(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type JoinRequestManagerCandidate = {
|
||||||
|
id: string;
|
||||||
|
role: string;
|
||||||
|
reportsTo: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function resolveJoinRequestAgentManagerId(
|
||||||
|
candidates: JoinRequestManagerCandidate[],
|
||||||
|
): string | null {
|
||||||
|
const ceoCandidates = candidates.filter((candidate) => candidate.role === "ceo");
|
||||||
|
if (ceoCandidates.length === 0) return null;
|
||||||
|
const rootCeo = ceoCandidates.find((candidate) => candidate.reportsTo === null);
|
||||||
|
return (rootCeo ?? ceoCandidates[0] ?? null)?.id ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
function isInviteTokenHashCollisionError(error: unknown) {
|
function isInviteTokenHashCollisionError(error: unknown) {
|
||||||
const candidates = [
|
const candidates = [
|
||||||
error,
|
error,
|
||||||
@@ -1604,12 +1619,17 @@ export function accessRoutes(
|
|||||||
req.actor.userId ?? null,
|
req.actor.userId ?? null,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
const managerId = resolveJoinRequestAgentManagerId(await agents.list(companyId));
|
||||||
|
if (!managerId) {
|
||||||
|
throw conflict("Join request cannot be approved because this company has no active CEO");
|
||||||
|
}
|
||||||
|
|
||||||
const created = await agents.create(companyId, {
|
const created = await agents.create(companyId, {
|
||||||
name: existing.agentName ?? "New Agent",
|
name: existing.agentName ?? "New Agent",
|
||||||
role: "general",
|
role: "general",
|
||||||
title: null,
|
title: null,
|
||||||
status: "idle",
|
status: "idle",
|
||||||
reportsTo: null,
|
reportsTo: managerId,
|
||||||
capabilities: existing.capabilities ?? null,
|
capabilities: existing.capabilities ?? null,
|
||||||
adapterType: existing.adapterType ?? "process",
|
adapterType: existing.adapterType ?? "process",
|
||||||
adapterConfig:
|
adapterConfig:
|
||||||
|
|||||||
Reference in New Issue
Block a user