From 02c9019865dde84be1e8df7536295c6842d361e7 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 09:35:50 +0000 Subject: [PATCH 1/4] fix: add invited members to agent's team using stored team_id Pass the agent's team_id through the invite form as a hidden field instead of re-querying the agent on submit. The team_id is already available from the parent route's loader data. Closes #301 Co-authored-by: Heinrich Wendel --- apps/console/app/lib/auth/better-auth.ts | 3 ++- apps/console/app/lib/auth/dummy-auth.ts | 2 +- apps/console/app/lib/auth/types.ts | 2 +- .../_shell.agent.$agentSlug.settings.members/route.tsx | 7 ++++++- .../routes/_shell.agent.$agentSlug.settings/members.tsx | 3 +++ .../app/routes/_shell.agent.$agentSlug.settings/route.tsx | 1 + 6 files changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/console/app/lib/auth/better-auth.ts b/apps/console/app/lib/auth/better-auth.ts index 1cdf40054..3ba22ed62 100644 --- a/apps/console/app/lib/auth/better-auth.ts +++ b/apps/console/app/lib/auth/better-auth.ts @@ -203,10 +203,11 @@ export const authService: AuthService = { globalThis.location.replace("/"); }, - async inviteMember(email, role) { + async inviteMember(email, role, teamId) { const { error } = await authClient.organization.inviteMember({ email, role: role as "member" | "admin" | "owner", + ...(teamId && { teamId }), }); if (error) throw new Error(error.message); }, diff --git a/apps/console/app/lib/auth/dummy-auth.ts b/apps/console/app/lib/auth/dummy-auth.ts index b182254b7..cd05f5142 100644 --- a/apps/console/app/lib/auth/dummy-auth.ts +++ b/apps/console/app/lib/auth/dummy-auth.ts @@ -157,7 +157,7 @@ export const authService = { globalThis.location.replace("/"); }, - async inviteMember(email, role) { + async inviteMember(email, role, _teamId) { const organizationId = shellStore.user?.organizationId; if (!organizationId) throw new Error("No active organization"); void invitations.create({ diff --git a/apps/console/app/lib/auth/types.ts b/apps/console/app/lib/auth/types.ts index edaffa141..84e76419d 100644 --- a/apps/console/app/lib/auth/types.ts +++ b/apps/console/app/lib/auth/types.ts @@ -10,7 +10,7 @@ export interface AuthService { // Organization getOrganization(): Promise<{ members: OrgMember[]; invitations: OrgInvitation[] }>; setActiveOrganization(orgId: string): Promise; - inviteMember(email: string, role: string): Promise; + inviteMember(email: string, role: string, teamId?: string): Promise; removeMember(memberIdOrEmail: string): Promise; cancelInvitation(invitationId: string): Promise; acceptInvitation(invitationId: string): Promise; diff --git a/apps/console/app/routes/_shell.agent.$agentSlug.settings.members/route.tsx b/apps/console/app/routes/_shell.agent.$agentSlug.settings.members/route.tsx index 799f2350d..ebea7a210 100644 --- a/apps/console/app/routes/_shell.agent.$agentSlug.settings.members/route.tsx +++ b/apps/console/app/routes/_shell.agent.$agentSlug.settings.members/route.tsx @@ -12,8 +12,13 @@ export async function clientAction({ request }: { request: Request }) { if (intent === "invite") { const submission = parseWithZod(formData, { schema: inviteSchema }); if (submission.status !== "success") return { intent, submission: submission.reply() }; + const teamId = formData.get("teamId"); try { - await authService.inviteMember(submission.value.email, submission.value.role); + await authService.inviteMember( + submission.value.email, + submission.value.role, + typeof teamId === "string" ? teamId : undefined, + ); } catch (error) { return { intent, submission: submission.reply({ formErrors: [parseError(error).message] }) }; } diff --git a/apps/console/app/routes/_shell.agent.$agentSlug.settings/members.tsx b/apps/console/app/routes/_shell.agent.$agentSlug.settings/members.tsx index 024cbf871..d1a08b2a5 100644 --- a/apps/console/app/routes/_shell.agent.$agentSlug.settings/members.tsx +++ b/apps/console/app/routes/_shell.agent.$agentSlug.settings/members.tsx @@ -45,6 +45,7 @@ type MembersSettingsProps = { invitations: OrgInvitation[]; isOwner: boolean; canManage: boolean; + teamId?: string | null; }; export function MembersSettings({ @@ -52,6 +53,7 @@ export function MembersSettings({ invitations, isOwner, canManage, + teamId, }: MembersSettingsProps) { const fetcher = useFetcher<{ intent: string; submission: any }>(); const [role, setRole] = useState("member"); @@ -159,6 +161,7 @@ export function MembersSettings({ className="flex items-end gap-3" > + {teamId && } Email diff --git a/apps/console/app/routes/_shell.agent.$agentSlug.settings/route.tsx b/apps/console/app/routes/_shell.agent.$agentSlug.settings/route.tsx index 857fc7d70..9cc9ea75d 100644 --- a/apps/console/app/routes/_shell.agent.$agentSlug.settings/route.tsx +++ b/apps/console/app/routes/_shell.agent.$agentSlug.settings/route.tsx @@ -28,6 +28,7 @@ export default function Settings({ loaderData }: Route.ComponentProps) { invitations={loaderData.invitations} isOwner={loaderData.isOwner} canManage={loaderData.canManage} + teamId={agent.team_id} /> From 03d8b9bbdfb57dddccd19171935c744bef7ce9be Mon Sep 17 00:00:00 2001 From: Heinrich Wendel Date: Fri, 20 Mar 2026 18:02:49 +0800 Subject: [PATCH 2/4] fix: expose team_id via API --- apps/api/prisma/schema.prisma | 2 +- .../routes/_shell.agent.$agentSlug.settings/members.tsx | 7 +++---- .../app/routes/_shell.agent.$agentSlug.settings/route.tsx | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 5a106df10..c0993136c 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -44,7 +44,7 @@ model agents { deleted_at DateTime? /// @prismabox.hide organization_id String - /// @prismabox.hide + /// @prismabox.input.hide team_id String } diff --git a/apps/console/app/routes/_shell.agent.$agentSlug.settings/members.tsx b/apps/console/app/routes/_shell.agent.$agentSlug.settings/members.tsx index d1a08b2a5..3407071e9 100644 --- a/apps/console/app/routes/_shell.agent.$agentSlug.settings/members.tsx +++ b/apps/console/app/routes/_shell.agent.$agentSlug.settings/members.tsx @@ -45,15 +45,14 @@ type MembersSettingsProps = { invitations: OrgInvitation[]; isOwner: boolean; canManage: boolean; - teamId?: string | null; + agent: { team_id: string }; }; - export function MembersSettings({ members, invitations, isOwner, canManage, - teamId, + agent, }: MembersSettingsProps) { const fetcher = useFetcher<{ intent: string; submission: any }>(); const [role, setRole] = useState("member"); @@ -161,7 +160,7 @@ export function MembersSettings({ className="flex items-end gap-3" > - {teamId && } + Email diff --git a/apps/console/app/routes/_shell.agent.$agentSlug.settings/route.tsx b/apps/console/app/routes/_shell.agent.$agentSlug.settings/route.tsx index 9cc9ea75d..86b757800 100644 --- a/apps/console/app/routes/_shell.agent.$agentSlug.settings/route.tsx +++ b/apps/console/app/routes/_shell.agent.$agentSlug.settings/route.tsx @@ -28,7 +28,7 @@ export default function Settings({ loaderData }: Route.ComponentProps) { invitations={loaderData.invitations} isOwner={loaderData.isOwner} canManage={loaderData.canManage} - teamId={agent.team_id} + agent={agent} /> From 681b3a60b08aaa9aa93b7d6adc09abe949f8e550 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 11:06:21 +0000 Subject: [PATCH 3/4] fix: validate teamId in schema, scope invitations to team, add resend - Add teamId to inviteSchema for Zod validation instead of raw formData - Add teamId to OrgInvitation type and preserve it from Better Auth response - Filter displayed invitations by agent's team_id - Add resend: true to inviteMember to update existing pending invitations Co-authored-by: Heinrich Wendel --- apps/console/app/lib/auth/better-auth.ts | 14 +++++++++++--- apps/console/app/lib/auth/types.ts | 1 + .../invite-schema.ts | 1 + .../route.tsx | 3 +-- .../_shell.agent.$agentSlug.settings/members.tsx | 3 ++- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/apps/console/app/lib/auth/better-auth.ts b/apps/console/app/lib/auth/better-auth.ts index 3ba22ed62..1bdd85b88 100644 --- a/apps/console/app/lib/auth/better-auth.ts +++ b/apps/console/app/lib/auth/better-auth.ts @@ -188,9 +188,16 @@ export const authService: AuthService = { } } - const invitations = ((data.invitations ?? []) as unknown as OrgInvitation[]).filter( - (i) => i.status === "pending", - ); + const invitations = ((data.invitations ?? []) as unknown as (OrgInvitation & Record)[]) + .filter((i) => i.status === "pending") + .map(({ id, email, role, expiresAt, status, teamId }) => ({ + id, + email, + role, + expiresAt, + status, + teamId: typeof teamId === "string" ? teamId : undefined, + })); return { members, invitations }; }, @@ -207,6 +214,7 @@ export const authService: AuthService = { const { error } = await authClient.organization.inviteMember({ email, role: role as "member" | "admin" | "owner", + resend: true, ...(teamId && { teamId }), }); if (error) throw new Error(error.message); diff --git a/apps/console/app/lib/auth/types.ts b/apps/console/app/lib/auth/types.ts index 84e76419d..540425853 100644 --- a/apps/console/app/lib/auth/types.ts +++ b/apps/console/app/lib/auth/types.ts @@ -47,6 +47,7 @@ export type OrgInvitation = { role: string; expiresAt: string; status: string; + teamId?: string; }; export type ApiKey = { diff --git a/apps/console/app/routes/_shell.agent.$agentSlug.settings.members/invite-schema.ts b/apps/console/app/routes/_shell.agent.$agentSlug.settings.members/invite-schema.ts index 45e482405..3ffeb3954 100644 --- a/apps/console/app/routes/_shell.agent.$agentSlug.settings.members/invite-schema.ts +++ b/apps/console/app/routes/_shell.agent.$agentSlug.settings.members/invite-schema.ts @@ -3,4 +3,5 @@ import { z } from "zod"; export const inviteSchema = z.object({ email: z.email("Enter a valid email address"), role: z.enum(["member", "admin"]), + teamId: z.string().optional(), }); diff --git a/apps/console/app/routes/_shell.agent.$agentSlug.settings.members/route.tsx b/apps/console/app/routes/_shell.agent.$agentSlug.settings.members/route.tsx index ebea7a210..080bc6957 100644 --- a/apps/console/app/routes/_shell.agent.$agentSlug.settings.members/route.tsx +++ b/apps/console/app/routes/_shell.agent.$agentSlug.settings.members/route.tsx @@ -12,12 +12,11 @@ export async function clientAction({ request }: { request: Request }) { if (intent === "invite") { const submission = parseWithZod(formData, { schema: inviteSchema }); if (submission.status !== "success") return { intent, submission: submission.reply() }; - const teamId = formData.get("teamId"); try { await authService.inviteMember( submission.value.email, submission.value.role, - typeof teamId === "string" ? teamId : undefined, + submission.value.teamId, ); } catch (error) { return { intent, submission: submission.reply({ formErrors: [parseError(error).message] }) }; diff --git a/apps/console/app/routes/_shell.agent.$agentSlug.settings/members.tsx b/apps/console/app/routes/_shell.agent.$agentSlug.settings/members.tsx index 3407071e9..fdf8b6398 100644 --- a/apps/console/app/routes/_shell.agent.$agentSlug.settings/members.tsx +++ b/apps/console/app/routes/_shell.agent.$agentSlug.settings/members.tsx @@ -49,11 +49,12 @@ type MembersSettingsProps = { }; export function MembersSettings({ members, - invitations, + invitations: allInvitations, isOwner, canManage, agent, }: MembersSettingsProps) { + const invitations = allInvitations.filter((i) => i.teamId === agent.team_id); const fetcher = useFetcher<{ intent: string; submission: any }>(); const [role, setRole] = useState("member"); From 6bd2e28f018ebfdc012989640691cfa82384cd19 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 20 Mar 2026 11:26:10 +0000 Subject: [PATCH 4/4] fix: simplify invitation handling and move team filtering to settings route MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove redundant .map() in getOrganization() — the cast to OrgInvitation[] is sufficient since the type already includes teamId. Move team-scoped filtering from MembersSettings component up to the settings route component where both loader data and agent context are available. Co-authored-by: Heinrich Wendel Co-Authored-By: Claude Opus 4.6 --- apps/console/app/lib/auth/better-auth.ts | 13 +++---------- .../_shell.agent.$agentSlug.settings/members.tsx | 3 +-- .../_shell.agent.$agentSlug.settings/route.tsx | 2 +- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/apps/console/app/lib/auth/better-auth.ts b/apps/console/app/lib/auth/better-auth.ts index 1bdd85b88..4f65b83e7 100644 --- a/apps/console/app/lib/auth/better-auth.ts +++ b/apps/console/app/lib/auth/better-auth.ts @@ -188,16 +188,9 @@ export const authService: AuthService = { } } - const invitations = ((data.invitations ?? []) as unknown as (OrgInvitation & Record)[]) - .filter((i) => i.status === "pending") - .map(({ id, email, role, expiresAt, status, teamId }) => ({ - id, - email, - role, - expiresAt, - status, - teamId: typeof teamId === "string" ? teamId : undefined, - })); + const invitations = ((data.invitations ?? []) as unknown as OrgInvitation[]).filter( + (i) => i.status === "pending", + ); return { members, invitations }; }, diff --git a/apps/console/app/routes/_shell.agent.$agentSlug.settings/members.tsx b/apps/console/app/routes/_shell.agent.$agentSlug.settings/members.tsx index fdf8b6398..3407071e9 100644 --- a/apps/console/app/routes/_shell.agent.$agentSlug.settings/members.tsx +++ b/apps/console/app/routes/_shell.agent.$agentSlug.settings/members.tsx @@ -49,12 +49,11 @@ type MembersSettingsProps = { }; export function MembersSettings({ members, - invitations: allInvitations, + invitations, isOwner, canManage, agent, }: MembersSettingsProps) { - const invitations = allInvitations.filter((i) => i.teamId === agent.team_id); const fetcher = useFetcher<{ intent: string; submission: any }>(); const [role, setRole] = useState("member"); diff --git a/apps/console/app/routes/_shell.agent.$agentSlug.settings/route.tsx b/apps/console/app/routes/_shell.agent.$agentSlug.settings/route.tsx index 86b757800..382f89462 100644 --- a/apps/console/app/routes/_shell.agent.$agentSlug.settings/route.tsx +++ b/apps/console/app/routes/_shell.agent.$agentSlug.settings/route.tsx @@ -25,7 +25,7 @@ export default function Settings({ loaderData }: Route.ComponentProps) { i.teamId === agent.team_id)} isOwner={loaderData.isOwner} canManage={loaderData.canManage} agent={agent}