@etazio/agent-sdk 0.1.0

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.
@@ -0,0 +1,317 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * KI-Office-Generator (G1b/G1c, specs/ai-office-generator-spec.md §4–§5): Auftrag, Zustände, Fortschritt.
4
+ * Brief/Plan sind hier untypisiert (`unknown`) – ihre Schemata (`officeBriefSchema`, `officePlanSchema`) leben in
5
+ * @huddle/scene-schema, damit shared nicht davon abhängt; API und Client prüfen sie dort.
6
+ */
7
+ export declare const GENERATION_STATUSES: readonly ['queued', 'analyzing', 'briefing', 'awaiting_brief', 'planning', 'compiling', 'validating', 'ready', 'applied', 'failed', 'cancelled'];
8
+ export type GenerationStatus = (typeof GENERATION_STATUSES)[number];
9
+ /** Zustände, in denen der Job noch läuft oder auf den Nutzer wartet */
10
+ export declare const ACTIVE_GENERATION_STATUSES: ReadonlySet<GenerationStatus>;
11
+ export declare const GENERATION_STATUS_LABELS: Record<GenerationStatus, string>;
12
+ export declare const GENERATOR_LIMITS: {
13
+ readonly maxPrompt: 2000;
14
+ /** Jobs je Tag und Workspace (Default; Server: GENERATOR_DAILY_LIMIT) */
15
+ readonly dailyDefault: 20;
16
+ /** Verlauf: letzte Jobs */
17
+ readonly historySize: 20;
18
+ };
19
+ /** Referenz-Uploads (G2/G3, SPA-121): Grundriss oder Stil-/Inhaltsbild */
20
+ export declare const GENERATOR_FILE_LIMITS: {
21
+ readonly maxFiles: 6;
22
+ readonly maxBytes: number;
23
+ /** PDF: so viele Seiten werden höchstens betrachtet */
24
+ readonly maxPdfPages: 20;
25
+ /** Lange Kante nach der Normalisierung */
26
+ readonly maxEdge: 2048;
27
+ /** Uploads je Nutzer und Stunde */
28
+ readonly perHour: 30;
29
+ readonly maxName: 120;
30
+ };
31
+ export declare const GENERATOR_FILE_MIMES: readonly ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
32
+ export type GeneratorFileMime = (typeof GENERATOR_FILE_MIMES)[number];
33
+ export declare const GENERATOR_FILE_ROLES: readonly ['floorplan', 'style'];
34
+ export type GeneratorFileRole = (typeof GENERATOR_FILE_ROLES)[number];
35
+ export declare const GENERATOR_FILE_ROLE_LABELS: Record<GeneratorFileRole, string>;
36
+ /** Upload als Data-URL (wie die Bildmediathek) – der Typ wird aus den Bytes bestimmt */
37
+ export declare const generatorFileUploadSchema: z.ZodObject<{
38
+ name: z.ZodString;
39
+ data: z.ZodString;
40
+ }, z.core.$strip>;
41
+ export declare const generatorFileIdSchema: z.ZodString;
42
+ export declare const generatorFileRefSchema: z.ZodObject<{
43
+ id: z.ZodString;
44
+ role: z.ZodEnum<{
45
+ floorplan: "floorplan";
46
+ style: "style";
47
+ }>;
48
+ page: z.ZodOptional<z.ZodNumber>;
49
+ scaleMeters: z.ZodOptional<z.ZodNumber>;
50
+ }, z.core.$strip>;
51
+ export type GeneratorFileRef = z.infer<typeof generatorFileRefSchema>;
52
+ export interface GeneratorFileDto {
53
+ id: string;
54
+ name: string;
55
+ mime: GeneratorFileMime;
56
+ bytes: number;
57
+ /** Maße des normalisierten Bildes (bei PDF: Seite 1) */
58
+ width: number;
59
+ height: number;
60
+ pages: number;
61
+ suggestedRole: GeneratorFileRole;
62
+ roleConfidence: number;
63
+ roleReason: string;
64
+ createdAt: string;
65
+ }
66
+ /** Auswertung einer Referenz am Job */
67
+ export interface GenerationFileDto {
68
+ id: string;
69
+ name: string;
70
+ role: GeneratorFileRole;
71
+ page: number;
72
+ scaleMeters: number | null;
73
+ width: number;
74
+ height: number;
75
+ status: 'pending' | 'ok' | 'failed' | 'skipped';
76
+ error: string | null;
77
+ /** Kurzfassung der Extraktion (Anzeige) */
78
+ summary: string;
79
+ }
80
+ /** Overlay in Schritt 2: Grundriss halbtransparent unter dem Raster */
81
+ export interface FloorplanOverlayDto {
82
+ fileId: string;
83
+ page: number;
84
+ /** Bildlage im Kachel-Rahmen (Meter; Kachel i reicht von 2i−1 bis 2i+1) */
85
+ image: {
86
+ minX: number;
87
+ maxX: number;
88
+ minZ: number;
89
+ maxZ: number;
90
+ };
91
+ grid: {
92
+ minI: number;
93
+ maxI: number;
94
+ minJ: number;
95
+ maxJ: number;
96
+ };
97
+ rooms: Array<{
98
+ id: string;
99
+ name: string;
100
+ kind: string;
101
+ rect: {
102
+ minI: number;
103
+ maxI: number;
104
+ minJ: number;
105
+ maxJ: number;
106
+ };
107
+ source: {
108
+ minX: number;
109
+ maxX: number;
110
+ minZ: number;
111
+ maxZ: number;
112
+ } | null;
113
+ deviation: number;
114
+ doors: Array<{
115
+ a: [number, number];
116
+ b: [number, number];
117
+ }>;
118
+ }>;
119
+ entrance: {
120
+ roomId: string;
121
+ a: [number, number];
122
+ b: [number, number];
123
+ };
124
+ scale: {
125
+ widthMeters: number;
126
+ heightMeters: number;
127
+ source: string;
128
+ buildingMeters: {
129
+ w: number;
130
+ h: number;
131
+ };
132
+ };
133
+ maxDeviation: number;
134
+ strongRounding: boolean;
135
+ notes: string[];
136
+ }
137
+ export declare const generatorParamsSchema: z.ZodObject<{
138
+ seats: z.ZodOptional<z.ZodNumber>;
139
+ meetingRooms: z.ZodOptional<z.ZodNumber>;
140
+ lounge: z.ZodOptional<z.ZodBoolean>;
141
+ fun: z.ZodOptional<z.ZodBoolean>;
142
+ kitchen: z.ZodOptional<z.ZodBoolean>;
143
+ focus: z.ZodOptional<z.ZodBoolean>;
144
+ outdoor: z.ZodOptional<z.ZodBoolean>;
145
+ style: z.ZodOptional<z.ZodEnum<{
146
+ bunt: "bunt";
147
+ homeoffice: "homeoffice";
148
+ industrial: "industrial";
149
+ minimal: "minimal";
150
+ skandinavisch: "skandinavisch";
151
+ }>>;
152
+ sky: z.ZodOptional<z.ZodEnum<{
153
+ day: "day";
154
+ evening: "evening";
155
+ night: "night";
156
+ }>>;
157
+ size: z.ZodOptional<z.ZodEnum<{
158
+ grosszuegig: "grosszuegig";
159
+ kompakt: "kompakt";
160
+ normal: "normal";
161
+ }>>;
162
+ }, z.core.$strip>;
163
+ export type GeneratorParams = z.infer<typeof generatorParamsSchema>;
164
+ export declare const createGenerationJobSchema: z.ZodObject<{
165
+ prompt: z.ZodDefault<z.ZodString>;
166
+ params: z.ZodDefault<z.ZodObject<{
167
+ seats: z.ZodOptional<z.ZodNumber>;
168
+ meetingRooms: z.ZodOptional<z.ZodNumber>;
169
+ lounge: z.ZodOptional<z.ZodBoolean>;
170
+ fun: z.ZodOptional<z.ZodBoolean>;
171
+ kitchen: z.ZodOptional<z.ZodBoolean>;
172
+ focus: z.ZodOptional<z.ZodBoolean>;
173
+ outdoor: z.ZodOptional<z.ZodBoolean>;
174
+ style: z.ZodOptional<z.ZodEnum<{
175
+ bunt: "bunt";
176
+ homeoffice: "homeoffice";
177
+ industrial: "industrial";
178
+ minimal: "minimal";
179
+ skandinavisch: "skandinavisch";
180
+ }>>;
181
+ sky: z.ZodOptional<z.ZodEnum<{
182
+ day: "day";
183
+ evening: "evening";
184
+ night: "night";
185
+ }>>;
186
+ size: z.ZodOptional<z.ZodEnum<{
187
+ grosszuegig: "grosszuegig";
188
+ kompakt: "kompakt";
189
+ normal: "normal";
190
+ }>>;
191
+ }, z.core.$strip>>;
192
+ template: z.ZodOptional<z.ZodString>;
193
+ mode: z.ZodDefault<z.ZodEnum<{
194
+ neu: "neu";
195
+ }>>;
196
+ skipBrief: z.ZodDefault<z.ZodBoolean>;
197
+ seed: z.ZodOptional<z.ZodNumber>;
198
+ files: z.ZodDefault<z.ZodArray<z.ZodObject<{
199
+ id: z.ZodString;
200
+ role: z.ZodEnum<{
201
+ floorplan: "floorplan";
202
+ style: "style";
203
+ }>;
204
+ page: z.ZodOptional<z.ZodNumber>;
205
+ scaleMeters: z.ZodOptional<z.ZodNumber>;
206
+ }, z.core.$strip>>>;
207
+ }, z.core.$strip>;
208
+ export type CreateGenerationJobInput = z.input<typeof createGenerationJobSchema>;
209
+ export declare const confirmBriefSchema: z.ZodObject<{
210
+ brief: z.ZodUnknown;
211
+ answers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
212
+ }, z.core.$strip>;
213
+ export declare const applyGenerationSchema: z.ZodObject<{
214
+ baseVersion: z.ZodNumber;
215
+ }, z.core.$strip>;
216
+ export declare const revertGenerationSchema: z.ZodObject<{
217
+ baseVersion: z.ZodNumber;
218
+ }, z.core.$strip>;
219
+ export declare const generationJobIdSchema: z.ZodString;
220
+ export interface GenerationUsageDto {
221
+ inputTokens: number;
222
+ outputTokens: number;
223
+ calls: number;
224
+ }
225
+ export interface GenerationStatsDto {
226
+ rooms: number;
227
+ seatsPlanned: number;
228
+ seatsBuilt: number;
229
+ placements: number;
230
+ ops: number;
231
+ batches: number;
232
+ dropped: number;
233
+ repairs: number;
234
+ }
235
+ export interface GenerationJobDto {
236
+ id: string;
237
+ officeId: string;
238
+ status: GenerationStatus;
239
+ /** Fortschritt 0–100 und Meldung der aktuellen Stufe */
240
+ progress: number;
241
+ message: string;
242
+ prompt: string;
243
+ params: GeneratorParams;
244
+ template: string | null;
245
+ mode: 'neu';
246
+ seed: number;
247
+ model: string;
248
+ createdBy: {
249
+ userId: string;
250
+ displayName: string;
251
+ };
252
+ createdAt: string;
253
+ updatedAt: string;
254
+ /** OfficeBrief (Schema in scene-schema) – ab `awaiting_brief` */
255
+ brief: unknown | null;
256
+ briefConfirmedAt: string | null;
257
+ /** Ergebnis (ab `ready`) */
258
+ stats: GenerationStatsDto | null;
259
+ issues: Array<{
260
+ code: string;
261
+ message: string;
262
+ targetId?: string;
263
+ otherId?: string;
264
+ }>;
265
+ dropped: Array<{
266
+ roomId: string;
267
+ reason: string;
268
+ message: string;
269
+ }>;
270
+ /** Raum-ID → Rechteck in Metern (Karte/Kamera) */
271
+ roomBounds: Record<string, {
272
+ minX: number;
273
+ maxX: number;
274
+ minZ: number;
275
+ maxZ: number;
276
+ }> | null;
277
+ notes: string[];
278
+ /** Entwurfsversion nach dem Übernehmen (für „Verwerfen“) */
279
+ appliedDraftVersion: number | null;
280
+ error: string | null;
281
+ usage: GenerationUsageDto;
282
+ /** Referenzen und ihre Auswertung (G2/G3) */
283
+ files: GenerationFileDto[];
284
+ /** Gerasterter Grundriss für das Overlay (G3) */
285
+ floorplan: FloorplanOverlayDto | null;
286
+ }
287
+ export interface GeneratorStatusDto {
288
+ /** LLM verfügbar (Key gesetzt) – ohne ist nur die Vorlagen-Variante möglich */
289
+ llm: boolean;
290
+ provider: string;
291
+ model: string;
292
+ dailyLimit: number;
293
+ usedToday: number;
294
+ /** Laufender Job dieses Offices (höchstens einer) */
295
+ activeJobId: string | null;
296
+ templates: Array<{
297
+ id: string;
298
+ label: string;
299
+ description: string;
300
+ }>;
301
+ /** Referenz-Uploads möglich (Grundriss braucht die KI; PDF braucht pdftoppm auf dem Server) */
302
+ uploads: {
303
+ enabled: boolean;
304
+ pdf: boolean;
305
+ floorplanNeedsLlm: boolean;
306
+ limits: typeof GENERATOR_FILE_LIMITS;
307
+ };
308
+ }
309
+ /** Socket `generator:progress` */
310
+ export interface GeneratorProgressPayload {
311
+ workspaceId: string;
312
+ officeId: string;
313
+ jobId: string;
314
+ status: GenerationStatus;
315
+ progress: number;
316
+ message: string;
317
+ }
@@ -0,0 +1,79 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Bild-Uploads (Phase 7d): ein Workspace hat eine kleine Bildmediathek. Die Bilder hängen anschließend in
4
+ * Rahmen/Postern/Logoflächen (Katalogfeld `picture`, Vertrag v2.5) und können als Diashow am Fernseher laufen.
5
+ * Gespeichert wird die Datei im Volume, die Metadaten in Mongo; der Objektzustand trägt nur die Id.
6
+ */
7
+ export declare const IMAGE_LIMITS: {
8
+ /** Rohbytes je Bild (der Upload kommt als Base64 in JSON – der Body darf ~4/3 davon groß sein) */
9
+ readonly maxBytes: number;
10
+ /** Kantenlänge und Gesamtfläche (Schutz gegen Dekomprimierungsbomben) */
11
+ readonly maxDimension: 8000;
12
+ readonly maxPixels: 40000000;
13
+ /** Bilder je Workspace */
14
+ readonly maxPerWorkspace: 300;
15
+ readonly maxNameLength: 80;
16
+ };
17
+ export declare const IMAGE_MIME_TYPES: readonly ['image/png', 'image/jpeg', 'image/webp'];
18
+ export type ImageMime = (typeof IMAGE_MIME_TYPES)[number];
19
+ export declare const IMAGE_EXTENSIONS: Record<ImageMime, string>;
20
+ export interface WorkspaceImageDto {
21
+ id: string;
22
+ name: string;
23
+ mime: ImageMime;
24
+ bytes: number;
25
+ width: number;
26
+ height: number;
27
+ /** Seitenverhältnis w/h – der Browser rechnet damit `cover`/`contain` aus, bevor die Datei geladen ist */
28
+ ar: number;
29
+ createdAt: string;
30
+ createdBy: string | null;
31
+ createdByName: string | null;
32
+ }
33
+ /** Pfad der Bilddatei (gleicher Origin wie die API, Cookie-Auth, unveränderlich cachebar) */
34
+ export declare function imageFileUrl(workspaceId: string, imageId: string): string;
35
+ /** Eintrag einer Diashow: entweder eine hochgeladene Bild-Id (`img:<id>`) oder eine https-Adresse */
36
+ export declare const SLIDESHOW_IMAGE_PREFIX = "img:";
37
+ export declare function slideshowEntryUrl(workspaceId: string, entry: string): string;
38
+ export declare const imageIdSchema: z.ZodString;
39
+ export declare const imageNameSchema: z.ZodString;
40
+ /** Upload: Dateiname und Datei als Data-URL. Die Bytes prüft der Server zusätzlich am Kopf der Datei. */
41
+ export declare const imageUploadSchema: z.ZodObject<{
42
+ name: z.ZodString;
43
+ data: z.ZodString;
44
+ }, z.core.$strip>;
45
+ export type ImageUploadInput = z.infer<typeof imageUploadSchema>;
46
+ export declare const imageRenameSchema: z.ZodObject<{
47
+ name: z.ZodString;
48
+ }, z.core.$strip>;
49
+ /**
50
+ * Zuschnitt einer Bildfläche: `cover` füllt formatfüllend und schneidet über, `contain` passt vollständig ein
51
+ * und lässt den Rest durchsichtig. Der Katalog (`picture.fit`) gibt die Voreinstellung je Asset vor (SPA-58),
52
+ * seit SPA-91 darf jeder Rahmen davon abweichen.
53
+ */
54
+ export declare const PICTURE_FITS: readonly ['cover', 'contain'];
55
+ export type PictureFit = (typeof PICTURE_FITS)[number];
56
+ export declare function isPictureFit(v: unknown): v is PictureFit;
57
+ /**
58
+ * Bildzustand eines Rahmens (Objektzustand bzw. `placement.state`): `{ picture: '<id>', fit?: 'cover'|'contain' }`,
59
+ * leer = kein Bild. Das Seitenverhältnis steht weiter fest im Katalog (`picture.ar`) – es gehört zur Fläche,
60
+ * nicht zum Bild. Ohne `fit` gilt der Katalogwert.
61
+ */
62
+ export interface PictureState {
63
+ picture?: string;
64
+ fit?: PictureFit;
65
+ }
66
+ export declare function validatePictureState(state: Record<string, unknown>): {
67
+ ok: true;
68
+ state: PictureState;
69
+ } | {
70
+ ok: false;
71
+ error: string;
72
+ };
73
+ /** Welche Bild-Id hängt hier? Laufzeitzustand schlägt den Editor-Standard. */
74
+ export declare function pictureIdOf(state: Record<string, unknown> | undefined, defaults: Record<string, unknown> | undefined): string | null;
75
+ /**
76
+ * Welcher Zuschnitt gilt hier (SPA-91)? Laufzeitzustand → Editor-Standard → Katalog. Ein Zustand ohne `fit`
77
+ * überspringt die Stufe, statt den Katalogwert zu erzwingen – ein Rahmen ohne eigene Wahl folgt dem Katalog.
78
+ */
79
+ export declare function pictureFitOf(state: Record<string, unknown> | undefined, defaults: Record<string, unknown> | undefined, fallback: PictureFit): PictureFit;
@@ -0,0 +1,17 @@
1
+ export * from './roles.js';
2
+ export * from './errors.js';
3
+ export * from './chat.js';
4
+ export * from './markdown.js';
5
+ export * from './schemas.js';
6
+ export * from './dto.js';
7
+ export * from './events.js';
8
+ export * from './avatar.js';
9
+ export * from './agents.js';
10
+ export * from './view.js';
11
+ export * from './board.js';
12
+ export * from './tv.js';
13
+ export * from './generator.js';
14
+ export * from './web.js';
15
+ export * from './images.js';
16
+ export * from './sso.js';
17
+ export * from './calendar.js';
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Markdown für Chat-Nachrichten (SPA-117).
3
+ *
4
+ * Der Parser lebt bewusst hier und nicht im UI-Paket: er erzeugt einen reinen Datenbaum, kein HTML
5
+ * und keine React-Knoten. Damit ist er ohne Browser testbar (die Sanitizing-Proben laufen in der
6
+ * API-Suite mit) und die Darstellung kann nichts einschleusen, was der Parser nicht vorgesehen hat.
7
+ * Es gibt keinen Weg von Nutzertext zu Markup: aus einem HTML-Schnipsel wird ein Textknoten, aus
8
+ * `javascript:…` ein Textknoten statt eines Links.
9
+ *
10
+ * Absichtlich klein gehalten: Fett, kursiv, durchgestrichen, Code (inline und Block), Zitate,
11
+ * Listen, Links, Trennlinien, Erwähnungen, Kanalverweise. Kein HTML, keine Tabellen, keine Bilder.
12
+ */
13
+ export type MdInline = {
14
+ t: 'text';
15
+ v: string;
16
+ } | {
17
+ t: 'code';
18
+ v: string;
19
+ } | {
20
+ t: 'strong';
21
+ c: MdInline[];
22
+ } | {
23
+ t: 'em';
24
+ c: MdInline[];
25
+ } | {
26
+ t: 'del';
27
+ c: MdInline[];
28
+ } | {
29
+ t: 'link';
30
+ href: string;
31
+ c: MdInline[];
32
+ }
33
+ /** `@Name` – die Darstellung macht daraus einen klickbaren Chip */
34
+ | {
35
+ t: 'mention';
36
+ name: string;
37
+ }
38
+ /** `#raum` – Verweis auf einen Kanal */
39
+ | {
40
+ t: 'channel';
41
+ name: string;
42
+ } | {
43
+ t: 'br';
44
+ };
45
+ export type MdBlock = {
46
+ t: 'p';
47
+ c: MdInline[];
48
+ } | {
49
+ t: 'quote';
50
+ c: MdBlock[];
51
+ } | {
52
+ t: 'code';
53
+ lang: string | null;
54
+ v: string;
55
+ } | {
56
+ t: 'list';
57
+ ordered: boolean;
58
+ start: number;
59
+ items: MdInline[][];
60
+ } | {
61
+ t: 'hr';
62
+ };
63
+ export interface MarkdownOptions {
64
+ /** Bekannte Namen für `@`-Erwähnungen. Namen dürfen Leerzeichen enthalten, deshalb die Liste. */
65
+ names?: readonly string[];
66
+ }
67
+ /**
68
+ * Prüft eine Ziel-Adresse und gibt sie normalisiert zurück – oder `null`, wenn daraus kein Link
69
+ * werden darf. `javascript:`, `data:`, `vbscript:` und alles Unbekannte fallen hier heraus, auch
70
+ * wenn sie mit Steuerzeichen oder Groß-/Kleinschreibung getarnt sind.
71
+ */
72
+ export declare function safeHref(raw: string): string | null;
73
+ /** Nachricht in Blöcke zerlegen. Eingabe ist immer Nutzertext – der Parser wirft nie. */
74
+ export declare function parseMarkdown(src: string, opts?: MarkdownOptions): MdBlock[];
75
+ /** Nachricht als reiner Text – für Vorschauen, Benachrichtigungen und die Seitenleiste. */
76
+ export declare function markdownToPlain(src: string, opts?: MarkdownOptions): string;
@@ -0,0 +1,29 @@
1
+ /** Rollen pro Workspace (Roadmap §3). `owner` ist genau einmal pro Workspace vergeben. */
2
+ export declare const WORKSPACE_ROLES: readonly ['owner', 'admin', 'member', 'guest'];
3
+ export type WorkspaceRole = (typeof WORKSPACE_ROLES)[number];
4
+ /** Rollen, die per Einladung oder Rollenänderung vergeben werden dürfen (nie `owner`; Eigentum wird übertragen). */
5
+ export declare const ASSIGNABLE_ROLES: readonly ['admin', 'member', 'guest'];
6
+ export type AssignableRole = (typeof ASSIGNABLE_ROLES)[number];
7
+ export declare const PERMISSIONS: readonly ['workspace.delete', 'workspace.transfer', 'workspace.update', 'members.read', 'members.manage', 'invitations.manage', 'office.edit', 'office.publish', 'desk.assign', 'desk.editOwn',
8
+ /** Freien Schreibtisch selbst sichern und eigenen Platz freigeben (SPA-125) – nur wenn der Workspace es erlaubt */
9
+ 'desk.claim', 'avatar.edit', 'meeting.join', 'audit.read',
10
+ /** KI-Agenten sehen (SPA-66) */
11
+ 'agents.read',
12
+ /** KI-Agenten anlegen, Scopes setzen, Token rotieren, abschalten (SPA-66) */
13
+ 'agents.manage'];
14
+ export type Permission = (typeof PERMISSIONS)[number];
15
+ /** Sichtbare Rollennamen – eine Quelle für UI, Mails und Agenten-Texte. */
16
+ export declare const ROLE_LABELS: Record<WorkspaceRole, string>;
17
+ /** Ein Satz je Rolle, in Kundensprache – erklärt, was die Rolle darf. */
18
+ export declare const ROLE_HINTS: Record<WorkspaceRole, string>;
19
+ /** Dieselbe Aussage in der Anrede – für Mails an die betroffene Person. */
20
+ export declare const ROLE_HINTS_YOU: Record<WorkspaceRole, string>;
21
+ export declare function can(role: WorkspaceRole, permission: Permission): boolean;
22
+ /**
23
+ * Darf `actor` die Rolle von `target` auf `next` setzen?
24
+ * - Nur `owner` vergibt/entzieht nichts am Owner (Transfer ist ein eigener Vorgang).
25
+ * - Admins verwalten Admins, Mitglieder und Gäste, aber nicht den Owner.
26
+ * - `owner` ist nie Zielrolle einer Rollenänderung.
27
+ */
28
+ export declare function canChangeRole(actor: WorkspaceRole, target: WorkspaceRole, next: WorkspaceRole): boolean;
29
+ export declare function canRemoveMember(actor: WorkspaceRole, target: WorkspaceRole, isSelf: boolean): boolean;