@company-semantics/contracts 0.106.0 → 0.108.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@company-semantics/contracts",
3
- "version": "0.106.0",
3
+ "version": "0.108.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -90,6 +90,9 @@
90
90
  "engines": {
91
91
  "node": "22.x"
92
92
  },
93
+ "dependencies": {
94
+ "zod": "^4.2.1"
95
+ },
93
96
  "devDependencies": {
94
97
  "@types/node": "^25.0.3",
95
98
  "husky": "^9.1.7",
@@ -270,6 +270,65 @@ export interface paths {
270
270
  patch?: never;
271
271
  trace?: never;
272
272
  };
273
+ "/api/user/preferences/dismissed-banners": {
274
+ parameters: {
275
+ query?: never;
276
+ header?: never;
277
+ path?: never;
278
+ cookie?: never;
279
+ };
280
+ /**
281
+ * List dismissed banner IDs for the current user
282
+ * @description Returns the list of banner IDs that the authenticated user has dismissed.
283
+ * Used to suppress previously-dismissed UI banners on page load.
284
+ */
285
+ get: operations["getDismissedBanners"];
286
+ put?: never;
287
+ post?: never;
288
+ delete?: never;
289
+ options?: never;
290
+ head?: never;
291
+ patch?: never;
292
+ trace?: never;
293
+ };
294
+ "/api/user/preferences/dismissed-banners/{bannerId}": {
295
+ parameters: {
296
+ query?: never;
297
+ header?: never;
298
+ path?: never;
299
+ cookie?: never;
300
+ };
301
+ get?: never;
302
+ put?: never;
303
+ /** Dismiss a banner for the current user */
304
+ post: operations["dismissBanner"];
305
+ delete?: never;
306
+ options?: never;
307
+ head?: never;
308
+ patch?: never;
309
+ trace?: never;
310
+ };
311
+ "/api/user/resync-slack-avatar": {
312
+ parameters: {
313
+ query?: never;
314
+ header?: never;
315
+ path?: never;
316
+ cookie?: never;
317
+ };
318
+ get?: never;
319
+ put?: never;
320
+ /**
321
+ * Resync user avatar from Slack
322
+ * @description Manually resync the current user's avatar from their Slack profile.
323
+ * Requires user to have a linked Slack identity and the org to have an active Slack connection.
324
+ */
325
+ post: operations["resyncSlackAvatar"];
326
+ delete?: never;
327
+ options?: never;
328
+ head?: never;
329
+ patch?: never;
330
+ trace?: never;
331
+ };
273
332
  "/connect/{provider}": {
274
333
  parameters: {
275
334
  query?: never;
@@ -1156,6 +1215,50 @@ export interface paths {
1156
1215
  patch?: never;
1157
1216
  trace?: never;
1158
1217
  };
1218
+ "/api/workspace/resync-slack-logo": {
1219
+ parameters: {
1220
+ query?: never;
1221
+ header?: never;
1222
+ path?: never;
1223
+ cookie?: never;
1224
+ };
1225
+ get?: never;
1226
+ put?: never;
1227
+ /**
1228
+ * Resync workspace logo from Slack
1229
+ * @description Manually resync the workspace logo from the connected Slack workspace icon.
1230
+ * Requires org.view_workspace capability and an active Slack connection.
1231
+ */
1232
+ post: operations["resyncSlackLogo"];
1233
+ delete?: never;
1234
+ options?: never;
1235
+ head?: never;
1236
+ patch?: never;
1237
+ trace?: never;
1238
+ };
1239
+ "/api/workspace/resolve-path": {
1240
+ parameters: {
1241
+ query?: never;
1242
+ header?: never;
1243
+ path?: never;
1244
+ cookie?: never;
1245
+ };
1246
+ get?: never;
1247
+ put?: never;
1248
+ /**
1249
+ * Resolve a tokenized URL path to hydrated navigation layers
1250
+ * @description Resolves a tokenized workspace URL path to hydrated entity data.
1251
+ * Used by the frontend for cold-start deep linking and browser back/forward.
1252
+ * Path segments follow a [type, slug] pair grammar: "dept" before "team",
1253
+ * "team" requires preceding "dept", and "members" must be terminal.
1254
+ */
1255
+ post: operations["resolveWorkspacePath"];
1256
+ delete?: never;
1257
+ options?: never;
1258
+ head?: never;
1259
+ patch?: never;
1260
+ trace?: never;
1261
+ };
1159
1262
  "/api/account/sessions": {
1160
1263
  parameters: {
1161
1264
  query?: never;
@@ -1981,6 +2084,57 @@ export interface components {
1981
2084
  displayName: string;
1982
2085
  };
1983
2086
  };
2087
+ ResyncSlackLogoResponse: {
2088
+ /** @constant */
2089
+ success: true;
2090
+ logoUrl: string | null;
2091
+ };
2092
+ ResyncSlackAvatarResponse: {
2093
+ /** @constant */
2094
+ success: true;
2095
+ avatar: components["schemas"]["ResolvedAvatar"];
2096
+ };
2097
+ ResolvedAvatar: {
2098
+ /** @enum {string} */
2099
+ source: "slack" | "initials";
2100
+ /** @description Slack profile image URL (present only if source is slack) */
2101
+ url?: string;
2102
+ /** @description User initials for fallback display */
2103
+ initials: string;
2104
+ };
2105
+ ResolvedLayer: components["schemas"]["ResolvedLayerDept"] | components["schemas"]["ResolvedLayerTeam"] | components["schemas"]["ResolvedLayerMembers"];
2106
+ ResolvedLayerDept: {
2107
+ /**
2108
+ * @description discriminator enum property added by openapi-typescript
2109
+ * @enum {string}
2110
+ */
2111
+ type: "dept";
2112
+ entity: components["schemas"]["ResolvedLayerEntity"];
2113
+ };
2114
+ ResolvedLayerTeam: {
2115
+ /**
2116
+ * @description discriminator enum property added by openapi-typescript
2117
+ * @enum {string}
2118
+ */
2119
+ type: "team";
2120
+ entity: components["schemas"]["ResolvedLayerEntity"];
2121
+ };
2122
+ ResolvedLayerMembers: {
2123
+ /**
2124
+ * @description discriminator enum property added by openapi-typescript
2125
+ * @enum {string}
2126
+ */
2127
+ type: "members";
2128
+ /** @enum {string} */
2129
+ scope: "org" | "dept" | "team";
2130
+ };
2131
+ ResolvedLayerEntity: {
2132
+ /** Format: uuid */
2133
+ id: string;
2134
+ name: string;
2135
+ slug: string;
2136
+ memberCount: number;
2137
+ };
1984
2138
  UserOrgsResponse: {
1985
2139
  orgs: components["schemas"]["UserOrgMembership"][];
1986
2140
  };
@@ -3284,6 +3438,126 @@ export interface operations {
3284
3438
  };
3285
3439
  };
3286
3440
  };
3441
+ getDismissedBanners: {
3442
+ parameters: {
3443
+ query?: never;
3444
+ header?: never;
3445
+ path?: never;
3446
+ cookie?: never;
3447
+ };
3448
+ requestBody?: never;
3449
+ responses: {
3450
+ /** @description Dismissed banner IDs */
3451
+ 200: {
3452
+ headers: {
3453
+ [name: string]: unknown;
3454
+ };
3455
+ content: {
3456
+ "application/json": {
3457
+ bannerIds: string[];
3458
+ };
3459
+ };
3460
+ };
3461
+ /** @description Not authenticated */
3462
+ 401: {
3463
+ headers: {
3464
+ [name: string]: unknown;
3465
+ };
3466
+ content: {
3467
+ "application/json": components["schemas"]["ErrorResponse"];
3468
+ };
3469
+ };
3470
+ };
3471
+ };
3472
+ dismissBanner: {
3473
+ parameters: {
3474
+ query?: never;
3475
+ header?: never;
3476
+ path: {
3477
+ bannerId: string;
3478
+ };
3479
+ cookie?: never;
3480
+ };
3481
+ requestBody?: never;
3482
+ responses: {
3483
+ /** @description Banner dismissed */
3484
+ 200: {
3485
+ headers: {
3486
+ [name: string]: unknown;
3487
+ };
3488
+ content: {
3489
+ "application/json": {
3490
+ success: boolean;
3491
+ };
3492
+ };
3493
+ };
3494
+ /** @description Invalid banner ID */
3495
+ 400: {
3496
+ headers: {
3497
+ [name: string]: unknown;
3498
+ };
3499
+ content: {
3500
+ "application/json": components["schemas"]["ErrorResponse"];
3501
+ };
3502
+ };
3503
+ /** @description Not authenticated */
3504
+ 401: {
3505
+ headers: {
3506
+ [name: string]: unknown;
3507
+ };
3508
+ content: {
3509
+ "application/json": components["schemas"]["ErrorResponse"];
3510
+ };
3511
+ };
3512
+ };
3513
+ };
3514
+ resyncSlackAvatar: {
3515
+ parameters: {
3516
+ query?: never;
3517
+ header?: never;
3518
+ path?: never;
3519
+ cookie?: never;
3520
+ };
3521
+ requestBody?: never;
3522
+ responses: {
3523
+ /** @description Avatar resynced successfully */
3524
+ 200: {
3525
+ headers: {
3526
+ [name: string]: unknown;
3527
+ };
3528
+ content: {
3529
+ "application/json": components["schemas"]["ResyncSlackAvatarResponse"];
3530
+ };
3531
+ };
3532
+ /** @description Not authenticated */
3533
+ 401: {
3534
+ headers: {
3535
+ [name: string]: unknown;
3536
+ };
3537
+ content: {
3538
+ "application/json": components["schemas"]["ErrorResponse"];
3539
+ };
3540
+ };
3541
+ /** @description User not found */
3542
+ 404: {
3543
+ headers: {
3544
+ [name: string]: unknown;
3545
+ };
3546
+ content: {
3547
+ "application/json": components["schemas"]["ErrorResponse"];
3548
+ };
3549
+ };
3550
+ /** @description Rate limit exceeded */
3551
+ 429: {
3552
+ headers: {
3553
+ [name: string]: unknown;
3554
+ };
3555
+ content: {
3556
+ "application/json": components["schemas"]["ErrorResponse"];
3557
+ };
3558
+ };
3559
+ };
3560
+ };
3287
3561
  initiateOAuth: {
3288
3562
  parameters: {
3289
3563
  query?: {
@@ -5194,6 +5468,127 @@ export interface operations {
5194
5468
  };
5195
5469
  };
5196
5470
  };
5471
+ resyncSlackLogo: {
5472
+ parameters: {
5473
+ query?: never;
5474
+ header?: never;
5475
+ path?: never;
5476
+ cookie?: never;
5477
+ };
5478
+ requestBody?: never;
5479
+ responses: {
5480
+ /** @description Logo resynced */
5481
+ 200: {
5482
+ headers: {
5483
+ [name: string]: unknown;
5484
+ };
5485
+ content: {
5486
+ "application/json": components["schemas"]["ResyncSlackLogoResponse"];
5487
+ };
5488
+ };
5489
+ /** @description Slack not connected */
5490
+ 400: {
5491
+ headers: {
5492
+ [name: string]: unknown;
5493
+ };
5494
+ content: {
5495
+ "application/json": components["schemas"]["ErrorResponse"];
5496
+ };
5497
+ };
5498
+ /** @description Not authenticated */
5499
+ 401: {
5500
+ headers: {
5501
+ [name: string]: unknown;
5502
+ };
5503
+ content: {
5504
+ "application/json": components["schemas"]["ErrorResponse"];
5505
+ };
5506
+ };
5507
+ /** @description No org.view_workspace capability */
5508
+ 403: {
5509
+ headers: {
5510
+ [name: string]: unknown;
5511
+ };
5512
+ content: {
5513
+ "application/json": components["schemas"]["ErrorResponse"];
5514
+ };
5515
+ };
5516
+ /** @description Rate limit exceeded */
5517
+ 429: {
5518
+ headers: {
5519
+ [name: string]: unknown;
5520
+ };
5521
+ content: {
5522
+ "application/json": components["schemas"]["ErrorResponse"];
5523
+ };
5524
+ };
5525
+ };
5526
+ };
5527
+ resolveWorkspacePath: {
5528
+ parameters: {
5529
+ query?: never;
5530
+ header?: never;
5531
+ path?: never;
5532
+ cookie?: never;
5533
+ };
5534
+ requestBody: {
5535
+ content: {
5536
+ "application/json": {
5537
+ /** @description Flat tokenized segments, e.g. ["dept", "engineering", "team", "front-end"] */
5538
+ path: string[];
5539
+ };
5540
+ };
5541
+ };
5542
+ responses: {
5543
+ /** @description Resolved layers */
5544
+ 200: {
5545
+ headers: {
5546
+ [name: string]: unknown;
5547
+ };
5548
+ content: {
5549
+ "application/json": {
5550
+ layers: components["schemas"]["ResolvedLayer"][];
5551
+ };
5552
+ };
5553
+ };
5554
+ /** @description Invalid segment grammar */
5555
+ 400: {
5556
+ headers: {
5557
+ [name: string]: unknown;
5558
+ };
5559
+ content: {
5560
+ "application/json": components["schemas"]["ErrorResponse"];
5561
+ };
5562
+ };
5563
+ /** @description Not authenticated */
5564
+ 401: {
5565
+ headers: {
5566
+ [name: string]: unknown;
5567
+ };
5568
+ content: {
5569
+ "application/json": components["schemas"]["ErrorResponse"];
5570
+ };
5571
+ };
5572
+ /** @description No org.view_workspace capability */
5573
+ 403: {
5574
+ headers: {
5575
+ [name: string]: unknown;
5576
+ };
5577
+ content: {
5578
+ "application/json": components["schemas"]["ErrorResponse"];
5579
+ };
5580
+ };
5581
+ /** @description Entity slug not found */
5582
+ 404: {
5583
+ headers: {
5584
+ [name: string]: unknown;
5585
+ };
5586
+ content: {
5587
+ "application/json": components["schemas"]["ErrorResponse"];
5588
+ };
5589
+ };
5590
+ };
5591
+ };
5197
5592
  listAccountSessions: {
5198
5593
  parameters: {
5199
5594
  query?: never;
package/src/chat/index.ts CHANGED
@@ -40,6 +40,33 @@ export type {
40
40
  ChatSseEvent,
41
41
  } from './types'
42
42
 
43
+ // =============================================================================
44
+ // Schemas (Zod runtime validation)
45
+ // =============================================================================
46
+
47
+ export {
48
+ IsoDateString,
49
+ ChatVisibilitySchema,
50
+ TitleSourceSchema,
51
+ InvalidationReasonSchema,
52
+ ChatChangedFieldSchema,
53
+ ChatSummarySchema,
54
+ ChatSummaryExtendedSchema,
55
+ TitleGenerationResponseSchema,
56
+ SharedChatMessageSchema,
57
+ ChatShareInfoSchema,
58
+ SharedChatViewSchema,
59
+ BaseEventSchema,
60
+ ChatCreatedEventSchema,
61
+ ChatUpdatedEventSchema,
62
+ ChatDeletedEventSchema,
63
+ InvalidateChatEventSchema,
64
+ InvalidateChatListEventSchema,
65
+ ChatDomainEventSchema,
66
+ ChatInvalidationEventSchema,
67
+ ChatSseEventSchema,
68
+ } from './schemas'
69
+
43
70
  // Runtime profile types and constants
44
71
  export type { ChatRuntimeProfile, ChatRuntimeProfileInfo } from './runtime-profile'
45
72
  export { CHAT_RUNTIME_PROFILES, DEFAULT_CHAT_RUNTIME_PROFILE } from './runtime-profile'
@@ -0,0 +1,154 @@
1
+ import { z } from 'zod'
2
+
3
+ // =============================================================================
4
+ // Reusable Primitives
5
+ // =============================================================================
6
+
7
+ /** ISO 8601 datetime string — the runtime safety net for date serialization. */
8
+ export const IsoDateString = z.string().datetime()
9
+
10
+ // =============================================================================
11
+ // Enums
12
+ // =============================================================================
13
+
14
+ export const ChatVisibilitySchema = z.enum(['private', 'public'])
15
+ export const TitleSourceSchema = z.enum(['auto', 'manual'])
16
+ export const InvalidationReasonSchema = z.enum(['external-mutation', 'bulk-operation', 'sync-required'])
17
+ export const ChatChangedFieldSchema = z.enum(['title', 'titleSource', 'pinnedAt'])
18
+
19
+ // =============================================================================
20
+ // Core Entities
21
+ // =============================================================================
22
+
23
+ export const ChatSummarySchema = z.object({
24
+ id: z.string(),
25
+ title: z.string().nullable(),
26
+ messageCount: z.number().int(),
27
+ createdAt: IsoDateString,
28
+ updatedAt: IsoDateString,
29
+ isShared: z.boolean(),
30
+ })
31
+
32
+ export const ChatSummaryExtendedSchema = ChatSummarySchema.extend({
33
+ pinnedAt: IsoDateString.nullable(),
34
+ titleSource: TitleSourceSchema.nullable(),
35
+ titleGeneratedAt: IsoDateString.nullable(),
36
+ })
37
+
38
+ export const TitleGenerationResponseSchema = z.object({
39
+ title: z.string(),
40
+ isAutoGenerated: z.boolean(),
41
+ })
42
+
43
+ // =============================================================================
44
+ // Share Types
45
+ // =============================================================================
46
+
47
+ export const SharedChatMessageSchema = z.object({
48
+ role: z.enum(['user', 'assistant']),
49
+ content: z.string(),
50
+ parts: z.array(z.unknown()).optional(),
51
+ })
52
+
53
+ export const ChatShareInfoSchema = z.object({
54
+ id: z.string(),
55
+ chatId: z.string(),
56
+ token: z.string(),
57
+ visibility: ChatVisibilitySchema,
58
+ shareUrl: z.string(),
59
+ messageCountAtShare: z.number().int(),
60
+ titleAtShare: z.string(),
61
+ createdAt: IsoDateString,
62
+ createdByName: z.string(),
63
+ isRevoked: z.boolean(),
64
+ })
65
+
66
+ export const SharedChatViewSchema = z.object({
67
+ title: z.string(),
68
+ messages: z.array(SharedChatMessageSchema),
69
+ sharedAt: IsoDateString,
70
+ sharedByName: z.string(),
71
+ visibility: ChatVisibilitySchema,
72
+ })
73
+
74
+ // =============================================================================
75
+ // SSE Event Schemas
76
+ // =============================================================================
77
+
78
+ export const BaseEventSchema = z.object({
79
+ v: z.literal(1),
80
+ timestamp: IsoDateString,
81
+ eventId: z.string().optional(),
82
+ })
83
+
84
+ export const ChatCreatedEventSchema = BaseEventSchema.extend({
85
+ type: z.literal('chat.created'),
86
+ data: z.object({
87
+ chatId: z.string(),
88
+ interactionId: z.string().optional(),
89
+ title: z.string(),
90
+ titleSource: TitleSourceSchema,
91
+ pinnedAt: IsoDateString.nullable(),
92
+ messageCount: z.number().int(),
93
+ createdAt: IsoDateString,
94
+ updatedAt: IsoDateString,
95
+ isShared: z.boolean(),
96
+ }),
97
+ })
98
+
99
+ export const ChatUpdatedEventSchema = BaseEventSchema.extend({
100
+ type: z.literal('chat.updated'),
101
+ data: z.object({
102
+ chatId: z.string(),
103
+ title: z.string(),
104
+ titleSource: TitleSourceSchema.nullable(),
105
+ titleGeneratedAt: IsoDateString.nullable(),
106
+ pinnedAt: IsoDateString.nullable(),
107
+ messageCount: z.number().int(),
108
+ createdAt: IsoDateString,
109
+ updatedAt: IsoDateString,
110
+ isShared: z.boolean(),
111
+ }),
112
+ changed: z.array(ChatChangedFieldSchema),
113
+ })
114
+
115
+ export const ChatDeletedEventSchema = BaseEventSchema.extend({
116
+ type: z.literal('chat.deleted'),
117
+ data: z.object({
118
+ chatId: z.string(),
119
+ }),
120
+ })
121
+
122
+ export const InvalidateChatEventSchema = BaseEventSchema.extend({
123
+ type: z.literal('invalidate.chat'),
124
+ chatId: z.string(),
125
+ reason: InvalidationReasonSchema,
126
+ })
127
+
128
+ export const InvalidateChatListEventSchema = BaseEventSchema.extend({
129
+ type: z.literal('invalidate.chat-list'),
130
+ reason: InvalidationReasonSchema,
131
+ })
132
+
133
+ // =============================================================================
134
+ // SSE Event Unions
135
+ // =============================================================================
136
+
137
+ export const ChatDomainEventSchema = z.discriminatedUnion('type', [
138
+ ChatCreatedEventSchema,
139
+ ChatUpdatedEventSchema,
140
+ ChatDeletedEventSchema,
141
+ ])
142
+
143
+ export const ChatInvalidationEventSchema = z.discriminatedUnion('type', [
144
+ InvalidateChatEventSchema,
145
+ InvalidateChatListEventSchema,
146
+ ])
147
+
148
+ export const ChatSseEventSchema = z.discriminatedUnion('type', [
149
+ ChatCreatedEventSchema,
150
+ ChatUpdatedEventSchema,
151
+ ChatDeletedEventSchema,
152
+ InvalidateChatEventSchema,
153
+ InvalidateChatListEventSchema,
154
+ ])
package/src/chat/types.ts CHANGED
@@ -2,8 +2,32 @@
2
2
  * Chat Domain Types
3
3
  *
4
4
  * Shared types for chat persistence and sharing across Company Semantics codebases.
5
- * Types only - no runtime code, no business logic.
6
- */
5
+ * Response and event types are derived from Zod schemas (single source of truth).
6
+ * Request-only types remain as interfaces (no runtime validation needed).
7
+ */
8
+
9
+ import { z } from 'zod'
10
+ import {
11
+ ChatVisibilitySchema,
12
+ TitleSourceSchema,
13
+ InvalidationReasonSchema,
14
+ ChatChangedFieldSchema,
15
+ ChatSummarySchema,
16
+ ChatSummaryExtendedSchema,
17
+ TitleGenerationResponseSchema,
18
+ ChatShareInfoSchema,
19
+ SharedChatViewSchema,
20
+ SharedChatMessageSchema,
21
+ BaseEventSchema,
22
+ ChatCreatedEventSchema,
23
+ ChatUpdatedEventSchema,
24
+ ChatDeletedEventSchema,
25
+ InvalidateChatEventSchema,
26
+ InvalidateChatListEventSchema,
27
+ ChatDomainEventSchema,
28
+ ChatInvalidationEventSchema,
29
+ ChatSseEventSchema,
30
+ } from './schemas'
7
31
 
8
32
  // =============================================================================
9
33
  // Visibility
@@ -14,7 +38,7 @@
14
38
  * - private: Token exists but access requires authentication as chat owner
15
39
  * - public: Token grants anonymous access to anyone with the link
16
40
  */
17
- export type ChatVisibility = 'private' | 'public'
41
+ export type ChatVisibility = z.infer<typeof ChatVisibilitySchema>
18
42
 
19
43
  // =============================================================================
20
44
  // Chat Summary
@@ -23,16 +47,7 @@ export type ChatVisibility = 'private' | 'public'
23
47
  /**
24
48
  * Lightweight chat summary for list views.
25
49
  */
26
- export interface ChatSummary {
27
- id: string
28
- /** Title - null means intentionally untitled (title not yet generated) */
29
- title: string | null
30
- messageCount: number
31
- createdAt: string
32
- updatedAt: string
33
- /** True if this chat has an active (non-revoked) share */
34
- isShared: boolean
35
- }
50
+ export type ChatSummary = z.infer<typeof ChatSummarySchema>
36
51
 
37
52
  // =============================================================================
38
53
  // Share Types
@@ -45,42 +60,18 @@ export interface ChatSummary {
45
60
  * A chat share must never expose messages with sequenceNumber > messageCountAtShare.
46
61
  * Enforced in ChatShareService.viewSharedChat() only.
47
62
  */
48
- export interface ChatShareInfo {
49
- id: string
50
- chatId: string
51
- token: string
52
- visibility: ChatVisibility
53
- shareUrl: string
54
- /** Message count at time of sharing - the snapshot boundary */
55
- messageCountAtShare: number
56
- /** Title snapshot at share time */
57
- titleAtShare: string
58
- createdAt: string
59
- createdByName: string
60
- isRevoked: boolean
61
- }
63
+ export type ChatShareInfo = z.infer<typeof ChatShareInfoSchema>
62
64
 
63
65
  /**
64
66
  * Shared chat view returned to viewers.
65
67
  * Messages are limited by the snapshot boundary.
66
68
  */
67
- export interface SharedChatView {
68
- title: string
69
- messages: SharedChatMessage[]
70
- sharedAt: string
71
- sharedByName: string
72
- visibility: ChatVisibility
73
- }
69
+ export type SharedChatView = z.infer<typeof SharedChatViewSchema>
74
70
 
75
71
  /**
76
72
  * Individual message in a shared chat view.
77
73
  */
78
- export interface SharedChatMessage {
79
- role: 'user' | 'assistant'
80
- content: string
81
- /** Optional rich content parts (tool calls, artifacts, etc.) */
82
- parts?: unknown[]
83
- }
74
+ export type SharedChatMessage = z.infer<typeof SharedChatMessageSchema>
84
75
 
85
76
  // =============================================================================
86
77
  // API Request/Response Types
@@ -114,7 +105,7 @@ export interface UpdateShareRequest {
114
105
  * - auto: Generated automatically by LLM
115
106
  * - manual: Set by user (disables future auto-generation)
116
107
  */
117
- export type TitleSource = 'auto' | 'manual'
108
+ export type TitleSource = z.infer<typeof TitleSourceSchema>
118
109
 
119
110
  /**
120
111
  * Filters for listing chats.
@@ -133,14 +124,7 @@ export interface ChatListFilters {
133
124
  /**
134
125
  * Extended chat summary with pin/title status.
135
126
  */
136
- export interface ChatSummaryExtended extends ChatSummary {
137
- /** If pinned, when it was pinned */
138
- pinnedAt: string | null
139
- /** How the title was set: 'auto' or 'manual' */
140
- titleSource: TitleSource | null
141
- /** When auto-title was generated (null if never auto-generated) */
142
- titleGeneratedAt: string | null
143
- }
127
+ export type ChatSummaryExtended = z.infer<typeof ChatSummaryExtendedSchema>
144
128
 
145
129
  /**
146
130
  * Request to generate a title for a chat.
@@ -154,11 +138,7 @@ export interface TitleGenerationRequest {
154
138
  /**
155
139
  * Response from title generation.
156
140
  */
157
- export interface TitleGenerationResponse {
158
- title: string
159
- /** Whether this was auto-generated */
160
- isAutoGenerated: boolean
161
- }
141
+ export type TitleGenerationResponse = z.infer<typeof TitleGenerationResponseSchema>
162
142
 
163
143
  // =============================================================================
164
144
  // Event System Types
@@ -174,26 +154,19 @@ export interface TitleGenerationResponse {
174
154
  * INV-EVENT-CONVERGENCE: Clients must assume events may be missed during SSE
175
155
  * disconnects. Invalidation events ensure convergence.
176
156
  */
177
- export interface BaseEvent {
178
- /** Protocol version for forward compatibility */
179
- v: 1
180
- /** ISO 8601 timestamp when event was created */
181
- timestamp: string
182
- /** Optional event ID for telemetry/debugging (not for deduplication logic) */
183
- eventId?: string
184
- }
157
+ export type BaseEvent = z.infer<typeof BaseEventSchema>
185
158
 
186
159
  /**
187
160
  * Reason for cache invalidation.
188
161
  * INV-REASON-TELEMETRY: This is for telemetry only. UI behavior must be
189
162
  * identical regardless of reason. Do not branch on this.
190
163
  */
191
- export type InvalidationReason = 'external-mutation' | 'bulk-operation' | 'sync-required'
164
+ export type InvalidationReason = z.infer<typeof InvalidationReasonSchema>
192
165
 
193
166
  /**
194
167
  * Fields that can change on a chat, used in changed[] array.
195
168
  */
196
- export type ChatChangedField = 'title' | 'titleSource' | 'pinnedAt'
169
+ export type ChatChangedField = z.infer<typeof ChatChangedFieldSchema>
197
170
 
198
171
  // =============================================================================
199
172
  // Domain Events (past-tense facts, carry full state)
@@ -206,60 +179,18 @@ export type ChatChangedField = 'title' | 'titleSource' | 'pinnedAt'
206
179
  * INV-EVENT-1: This event is emitted exactly once per successful stream.
207
180
  * INV-PERSIST-1: Chat record + messages are committed atomically before this event.
208
181
  */
209
- export interface ChatCreatedEvent extends BaseEvent {
210
- type: 'chat.created'
211
- data: {
212
- /** Canonical server-generated UUID - use this for URLs */
213
- chatId: string
214
- /** Client-provided ID for correlation (optional) */
215
- interactionId?: string
216
- /** Chat title */
217
- title: string
218
- /** How the title was set */
219
- titleSource: TitleSource
220
- /** When pinned (null if not pinned) */
221
- pinnedAt: string | null
222
- /** Message count */
223
- messageCount: number
224
- /** ISO timestamp */
225
- createdAt: string
226
- /** ISO timestamp */
227
- updatedAt: string
228
- /** Whether chat is shared */
229
- isShared: boolean
230
- }
231
- }
182
+ export type ChatCreatedEvent = z.infer<typeof ChatCreatedEventSchema>
232
183
 
233
184
  /**
234
185
  * Emitted when chat metadata changes (title, pin, etc.)
235
186
  * Carries full current state - clients should replace local state entirely.
236
187
  */
237
- export interface ChatUpdatedEvent extends BaseEvent {
238
- type: 'chat.updated'
239
- data: {
240
- chatId: string
241
- title: string
242
- titleSource: TitleSource | null
243
- titleGeneratedAt: string | null
244
- pinnedAt: string | null
245
- messageCount: number
246
- createdAt: string
247
- updatedAt: string
248
- isShared: boolean
249
- }
250
- /** What fields changed - for UI optimization only */
251
- changed: ChatChangedField[]
252
- }
188
+ export type ChatUpdatedEvent = z.infer<typeof ChatUpdatedEventSchema>
253
189
 
254
190
  /**
255
191
  * Emitted when a chat is deleted.
256
192
  */
257
- export interface ChatDeletedEvent extends BaseEvent {
258
- type: 'chat.deleted'
259
- data: {
260
- chatId: string
261
- }
262
- }
193
+ export type ChatDeletedEvent = z.infer<typeof ChatDeletedEventSchema>
263
194
 
264
195
  // =============================================================================
265
196
  // Invalidation Events (staleness signals)
@@ -269,20 +200,13 @@ export interface ChatDeletedEvent extends BaseEvent {
269
200
  * Single-entity staleness signal.
270
201
  * Emitted when a specific chat may be out of sync (e.g., external mutation).
271
202
  */
272
- export interface InvalidateChatEvent extends BaseEvent {
273
- type: 'invalidate.chat'
274
- chatId: string
275
- reason: InvalidationReason
276
- }
203
+ export type InvalidateChatEvent = z.infer<typeof InvalidateChatEventSchema>
277
204
 
278
205
  /**
279
206
  * Collection staleness signal.
280
207
  * Emitted when chat list ordering/filtering may be stale.
281
208
  */
282
- export interface InvalidateChatListEvent extends BaseEvent {
283
- type: 'invalidate.chat-list'
284
- reason: InvalidationReason
285
- }
209
+ export type InvalidateChatListEvent = z.infer<typeof InvalidateChatListEventSchema>
286
210
 
287
211
  // =============================================================================
288
212
  // SSE Event Union
@@ -291,16 +215,15 @@ export interface InvalidateChatListEvent extends BaseEvent {
291
215
  /**
292
216
  * All domain events that carry full state.
293
217
  */
294
- export type ChatDomainEvent = ChatCreatedEvent | ChatUpdatedEvent | ChatDeletedEvent
218
+ export type ChatDomainEvent = z.infer<typeof ChatDomainEventSchema>
295
219
 
296
220
  /**
297
221
  * All invalidation events that signal staleness.
298
222
  */
299
- export type ChatInvalidationEvent = InvalidateChatEvent | InvalidateChatListEvent
223
+ export type ChatInvalidationEvent = z.infer<typeof ChatInvalidationEventSchema>
300
224
 
301
225
  /**
302
226
  * SSE event types for chat updates.
303
227
  * Client receives these via SSE and handles accordingly.
304
228
  */
305
- export type ChatSseEvent = ChatDomainEvent | ChatInvalidationEvent
306
-
229
+ export type ChatSseEvent = z.infer<typeof ChatSseEventSchema>
@@ -40,6 +40,8 @@ export const openApiRoutes = {
40
40
  '/api/org/cancel-deletion': ['POST'],
41
41
  '/api/org/delete': ['POST'],
42
42
  '/api/org/deletion-eligibility': ['GET'],
43
+ '/api/org/system-events': ['GET'],
44
+ '/api/org/system-events/{id}/acknowledge': ['POST'],
43
45
  '/api/org/transfer-eligibility': ['GET'],
44
46
  '/api/org/transfer-ownership': ['DELETE', 'POST'],
45
47
  '/api/org/transfer-ownership/accept': ['POST'],
@@ -59,7 +61,10 @@ export const openApiRoutes = {
59
61
  '/api/user/active-org': ['POST'],
60
62
  '/api/user/orgs': ['GET'],
61
63
  '/api/user/orgs/{orgId}/leave': ['POST'],
64
+ '/api/user/preferences/dismissed-banners': ['GET'],
65
+ '/api/user/preferences/dismissed-banners/{bannerId}': ['POST'],
62
66
  '/api/user/profile': ['PATCH'],
67
+ '/api/user/resync-slack-avatar': ['POST'],
63
68
  '/api/workspace': ['GET'],
64
69
  '/api/workspace/access': ['GET'],
65
70
  '/api/workspace/audit': ['GET'],
@@ -77,6 +82,8 @@ export const openApiRoutes = {
77
82
  '/api/workspace/members': ['GET'],
78
83
  '/api/workspace/members/{id}': ['DELETE'],
79
84
  '/api/workspace/members/{id}/role': ['PATCH'],
85
+ '/api/workspace/resolve-path': ['POST'],
86
+ '/api/workspace/resync-slack-logo': ['POST'],
80
87
  '/auth/logout': ['POST'],
81
88
  '/auth/me': ['GET'],
82
89
  '/auth/sso/callback': ['GET', 'POST'],
@@ -85,6 +92,7 @@ export const openApiRoutes = {
85
92
  '/auth/verify': ['POST'],
86
93
  '/connect/{provider}': ['GET'],
87
94
  '/healthz': ['GET'],
95
+ '/healthz/details': ['GET'],
88
96
  '/oauth/{provider}/callback': ['GET'],
89
97
  } as const;
90
98