@agent-team-foundation/first-tree-hub 0.12.7 → 0.12.9

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.
@@ -2,10 +2,10 @@ import { a as __toCommonJS, o as __toESM, t as __commonJSMin } from "./chunk-BSw
2
2
  import { A as FIRST_TREE_HUB_ATTR, C as stampOrgScope, D as untrustedAttrs, E as startWsConnectionSpan, M as require_pino, O as withSpan, S as stampChatResource, _ as rootLogger$1, a as buildRateLimitError, c as currentTraceId, g as reportErrorToRoot, i as bodyCaptureOnSendHook, j as redactUrl, k as withWsMessageSpan, l as decodeJwtForTrace, m as observabilityPlugin, n as applyLoggerConfig, o as classifyJoseError, r as attachRequestContext, s as createLogger$1, t as adapterAttrs, u as endWsConnectionSpan, x as stampAgentResource, y as setWsConnectionAttrs } from "./observability-BAScT_5S-BcW9HgkG.mjs";
3
3
  import { C as resetConfigMeta, E as setConfigValue, S as resetConfig, T as serverConfigSchema, b as migrateLegacyHome, c as resolveServerUrl, d as DEFAULT_CONFIG_DIR, f as DEFAULT_DATA_DIR$1, g as collectMissingPrompts, h as clientConfigSchema, i as ensureFreshAccessToken, l as saveAgentConfig, m as agentConfigSchema, o as loadCredentials, p as DEFAULT_HOME_DIR$1, u as saveCredentials, v as initConfig, w as resolveConfigReadonly, y as loadAgents } from "./bootstrap-BCZC1ki6.mjs";
4
4
  import { a as print, i as blank, n as CLI_USER_AGENT, r as COMMAND_VERSION, s as status, t as cliFetch } from "./cli-fetch--tiwKm5S.mjs";
5
- import { A as delegateFeishuUserSchema, B as inboxPollQuerySchema, C as createAgentSchema, D as createOrgFromMeSchema, E as createMemberSchema, F as githubDevCallbackQuerySchema, G as listMeChatsQuerySchema, H as isRedactedEnvValue, I as githubStartQuerySchema, J as notificationQuerySchema, K as loginSchema, L as imageInlineContentSchema, N as githubAppInstallationClaimBodySchema, P as githubCallbackQuerySchema, R as inboxAckFrameSchema, S as createAdapterMappingSchema, St as wsAuthFrameSchema, T as createMeChatSchema, U as isReservedAgentName$1, V as isOrgSettingNamespace, W as joinByInvitationSchema, X as paginationQuerySchema, Y as onboardingEventSchema, Z as patchOnboardingSchema, _t as updateAgentSchema, a as AGENT_VISIBILITY, at as selfServiceFeishuBotSchema, b as contextTreeSnapshotSchema, bt as updateMemberSchema, c as ORG_SETTINGS_NAMESPACES$1, ct as sessionCompletionMessageSchema, d as addParticipantSchema, dt as sessionReconcileRequestSchema, et as rebindAgentSchema, f as agentBindRequestSchema, ft as sessionStateMessageSchema, g as chatMetadataSchema$1, gt as updateAgentRuntimeConfigSchema, h as agentTypeSchema$1, ht as updateAdapterConfigSchema, i as AGENT_STATUSES, j as dryRunAgentRuntimeConfigSchema, k as defaultRuntimeConfigPayload, l as WS_AUTH_FRAME_TIMEOUT_MS, lt as sessionEventMessageSchema, m as agentRuntimeConfigPayloadSchema$1, mt as submitQuestionAnswerSchema, n as AGENT_NAME_REGEX$1, nt as runtimeStateMessageSchema, ot as sendMessageSchema, p as agentPinnedMessageSchema$1, pt as stripCode, q as messageSourceSchema$1, r as AGENT_SELECTOR_HEADER$1, rt as safeRedirectPath, s as MENTION_REGEX, st as sendToAgentSchema, t as AGENT_BIND_REJECT_REASONS, tt as refreshTokenSchema, u as addMeChatParticipantsSchema, ut as sessionEventSchema$1, v as clientRegisterSchema, vt as updateChatSchema, w as createChatSchema, x as createAdapterConfigSchema, xt as updateOrganizationSchema, y as connectTokenExchangeSchema, yt as updateClientCapabilitiesSchema, z as inboxDeliverFrameSchema$1 } from "./dist-CTkhS6p5.mjs";
5
+ import { $ as patchOnboardingSchema, A as defaultRuntimeConfigPayload, B as inboxDeliverFrameSchema$1, C as createAdapterMappingSchema, Ct as updateOrganizationSchema, D as createMemberSchema, E as createMeChatSchema, F as githubCallbackQuerySchema, G as joinByInvitationSchema, H as isOrgSettingNamespace, I as githubDevCallbackQuerySchema, J as messageSourceSchema$1, K as listMeChatsQuerySchema, L as githubStartQuerySchema, M as dryRunAgentRuntimeConfigSchema, O as createOrgFromMeSchema, P as githubAppInstallationClaimBodySchema, Q as patchChatEngagementSchema, R as imageInlineContentSchema, S as createAdapterConfigSchema, St as updateMemberSchema, T as createChatSchema, U as isRedactedEnvValue, V as inboxPollQuerySchema, W as isReservedAgentName$1, X as onboardingEventSchema, Y as notificationQuerySchema, Z as paginationQuerySchema, _ as chatMetadataSchema$1, _t as updateAdapterConfigSchema, a as AGENT_VISIBILITY, at as safeRedirectPath, b as connectTokenExchangeSchema, bt as updateChatSchema, c as MENTION_REGEX, ct as sendMessageSchema, d as addMeChatParticipantsSchema, dt as sessionEventMessageSchema, f as addParticipantSchema, ft as sessionEventSchema$1, g as agentTypeSchema$1, gt as submitQuestionAnswerSchema, h as agentRuntimeConfigPayloadSchema$1, ht as stripCode, i as AGENT_STATUSES, it as runtimeStateMessageSchema, j as delegateFeishuUserSchema, l as ORG_SETTINGS_NAMESPACES$1, lt as sendToAgentSchema, m as agentPinnedMessageSchema$1, mt as sessionStateMessageSchema, n as AGENT_NAME_REGEX$1, nt as rebindAgentSchema, o as CHAT_ENGAGEMENT_STATUSES, p as agentBindRequestSchema, pt as sessionReconcileRequestSchema, q as loginSchema, r as AGENT_SELECTOR_HEADER$1, rt as refreshTokenSchema, st as selfServiceFeishuBotSchema, t as AGENT_BIND_REJECT_REASONS, u as WS_AUTH_FRAME_TIMEOUT_MS, ut as sessionCompletionMessageSchema, vt as updateAgentRuntimeConfigSchema, w as createAgentSchema, wt as wsAuthFrameSchema, x as contextTreeSnapshotSchema, xt as updateClientCapabilitiesSchema, y as clientRegisterSchema, yt as updateAgentSchema, z as inboxAckFrameSchema } from "./dist-CnjqakXS.mjs";
6
6
  import { a as ConflictError, c as UnauthorizedError, i as ClientUserMismatchError$1, l as organizations, n as BadRequestError, o as ForbiddenError, r as ClientOrgMismatchError$1, s as NotFoundError, t as AppError, u as users } from "./errors-CF5evtJt-B0NTIVPt.mjs";
7
7
  import { n as init_esm, r as trace, t as esm_exports } from "./esm-iadMkGbV.mjs";
8
- import { $ as messages, A as getOnlineCount, B as listActiveAgentsPinnedToClient, C as ensureCanJoin, D as getCachedAudience, E as getActivityOverview, F as invalidateChatAudience, G as listChatsForMember, H as listAgentsWithRuntime, I as joinAsParticipant, J as listMessages, K as listClients, L as joinChat, M as heartbeatClient, N as heartbeatInstance, O as getChatDetail, P as inboxEntries, Q as members, R as leaveAsParticipant, S as editMessage, T as findOrCreateDirectChat, U as listChatParticipantsWithNames, V as listAgentsManagedByUser, W as listChats, X as markStaleAgents, Z as markSupersededByChat, _ as clients, _t as touchAgent, a as agentVisibilityCondition, at as registerChatMessageDispatcher, b as deriveAuthState, bt as upsertSessionState, c as assertParticipant, ct as resetActivity, d as chatParticipants, dt as sendMessage, et as notifyRecipients, f as chatSubscriptions, ft as sendToAgent$1, g as cleanupStalePresence, gt as submitAnswer, h as cleanupStaleClients, ht as setRuntimeState, i as agentPresence, it as recomputeWatchersForMember, j as getPresence, k as getClient, l as bindAgent, lt as resolveChatMembership, m as claimClient, mt as setOffline, n as addParticipant, nt as recomputeChatWatchers, o as agents, ot as registerClient, p as chats, pt as serverInstances, q as listClientsForOrgAdmin, r as agentChatSessions, rt as recomputeWatchersForAgent, s as assertClientOwner, st as removeParticipant, t as addChatParticipants, tt as pendingQuestions, u as changeChatType, ut as retireClient, v as createChat, vt as unbindAgent, w as ensureParticipant$1, x as disconnectClient, y as createNotifier, yt as updateClientCapabilities, z as leaveChat } from "./client-h5l7mi0m-OEX7MOBg.mjs";
8
+ import { $ as notifyRecipients, A as getPresence, B as listAgentsManagedByUser, C as ensureParticipant, D as getChatDetail, E as getCachedAudience, F as joinAsParticipant, G as listClients, H as listChatParticipantsWithNames, I as joinChat, K as listClientsForOrgAdmin, L as leaveAsParticipant, M as heartbeatInstance, N as inboxEntries, O as getClient, P as invalidateChatAudience, Q as messages, R as leaveChat, S as ensureCanJoin, T as getActivityOverview, U as listChats, V as listAgentsWithRuntime, W as listChatsForMember, X as markSupersededByChat, Y as markStaleAgents, Z as members, _ as createChat, _t as unbindAgent, a as agentVisibilityCondition, at as registerClient, b as disconnectClient, c as assertParticipant, ct as resolveChatMembership, d as chatMembership, dt as sendToAgent$1, et as pendingQuestions, f as chats, ft as serverInstances, g as clients, gt as touchAgent, h as cleanupStalePresence, ht as submitAnswer, i as agentPresence, it as registerChatMessageDispatcher, j as heartbeatClient, k as getOnlineCount, l as bindAgent, lt as retireClient, m as cleanupStaleClients, mt as setRuntimeState, n as addParticipant, nt as recomputeWatchersForAgent, o as agents, ot as removeParticipant, p as claimClient, pt as setOffline, q as listMessages, r as agentChatSessions, rt as recomputeWatchersForMember, s as assertClientOwner, st as resetActivity, t as addChatParticipants, tt as recomputeChatWatchers, u as changeChatType, ut as sendMessage, v as createNotifier, vt as updateClientCapabilities, w as findOrCreateDirectChat, x as editMessage, y as deriveAuthState, yt as upsertSessionState, z as listActiveAgentsPinnedToClient } from "./client-OMwJMCQt-R1T06ZH6.mjs";
9
9
  import { a as invitationRedemptions, c as recordRedemption, i as getActiveInvitation, l as rotateInvitation, n as ensureActiveInvitation, o as invitations, r as findActiveByToken, t as buildInviteUrl, u as uuidv7 } from "./invitation-Bg0TRiyx-BsZH4GCS.mjs";
10
10
  import { createRequire } from "node:module";
11
11
  import { ZodError, z } from "zod";
@@ -764,6 +764,12 @@ const chatTypeSchema = z.enum([
764
764
  "group",
765
765
  "thread"
766
766
  ]);
767
+ const chatEngagementStatusSchema = z.enum([
768
+ "active",
769
+ "archived",
770
+ "deleted"
771
+ ]);
772
+ z.object({ status: chatEngagementStatusSchema });
767
773
  z.object({
768
774
  type: chatTypeSchema,
769
775
  topic: z.string().max(500).optional(),
@@ -793,7 +799,8 @@ z.object({
793
799
  }).extend({
794
800
  participants: z.array(chatParticipantSchema),
795
801
  title: z.string(),
796
- firstMessagePreview: z.string().nullable()
802
+ firstMessagePreview: z.string().nullable(),
803
+ engagementStatus: chatEngagementStatusSchema
797
804
  });
798
805
  z.object({ topic: z.string().trim().max(500).nullable() });
799
806
  z.object({ agentId: z.string().min(1) });
@@ -1242,10 +1249,16 @@ const meChatFilterSchema = z.enum([
1242
1249
  "watching"
1243
1250
  ]);
1244
1251
  const meChatMembershipKindSchema = z.enum(["participant", "watching"]);
1252
+ const chatEngagementViewSchema = z.enum([
1253
+ "active",
1254
+ "archived",
1255
+ "all"
1256
+ ]);
1245
1257
  z.object({
1246
1258
  cursor: z.string().optional(),
1247
1259
  limit: z.coerce.number().int().min(1).max(200).default(50),
1248
- filter: meChatFilterSchema.default("all")
1260
+ filter: meChatFilterSchema.default("all"),
1261
+ engagement: chatEngagementViewSchema.default("active")
1249
1262
  });
1250
1263
  const meChatParticipantSchema = z.object({
1251
1264
  agentId: z.string(),
@@ -1263,7 +1276,8 @@ const meChatRowSchema = z.object({
1263
1276
  lastMessageAt: z.string().nullable(),
1264
1277
  lastMessagePreview: z.string().nullable(),
1265
1278
  unreadMentionCount: z.number().int(),
1266
- canReply: z.boolean()
1279
+ canReply: z.boolean(),
1280
+ engagementStatus: chatEngagementStatusSchema
1267
1281
  });
1268
1282
  z.object({
1269
1283
  rows: z.array(meChatRowSchema),
@@ -3240,7 +3254,9 @@ You are running inside **Agent Hub**, a messaging platform for agent teams.
3240
3254
  - **Your final text response is automatically delivered** to the chat — just respond normally
3241
3255
  - For **proactive communication** (sending to other agents, other chats, or structured data),
3242
3256
  use the \`first-tree-hub\` CLI below
3243
- - **Use your judgment about when to respond.** Not every message requires a reply.
3257
+ - **Use your judgment about when to respond.** Not every message requires
3258
+ a reply — if you have nothing new for the recipient, output nothing and
3259
+ the runtime will end the turn silently.
3244
3260
  Your role and responsibilities are injected via the Hub-managed system prompt.
3245
3261
 
3246
3262
  ## Environment Variables
@@ -5844,6 +5860,11 @@ function createResultSink(deps) {
5844
5860
  return { mentions: [trigger.senderId] };
5845
5861
  }
5846
5862
  return async function forwardResult(text) {
5863
+ if (text.trim().length === 0) {
5864
+ deps.clearTrigger();
5865
+ deps.log("silent turn: agent produced empty output, skipping delivery");
5866
+ return;
5867
+ }
5847
5868
  const trigger = deps.getTrigger();
5848
5869
  deps.clearTrigger();
5849
5870
  const metadata = await buildMetadata(trigger);
@@ -9422,7 +9443,7 @@ async function onboardCreate(args) {
9422
9443
  }
9423
9444
  const runtimeAgent = args.type === "human" ? args.assistant : args.id;
9424
9445
  if (args.feishuBotAppId && args.feishuBotAppSecret) {
9425
- const { bindFeishuBot } = await import("./feishu-DJm0EaZP.mjs").then((n) => n.r);
9446
+ const { bindFeishuBot } = await import("./feishu-DrnBbl8T.mjs").then((n) => n.r);
9426
9447
  const targetAgentUuid = args.type === "human" ? assistantUuid : primary.uuid;
9427
9448
  if (!targetAgentUuid) print.line(`Warning: Cannot bind Feishu bot — no runtime agent available for "${args.id}".\n`);
9428
9449
  else {
@@ -10635,7 +10656,7 @@ function createFeedbackHandler(config) {
10635
10656
  return { handle };
10636
10657
  }
10637
10658
  //#endregion
10638
- //#region ../server/dist/app-BGjMcVXo.mjs
10659
+ //#region ../server/dist/app-BpskScln.mjs
10639
10660
  var import_fastify_opentelemetry = /* @__PURE__ */ __toESM(require_fastify_opentelemetry(), 1);
10640
10661
  init_esm();
10641
10662
  var __defProp = Object.defineProperty;
@@ -10742,7 +10763,7 @@ async function requireChatAccess(request, db) {
10742
10763
  role: caller.role,
10743
10764
  humanAgentId: caller.humanAgentId
10744
10765
  };
10745
- const [direct] = await db.select({ chatId: chatParticipants.chatId }).from(chatParticipants).where(and(eq(chatParticipants.chatId, chatId), eq(chatParticipants.agentId, caller.humanAgentId))).limit(1);
10766
+ const [direct] = await db.select({ chatId: chatMembership.chatId }).from(chatMembership).where(and(eq(chatMembership.chatId, chatId), eq(chatMembership.agentId, caller.humanAgentId), eq(chatMembership.accessMode, "speaker"))).limit(1);
10746
10767
  if (direct) {
10747
10768
  stampOrgScope(request, scope);
10748
10769
  stampChatResource(request, chat);
@@ -10751,7 +10772,7 @@ async function requireChatAccess(request, db) {
10751
10772
  scope
10752
10773
  };
10753
10774
  }
10754
- const participantRows = await db.select({ agentId: chatParticipants.agentId }).from(chatParticipants).where(eq(chatParticipants.chatId, chatId));
10775
+ const participantRows = await db.select({ agentId: chatMembership.agentId }).from(chatMembership).where(and(eq(chatMembership.chatId, chatId), eq(chatMembership.accessMode, "speaker")));
10755
10776
  if (participantRows.length === 0) throw new NotFoundError(`Chat "${chatId}" not found`);
10756
10777
  const participantIds = participantRows.map((p) => p.agentId);
10757
10778
  const [managed] = await db.select({ uuid: agents.uuid }).from(agents).where(and(inArray(agents.uuid, participantIds), eq(agents.managerId, caller.memberId))).limit(1);
@@ -11843,19 +11864,6 @@ async function findOrCreateChatForChannel(db, data) {
11843
11864
  return chatId;
11844
11865
  });
11845
11866
  }
11846
- /**
11847
- * Ensure an agent is a participant of a chat (no-op if already). Mode is
11848
- * derived via the canonical entrypoint — pre-fix this also wrote `mode:`
11849
- * implicitly via schema default `'full'`, which is wrong for non-human
11850
- * agents in a group chat (the bug §1.1 of the Phase 1 design doc fixes).
11851
- */
11852
- async function ensureParticipant(db, chatId, agentId) {
11853
- const [exists] = await db.select({ chatId: chatParticipants.chatId }).from(chatParticipants).where(and(eq(chatParticipants.chatId, chatId), eq(chatParticipants.agentId, agentId))).limit(1);
11854
- if (!exists) await addChatParticipants(db, chatId, [{
11855
- agentId,
11856
- role: "member"
11857
- }], { onConflictDoNothing: true });
11858
- }
11859
11867
  /** Store a cross-reference between internal and external message. */
11860
11868
  async function createMessageReference(db, data) {
11861
11869
  await db.insert(adapterMessageReferences).values({
@@ -11938,9 +11946,9 @@ async function buildClientMessagePayloadsForInbox(db, inboxId, items) {
11938
11946
  const modeByChat = /* @__PURE__ */ new Map();
11939
11947
  if (chatIds.length > 0) {
11940
11948
  const rows = await db.select({
11941
- chatId: chatParticipants.chatId,
11942
- mode: chatParticipants.mode
11943
- }).from(chatParticipants).where(and(eq(chatParticipants.agentId, agentId), inArray(chatParticipants.chatId, chatIds)));
11949
+ chatId: chatMembership.chatId,
11950
+ mode: chatMembership.mode
11951
+ }).from(chatMembership).where(and(eq(chatMembership.agentId, agentId), inArray(chatMembership.chatId, chatIds), eq(chatMembership.accessMode, "speaker")));
11944
11952
  for (const r of rows) modeByChat.set(r.chatId, normaliseMode(r.mode));
11945
11953
  }
11946
11954
  const inReplyToIds = [...new Set(items.map((it) => it.message.inReplyTo).filter((id) => id !== null))];
@@ -12341,7 +12349,7 @@ async function prepareImageOutbound(db, notifier, chatId, data) {
12341
12349
  * chat reply (see `services/message.ts` replyTo routing).
12342
12350
  */
12343
12351
  async function collectTargetInboxes(db, chatId, inReplyTo) {
12344
- const participants = await db.select({ inboxId: agents.inboxId }).from(chatParticipants).innerJoin(agents, eq(chatParticipants.agentId, agents.uuid)).where(eq(chatParticipants.chatId, chatId));
12352
+ const participants = await db.select({ inboxId: agents.inboxId }).from(chatMembership).innerJoin(agents, eq(chatMembership.agentId, agents.uuid)).where(and(eq(chatMembership.chatId, chatId), eq(chatMembership.accessMode, "speaker")));
12345
12353
  const set = new Set(participants.map((p) => p.inboxId));
12346
12354
  if (inReplyTo) {
12347
12355
  const [original] = await db.select({ replyToInbox: messages.replyToInbox }).from(messages).where(eq(messages.id, inReplyTo)).limit(1);
@@ -15267,6 +15275,47 @@ async function bootstrapConfigRoutes(_app) {
15267
15275
  return { allowedOrg: null };
15268
15276
  });
15269
15277
  }
15278
+ /**
15279
+ * Per-(chat, agent) user state — independent from membership structure.
15280
+ *
15281
+ * This is the third layer of the chat data model: while `chats` owns
15282
+ * the entity and `chat_membership` owns the structural relation
15283
+ * (who can speak, who watches), this table owns the user's private
15284
+ * state about a chat. The reason it lives apart: structural changes
15285
+ * (speaker ↔ watcher, manager rebind, recompute) must never overwrite
15286
+ * user-private state — physical separation makes that an invariant
15287
+ * rather than a service-layer discipline.
15288
+ *
15289
+ * Columns evolve incrementally as new per-user state is needed.
15290
+ * Currently:
15291
+ * - `last_read_at`, `unread_mention_count` — seeded by PR-A from
15292
+ * the legacy `chat_participants` / `chat_subscriptions` columns.
15293
+ * - `engagement_status` — added in 0040; per-(chat, user) view
15294
+ * state (active / archived / deleted). Auto-revives archived →
15295
+ * active on new message; deleted is sticky (only the user can
15296
+ * restore from the chat detail page).
15297
+ *
15298
+ * Future fields slated for this table: pinned, mute_until, draft,
15299
+ * custom_title, last_seen_at — each as a separate change.
15300
+ *
15301
+ * Rows are lazy-upserted on first user write (markRead / mention
15302
+ * counter bump / engagement transition). Reads use COALESCE for
15303
+ * defaults so callers see `'active'` etc. even when no row exists.
15304
+ * Service-layer integrity (no FK / CHECK / trigger).
15305
+ *
15306
+ * See proposals/chat-data-model-restructure.20260512.md §8.6.
15307
+ */
15308
+ const chatUserState = pgTable("chat_user_state", {
15309
+ chatId: text("chat_id").notNull(),
15310
+ agentId: text("agent_id").notNull(),
15311
+ lastReadAt: timestamp("last_read_at", { withTimezone: true }),
15312
+ unreadMentionCount: integer("unread_mention_count").notNull().default(0),
15313
+ engagementStatus: text("engagement_status").notNull().default("active")
15314
+ }, (table) => [
15315
+ primaryKey({ columns: [table.chatId, table.agentId] }),
15316
+ index("idx_user_state_agent").on(table.agentId),
15317
+ index("idx_user_state_unread").on(table.agentId).where(sql`unread_mention_count > 0`)
15318
+ ]);
15270
15319
  /** Extract a plain-text summary from a message's JSONB content field.
15271
15320
  * Used as the auto-title fallback in chat list rendering — see
15272
15321
  * `me-chat.ts:resolveChatTitle` and `admin/chats.ts:getChat`.
@@ -15462,7 +15511,7 @@ async function transitionSessionState(db, agentId, chatId, target, from, organiz
15462
15511
  async function filterSessionsByParticipant(db, sessions, participantAgentId) {
15463
15512
  if (sessions.length === 0) return [];
15464
15513
  const chatIds = sessions.map((s) => s.chatId);
15465
- const participantRows = await db.select({ chatId: chatParticipants.chatId }).from(chatParticipants).where(and(inArray(chatParticipants.chatId, chatIds), eq(chatParticipants.agentId, participantAgentId)));
15514
+ const participantRows = await db.select({ chatId: chatMembership.chatId }).from(chatMembership).where(and(inArray(chatMembership.chatId, chatIds), eq(chatMembership.agentId, participantAgentId), eq(chatMembership.accessMode, "speaker")));
15466
15515
  const allowedChatIds = new Set(participantRows.map((r) => r.chatId));
15467
15516
  return sessions.filter((s) => allowedChatIds.has(s.chatId));
15468
15517
  }
@@ -15471,16 +15520,17 @@ async function filterSessionsByParticipant(db, sessions, participantAgentId) {
15471
15520
  * workspace).
15472
15521
  *
15473
15522
  * Responsibilities:
15474
- * - Cursor-paginated conversation list across participant + watcher rows
15475
- * for the caller's human agent.
15523
+ * - Cursor-paginated conversation list (single-stream JOIN over the
15524
+ * unified `chat_membership` + `chat_user_state` tables).
15476
15525
  * - Create a new chat (no dedupe, runs `recomputeChatWatchers` after).
15477
- * - Add participants (idempotent, runs `recomputeChatWatchers` after).
15478
- * - Mark-read (touches whichever of the two tables holds the user's row).
15479
- * - Join → state-carry watcher → speaker (delegates to `watcher.ts`).
15480
- * - Leavestate-carry speaker → watcher (delegates to `watcher.ts`).
15526
+ * - Add participants (idempotent, UPSERT into `chat_membership`,
15527
+ * runs `recomputeChatWatchers` after).
15528
+ * - Mark-read (UPSERT into `chat_user_state`).
15529
+ * - Joinwatcher to speaker (delegates to `watcher.ts`).
15530
+ * - Leave → speaker to watcher or detach (delegates to `watcher.ts`).
15481
15531
  *
15482
- * See docs/chat-first-workspace-product-design.md "API Contract" + "Data
15483
- * Model".
15532
+ * See proposals/chat-data-model-restructure.20260512.md §8 (schema)
15533
+ * and §11.1 (per-route mapping).
15484
15534
  */
15485
15535
  function encodeCursor(lastMessageAt, chatId) {
15486
15536
  const payload = `${lastMessageAt ? lastMessageAt.toISOString() : ""}|${chatId}`;
@@ -15504,17 +15554,61 @@ function decodeCursor(cursor) {
15504
15554
  return null;
15505
15555
  }
15506
15556
  }
15557
+ const { ACTIVE, ARCHIVED, DELETED } = CHAT_ENGAGEMENT_STATUSES;
15558
+ /**
15559
+ * SQL predicate for each engagement view tab. `deleted` is never a valid view
15560
+ * value — deleted rows are reachable only through `GET /chats/:chatId` + the
15561
+ * Restore banner on the chat detail page.
15562
+ */
15563
+ const ENGAGEMENT_VIEW_PREDICATE = {
15564
+ active: sql`COALESCE(cus.engagement_status, ${ACTIVE}) = ${ACTIVE}`,
15565
+ archived: sql`COALESCE(cus.engagement_status, ${ACTIVE}) = ${ARCHIVED}`,
15566
+ all: sql`COALESCE(cus.engagement_status, ${ACTIVE}) IN (${ACTIVE}, ${ARCHIVED})`
15567
+ };
15568
+ /**
15569
+ * Write the caller's engagement state for this chat. UPSERT into
15570
+ * `chat_user_state` — the row may not yet exist (the user might not have
15571
+ * marked-read or been @-mentioned), so an INSERT with the engagement value
15572
+ * is the first write; subsequent transitions are UPDATEs.
15573
+ *
15574
+ * Idempotent. Mirrors the UPSERT shape used by `markMeChatRead`.
15575
+ */
15576
+ async function setChatEngagement(db, chatId, agentId, status) {
15577
+ await db.insert(chatUserState).values({
15578
+ chatId,
15579
+ agentId,
15580
+ unreadMentionCount: 0,
15581
+ engagementStatus: status
15582
+ }).onConflictDoUpdate({
15583
+ target: [chatUserState.chatId, chatUserState.agentId],
15584
+ set: { engagementStatus: status }
15585
+ });
15586
+ }
15587
+ /**
15588
+ * Read the caller's engagement state. Returns `'active'` when no
15589
+ * `chat_user_state` row exists yet (lazy-materialised; matches the SQL
15590
+ * `COALESCE(..., 'active')` used elsewhere).
15591
+ */
15592
+ async function getCallerEngagement(db, chatId, agentId) {
15593
+ const [row] = await db.select({ engagementStatus: chatUserState.engagementStatus }).from(chatUserState).where(and(eq(chatUserState.chatId, chatId), eq(chatUserState.agentId, agentId))).limit(1);
15594
+ return row?.engagementStatus ?? ACTIVE;
15595
+ }
15507
15596
  /**
15508
15597
  * GET /me/chats — cursor-paginated conversation list.
15509
15598
  *
15510
15599
  * SQL strategy:
15511
- * - One query that UNIONs participant rows and subscription rows for the
15512
- * caller's human agent, joined to chats. The UNION+coalesce keeps both
15513
- * `unread_mention_count` and `membership_kind` per row.
15514
- * - Filter `parent_chat_id IS NULL` (threads are excluded in v1).
15600
+ * - Single-stream query: `chats JOIN chat_membership LEFT JOIN
15601
+ * chat_user_state`. The membership row carries access_mode
15602
+ * (speaker "participant" / watcher → "watching"); the user
15603
+ * state row supplies the unread counter (COALESCE 0 when
15604
+ * row is missing).
15605
+ * - Filter `parent_chat_id IS NULL` (threads excluded in v1).
15606
+ * - Filter `c.organization_id = ?` to defend against historical
15607
+ * cross-org pollution rows that may still reference the caller
15608
+ * (see fix/cross-org-direct-chat-pollution).
15515
15609
  * - Sort `(last_message_at DESC NULLS LAST, chat_id DESC)`.
15516
- * - Cursor narrows the result to rows STRICTLY before `(cursor.ts, cursor.id)`.
15517
- * - Followed by a small participant-list lookup for the page only.
15610
+ * - Cursor narrows the result to rows STRICTLY before the cursor.
15611
+ * - Followed by a participants-list lookup for the page only.
15518
15612
  */
15519
15613
  async function listMeChats(db, humanAgentId, organizationId, query) {
15520
15614
  const limit = query.limit;
@@ -15522,28 +15616,12 @@ async function listMeChats(db, humanAgentId, organizationId, query) {
15522
15616
  if (query.cursor && !cursor) throw new BadRequestError("Invalid cursor");
15523
15617
  const filterUnreadOnly = query.filter === "unread";
15524
15618
  const filterWatchingOnly = query.filter === "watching";
15619
+ const engagementPredicate = ENGAGEMENT_VIEW_PREDICATE[query.engagement];
15525
15620
  const cursorTsIso = cursor?.lastMessageAt ? cursor.lastMessageAt.toISOString() : null;
15526
15621
  const cursorPredicate = !cursor ? sql`TRUE` : cursor.lastMessageAt === null ? sql`(c.last_message_at IS NULL AND c.id < ${cursor.chatId})` : sql`(c.last_message_at IS NULL
15527
15622
  OR c.last_message_at < ${cursorTsIso}::timestamptz
15528
15623
  OR (c.last_message_at = ${cursorTsIso}::timestamptz AND c.id < ${cursor.chatId}))`;
15529
15624
  const rawRows = await db.execute(sql`
15530
- WITH membership AS (
15531
- SELECT chat_id, 'participant'::text AS membership_kind, unread_mention_count
15532
- FROM chat_participants
15533
- WHERE agent_id = ${humanAgentId}
15534
- UNION ALL
15535
- SELECT chat_id, 'watching'::text AS membership_kind, unread_mention_count
15536
- FROM chat_subscriptions
15537
- WHERE agent_id = ${humanAgentId}
15538
- ),
15539
- /* Resolve duplicates (should not happen post-invariant-1, but cheap) by
15540
- preferring the participant row. */
15541
- deduped AS (
15542
- SELECT DISTINCT ON (chat_id)
15543
- chat_id, membership_kind, unread_mention_count
15544
- FROM membership
15545
- ORDER BY chat_id, CASE WHEN membership_kind = 'participant' THEN 0 ELSE 1 END
15546
- )
15547
15625
  SELECT
15548
15626
  c.id AS chat_id,
15549
15627
  c.type AS type,
@@ -15551,20 +15629,26 @@ async function listMeChats(db, humanAgentId, organizationId, query) {
15551
15629
  c.parent_chat_id AS parent_chat_id,
15552
15630
  c.last_message_at AS last_message_at,
15553
15631
  c.last_message_preview AS last_message_preview,
15554
- (SELECT count(*) FROM chat_participants WHERE chat_id = c.id) AS participant_count,
15555
- d.membership_kind AS membership_kind,
15556
- d.unread_mention_count AS unread_mention_count
15632
+ (SELECT count(*) FROM chat_membership
15633
+ WHERE chat_id = c.id AND access_mode = 'speaker') AS participant_count,
15634
+ cm.access_mode AS access_mode,
15635
+ COALESCE(cus.unread_mention_count, 0) AS unread_mention_count,
15636
+ COALESCE(cus.engagement_status, ${ACTIVE}) AS engagement_status
15557
15637
  FROM chats c
15558
- JOIN deduped d ON d.chat_id = c.id
15638
+ JOIN chat_membership cm
15639
+ ON cm.chat_id = c.id AND cm.agent_id = ${humanAgentId}
15640
+ LEFT JOIN chat_user_state cus
15641
+ ON cus.chat_id = c.id AND cus.agent_id = ${humanAgentId}
15559
15642
  WHERE c.parent_chat_id IS NULL
15560
- /* Scope to the caller's org. Without this, cross-org dirty chats
15561
- whose chat_participants still reference the caller's human agent
15562
- (historical pollution — see fix/cross-org-direct-chat-pollution)
15563
- would leak into the list and 404 on click via requireChatAccess. */
15643
+ /* Scope to the caller's org. Without this, cross-org dirty
15644
+ chats whose chat_membership still references the caller's
15645
+ human agent (historical pollution — see
15646
+ fix/cross-org-direct-chat-pollution) would leak into the
15647
+ list and 404 on click via requireChatAccess. */
15564
15648
  AND c.organization_id = ${organizationId}
15565
- /* Filter: unread / watching */
15566
- AND (${!filterUnreadOnly}::bool OR d.unread_mention_count > 0)
15567
- AND (${!filterWatchingOnly}::bool OR d.membership_kind = 'watching')
15649
+ AND (${!filterUnreadOnly}::bool OR COALESCE(cus.unread_mention_count, 0) > 0)
15650
+ AND (${!filterWatchingOnly}::bool OR cm.access_mode = 'watcher')
15651
+ AND ${engagementPredicate}
15568
15652
  AND ${cursorPredicate}
15569
15653
  ORDER BY c.last_message_at DESC NULLS LAST, c.id DESC
15570
15654
  LIMIT ${limit + 1}
@@ -15583,11 +15667,11 @@ async function listMeChats(db, humanAgentId, organizationId, query) {
15583
15667
  };
15584
15668
  const chatIds = pageRaw.map((r) => r.chat_id);
15585
15669
  const participantRows = await db.select({
15586
- chatId: chatParticipants.chatId,
15587
- agentId: chatParticipants.agentId,
15670
+ chatId: chatMembership.chatId,
15671
+ agentId: chatMembership.agentId,
15588
15672
  displayName: agents.displayName,
15589
15673
  type: agents.type
15590
- }).from(chatParticipants).innerJoin(agents, eq(chatParticipants.agentId, agents.uuid)).where(inArray(chatParticipants.chatId, chatIds));
15674
+ }).from(chatMembership).innerJoin(agents, eq(chatMembership.agentId, agents.uuid)).where(and(inArray(chatMembership.chatId, chatIds), eq(chatMembership.accessMode, "speaker")));
15591
15675
  const participantsByChat = /* @__PURE__ */ new Map();
15592
15676
  for (const p of participantRows) {
15593
15677
  const list = participantsByChat.get(p.chatId) ?? [];
@@ -15611,10 +15695,11 @@ async function listMeChats(db, humanAgentId, organizationId, query) {
15611
15695
  rows: pageRaw.map((r) => {
15612
15696
  const participants = participantsByChat.get(r.chat_id) ?? [];
15613
15697
  const title = resolveChatTitle(r.topic, firstMessageSummary.get(r.chat_id) ?? null, participants, humanAgentId);
15698
+ const isSpeaker = r.access_mode === "speaker";
15614
15699
  return {
15615
15700
  chatId: r.chat_id,
15616
15701
  type: r.type,
15617
- membershipKind: r.membership_kind,
15702
+ membershipKind: isSpeaker ? "participant" : "watching",
15618
15703
  title,
15619
15704
  topic: r.topic,
15620
15705
  participants,
@@ -15622,7 +15707,8 @@ async function listMeChats(db, humanAgentId, organizationId, query) {
15622
15707
  lastMessageAt: toDate(r.last_message_at)?.toISOString() ?? null,
15623
15708
  lastMessagePreview: r.last_message_preview,
15624
15709
  unreadMentionCount: r.unread_mention_count,
15625
- canReply: r.membership_kind === "participant"
15710
+ canReply: isSpeaker,
15711
+ engagementStatus: r.engagement_status
15626
15712
  };
15627
15713
  }),
15628
15714
  nextCursor
@@ -15634,11 +15720,6 @@ async function listMeChats(db, humanAgentId, organizationId, query) {
15634
15720
  * 1. `chat.topic` (manual, set via `PATCH /chats/:chatId`)
15635
15721
  * 2. First message summary (auto, ≤ 50 chars from `extractSummary`)
15636
15722
  * 3. Participant join (fallback when chat has no messages yet)
15637
- *
15638
- * The first-message fallback is the chat-first equivalent of how
15639
- * ChatGPT / Claude.ai name conversations from the user's opening
15640
- * prompt — gives same-agent multi-chats distinct identities and
15641
- * removes the "title duplicates participants chip row" anti-pattern.
15642
15723
  */
15643
15724
  function resolveChatTitle(topic, firstMessageSummary, participants, selfAgentId) {
15644
15725
  if (topic && topic.length > 0) return topic;
@@ -15692,7 +15773,7 @@ async function addMeChatParticipants(db, chatId, callerHumanAgentId, callerOrgan
15692
15773
  }).from(chats).where(eq(chats.id, chatId)).limit(1);
15693
15774
  if (!chat) throw new NotFoundError(`Chat "${chatId}" not found`);
15694
15775
  if (chat.organizationId !== callerOrganizationId) throw new NotFoundError(`Chat "${chatId}" not found`);
15695
- const [callerRow] = await db.select({ chatId: chatParticipants.chatId }).from(chatParticipants).where(and(eq(chatParticipants.chatId, chatId), eq(chatParticipants.agentId, callerHumanAgentId))).limit(1);
15776
+ const [callerRow] = await db.select({ chatId: chatMembership.chatId }).from(chatMembership).where(and(eq(chatMembership.chatId, chatId), eq(chatMembership.agentId, callerHumanAgentId), eq(chatMembership.accessMode, "speaker"))).limit(1);
15696
15777
  if (!callerRow) throw new NotFoundError(`Chat "${chatId}" not found`);
15697
15778
  const found = await db.select({
15698
15779
  uuid: agents.uuid,
@@ -15706,45 +15787,36 @@ async function addMeChatParticipants(db, chatId, callerHumanAgentId, callerOrgan
15706
15787
  const crossOrg = found.filter((a) => a.organizationId !== chat.organizationId);
15707
15788
  if (crossOrg.length > 0) throw new BadRequestError(`Cross-organization participant rejected: ${crossOrg.map((a) => a.uuid).join(", ")}`);
15708
15789
  await db.transaction(async (tx) => {
15709
- const existing = await tx.select({ agentId: chatParticipants.agentId }).from(chatParticipants).where(eq(chatParticipants.chatId, chatId));
15710
- const existingSet = new Set(existing.map((e) => e.agentId));
15711
- const toInsert = distinct.filter((id) => !existingSet.has(id));
15712
- if (toInsert.length === 0) {
15790
+ const existingSpeakers = await tx.select({ agentId: chatMembership.agentId }).from(chatMembership).where(and(eq(chatMembership.chatId, chatId), eq(chatMembership.accessMode, "speaker")));
15791
+ const existingSpeakerSet = new Set(existingSpeakers.map((e) => e.agentId));
15792
+ const toUpsert = distinct.filter((id) => !existingSpeakerSet.has(id));
15793
+ if (toUpsert.length === 0) {
15713
15794
  await recomputeChatWatchers(tx, chatId);
15714
15795
  return;
15715
15796
  }
15716
- if (existing.length + toInsert.length >= 3 && chat.type === "direct") await changeChatType(tx, chatId, "group");
15717
- const carriedRows = await tx.delete(chatSubscriptions).where(and(eq(chatSubscriptions.chatId, chatId), inArray(chatSubscriptions.agentId, toInsert))).returning({
15718
- agentId: chatSubscriptions.agentId,
15719
- lastReadAt: chatSubscriptions.lastReadAt,
15720
- unreadMentionCount: chatSubscriptions.unreadMentionCount
15721
- });
15722
- const carriedByAgent = new Map(carriedRows.map((r) => [r.agentId, r]));
15723
- await addChatParticipants(tx, chatId, toInsert.map((agentId) => {
15724
- const carried = carriedByAgent.get(agentId);
15725
- return {
15726
- agentId,
15727
- role: "member",
15728
- carriedReadState: carried ? {
15729
- lastReadAt: carried.lastReadAt,
15730
- unreadMentionCount: carried.unreadMentionCount
15731
- } : void 0
15732
- };
15733
- }), { onConflictDoNothing: true });
15797
+ if (existingSpeakers.length + toUpsert.length >= 3 && chat.type === "direct") await changeChatType(tx, chatId, "group");
15798
+ await addChatParticipants(tx, chatId, toUpsert.map((agentId) => ({
15799
+ agentId,
15800
+ role: "member"
15801
+ })), { upgradeWatcherToSpeaker: true });
15734
15802
  await recomputeChatWatchers(tx, chatId);
15735
15803
  });
15736
15804
  invalidateChatAudience(chatId);
15737
15805
  }
15738
15806
  async function markMeChatRead(db, chatId, humanAgentId) {
15739
15807
  const now = /* @__PURE__ */ new Date();
15740
- await db.update(chatParticipants).set({
15741
- lastReadAt: now,
15742
- unreadMentionCount: 0
15743
- }).where(and(eq(chatParticipants.chatId, chatId), eq(chatParticipants.agentId, humanAgentId)));
15744
- await db.update(chatSubscriptions).set({
15808
+ await db.insert(chatUserState).values({
15809
+ chatId,
15810
+ agentId: humanAgentId,
15745
15811
  lastReadAt: now,
15746
15812
  unreadMentionCount: 0
15747
- }).where(and(eq(chatSubscriptions.chatId, chatId), eq(chatSubscriptions.agentId, humanAgentId)));
15813
+ }).onConflictDoUpdate({
15814
+ target: [chatUserState.chatId, chatUserState.agentId],
15815
+ set: {
15816
+ lastReadAt: now,
15817
+ unreadMentionCount: 0
15818
+ }
15819
+ });
15748
15820
  return {
15749
15821
  chatId,
15750
15822
  lastReadAt: now.toISOString(),
@@ -15770,7 +15842,7 @@ async function leaveMeChat(db, chatId, humanAgentId) {
15770
15842
  async function chatRoutes(app) {
15771
15843
  app.get("/:chatId", async (request) => {
15772
15844
  const { chat, scope } = await requireChatAccess(request, app.db);
15773
- const participants = await app.db.select().from(chatParticipants).where(eq(chatParticipants.chatId, chat.id));
15845
+ const participants = await app.db.select().from(chatMembership).where(and(eq(chatMembership.chatId, chat.id), eq(chatMembership.accessMode, "speaker")));
15774
15846
  const firstMsgRows = await app.db.execute(sql`
15775
15847
  SELECT content FROM messages
15776
15848
  WHERE chat_id = ${chat.id}
@@ -15794,10 +15866,12 @@ async function chatRoutes(app) {
15794
15866
  };
15795
15867
  });
15796
15868
  const title = resolveChatTitle(chat.topic, firstMessagePreview, participantsForTitle, scope.humanAgentId);
15869
+ const engagementStatus = await getCallerEngagement(app.db, chat.id, scope.humanAgentId);
15797
15870
  return {
15798
15871
  ...chat,
15799
15872
  title,
15800
15873
  firstMessagePreview,
15874
+ engagementStatus,
15801
15875
  createdAt: chat.createdAt.toISOString(),
15802
15876
  updatedAt: chat.updatedAt.toISOString(),
15803
15877
  participants: participants.map((p) => ({
@@ -15808,6 +15882,15 @@ async function chatRoutes(app) {
15808
15882
  }))
15809
15883
  };
15810
15884
  });
15885
+ app.post("/:chatId/engagement", { config: { otelRecordBody: true } }, async (request, reply) => {
15886
+ const { scope } = await requireChatAccess(request, app.db);
15887
+ const body = patchChatEngagementSchema.parse(request.body);
15888
+ await setChatEngagement(app.db, request.params.chatId, scope.humanAgentId, body.status);
15889
+ return reply.status(200).send({
15890
+ chatId: request.params.chatId,
15891
+ engagementStatus: body.status
15892
+ });
15893
+ });
15811
15894
  app.patch("/:chatId", { config: { otelRecordBody: true } }, async (request) => {
15812
15895
  await requireChatAccess(request, app.db);
15813
15896
  const body = updateChatSchema.parse(request.body);
@@ -15900,7 +15983,7 @@ async function chatRoutes(app) {
15900
15983
  app.post("/:chatId/messages", async (request, reply) => {
15901
15984
  const { scope } = await requireChatAccess(request, app.db);
15902
15985
  const body = sendMessageSchema.parse(request.body);
15903
- await ensureParticipant$1(app.db, request.params.chatId, scope.humanAgentId);
15986
+ await ensureParticipant(app.db, request.params.chatId, scope.humanAgentId);
15904
15987
  const prepared = await prepareImageOutbound(app.db, app.notifier, request.params.chatId, {
15905
15988
  ...body,
15906
15989
  source: "hub_ui"
@@ -15927,7 +16010,7 @@ async function chatRoutes(app) {
15927
16010
  app.post("/:chatId/questions/:correlationId/answer", { config: { otelRecordBody: false } }, async (request, reply) => {
15928
16011
  const { scope } = await requireChatAccess(request, app.db);
15929
16012
  const body = submitQuestionAnswerSchema.parse(request.body);
15930
- await ensureParticipant$1(app.db, request.params.chatId, scope.humanAgentId);
16013
+ await ensureParticipant(app.db, request.params.chatId, scope.humanAgentId);
15931
16014
  const result = await submitAnswer(app.db, app.notifier, {
15932
16015
  correlationId: request.params.correlationId,
15933
16016
  chatId: request.params.chatId,
@@ -17324,7 +17407,7 @@ async function healthzRoutes(app) {
17324
17407
  * `api/orgs/invitations.ts` (Class B, admin-gated).
17325
17408
  */
17326
17409
  async function publicInvitationRoutes(app) {
17327
- const { previewInvitation } = await import("./invitation-C299fxkP-jQiGR5fl.mjs");
17410
+ const { previewInvitation } = await import("./invitation-C299fxkP-KKslbta2.mjs");
17328
17411
  app.get("/:token/preview", async (request, reply) => {
17329
17412
  if (!request.params.token) throw new UnauthorizedError("Token required");
17330
17413
  const preview = await previewInvitation(app.db, request.params.token);
@@ -17590,7 +17673,7 @@ async function meRoutes(app) {
17590
17673
  */
17591
17674
  app.get("/me/pinned-agents", async (request) => {
17592
17675
  const { userId } = requireUser(request);
17593
- const { listMyPinnedAgents } = await import("./client-93HZWg84-MIPzQD9A.mjs");
17676
+ const { listMyPinnedAgents } = await import("./client-CjGIGddS-BrpazWa3.mjs");
17594
17677
  return listMyPinnedAgents(app.db, { userId });
17595
17678
  });
17596
17679
  /**
@@ -18000,7 +18083,7 @@ async function orgChatRoutes(app) {
18000
18083
  createdAt: chats.createdAt,
18001
18084
  updatedAt: chats.updatedAt,
18002
18085
  participantCount: sql`(
18003
- SELECT count(*)::int FROM chat_participants WHERE chat_id = ${chats.id}
18086
+ SELECT count(*)::int FROM chat_membership WHERE chat_id = ${chats.id} AND access_mode = 'speaker'
18004
18087
  )`
18005
18088
  }).from(chats).where(and(...conditions)).orderBy(desc(chats.createdAt)).limit(query.limit + 1);
18006
18089
  const hasMore = rows.length > query.limit;
@@ -19526,8 +19609,8 @@ var schema_exports = /* @__PURE__ */ __exportAll({
19526
19609
  agentPresence: () => agentPresence,
19527
19610
  agents: () => agents,
19528
19611
  authIdentities: () => authIdentities,
19529
- chatParticipants: () => chatParticipants,
19530
- chatSubscriptions: () => chatSubscriptions,
19612
+ chatMembership: () => chatMembership,
19613
+ chatUserState: () => chatUserState,
19531
19614
  chats: () => chats,
19532
19615
  clients: () => clients,
19533
19616
  githubAppInstallations: () => githubAppInstallations,
@@ -1,4 +1,4 @@
1
- import{c as r,j as e,U as o,L as s,A as i,F as a,r as l}from"./index-BNM-YSSu.js";/**
1
+ import{c as r,j as e,U as o,L as s,A as i,F as a,r as l}from"./index-ntmzuk5X.js";/**
2
2
  * @license lucide-react v0.577.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.