@tangle-network/agent-app 0.42.3 → 0.42.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/InvitationsPanel-ZKAUYZYU.js +8 -0
- package/dist/InviteAcceptPage-P3QJC3C6.js +7 -0
- package/dist/{MembersPanel-4CGUQRPF.js → MembersPanel-FOVHWUQ3.js} +2 -2
- package/dist/MembersPanel-FOVHWUQ3.js.map +1 -0
- package/dist/{access-ChAHG4Tp.d.ts → access-BZ81Btt5.d.ts} +1 -1
- package/dist/chunk-F7D7YCUL.js +206 -0
- package/dist/chunk-F7D7YCUL.js.map +1 -0
- package/dist/{chunk-WOVCWPFF.js → chunk-FJUOBNIY.js} +1 -1
- package/dist/{chunk-3G334FVA.js → chunk-GNL3MG5J.js} +4 -3
- package/dist/chunk-GNL3MG5J.js.map +1 -0
- package/dist/chunk-SWUVTGMR.js +262 -0
- package/dist/chunk-SWUVTGMR.js.map +1 -0
- package/dist/{chunk-535V6BH6.js → chunk-VCPZ3HTN.js} +44 -4
- package/dist/chunk-VCPZ3HTN.js.map +1 -0
- package/dist/chunk-WEBBJBDH.js +58 -0
- package/dist/chunk-WEBBJBDH.js.map +1 -0
- package/dist/design-canvas-react/index.js +1 -1
- package/dist/design-canvas-react/lazy.js +1 -1
- package/dist/invitations-CZ5G2tIn.d.ts +56 -0
- package/dist/invitations-schema-DaMO4hZE.d.ts +280 -0
- package/dist/{roles-CLtYKHHl.d.ts → roles-BC1n4t37.d.ts} +1 -1
- package/dist/teams/drizzle.d.ts +4 -3
- package/dist/teams/drizzle.js +31 -1
- package/dist/teams/drizzle.js.map +1 -1
- package/dist/teams/index.d.ts +2 -1
- package/dist/teams/index.js +16 -0
- package/dist/teams/invitations-api.d.ts +169 -0
- package/dist/teams/invitations-api.js +338 -0
- package/dist/teams/invitations-api.js.map +1 -0
- package/dist/teams/members-api.d.ts +2 -2
- package/dist/teams/members-api.js +5 -255
- package/dist/teams/members-api.js.map +1 -1
- package/dist/teams-react/index.d.ts +66 -7
- package/dist/teams-react/index.js +6 -2
- package/dist/teams-react/lazy.d.ts +6 -4
- package/dist/teams-react/lazy.js +6 -2
- package/dist/teams-react/lazy.js.map +1 -1
- package/dist/web-react/index.d.ts +17 -1
- package/dist/web-react/index.js +101 -19
- package/dist/web-react/index.js.map +1 -1
- package/package.json +6 -1
- package/dist/InviteAcceptPage-EUOGZO2X.js +0 -7
- package/dist/chunk-3G334FVA.js.map +0 -1
- package/dist/chunk-535V6BH6.js.map +0 -1
- /package/dist/{InviteAcceptPage-EUOGZO2X.js.map → InvitationsPanel-ZKAUYZYU.js.map} +0 -0
- /package/dist/{MembersPanel-4CGUQRPF.js.map → InviteAcceptPage-P3QJC3C6.js.map} +0 -0
- /package/dist/{chunk-WOVCWPFF.js.map → chunk-FJUOBNIY.js.map} +0 -0
|
@@ -1,259 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
isAssignableWorkspaceRole,
|
|
8
|
-
organizationRoleGrantsWorkspaceOwner
|
|
9
|
-
} from "../chunk-63CE7FEZ.js";
|
|
10
|
-
|
|
11
|
-
// src/teams/members-api.ts
|
|
12
|
-
import { and, eq, isNull } from "drizzle-orm";
|
|
13
|
-
var SeatLimitError = class extends Error {
|
|
14
|
-
status;
|
|
15
|
-
capability;
|
|
16
|
-
requiredPlan;
|
|
17
|
-
constructor(message, opts = {}) {
|
|
18
|
-
super(message);
|
|
19
|
-
this.name = "SeatLimitError";
|
|
20
|
-
this.status = opts.status ?? 402;
|
|
21
|
-
this.capability = opts.capability;
|
|
22
|
-
this.requiredPlan = opts.requiredPlan;
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
function createMembersApi(opts) {
|
|
26
|
-
const { db, tables, userTable, workspaceTable, access, enforceSeat, memberSyncSeam } = opts;
|
|
27
|
-
const { organizations, organizationMembers, workspaceMembers } = tables;
|
|
28
|
-
const users = userTable;
|
|
29
|
-
const workspaces = workspaceTable;
|
|
30
|
-
function fireSync(op) {
|
|
31
|
-
try {
|
|
32
|
-
Promise.resolve(op()).catch(() => {
|
|
33
|
-
});
|
|
34
|
-
} catch {
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
async function listMembers(input) {
|
|
38
|
-
const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.actor.id);
|
|
39
|
-
if (!accessRow) return Response.json({ error: "Workspace not found" }, { status: 404 });
|
|
40
|
-
const organizationId = accessRow.workspace.organizationId;
|
|
41
|
-
const [projectMembers, orgAdmins] = await Promise.all([
|
|
42
|
-
db.select({
|
|
43
|
-
id: workspaceMembers.id,
|
|
44
|
-
organizationMemberId: workspaceMembers.organizationMemberId,
|
|
45
|
-
userId: workspaceMembers.userId,
|
|
46
|
-
role: workspaceMembers.role,
|
|
47
|
-
invitedAt: workspaceMembers.invitedAt,
|
|
48
|
-
acceptedAt: workspaceMembers.acceptedAt,
|
|
49
|
-
inviteEmail: workspaceMembers.inviteEmail,
|
|
50
|
-
userName: users.name,
|
|
51
|
-
userEmail: users.email
|
|
52
|
-
}).from(workspaceMembers).leftJoin(users, eq(users.id, workspaceMembers.userId)).where(eq(workspaceMembers.workspaceId, input.workspaceId)),
|
|
53
|
-
db.select({
|
|
54
|
-
id: organizationMembers.id,
|
|
55
|
-
userId: organizationMembers.userId,
|
|
56
|
-
orgRole: organizationMembers.role,
|
|
57
|
-
userName: users.name,
|
|
58
|
-
userEmail: users.email,
|
|
59
|
-
createdAt: organizationMembers.createdAt
|
|
60
|
-
}).from(organizationMembers).innerJoin(users, eq(users.id, organizationMembers.userId)).where(eq(organizationMembers.organizationId, organizationId))
|
|
61
|
-
]);
|
|
62
|
-
const explicitOrgMemberIds = new Set(
|
|
63
|
-
projectMembers.map((m) => m.organizationMemberId).filter(Boolean)
|
|
64
|
-
);
|
|
65
|
-
const members = [
|
|
66
|
-
...orgAdmins.filter((m) => organizationRoleGrantsWorkspaceOwner(m.orgRole) && !explicitOrgMemberIds.has(m.id)).map((m) => ({
|
|
67
|
-
id: `org:${m.id}`,
|
|
68
|
-
userId: m.userId,
|
|
69
|
-
organizationMemberId: m.id,
|
|
70
|
-
role: "owner",
|
|
71
|
-
name: m.userName,
|
|
72
|
-
email: m.userEmail,
|
|
73
|
-
invitedAt: m.createdAt,
|
|
74
|
-
acceptedAt: m.createdAt,
|
|
75
|
-
inherited: true
|
|
76
|
-
})),
|
|
77
|
-
...projectMembers.map((m) => ({
|
|
78
|
-
id: m.id,
|
|
79
|
-
userId: m.userId,
|
|
80
|
-
organizationMemberId: m.organizationMemberId,
|
|
81
|
-
role: m.role,
|
|
82
|
-
name: m.userName,
|
|
83
|
-
email: m.userEmail ?? m.inviteEmail,
|
|
84
|
-
invitedAt: m.invitedAt,
|
|
85
|
-
acceptedAt: m.acceptedAt,
|
|
86
|
-
inherited: false
|
|
87
|
-
}))
|
|
88
|
-
];
|
|
89
|
-
return Response.json({ members, currentRole: accessRow.role, organizationId });
|
|
90
|
-
}
|
|
91
|
-
async function inviteMember(input) {
|
|
92
|
-
if (!input.email) return Response.json({ error: "Missing email" }, { status: 400 });
|
|
93
|
-
const requestedRole = input.role ?? "editor";
|
|
94
|
-
if (!isAssignableWorkspaceRole(requestedRole)) {
|
|
95
|
-
return Response.json({ error: "Invalid role. Must be viewer, editor, or admin." }, { status: 400 });
|
|
96
|
-
}
|
|
97
|
-
const assignRole = requestedRole;
|
|
98
|
-
const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.actor.id, "admin");
|
|
99
|
-
if (!accessRow) return Response.json({ error: "Workspace not found" }, { status: 404 });
|
|
100
|
-
if (!hasWorkspaceRole(accessRow.role, assignRole)) {
|
|
101
|
-
return Response.json({ error: "Cannot assign a role higher than your own" }, { status: 403 });
|
|
102
|
-
}
|
|
103
|
-
const organizationId = accessRow.workspace.organizationId;
|
|
104
|
-
const normalizedEmail = input.email.toLowerCase().trim();
|
|
105
|
-
const invitee = await getUserByEmail(normalizedEmail);
|
|
106
|
-
const existingOrgMember = invitee ? await getOrganizationMember(organizationId, invitee.id) : null;
|
|
107
|
-
if (organizationRoleGrantsWorkspaceOwner(existingOrgMember?.role)) {
|
|
108
|
-
return Response.json({ error: "Organization admins already have project access" }, { status: 409 });
|
|
109
|
-
}
|
|
110
|
-
const existingInvite = await findExistingProjectInvite(input.workspaceId, normalizedEmail, invitee?.id);
|
|
111
|
-
if (existingInvite) {
|
|
112
|
-
return Response.json({ error: "This collaborator is already invited to this project" }, { status: 409 });
|
|
113
|
-
}
|
|
114
|
-
if (enforceSeat && !existingOrgMember && !await hasPendingOrgInvite(organizationId, normalizedEmail)) {
|
|
115
|
-
try {
|
|
116
|
-
await enforceSeat({ actorId: input.actor.id, organizationId });
|
|
117
|
-
} catch (err) {
|
|
118
|
-
if (err instanceof SeatLimitError) {
|
|
119
|
-
return Response.json(
|
|
120
|
-
{ error: err.message, capability: err.capability, requiredPlan: err.requiredPlan },
|
|
121
|
-
{ status: err.status }
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
throw err;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
const token = generateInviteToken();
|
|
128
|
-
const [member] = await db.insert(workspaceMembers).values({
|
|
129
|
-
workspaceId: input.workspaceId,
|
|
130
|
-
organizationMemberId: existingOrgMember?.id ?? null,
|
|
131
|
-
userId: invitee?.id ?? null,
|
|
132
|
-
role: assignRole,
|
|
133
|
-
invitedBy: input.actor.id,
|
|
134
|
-
inviteEmail: normalizedEmail,
|
|
135
|
-
inviteToken: token
|
|
136
|
-
}).returning();
|
|
137
|
-
return Response.json({ member, inviteToken: token });
|
|
138
|
-
}
|
|
139
|
-
async function updateMemberRole(input) {
|
|
140
|
-
if (!input.memberId || !input.role) {
|
|
141
|
-
return Response.json({ error: "Missing memberId or role" }, { status: 400 });
|
|
142
|
-
}
|
|
143
|
-
if (input.memberId.startsWith("org:")) {
|
|
144
|
-
return Response.json({ error: "Organization owner/admin project access is managed at the organization level" }, { status: 403 });
|
|
145
|
-
}
|
|
146
|
-
if (!isAssignableWorkspaceRole(input.role)) {
|
|
147
|
-
return Response.json({ error: "Invalid role" }, { status: 400 });
|
|
148
|
-
}
|
|
149
|
-
const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.actor.id, "admin");
|
|
150
|
-
if (!accessRow) return Response.json({ error: "Workspace not found" }, { status: 404 });
|
|
151
|
-
if (!hasWorkspaceRole(accessRow.role, input.role)) {
|
|
152
|
-
return Response.json({ error: "Cannot assign a role higher than your own" }, { status: 403 });
|
|
153
|
-
}
|
|
154
|
-
const [target] = await db.select({ id: workspaceMembers.id, role: workspaceMembers.role, userId: workspaceMembers.userId }).from(workspaceMembers).where(and(eq(workspaceMembers.id, input.memberId), eq(workspaceMembers.workspaceId, input.workspaceId))).limit(1);
|
|
155
|
-
if (!target) return Response.json({ error: "Member not found" }, { status: 404 });
|
|
156
|
-
if (target.userId === input.actor.id) return Response.json({ error: "Cannot change your own role" }, { status: 403 });
|
|
157
|
-
if (!canManageWorkspaceMemberRole(accessRow.role, target.role)) {
|
|
158
|
-
return Response.json({ error: "Cannot modify a member with equal or higher role" }, { status: 403 });
|
|
159
|
-
}
|
|
160
|
-
await db.update(workspaceMembers).set({ role: input.role }).where(eq(workspaceMembers.id, input.memberId));
|
|
161
|
-
if (target.userId && memberSyncSeam?.role) {
|
|
162
|
-
const userId = target.userId;
|
|
163
|
-
const nextRole = input.role;
|
|
164
|
-
fireSync(() => memberSyncSeam.role({ workspaceId: input.workspaceId, userId, role: nextRole }));
|
|
165
|
-
}
|
|
166
|
-
return Response.json({ success: true });
|
|
167
|
-
}
|
|
168
|
-
async function removeMember(input) {
|
|
169
|
-
if (!input.memberId) return Response.json({ error: "Missing memberId" }, { status: 400 });
|
|
170
|
-
if (input.memberId.startsWith("org:")) {
|
|
171
|
-
return Response.json({ error: "Organization owner/admin project access is managed at the organization level" }, { status: 403 });
|
|
172
|
-
}
|
|
173
|
-
const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.actor.id, "admin");
|
|
174
|
-
if (!accessRow) return Response.json({ error: "Workspace not found" }, { status: 404 });
|
|
175
|
-
const [target] = await db.select({ id: workspaceMembers.id, role: workspaceMembers.role, userId: workspaceMembers.userId }).from(workspaceMembers).where(and(eq(workspaceMembers.id, input.memberId), eq(workspaceMembers.workspaceId, input.workspaceId))).limit(1);
|
|
176
|
-
if (!target) return Response.json({ error: "Member not found" }, { status: 404 });
|
|
177
|
-
if (target.userId === input.actor.id) return Response.json({ error: "Cannot remove yourself from this project" }, { status: 403 });
|
|
178
|
-
if (!canManageWorkspaceMemberRole(accessRow.role, target.role)) {
|
|
179
|
-
return Response.json({ error: "Cannot remove a member with equal or higher role" }, { status: 403 });
|
|
180
|
-
}
|
|
181
|
-
await db.delete(workspaceMembers).where(eq(workspaceMembers.id, input.memberId));
|
|
182
|
-
if (target.userId && memberSyncSeam?.remove) {
|
|
183
|
-
const userId = target.userId;
|
|
184
|
-
fireSync(() => memberSyncSeam.remove({ workspaceId: input.workspaceId, userId }));
|
|
185
|
-
}
|
|
186
|
-
return Response.json({ success: true });
|
|
187
|
-
}
|
|
188
|
-
async function acceptInvite(input) {
|
|
189
|
-
if (!input.token || typeof input.token !== "string") {
|
|
190
|
-
return Response.json({ error: "Missing invite token" }, { status: 400 });
|
|
191
|
-
}
|
|
192
|
-
const [invite] = await db.select().from(workspaceMembers).where(eq(workspaceMembers.inviteToken, input.token)).limit(1);
|
|
193
|
-
if (!invite) return Response.json({ error: "Invalid or expired invite" }, { status: 404 });
|
|
194
|
-
if (invite.acceptedAt) return Response.json({ error: "Invite already accepted" }, { status: 409 });
|
|
195
|
-
if (invite.inviteEmail && invite.inviteEmail.toLowerCase() !== input.actor.email?.toLowerCase()) {
|
|
196
|
-
return Response.json(
|
|
197
|
-
{ error: `This invite was sent to ${invite.inviteEmail}. Please sign in with that email.` },
|
|
198
|
-
{ status: 403 }
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
const [workspace] = await db.select({ id: workspaces.id, organizationId: workspaces.organizationId }).from(workspaces).where(eq(workspaces.id, invite.workspaceId)).limit(1);
|
|
202
|
-
if (!workspace) return Response.json({ error: "Project not found" }, { status: 404 });
|
|
203
|
-
const organizationId = workspace.organizationId;
|
|
204
|
-
const updated = await db.update(workspaceMembers).set({
|
|
205
|
-
userId: input.actor.id,
|
|
206
|
-
acceptedAt: /* @__PURE__ */ new Date(),
|
|
207
|
-
inviteToken: null
|
|
208
|
-
}).where(and(
|
|
209
|
-
eq(workspaceMembers.inviteToken, input.token),
|
|
210
|
-
isNull(workspaceMembers.acceptedAt)
|
|
211
|
-
)).returning({ id: workspaceMembers.id });
|
|
212
|
-
if (updated.length === 0) return Response.json({ error: "Invite already accepted" }, { status: 409 });
|
|
213
|
-
const [orgMember] = await db.insert(organizationMembers).values({
|
|
214
|
-
organizationId,
|
|
215
|
-
userId: input.actor.id,
|
|
216
|
-
role: "member"
|
|
217
|
-
}).onConflictDoUpdate({
|
|
218
|
-
target: [organizationMembers.organizationId, organizationMembers.userId],
|
|
219
|
-
set: { updatedAt: /* @__PURE__ */ new Date() }
|
|
220
|
-
}).returning();
|
|
221
|
-
await db.update(workspaceMembers).set({ organizationMemberId: invite.organizationMemberId ?? orgMember.id }).where(eq(workspaceMembers.id, updated[0].id));
|
|
222
|
-
if (memberSyncSeam?.add) {
|
|
223
|
-
const role = invite.role;
|
|
224
|
-
fireSync(() => memberSyncSeam.add({ workspaceId: invite.workspaceId, userId: input.actor.id, role }));
|
|
225
|
-
}
|
|
226
|
-
return Response.json({
|
|
227
|
-
success: true,
|
|
228
|
-
workspaceId: invite.workspaceId,
|
|
229
|
-
role: invite.role
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
async function getUserByEmail(email) {
|
|
233
|
-
const [user] = await db.select({ id: users.id, email: users.email }).from(users).where(eq(users.email, email)).limit(1);
|
|
234
|
-
return user ?? null;
|
|
235
|
-
}
|
|
236
|
-
async function getOrganizationMember(organizationId, userId) {
|
|
237
|
-
const [member] = await db.select().from(organizationMembers).where(and(eq(organizationMembers.organizationId, organizationId), eq(organizationMembers.userId, userId))).limit(1);
|
|
238
|
-
return member ?? null;
|
|
239
|
-
}
|
|
240
|
-
async function findExistingProjectInvite(workspaceId, email, userId) {
|
|
241
|
-
const byEmail = await db.select({ id: workspaceMembers.id }).from(workspaceMembers).where(and(eq(workspaceMembers.workspaceId, workspaceId), eq(workspaceMembers.inviteEmail, email))).limit(1);
|
|
242
|
-
if (byEmail[0]) return byEmail[0];
|
|
243
|
-
if (!userId) return null;
|
|
244
|
-
const byUser = await db.select({ id: workspaceMembers.id }).from(workspaceMembers).where(and(eq(workspaceMembers.workspaceId, workspaceId), eq(workspaceMembers.userId, userId))).limit(1);
|
|
245
|
-
return byUser[0] ?? null;
|
|
246
|
-
}
|
|
247
|
-
async function hasPendingOrgInvite(organizationId, email) {
|
|
248
|
-
const [row] = await db.select({ id: workspaceMembers.id }).from(workspaceMembers).innerJoin(workspaces, eq(workspaces.id, workspaceMembers.workspaceId)).where(and(
|
|
249
|
-
eq(workspaces.organizationId, organizationId),
|
|
250
|
-
eq(workspaceMembers.inviteEmail, email),
|
|
251
|
-
isNull(workspaceMembers.acceptedAt)
|
|
252
|
-
)).limit(1);
|
|
253
|
-
return Boolean(row);
|
|
254
|
-
}
|
|
255
|
-
return { listMembers, inviteMember, updateMemberRole, removeMember, acceptInvite };
|
|
256
|
-
}
|
|
2
|
+
SeatLimitError,
|
|
3
|
+
createMembersApi
|
|
4
|
+
} from "../chunk-SWUVTGMR.js";
|
|
5
|
+
import "../chunk-3SVAA3MA.js";
|
|
6
|
+
import "../chunk-63CE7FEZ.js";
|
|
257
7
|
export {
|
|
258
8
|
SeatLimitError,
|
|
259
9
|
createMembersApi
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/teams/members-api.ts"],"sourcesContent":["/**\n * Framework-neutral members API for the teams module: the invite / list /\n * update-role / remove / accept-invite logic, lifted out of any one app's route\n * file. Each app mounts these in its own route with its own auth — the handlers\n * take an already-authenticated `actor`, the parsed inputs, the `db` + `tables`\n * from `createTeamTables`, the product's user + workspace tables, and the\n * workspace-access API from `createWorkspaceAccess`. They return web-standard\n * `Response`s (available in Workers, Node 18+, Deno, browsers), so\n * \"framework-neutral\" is literal: no Remix/React-Router/Express import anywhere.\n *\n * Two OPTIONAL seams an app wires only if it needs them:\n * - `enforceSeat` — billing/seat-limit gate, called at invite time only when\n * the invite would consume a NEW billable seat. An app without seat billing\n * passes nothing and seats are never checked.\n * - `memberSyncSeam` — fire-and-forget propagation of membership changes to a\n * sandbox/external system (add on accept, role on update, remove on\n * delete). An app without sandbox sync passes nothing. Fail-soft by\n * contract: a thrown sync is caught and never blocks the DB mutation.\n *\n * Imports `drizzle-orm`, so this is a subpath, never re-exported from root.\n */\n\nimport { and, eq, isNull } from 'drizzle-orm'\nimport {\n type AssignableWorkspaceRole,\n type WorkspaceRole,\n canManageWorkspaceMemberRole,\n hasWorkspaceRole,\n isAssignableWorkspaceRole,\n organizationRoleGrantsWorkspaceOwner,\n} from './roles'\nimport { generateInviteToken } from './invite'\nimport type { TeamDatabase, WorkspaceAccessApi } from './drizzle/access'\nimport type { TeamParentTable, TeamTables } from './drizzle/schema'\n\n/** The authenticated caller — apps resolve this from their own session layer. */\nexport interface MembersApiActor {\n id: string\n email?: string | null\n}\n\n/**\n * The product's user table, narrowed to the columns the member queries read.\n * Adopters pass their real drizzle user table.\n */\nexport interface UserLookupTable {\n id: any\n name: any\n email: any\n}\n\n/**\n * The product's workspace table, narrowed to the columns the member queries\n * read. The handlers join it to resolve a workspace's organization (the same\n * table passed to createTeamTables as workspaceTable).\n */\nexport interface WorkspaceLookupTable {\n id: any\n organizationId: any\n}\n\n/**\n * Optional billing seat gate. The seam resolves the count input itself (it\n * owns the plan/seat model) and throws when over the limit; the handler turns a\n * thrown `SeatLimitError` into a 402. Called only when an invite would consume\n * a NEW seat (invitee is not already an org member and has no pending org\n * invite) — the same guard gtm uses, so the seam never fires on a no-op invite.\n */\nexport interface EnforceSeatSeam {\n (input: { actorId: string; organizationId: string }): Promise<void> | void\n}\n\n/** Thrown by an `enforceSeat` seam to deny an invite; serialized to a 402. */\nexport class SeatLimitError extends Error {\n readonly status: number\n readonly capability?: string\n readonly requiredPlan?: string\n constructor(message: string, opts: { status?: number; capability?: string; requiredPlan?: string } = {}) {\n super(message)\n this.name = 'SeatLimitError'\n this.status = opts.status ?? 402\n this.capability = opts.capability\n this.requiredPlan = opts.requiredPlan\n }\n}\n\n/**\n * Optional membership-change propagation to an external system (e.g. sandbox).\n * Every method is fire-and-forget and fail-soft: the handler awaits nothing and\n * swallows rejections, so an unavailable downstream never blocks the mutation.\n * Each fires only for members with a real `userId` (never email-only invites).\n */\nexport interface MemberSyncSeam {\n add?(input: { workspaceId: string; userId: string; role: WorkspaceRole }): Promise<void> | void\n role?(input: { workspaceId: string; userId: string; role: WorkspaceRole }): Promise<void> | void\n remove?(input: { workspaceId: string; userId: string }): Promise<void> | void\n}\n\nexport interface MembersApiOptions {\n db: TeamDatabase\n tables: TeamTables\n /** The product's user table (FK target passed to createTeamTables). */\n userTable: TeamParentTable & UserLookupTable\n /** The product's workspace table (FK target passed to createTeamTables). */\n workspaceTable: TeamParentTable & WorkspaceLookupTable\n /** Workspace-access API from createWorkspaceAccess — RBAC stays one source. */\n access: Pick<WorkspaceAccessApi, 'getWorkspaceAccess'>\n enforceSeat?: EnforceSeatSeam\n memberSyncSeam?: MemberSyncSeam\n}\n\nexport interface MemberListEntry {\n id: string\n userId: string | null\n organizationMemberId: string | null\n role: WorkspaceRole\n name: string | null\n email: string | null\n invitedAt: Date | number | null\n acceptedAt: Date | number | null\n inherited: boolean\n}\n\n/**\n * Build the members API bound to one product's db/tables/access. Returns five\n * handlers; an app maps its route methods onto them (GET→list, POST→invite,\n * PATCH→updateRole, DELETE→remove; accept on its own route).\n */\nexport function createMembersApi(opts: MembersApiOptions) {\n const { db, tables, userTable, workspaceTable, access, enforceSeat, memberSyncSeam } = opts\n const { organizations, organizationMembers, workspaceMembers } = tables\n const users = userTable\n const workspaces = workspaceTable\n\n function fireSync(op: () => Promise<void> | void) {\n try {\n Promise.resolve(op()).catch(() => {})\n } catch {\n // seam threw synchronously — swallow; sync is best-effort by contract.\n }\n }\n\n async function listMembers(input: { workspaceId: string; actor: MembersApiActor }): Promise<Response> {\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.actor.id)\n if (!accessRow) return Response.json({ error: 'Workspace not found' }, { status: 404 })\n\n const organizationId = (accessRow.workspace as { organizationId: string }).organizationId\n\n const [projectMembers, orgAdmins] = await Promise.all([\n db\n .select({\n id: workspaceMembers.id,\n organizationMemberId: workspaceMembers.organizationMemberId,\n userId: workspaceMembers.userId,\n role: workspaceMembers.role,\n invitedAt: workspaceMembers.invitedAt,\n acceptedAt: workspaceMembers.acceptedAt,\n inviteEmail: workspaceMembers.inviteEmail,\n userName: users.name,\n userEmail: users.email,\n })\n .from(workspaceMembers)\n .leftJoin(users, eq(users.id, workspaceMembers.userId))\n .where(eq(workspaceMembers.workspaceId, input.workspaceId)),\n db\n .select({\n id: organizationMembers.id,\n userId: organizationMembers.userId,\n orgRole: organizationMembers.role,\n userName: users.name,\n userEmail: users.email,\n createdAt: organizationMembers.createdAt,\n })\n .from(organizationMembers)\n .innerJoin(users, eq(users.id, organizationMembers.userId))\n .where(eq(organizationMembers.organizationId, organizationId)),\n ])\n\n const explicitOrgMemberIds = new Set(\n projectMembers.map((m: any) => m.organizationMemberId).filter(Boolean),\n )\n const members: MemberListEntry[] = [\n ...orgAdmins\n .filter((m: any) => organizationRoleGrantsWorkspaceOwner(m.orgRole) && !explicitOrgMemberIds.has(m.id))\n .map((m: any) => ({\n id: `org:${m.id}`,\n userId: m.userId,\n organizationMemberId: m.id,\n role: 'owner' as WorkspaceRole,\n name: m.userName,\n email: m.userEmail,\n invitedAt: m.createdAt,\n acceptedAt: m.createdAt,\n inherited: true,\n })),\n ...projectMembers.map((m: any) => ({\n id: m.id,\n userId: m.userId,\n organizationMemberId: m.organizationMemberId,\n role: m.role as WorkspaceRole,\n name: m.userName,\n email: m.userEmail ?? m.inviteEmail,\n invitedAt: m.invitedAt,\n acceptedAt: m.acceptedAt,\n inherited: false,\n })),\n ]\n\n return Response.json({ members, currentRole: accessRow.role, organizationId })\n }\n\n async function inviteMember(input: {\n workspaceId: string\n actor: MembersApiActor\n email?: string\n role?: string\n }): Promise<Response> {\n if (!input.email) return Response.json({ error: 'Missing email' }, { status: 400 })\n const requestedRole = input.role ?? 'editor'\n if (!isAssignableWorkspaceRole(requestedRole)) {\n return Response.json({ error: 'Invalid role. Must be viewer, editor, or admin.' }, { status: 400 })\n }\n const assignRole: AssignableWorkspaceRole = requestedRole\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.actor.id, 'admin')\n if (!accessRow) return Response.json({ error: 'Workspace not found' }, { status: 404 })\n if (!hasWorkspaceRole(accessRow.role, assignRole)) {\n return Response.json({ error: 'Cannot assign a role higher than your own' }, { status: 403 })\n }\n\n const organizationId = (accessRow.workspace as { organizationId: string }).organizationId\n const normalizedEmail = input.email.toLowerCase().trim()\n const invitee = await getUserByEmail(normalizedEmail)\n const existingOrgMember = invitee ? await getOrganizationMember(organizationId, invitee.id) : null\n\n if (organizationRoleGrantsWorkspaceOwner(existingOrgMember?.role)) {\n return Response.json({ error: 'Organization admins already have project access' }, { status: 409 })\n }\n\n const existingInvite = await findExistingProjectInvite(input.workspaceId, normalizedEmail, invitee?.id)\n if (existingInvite) {\n return Response.json({ error: 'This collaborator is already invited to this project' }, { status: 409 })\n }\n\n if (enforceSeat && !existingOrgMember && !(await hasPendingOrgInvite(organizationId, normalizedEmail))) {\n try {\n await enforceSeat({ actorId: input.actor.id, organizationId })\n } catch (err) {\n if (err instanceof SeatLimitError) {\n return Response.json(\n { error: err.message, capability: err.capability, requiredPlan: err.requiredPlan },\n { status: err.status },\n )\n }\n throw err\n }\n }\n\n const token = generateInviteToken()\n const [member] = await db\n .insert(workspaceMembers)\n .values({\n workspaceId: input.workspaceId,\n organizationMemberId: existingOrgMember?.id ?? null,\n userId: invitee?.id ?? null,\n role: assignRole,\n invitedBy: input.actor.id,\n inviteEmail: normalizedEmail,\n inviteToken: token,\n })\n .returning()\n\n return Response.json({ member, inviteToken: token })\n }\n\n async function updateMemberRole(input: {\n workspaceId: string\n actor: MembersApiActor\n memberId?: string\n role?: string\n }): Promise<Response> {\n if (!input.memberId || !input.role) {\n return Response.json({ error: 'Missing memberId or role' }, { status: 400 })\n }\n if (input.memberId.startsWith('org:')) {\n return Response.json({ error: 'Organization owner/admin project access is managed at the organization level' }, { status: 403 })\n }\n if (!isAssignableWorkspaceRole(input.role)) {\n return Response.json({ error: 'Invalid role' }, { status: 400 })\n }\n\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.actor.id, 'admin')\n if (!accessRow) return Response.json({ error: 'Workspace not found' }, { status: 404 })\n if (!hasWorkspaceRole(accessRow.role, input.role)) {\n return Response.json({ error: 'Cannot assign a role higher than your own' }, { status: 403 })\n }\n\n const [target] = await db\n .select({ id: workspaceMembers.id, role: workspaceMembers.role, userId: workspaceMembers.userId })\n .from(workspaceMembers)\n .where(and(eq(workspaceMembers.id, input.memberId), eq(workspaceMembers.workspaceId, input.workspaceId)))\n .limit(1)\n\n if (!target) return Response.json({ error: 'Member not found' }, { status: 404 })\n if (target.userId === input.actor.id) return Response.json({ error: 'Cannot change your own role' }, { status: 403 })\n if (!canManageWorkspaceMemberRole(accessRow.role, target.role as WorkspaceRole)) {\n return Response.json({ error: 'Cannot modify a member with equal or higher role' }, { status: 403 })\n }\n\n await db.update(workspaceMembers).set({ role: input.role }).where(eq(workspaceMembers.id, input.memberId))\n if (target.userId && memberSyncSeam?.role) {\n const userId = target.userId\n const nextRole = input.role as WorkspaceRole\n fireSync(() => memberSyncSeam.role!({ workspaceId: input.workspaceId, userId, role: nextRole }))\n }\n return Response.json({ success: true })\n }\n\n async function removeMember(input: {\n workspaceId: string\n actor: MembersApiActor\n memberId?: string\n }): Promise<Response> {\n if (!input.memberId) return Response.json({ error: 'Missing memberId' }, { status: 400 })\n if (input.memberId.startsWith('org:')) {\n return Response.json({ error: 'Organization owner/admin project access is managed at the organization level' }, { status: 403 })\n }\n\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.actor.id, 'admin')\n if (!accessRow) return Response.json({ error: 'Workspace not found' }, { status: 404 })\n\n const [target] = await db\n .select({ id: workspaceMembers.id, role: workspaceMembers.role, userId: workspaceMembers.userId })\n .from(workspaceMembers)\n .where(and(eq(workspaceMembers.id, input.memberId), eq(workspaceMembers.workspaceId, input.workspaceId)))\n .limit(1)\n\n if (!target) return Response.json({ error: 'Member not found' }, { status: 404 })\n if (target.userId === input.actor.id) return Response.json({ error: 'Cannot remove yourself from this project' }, { status: 403 })\n if (!canManageWorkspaceMemberRole(accessRow.role, target.role as WorkspaceRole)) {\n return Response.json({ error: 'Cannot remove a member with equal or higher role' }, { status: 403 })\n }\n\n await db.delete(workspaceMembers).where(eq(workspaceMembers.id, input.memberId))\n if (target.userId && memberSyncSeam?.remove) {\n const userId = target.userId\n fireSync(() => memberSyncSeam.remove!({ workspaceId: input.workspaceId, userId }))\n }\n return Response.json({ success: true })\n }\n\n async function acceptInvite(input: { token?: string; actor: MembersApiActor }): Promise<Response> {\n if (!input.token || typeof input.token !== 'string') {\n return Response.json({ error: 'Missing invite token' }, { status: 400 })\n }\n\n const [invite] = await db\n .select()\n .from(workspaceMembers)\n .where(eq(workspaceMembers.inviteToken, input.token))\n .limit(1)\n\n if (!invite) return Response.json({ error: 'Invalid or expired invite' }, { status: 404 })\n if (invite.acceptedAt) return Response.json({ error: 'Invite already accepted' }, { status: 409 })\n\n if (invite.inviteEmail && invite.inviteEmail.toLowerCase() !== input.actor.email?.toLowerCase()) {\n return Response.json(\n { error: `This invite was sent to ${invite.inviteEmail}. Please sign in with that email.` },\n { status: 403 },\n )\n }\n\n const [workspace] = await db\n .select({ id: workspaces.id, organizationId: workspaces.organizationId })\n .from(workspaces)\n .where(eq(workspaces.id, invite.workspaceId))\n .limit(1)\n\n if (!workspace) return Response.json({ error: 'Project not found' }, { status: 404 })\n const organizationId = (workspace as { organizationId: string }).organizationId\n\n // Atomic accept: only succeeds if the token still exists and is unaccepted,\n // so two concurrent accepts can't both win.\n const updated = await db\n .update(workspaceMembers)\n .set({\n userId: input.actor.id,\n acceptedAt: new Date(),\n inviteToken: null,\n })\n .where(and(\n eq(workspaceMembers.inviteToken, input.token),\n isNull(workspaceMembers.acceptedAt),\n ))\n .returning({ id: workspaceMembers.id })\n\n if (updated.length === 0) return Response.json({ error: 'Invite already accepted' }, { status: 409 })\n\n const [orgMember] = await db\n .insert(organizationMembers)\n .values({\n organizationId,\n userId: input.actor.id,\n role: 'member',\n })\n .onConflictDoUpdate({\n target: [organizationMembers.organizationId, organizationMembers.userId],\n set: { updatedAt: new Date() },\n })\n .returning()\n\n await db\n .update(workspaceMembers)\n .set({ organizationMemberId: invite.organizationMemberId ?? orgMember!.id })\n .where(eq(workspaceMembers.id, updated[0]!.id))\n\n if (memberSyncSeam?.add) {\n const role = invite.role as WorkspaceRole\n fireSync(() => memberSyncSeam.add!({ workspaceId: invite.workspaceId, userId: input.actor.id, role }))\n }\n\n return Response.json({\n success: true,\n workspaceId: invite.workspaceId,\n role: invite.role,\n })\n }\n\n // ── internal query helpers ──\n\n async function getUserByEmail(email: string) {\n const [user] = await db\n .select({ id: users.id, email: users.email })\n .from(users)\n .where(eq(users.email, email))\n .limit(1)\n return user ?? null\n }\n\n async function getOrganizationMember(organizationId: string, userId: string) {\n const [member] = await db\n .select()\n .from(organizationMembers)\n .where(and(eq(organizationMembers.organizationId, organizationId), eq(organizationMembers.userId, userId)))\n .limit(1)\n return member ?? null\n }\n\n async function findExistingProjectInvite(workspaceId: string, email: string, userId?: string) {\n const byEmail = await db\n .select({ id: workspaceMembers.id })\n .from(workspaceMembers)\n .where(and(eq(workspaceMembers.workspaceId, workspaceId), eq(workspaceMembers.inviteEmail, email)))\n .limit(1)\n if (byEmail[0]) return byEmail[0]\n if (!userId) return null\n const byUser = await db\n .select({ id: workspaceMembers.id })\n .from(workspaceMembers)\n .where(and(eq(workspaceMembers.workspaceId, workspaceId), eq(workspaceMembers.userId, userId)))\n .limit(1)\n return byUser[0] ?? null\n }\n\n async function hasPendingOrgInvite(organizationId: string, email: string): Promise<boolean> {\n const [row] = await db\n .select({ id: workspaceMembers.id })\n .from(workspaceMembers)\n .innerJoin(workspaces, eq(workspaces.id, workspaceMembers.workspaceId))\n .where(and(\n eq(workspaces.organizationId, organizationId),\n eq(workspaceMembers.inviteEmail, email),\n isNull(workspaceMembers.acceptedAt),\n ))\n .limit(1)\n return Boolean(row)\n }\n\n return { listMembers, inviteMember, updateMemberRole, removeMember, acceptInvite }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { f as WorkspaceRole } from '../roles-
|
|
1
|
+
import { f as WorkspaceRole } from '../roles-BC1n4t37.js';
|
|
2
|
+
import { c as InvitationPermission, d as InvitationStatus, b as InvitationEmailStatus } from '../invitations-CZ5G2tIn.js';
|
|
2
3
|
import * as react from 'react';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Seams between the teams React surface and the host app. Everything here is
|
|
6
7
|
* interface-only and callback-driven: the components never import an app's
|
|
7
8
|
* router, fetch client, or toast system. The host passes data in and supplies
|
|
8
|
-
* the async callbacks (which it backs with `./teams/members-api`
|
|
9
|
-
* so the same panel mounts in any app.
|
|
9
|
+
* the async callbacks (which it backs with `./teams/members-api` or
|
|
10
|
+
* `./teams/invitations-api` over fetch), so the same panel mounts in any app.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
/** One member row as the panel renders it — the shape `members-api` returns. */
|
|
@@ -46,8 +47,55 @@ interface MembersPanelProps {
|
|
|
46
47
|
kind: 'success' | 'error';
|
|
47
48
|
message: string;
|
|
48
49
|
}): void;
|
|
50
|
+
/**
|
|
51
|
+
* Show the built-in invite form. Default `true`. An app that drives invites
|
|
52
|
+
* through `InvitationsPanel` (the rich email flow) sets this `false` so the
|
|
53
|
+
* members panel is list-only and the two don't present competing invite UIs.
|
|
54
|
+
*/
|
|
55
|
+
showInviteForm?: boolean;
|
|
49
56
|
}
|
|
50
|
-
|
|
57
|
+
/** One invitation row as `InvitationsPanel` renders it — the shape `invitations-api` returns. */
|
|
58
|
+
interface InvitationView {
|
|
59
|
+
id: string;
|
|
60
|
+
email: string;
|
|
61
|
+
permissions: InvitationPermission;
|
|
62
|
+
status: InvitationStatus;
|
|
63
|
+
emailStatus: InvitationEmailStatus;
|
|
64
|
+
/** Date | epoch-ms | ISO string — normalized to a Date for display. */
|
|
65
|
+
expiresAt: Date | number | string;
|
|
66
|
+
/** Shareable accept link, for copy-to-clipboard. */
|
|
67
|
+
inviteUrl: string;
|
|
68
|
+
}
|
|
69
|
+
interface InvitationsPanelProps {
|
|
70
|
+
invitations: InvitationView[];
|
|
71
|
+
/** The viewer's effective role — gates whether the invite controls are interactive. */
|
|
72
|
+
currentRole: WorkspaceRole;
|
|
73
|
+
/** Create an invitation (sends the email); resolve with the shareable URL (or void). */
|
|
74
|
+
onInvite(input: {
|
|
75
|
+
email: string;
|
|
76
|
+
role: WorkspaceRole;
|
|
77
|
+
}): Promise<{
|
|
78
|
+
inviteUrl?: string;
|
|
79
|
+
} | void>;
|
|
80
|
+
/** Resend the invitation email. */
|
|
81
|
+
onResend(input: {
|
|
82
|
+
invitationId: string;
|
|
83
|
+
}): Promise<void>;
|
|
84
|
+
/** Revoke a pending invitation. */
|
|
85
|
+
onRevoke(input: {
|
|
86
|
+
invitationId: string;
|
|
87
|
+
}): Promise<void>;
|
|
88
|
+
/** Copy the invite link; defaults to `navigator.clipboard.writeText`. */
|
|
89
|
+
onCopy?(input: {
|
|
90
|
+
inviteUrl: string;
|
|
91
|
+
}): void | Promise<void>;
|
|
92
|
+
/** Optional toast/notice hook; defaults to a no-op (host owns its UX). */
|
|
93
|
+
onNotice?(notice: {
|
|
94
|
+
kind: 'success' | 'error';
|
|
95
|
+
message: string;
|
|
96
|
+
}): void;
|
|
97
|
+
}
|
|
98
|
+
type InviteAcceptStatus = 'pending' | 'invalid' | 'already-accepted' | 'expired' | 'revoked';
|
|
51
99
|
interface InviteAcceptDetails {
|
|
52
100
|
status: InviteAcceptStatus;
|
|
53
101
|
/** Workspace the invite grants access to. */
|
|
@@ -60,6 +108,10 @@ interface InviteAcceptDetails {
|
|
|
60
108
|
inviteEmail?: string | null;
|
|
61
109
|
/** The signed-in user's email, or null if not signed in. */
|
|
62
110
|
currentUserEmail?: string | null;
|
|
111
|
+
/** When the invite expires (for display). Date | epoch-ms | ISO string. */
|
|
112
|
+
expiresAt?: Date | number | string | null;
|
|
113
|
+
/** True when the signed-in user must verify their email before they can accept. */
|
|
114
|
+
needsEmailVerification?: boolean;
|
|
63
115
|
}
|
|
64
116
|
interface InviteAcceptPageProps {
|
|
65
117
|
details: InviteAcceptDetails;
|
|
@@ -72,10 +124,17 @@ interface InviteAcceptPageProps {
|
|
|
72
124
|
kind: 'sign-in' | 'sign-up' | 'switch-account' | 'open-app';
|
|
73
125
|
workspaceId?: string;
|
|
74
126
|
}): void;
|
|
127
|
+
/**
|
|
128
|
+
* Resend the verification email. Only invoked on the `needsEmailVerification`
|
|
129
|
+
* branch; an app without email verification omits it and that branch never shows.
|
|
130
|
+
*/
|
|
131
|
+
onResendVerification?(): Promise<void>;
|
|
75
132
|
}
|
|
76
133
|
|
|
77
|
-
declare function MembersPanel({ members, currentRole, onInvite, onChangeRole, onRemove, onNotice, }: MembersPanelProps): react.JSX.Element;
|
|
134
|
+
declare function MembersPanel({ members, currentRole, onInvite, onChangeRole, onRemove, onNotice, showInviteForm, }: MembersPanelProps): react.JSX.Element;
|
|
135
|
+
|
|
136
|
+
declare function InvitationsPanel({ invitations, currentRole, onInvite, onResend, onRevoke, onCopy, onNotice, }: InvitationsPanelProps): react.JSX.Element;
|
|
78
137
|
|
|
79
|
-
declare function InviteAcceptPage({ details, onAccept, onNavigate }: InviteAcceptPageProps): react.JSX.Element;
|
|
138
|
+
declare function InviteAcceptPage({ details, onAccept, onNavigate, onResendVerification }: InviteAcceptPageProps): react.JSX.Element;
|
|
80
139
|
|
|
81
|
-
export { type InviteAcceptDetails, InviteAcceptPage, type InviteAcceptPageProps, type InviteAcceptStatus, type MemberView, MembersPanel, type MembersPanelProps };
|
|
140
|
+
export { type InvitationView, InvitationsPanel, type InvitationsPanelProps, type InviteAcceptDetails, InviteAcceptPage, type InviteAcceptPageProps, type InviteAcceptStatus, type MemberView, MembersPanel, type MembersPanelProps };
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MembersPanel
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-GNL3MG5J.js";
|
|
4
|
+
import {
|
|
5
|
+
InvitationsPanel
|
|
6
|
+
} from "../chunk-F7D7YCUL.js";
|
|
4
7
|
import {
|
|
5
8
|
InviteAcceptPage
|
|
6
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-VCPZ3HTN.js";
|
|
7
10
|
import "../chunk-63CE7FEZ.js";
|
|
8
11
|
export {
|
|
12
|
+
InvitationsPanel,
|
|
9
13
|
InviteAcceptPage,
|
|
10
14
|
MembersPanel
|
|
11
15
|
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { InviteAcceptPage, MembersPanel } from './index.js';
|
|
3
|
-
export { InviteAcceptPageProps, MembersPanelProps } from './index.js';
|
|
4
|
-
import '../roles-
|
|
2
|
+
import { InvitationsPanel, InviteAcceptPage, MembersPanel } from './index.js';
|
|
3
|
+
export { InvitationsPanelProps, InviteAcceptPageProps, MembersPanelProps } from './index.js';
|
|
4
|
+
import '../roles-BC1n4t37.js';
|
|
5
|
+
import '../invitations-CZ5G2tIn.js';
|
|
5
6
|
|
|
6
7
|
declare const MembersPanelLazy: react.LazyExoticComponent<typeof MembersPanel>;
|
|
8
|
+
declare const InvitationsPanelLazy: react.LazyExoticComponent<typeof InvitationsPanel>;
|
|
7
9
|
declare const InviteAcceptPageLazy: react.LazyExoticComponent<typeof InviteAcceptPage>;
|
|
8
10
|
|
|
9
|
-
export { InviteAcceptPageLazy, MembersPanelLazy };
|
|
11
|
+
export { InvitationsPanelLazy, InviteAcceptPageLazy, MembersPanelLazy };
|
package/dist/teams-react/lazy.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
// src/teams-react/lazy.tsx
|
|
2
2
|
import { lazy } from "react";
|
|
3
3
|
var MembersPanelLazy = lazy(
|
|
4
|
-
() => import("../MembersPanel-
|
|
4
|
+
() => import("../MembersPanel-FOVHWUQ3.js").then((m) => ({ default: m.MembersPanel }))
|
|
5
|
+
);
|
|
6
|
+
var InvitationsPanelLazy = lazy(
|
|
7
|
+
() => import("../InvitationsPanel-ZKAUYZYU.js").then((m) => ({ default: m.InvitationsPanel }))
|
|
5
8
|
);
|
|
6
9
|
var InviteAcceptPageLazy = lazy(
|
|
7
|
-
() => import("../InviteAcceptPage-
|
|
10
|
+
() => import("../InviteAcceptPage-P3QJC3C6.js").then((m) => ({ default: m.InviteAcceptPage }))
|
|
8
11
|
);
|
|
9
12
|
export {
|
|
13
|
+
InvitationsPanelLazy,
|
|
10
14
|
InviteAcceptPageLazy,
|
|
11
15
|
MembersPanelLazy
|
|
12
16
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/teams-react/lazy.tsx"],"sourcesContent":["/**\n * Code-split entries for the teams React surface. Products that don't need the\n * members UI on initial load import these `React.lazy` handles instead of the\n * components directly, so the panel/page chunks load on first render. Mount\n * inside a `<Suspense>` boundary; the product provides the fallback.\n */\n\nimport { lazy } from 'react'\nimport type { MembersPanelProps, InviteAcceptPageProps } from './contracts'\n\nexport type { MembersPanelProps, InviteAcceptPageProps }\n\nexport const MembersPanelLazy = lazy(\n () => import('./components/MembersPanel').then((m) => ({ default: m.MembersPanel })),\n)\n\nexport const InviteAcceptPageLazy = lazy(\n () => import('./components/InviteAcceptPage').then((m) => ({ default: m.InviteAcceptPage })),\n)\n"],"mappings":";AAOA,SAAS,YAAY;AAKd,IAAM,mBAAmB;AAAA,EAC9B,MAAM,OAAO,6BAA2B,EAAE,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE;AACrF;AAEO,IAAM,uBAAuB;AAAA,EAClC,MAAM,OAAO,iCAA+B,EAAE,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE;AAC7F;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/teams-react/lazy.tsx"],"sourcesContent":["/**\n * Code-split entries for the teams React surface. Products that don't need the\n * members UI on initial load import these `React.lazy` handles instead of the\n * components directly, so the panel/page chunks load on first render. Mount\n * inside a `<Suspense>` boundary; the product provides the fallback.\n */\n\nimport { lazy } from 'react'\nimport type { MembersPanelProps, InvitationsPanelProps, InviteAcceptPageProps } from './contracts'\n\nexport type { MembersPanelProps, InvitationsPanelProps, InviteAcceptPageProps }\n\nexport const MembersPanelLazy = lazy(\n () => import('./components/MembersPanel').then((m) => ({ default: m.MembersPanel })),\n)\n\nexport const InvitationsPanelLazy = lazy(\n () => import('./components/InvitationsPanel').then((m) => ({ default: m.InvitationsPanel })),\n)\n\nexport const InviteAcceptPageLazy = lazy(\n () => import('./components/InviteAcceptPage').then((m) => ({ default: m.InviteAcceptPage })),\n)\n"],"mappings":";AAOA,SAAS,YAAY;AAKd,IAAM,mBAAmB;AAAA,EAC9B,MAAM,OAAO,6BAA2B,EAAE,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE;AACrF;AAEO,IAAM,uBAAuB;AAAA,EAClC,MAAM,OAAO,iCAA+B,EAAE,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE;AAC7F;AAEO,IAAM,uBAAuB;AAAA,EAClC,MAAM,OAAO,iCAA+B,EAAE,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE;AAC7F;","names":[]}
|
|
@@ -464,12 +464,28 @@ interface ChatToolCallInfo {
|
|
|
464
464
|
declare function pendingApprovalOf(call: ChatToolCallInfo): {
|
|
465
465
|
proposalId: string;
|
|
466
466
|
} | null;
|
|
467
|
+
/** One ordered piece of an assistant turn: a run of answer text, or a tool
|
|
468
|
+
* call, in the sequence the agent emitted them. A message carrying `segments`
|
|
469
|
+
* is rendered in order — interleaving text and tool chips — so the agent's
|
|
470
|
+
* pre- and post-tool reasoning reads chronologically instead of as one text
|
|
471
|
+
* blob with the tool chips collected after it. */
|
|
472
|
+
type ChatMessageSegment = {
|
|
473
|
+
kind: 'text';
|
|
474
|
+
content: string;
|
|
475
|
+
} | {
|
|
476
|
+
kind: 'tool';
|
|
477
|
+
call: ChatToolCallInfo;
|
|
478
|
+
};
|
|
467
479
|
interface ChatUiMessage extends ChatMessageMetrics {
|
|
468
480
|
id: string;
|
|
469
481
|
role: 'user' | 'assistant' | 'system';
|
|
470
482
|
content: string;
|
|
471
483
|
reasoning?: string;
|
|
472
484
|
toolCalls?: ChatToolCallInfo[];
|
|
485
|
+
/** Ordered text/tool sequence for true chronological interleaving. When
|
|
486
|
+
* present and non-empty it is rendered in place of `content` + `toolCalls`;
|
|
487
|
+
* both remain the fallback for producers that don't segment a turn. */
|
|
488
|
+
segments?: ChatMessageSegment[];
|
|
473
489
|
}
|
|
474
490
|
interface ChatMessagesProps {
|
|
475
491
|
messages: ChatUiMessage[];
|
|
@@ -516,4 +532,4 @@ type ToolDetailRenderers = Record<string, (call: ChatToolCallInfo, message: Chat
|
|
|
516
532
|
*/
|
|
517
533
|
declare function ChatMessages({ messages, models, renderMarkdown, renderExtras, userLabel, agentLabel, loading, approval, onToolCallClick, toolRenderers, error, onRetry, renderEmpty, }: ChatMessagesProps): react.JSX.Element;
|
|
518
534
|
|
|
519
|
-
export { type ActivityTone, type AgentActivityPage, AgentActivityPanel, type AgentActivityPanelProps, type AgentActivityRecord, AgentSessionControls, type AgentSessionControlsProps, type ChatMessageMetrics, ChatMessages, type ChatMessagesProps, type ChatStreamCallbacks, type ChatStreamToolCall, type ChatStreamToolResult, type ChatToolCallInfo, type ChatUiMessage, type ConsumeChatStreamResult, EffortPicker, type EffortPickerProps, FlowWaterfall, type FlowWaterfallProps, MissionActivityLane, type MissionActivityLaneProps, ModelPicker, type ModelPickerProps, type ProposalApprovalHandlers, ProviderLogo, type ProviderLogoProps, RunDrillIn, type RunDrillInProps, type SandboxTerminalConnection, type SandboxTerminalConnectionResponse, SeatPaywall, type SeatPaywallProps, type SmoothRevealOptions, type StreamChatOptions, type TerminalStatusDisplay, type TerminalStatusTone, type ToolDetailRenderers, type ToolRunRecord, type ToolRunStep, type UseSandboxTerminalConnectionOptions, type UseSandboxTerminalConnectionResult, type WaterfallRow, WorkspaceTerminalPanel, type WorkspaceTerminalPanelProps, activityTone, consumeChatStream, dispatchChatStreamLine, formatActivityCost, formatActivityDuration, formatModelCost, formatTokensPerSecond, mergeActivityPages, nextRevealCount, pendingApprovalOf, streamChatTurn, tabTerminalConnectionId, usePending, usePopover, useSandboxTerminalConnection, useSmoothText, waterfallLayout };
|
|
535
|
+
export { type ActivityTone, type AgentActivityPage, AgentActivityPanel, type AgentActivityPanelProps, type AgentActivityRecord, AgentSessionControls, type AgentSessionControlsProps, type ChatMessageMetrics, type ChatMessageSegment, ChatMessages, type ChatMessagesProps, type ChatStreamCallbacks, type ChatStreamToolCall, type ChatStreamToolResult, type ChatToolCallInfo, type ChatUiMessage, type ConsumeChatStreamResult, EffortPicker, type EffortPickerProps, FlowWaterfall, type FlowWaterfallProps, MissionActivityLane, type MissionActivityLaneProps, ModelPicker, type ModelPickerProps, type ProposalApprovalHandlers, ProviderLogo, type ProviderLogoProps, RunDrillIn, type RunDrillInProps, type SandboxTerminalConnection, type SandboxTerminalConnectionResponse, SeatPaywall, type SeatPaywallProps, type SmoothRevealOptions, type StreamChatOptions, type TerminalStatusDisplay, type TerminalStatusTone, type ToolDetailRenderers, type ToolRunRecord, type ToolRunStep, type UseSandboxTerminalConnectionOptions, type UseSandboxTerminalConnectionResult, type WaterfallRow, WorkspaceTerminalPanel, type WorkspaceTerminalPanelProps, activityTone, consumeChatStream, dispatchChatStreamLine, formatActivityCost, formatActivityDuration, formatModelCost, formatTokensPerSecond, mergeActivityPages, nextRevealCount, pendingApprovalOf, streamChatTurn, tabTerminalConnectionId, usePending, usePopover, useSandboxTerminalConnection, useSmoothText, waterfallLayout };
|