@agent-team-foundation/first-tree-hub 0.12.7 → 0.12.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.mjs +39 -4
- package/dist/{client-93HZWg84-MIPzQD9A.mjs → client-DNEtPEBu-BtHkUya2.mjs} +2 -2
- package/dist/{client-h5l7mi0m-OEX7MOBg.mjs → client-bR8nwHaV-OxnjyKOk.mjs} +314 -276
- package/dist/{dist-CTkhS6p5.mjs → dist-CnjqakXS.mjs} +39 -10
- package/dist/drizzle/0038_chat_membership_user_state.sql +223 -0
- package/dist/drizzle/0039_drop_chat_participants_subscriptions.sql +26 -0
- package/dist/drizzle/0040_chat_user_state_engagement.sql +24 -0
- package/dist/drizzle/meta/_journal.json +21 -0
- package/dist/{feishu-DJm0EaZP.mjs → feishu-DrnBbl8T.mjs} +1 -1
- package/dist/index.mjs +4 -4
- package/dist/{invitation-C299fxkP-jQiGR5fl.mjs → invitation-C299fxkP-KKslbta2.mjs} +1 -1
- package/dist/{saas-connect-CY2NxeKx.mjs → saas-connect-CLcon-De.mjs} +192 -116
- package/dist/web/assets/{index-JGwkYWtM.js → index-BPMrSv_A.js} +1 -1
- package/dist/web/assets/index-DxAYxUpz.css +1 -0
- package/dist/web/assets/index-ntmzuk5X.js +421 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-BKbK8BhK.css +0 -1
- package/dist/web/assets/index-BNM-YSSu.js +0 -421
|
@@ -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
|
|
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
|
|
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-bR8nwHaV-OxnjyKOk.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),
|
|
@@ -9422,7 +9436,7 @@ async function onboardCreate(args) {
|
|
|
9422
9436
|
}
|
|
9423
9437
|
const runtimeAgent = args.type === "human" ? args.assistant : args.id;
|
|
9424
9438
|
if (args.feishuBotAppId && args.feishuBotAppSecret) {
|
|
9425
|
-
const { bindFeishuBot } = await import("./feishu-
|
|
9439
|
+
const { bindFeishuBot } = await import("./feishu-DrnBbl8T.mjs").then((n) => n.r);
|
|
9426
9440
|
const targetAgentUuid = args.type === "human" ? assistantUuid : primary.uuid;
|
|
9427
9441
|
if (!targetAgentUuid) print.line(`Warning: Cannot bind Feishu bot — no runtime agent available for "${args.id}".\n`);
|
|
9428
9442
|
else {
|
|
@@ -10635,7 +10649,7 @@ function createFeedbackHandler(config) {
|
|
|
10635
10649
|
return { handle };
|
|
10636
10650
|
}
|
|
10637
10651
|
//#endregion
|
|
10638
|
-
//#region ../server/dist/app-
|
|
10652
|
+
//#region ../server/dist/app-mkBHfGPl.mjs
|
|
10639
10653
|
var import_fastify_opentelemetry = /* @__PURE__ */ __toESM(require_fastify_opentelemetry(), 1);
|
|
10640
10654
|
init_esm();
|
|
10641
10655
|
var __defProp = Object.defineProperty;
|
|
@@ -10742,7 +10756,7 @@ async function requireChatAccess(request, db) {
|
|
|
10742
10756
|
role: caller.role,
|
|
10743
10757
|
humanAgentId: caller.humanAgentId
|
|
10744
10758
|
};
|
|
10745
|
-
const [direct] = await db.select({ chatId:
|
|
10759
|
+
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
10760
|
if (direct) {
|
|
10747
10761
|
stampOrgScope(request, scope);
|
|
10748
10762
|
stampChatResource(request, chat);
|
|
@@ -10751,7 +10765,7 @@ async function requireChatAccess(request, db) {
|
|
|
10751
10765
|
scope
|
|
10752
10766
|
};
|
|
10753
10767
|
}
|
|
10754
|
-
const participantRows = await db.select({ agentId:
|
|
10768
|
+
const participantRows = await db.select({ agentId: chatMembership.agentId }).from(chatMembership).where(and(eq(chatMembership.chatId, chatId), eq(chatMembership.accessMode, "speaker")));
|
|
10755
10769
|
if (participantRows.length === 0) throw new NotFoundError(`Chat "${chatId}" not found`);
|
|
10756
10770
|
const participantIds = participantRows.map((p) => p.agentId);
|
|
10757
10771
|
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 +11857,6 @@ async function findOrCreateChatForChannel(db, data) {
|
|
|
11843
11857
|
return chatId;
|
|
11844
11858
|
});
|
|
11845
11859
|
}
|
|
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
11860
|
/** Store a cross-reference between internal and external message. */
|
|
11860
11861
|
async function createMessageReference(db, data) {
|
|
11861
11862
|
await db.insert(adapterMessageReferences).values({
|
|
@@ -11938,9 +11939,9 @@ async function buildClientMessagePayloadsForInbox(db, inboxId, items) {
|
|
|
11938
11939
|
const modeByChat = /* @__PURE__ */ new Map();
|
|
11939
11940
|
if (chatIds.length > 0) {
|
|
11940
11941
|
const rows = await db.select({
|
|
11941
|
-
chatId:
|
|
11942
|
-
mode:
|
|
11943
|
-
}).from(
|
|
11942
|
+
chatId: chatMembership.chatId,
|
|
11943
|
+
mode: chatMembership.mode
|
|
11944
|
+
}).from(chatMembership).where(and(eq(chatMembership.agentId, agentId), inArray(chatMembership.chatId, chatIds), eq(chatMembership.accessMode, "speaker")));
|
|
11944
11945
|
for (const r of rows) modeByChat.set(r.chatId, normaliseMode(r.mode));
|
|
11945
11946
|
}
|
|
11946
11947
|
const inReplyToIds = [...new Set(items.map((it) => it.message.inReplyTo).filter((id) => id !== null))];
|
|
@@ -12341,7 +12342,7 @@ async function prepareImageOutbound(db, notifier, chatId, data) {
|
|
|
12341
12342
|
* chat reply (see `services/message.ts` replyTo routing).
|
|
12342
12343
|
*/
|
|
12343
12344
|
async function collectTargetInboxes(db, chatId, inReplyTo) {
|
|
12344
|
-
const participants = await db.select({ inboxId: agents.inboxId }).from(
|
|
12345
|
+
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
12346
|
const set = new Set(participants.map((p) => p.inboxId));
|
|
12346
12347
|
if (inReplyTo) {
|
|
12347
12348
|
const [original] = await db.select({ replyToInbox: messages.replyToInbox }).from(messages).where(eq(messages.id, inReplyTo)).limit(1);
|
|
@@ -15267,6 +15268,47 @@ async function bootstrapConfigRoutes(_app) {
|
|
|
15267
15268
|
return { allowedOrg: null };
|
|
15268
15269
|
});
|
|
15269
15270
|
}
|
|
15271
|
+
/**
|
|
15272
|
+
* Per-(chat, agent) user state — independent from membership structure.
|
|
15273
|
+
*
|
|
15274
|
+
* This is the third layer of the chat data model: while `chats` owns
|
|
15275
|
+
* the entity and `chat_membership` owns the structural relation
|
|
15276
|
+
* (who can speak, who watches), this table owns the user's private
|
|
15277
|
+
* state about a chat. The reason it lives apart: structural changes
|
|
15278
|
+
* (speaker ↔ watcher, manager rebind, recompute) must never overwrite
|
|
15279
|
+
* user-private state — physical separation makes that an invariant
|
|
15280
|
+
* rather than a service-layer discipline.
|
|
15281
|
+
*
|
|
15282
|
+
* Columns evolve incrementally as new per-user state is needed.
|
|
15283
|
+
* Currently:
|
|
15284
|
+
* - `last_read_at`, `unread_mention_count` — seeded by PR-A from
|
|
15285
|
+
* the legacy `chat_participants` / `chat_subscriptions` columns.
|
|
15286
|
+
* - `engagement_status` — added in 0040; per-(chat, user) view
|
|
15287
|
+
* state (active / archived / deleted). Auto-revives archived →
|
|
15288
|
+
* active on new message; deleted is sticky (only the user can
|
|
15289
|
+
* restore from the chat detail page).
|
|
15290
|
+
*
|
|
15291
|
+
* Future fields slated for this table: pinned, mute_until, draft,
|
|
15292
|
+
* custom_title, last_seen_at — each as a separate change.
|
|
15293
|
+
*
|
|
15294
|
+
* Rows are lazy-upserted on first user write (markRead / mention
|
|
15295
|
+
* counter bump / engagement transition). Reads use COALESCE for
|
|
15296
|
+
* defaults so callers see `'active'` etc. even when no row exists.
|
|
15297
|
+
* Service-layer integrity (no FK / CHECK / trigger).
|
|
15298
|
+
*
|
|
15299
|
+
* See proposals/chat-data-model-restructure.20260512.md §8.6.
|
|
15300
|
+
*/
|
|
15301
|
+
const chatUserState = pgTable("chat_user_state", {
|
|
15302
|
+
chatId: text("chat_id").notNull(),
|
|
15303
|
+
agentId: text("agent_id").notNull(),
|
|
15304
|
+
lastReadAt: timestamp("last_read_at", { withTimezone: true }),
|
|
15305
|
+
unreadMentionCount: integer("unread_mention_count").notNull().default(0),
|
|
15306
|
+
engagementStatus: text("engagement_status").notNull().default("active")
|
|
15307
|
+
}, (table) => [
|
|
15308
|
+
primaryKey({ columns: [table.chatId, table.agentId] }),
|
|
15309
|
+
index("idx_user_state_agent").on(table.agentId),
|
|
15310
|
+
index("idx_user_state_unread").on(table.agentId).where(sql`unread_mention_count > 0`)
|
|
15311
|
+
]);
|
|
15270
15312
|
/** Extract a plain-text summary from a message's JSONB content field.
|
|
15271
15313
|
* Used as the auto-title fallback in chat list rendering — see
|
|
15272
15314
|
* `me-chat.ts:resolveChatTitle` and `admin/chats.ts:getChat`.
|
|
@@ -15462,7 +15504,7 @@ async function transitionSessionState(db, agentId, chatId, target, from, organiz
|
|
|
15462
15504
|
async function filterSessionsByParticipant(db, sessions, participantAgentId) {
|
|
15463
15505
|
if (sessions.length === 0) return [];
|
|
15464
15506
|
const chatIds = sessions.map((s) => s.chatId);
|
|
15465
|
-
const participantRows = await db.select({ chatId:
|
|
15507
|
+
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
15508
|
const allowedChatIds = new Set(participantRows.map((r) => r.chatId));
|
|
15467
15509
|
return sessions.filter((s) => allowedChatIds.has(s.chatId));
|
|
15468
15510
|
}
|
|
@@ -15471,16 +15513,17 @@ async function filterSessionsByParticipant(db, sessions, participantAgentId) {
|
|
|
15471
15513
|
* workspace).
|
|
15472
15514
|
*
|
|
15473
15515
|
* Responsibilities:
|
|
15474
|
-
* - Cursor-paginated conversation list
|
|
15475
|
-
*
|
|
15516
|
+
* - Cursor-paginated conversation list (single-stream JOIN over the
|
|
15517
|
+
* unified `chat_membership` + `chat_user_state` tables).
|
|
15476
15518
|
* - Create a new chat (no dedupe, runs `recomputeChatWatchers` after).
|
|
15477
|
-
* - Add participants (idempotent,
|
|
15478
|
-
*
|
|
15479
|
-
* -
|
|
15480
|
-
* -
|
|
15519
|
+
* - Add participants (idempotent, UPSERT into `chat_membership`,
|
|
15520
|
+
* runs `recomputeChatWatchers` after).
|
|
15521
|
+
* - Mark-read (UPSERT into `chat_user_state`).
|
|
15522
|
+
* - Join → watcher to speaker (delegates to `watcher.ts`).
|
|
15523
|
+
* - Leave → speaker to watcher or detach (delegates to `watcher.ts`).
|
|
15481
15524
|
*
|
|
15482
|
-
* See
|
|
15483
|
-
*
|
|
15525
|
+
* See proposals/chat-data-model-restructure.20260512.md §8 (schema)
|
|
15526
|
+
* and §11.1 (per-route mapping).
|
|
15484
15527
|
*/
|
|
15485
15528
|
function encodeCursor(lastMessageAt, chatId) {
|
|
15486
15529
|
const payload = `${lastMessageAt ? lastMessageAt.toISOString() : ""}|${chatId}`;
|
|
@@ -15504,17 +15547,61 @@ function decodeCursor(cursor) {
|
|
|
15504
15547
|
return null;
|
|
15505
15548
|
}
|
|
15506
15549
|
}
|
|
15550
|
+
const { ACTIVE, ARCHIVED, DELETED } = CHAT_ENGAGEMENT_STATUSES;
|
|
15551
|
+
/**
|
|
15552
|
+
* SQL predicate for each engagement view tab. `deleted` is never a valid view
|
|
15553
|
+
* value — deleted rows are reachable only through `GET /chats/:chatId` + the
|
|
15554
|
+
* Restore banner on the chat detail page.
|
|
15555
|
+
*/
|
|
15556
|
+
const ENGAGEMENT_VIEW_PREDICATE = {
|
|
15557
|
+
active: sql`COALESCE(cus.engagement_status, ${ACTIVE}) = ${ACTIVE}`,
|
|
15558
|
+
archived: sql`COALESCE(cus.engagement_status, ${ACTIVE}) = ${ARCHIVED}`,
|
|
15559
|
+
all: sql`COALESCE(cus.engagement_status, ${ACTIVE}) IN (${ACTIVE}, ${ARCHIVED})`
|
|
15560
|
+
};
|
|
15561
|
+
/**
|
|
15562
|
+
* Write the caller's engagement state for this chat. UPSERT into
|
|
15563
|
+
* `chat_user_state` — the row may not yet exist (the user might not have
|
|
15564
|
+
* marked-read or been @-mentioned), so an INSERT with the engagement value
|
|
15565
|
+
* is the first write; subsequent transitions are UPDATEs.
|
|
15566
|
+
*
|
|
15567
|
+
* Idempotent. Mirrors the UPSERT shape used by `markMeChatRead`.
|
|
15568
|
+
*/
|
|
15569
|
+
async function setChatEngagement(db, chatId, agentId, status) {
|
|
15570
|
+
await db.insert(chatUserState).values({
|
|
15571
|
+
chatId,
|
|
15572
|
+
agentId,
|
|
15573
|
+
unreadMentionCount: 0,
|
|
15574
|
+
engagementStatus: status
|
|
15575
|
+
}).onConflictDoUpdate({
|
|
15576
|
+
target: [chatUserState.chatId, chatUserState.agentId],
|
|
15577
|
+
set: { engagementStatus: status }
|
|
15578
|
+
});
|
|
15579
|
+
}
|
|
15580
|
+
/**
|
|
15581
|
+
* Read the caller's engagement state. Returns `'active'` when no
|
|
15582
|
+
* `chat_user_state` row exists yet (lazy-materialised; matches the SQL
|
|
15583
|
+
* `COALESCE(..., 'active')` used elsewhere).
|
|
15584
|
+
*/
|
|
15585
|
+
async function getCallerEngagement(db, chatId, agentId) {
|
|
15586
|
+
const [row] = await db.select({ engagementStatus: chatUserState.engagementStatus }).from(chatUserState).where(and(eq(chatUserState.chatId, chatId), eq(chatUserState.agentId, agentId))).limit(1);
|
|
15587
|
+
return row?.engagementStatus ?? ACTIVE;
|
|
15588
|
+
}
|
|
15507
15589
|
/**
|
|
15508
15590
|
* GET /me/chats — cursor-paginated conversation list.
|
|
15509
15591
|
*
|
|
15510
15592
|
* SQL strategy:
|
|
15511
|
-
* -
|
|
15512
|
-
*
|
|
15513
|
-
*
|
|
15514
|
-
*
|
|
15593
|
+
* - Single-stream query: `chats JOIN chat_membership LEFT JOIN
|
|
15594
|
+
* chat_user_state`. The membership row carries access_mode
|
|
15595
|
+
* (speaker → "participant" / watcher → "watching"); the user
|
|
15596
|
+
* state row supplies the unread counter (COALESCE → 0 when
|
|
15597
|
+
* row is missing).
|
|
15598
|
+
* - Filter `parent_chat_id IS NULL` (threads excluded in v1).
|
|
15599
|
+
* - Filter `c.organization_id = ?` to defend against historical
|
|
15600
|
+
* cross-org pollution rows that may still reference the caller
|
|
15601
|
+
* (see fix/cross-org-direct-chat-pollution).
|
|
15515
15602
|
* - Sort `(last_message_at DESC NULLS LAST, chat_id DESC)`.
|
|
15516
|
-
* - Cursor narrows the result to rows STRICTLY before
|
|
15517
|
-
* - Followed by a
|
|
15603
|
+
* - Cursor narrows the result to rows STRICTLY before the cursor.
|
|
15604
|
+
* - Followed by a participants-list lookup for the page only.
|
|
15518
15605
|
*/
|
|
15519
15606
|
async function listMeChats(db, humanAgentId, organizationId, query) {
|
|
15520
15607
|
const limit = query.limit;
|
|
@@ -15522,28 +15609,12 @@ async function listMeChats(db, humanAgentId, organizationId, query) {
|
|
|
15522
15609
|
if (query.cursor && !cursor) throw new BadRequestError("Invalid cursor");
|
|
15523
15610
|
const filterUnreadOnly = query.filter === "unread";
|
|
15524
15611
|
const filterWatchingOnly = query.filter === "watching";
|
|
15612
|
+
const engagementPredicate = ENGAGEMENT_VIEW_PREDICATE[query.engagement];
|
|
15525
15613
|
const cursorTsIso = cursor?.lastMessageAt ? cursor.lastMessageAt.toISOString() : null;
|
|
15526
15614
|
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
15615
|
OR c.last_message_at < ${cursorTsIso}::timestamptz
|
|
15528
15616
|
OR (c.last_message_at = ${cursorTsIso}::timestamptz AND c.id < ${cursor.chatId}))`;
|
|
15529
15617
|
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
15618
|
SELECT
|
|
15548
15619
|
c.id AS chat_id,
|
|
15549
15620
|
c.type AS type,
|
|
@@ -15551,20 +15622,26 @@ async function listMeChats(db, humanAgentId, organizationId, query) {
|
|
|
15551
15622
|
c.parent_chat_id AS parent_chat_id,
|
|
15552
15623
|
c.last_message_at AS last_message_at,
|
|
15553
15624
|
c.last_message_preview AS last_message_preview,
|
|
15554
|
-
(SELECT count(*) FROM
|
|
15555
|
-
|
|
15556
|
-
|
|
15625
|
+
(SELECT count(*) FROM chat_membership
|
|
15626
|
+
WHERE chat_id = c.id AND access_mode = 'speaker') AS participant_count,
|
|
15627
|
+
cm.access_mode AS access_mode,
|
|
15628
|
+
COALESCE(cus.unread_mention_count, 0) AS unread_mention_count,
|
|
15629
|
+
COALESCE(cus.engagement_status, ${ACTIVE}) AS engagement_status
|
|
15557
15630
|
FROM chats c
|
|
15558
|
-
JOIN
|
|
15631
|
+
JOIN chat_membership cm
|
|
15632
|
+
ON cm.chat_id = c.id AND cm.agent_id = ${humanAgentId}
|
|
15633
|
+
LEFT JOIN chat_user_state cus
|
|
15634
|
+
ON cus.chat_id = c.id AND cus.agent_id = ${humanAgentId}
|
|
15559
15635
|
WHERE c.parent_chat_id IS NULL
|
|
15560
|
-
/* Scope to the caller's org. Without this, cross-org dirty
|
|
15561
|
-
whose
|
|
15562
|
-
(historical pollution — see
|
|
15563
|
-
would leak into the
|
|
15636
|
+
/* Scope to the caller's org. Without this, cross-org dirty
|
|
15637
|
+
chats whose chat_membership still references the caller's
|
|
15638
|
+
human agent (historical pollution — see
|
|
15639
|
+
fix/cross-org-direct-chat-pollution) would leak into the
|
|
15640
|
+
list and 404 on click via requireChatAccess. */
|
|
15564
15641
|
AND c.organization_id = ${organizationId}
|
|
15565
|
-
|
|
15566
|
-
AND (${!
|
|
15567
|
-
AND
|
|
15642
|
+
AND (${!filterUnreadOnly}::bool OR COALESCE(cus.unread_mention_count, 0) > 0)
|
|
15643
|
+
AND (${!filterWatchingOnly}::bool OR cm.access_mode = 'watcher')
|
|
15644
|
+
AND ${engagementPredicate}
|
|
15568
15645
|
AND ${cursorPredicate}
|
|
15569
15646
|
ORDER BY c.last_message_at DESC NULLS LAST, c.id DESC
|
|
15570
15647
|
LIMIT ${limit + 1}
|
|
@@ -15583,11 +15660,11 @@ async function listMeChats(db, humanAgentId, organizationId, query) {
|
|
|
15583
15660
|
};
|
|
15584
15661
|
const chatIds = pageRaw.map((r) => r.chat_id);
|
|
15585
15662
|
const participantRows = await db.select({
|
|
15586
|
-
chatId:
|
|
15587
|
-
agentId:
|
|
15663
|
+
chatId: chatMembership.chatId,
|
|
15664
|
+
agentId: chatMembership.agentId,
|
|
15588
15665
|
displayName: agents.displayName,
|
|
15589
15666
|
type: agents.type
|
|
15590
|
-
}).from(
|
|
15667
|
+
}).from(chatMembership).innerJoin(agents, eq(chatMembership.agentId, agents.uuid)).where(and(inArray(chatMembership.chatId, chatIds), eq(chatMembership.accessMode, "speaker")));
|
|
15591
15668
|
const participantsByChat = /* @__PURE__ */ new Map();
|
|
15592
15669
|
for (const p of participantRows) {
|
|
15593
15670
|
const list = participantsByChat.get(p.chatId) ?? [];
|
|
@@ -15611,10 +15688,11 @@ async function listMeChats(db, humanAgentId, organizationId, query) {
|
|
|
15611
15688
|
rows: pageRaw.map((r) => {
|
|
15612
15689
|
const participants = participantsByChat.get(r.chat_id) ?? [];
|
|
15613
15690
|
const title = resolveChatTitle(r.topic, firstMessageSummary.get(r.chat_id) ?? null, participants, humanAgentId);
|
|
15691
|
+
const isSpeaker = r.access_mode === "speaker";
|
|
15614
15692
|
return {
|
|
15615
15693
|
chatId: r.chat_id,
|
|
15616
15694
|
type: r.type,
|
|
15617
|
-
membershipKind:
|
|
15695
|
+
membershipKind: isSpeaker ? "participant" : "watching",
|
|
15618
15696
|
title,
|
|
15619
15697
|
topic: r.topic,
|
|
15620
15698
|
participants,
|
|
@@ -15622,7 +15700,8 @@ async function listMeChats(db, humanAgentId, organizationId, query) {
|
|
|
15622
15700
|
lastMessageAt: toDate(r.last_message_at)?.toISOString() ?? null,
|
|
15623
15701
|
lastMessagePreview: r.last_message_preview,
|
|
15624
15702
|
unreadMentionCount: r.unread_mention_count,
|
|
15625
|
-
canReply:
|
|
15703
|
+
canReply: isSpeaker,
|
|
15704
|
+
engagementStatus: r.engagement_status
|
|
15626
15705
|
};
|
|
15627
15706
|
}),
|
|
15628
15707
|
nextCursor
|
|
@@ -15634,11 +15713,6 @@ async function listMeChats(db, humanAgentId, organizationId, query) {
|
|
|
15634
15713
|
* 1. `chat.topic` (manual, set via `PATCH /chats/:chatId`)
|
|
15635
15714
|
* 2. First message summary (auto, ≤ 50 chars from `extractSummary`)
|
|
15636
15715
|
* 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
15716
|
*/
|
|
15643
15717
|
function resolveChatTitle(topic, firstMessageSummary, participants, selfAgentId) {
|
|
15644
15718
|
if (topic && topic.length > 0) return topic;
|
|
@@ -15692,7 +15766,7 @@ async function addMeChatParticipants(db, chatId, callerHumanAgentId, callerOrgan
|
|
|
15692
15766
|
}).from(chats).where(eq(chats.id, chatId)).limit(1);
|
|
15693
15767
|
if (!chat) throw new NotFoundError(`Chat "${chatId}" not found`);
|
|
15694
15768
|
if (chat.organizationId !== callerOrganizationId) throw new NotFoundError(`Chat "${chatId}" not found`);
|
|
15695
|
-
const [callerRow] = await db.select({ chatId:
|
|
15769
|
+
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
15770
|
if (!callerRow) throw new NotFoundError(`Chat "${chatId}" not found`);
|
|
15697
15771
|
const found = await db.select({
|
|
15698
15772
|
uuid: agents.uuid,
|
|
@@ -15706,45 +15780,36 @@ async function addMeChatParticipants(db, chatId, callerHumanAgentId, callerOrgan
|
|
|
15706
15780
|
const crossOrg = found.filter((a) => a.organizationId !== chat.organizationId);
|
|
15707
15781
|
if (crossOrg.length > 0) throw new BadRequestError(`Cross-organization participant rejected: ${crossOrg.map((a) => a.uuid).join(", ")}`);
|
|
15708
15782
|
await db.transaction(async (tx) => {
|
|
15709
|
-
const
|
|
15710
|
-
const
|
|
15711
|
-
const
|
|
15712
|
-
if (
|
|
15783
|
+
const existingSpeakers = await tx.select({ agentId: chatMembership.agentId }).from(chatMembership).where(and(eq(chatMembership.chatId, chatId), eq(chatMembership.accessMode, "speaker")));
|
|
15784
|
+
const existingSpeakerSet = new Set(existingSpeakers.map((e) => e.agentId));
|
|
15785
|
+
const toUpsert = distinct.filter((id) => !existingSpeakerSet.has(id));
|
|
15786
|
+
if (toUpsert.length === 0) {
|
|
15713
15787
|
await recomputeChatWatchers(tx, chatId);
|
|
15714
15788
|
return;
|
|
15715
15789
|
}
|
|
15716
|
-
if (
|
|
15717
|
-
|
|
15718
|
-
agentId
|
|
15719
|
-
|
|
15720
|
-
|
|
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 });
|
|
15790
|
+
if (existingSpeakers.length + toUpsert.length >= 3 && chat.type === "direct") await changeChatType(tx, chatId, "group");
|
|
15791
|
+
await addChatParticipants(tx, chatId, toUpsert.map((agentId) => ({
|
|
15792
|
+
agentId,
|
|
15793
|
+
role: "member"
|
|
15794
|
+
})), { upgradeWatcherToSpeaker: true });
|
|
15734
15795
|
await recomputeChatWatchers(tx, chatId);
|
|
15735
15796
|
});
|
|
15736
15797
|
invalidateChatAudience(chatId);
|
|
15737
15798
|
}
|
|
15738
15799
|
async function markMeChatRead(db, chatId, humanAgentId) {
|
|
15739
15800
|
const now = /* @__PURE__ */ new Date();
|
|
15740
|
-
await db.
|
|
15741
|
-
|
|
15742
|
-
|
|
15743
|
-
}).where(and(eq(chatParticipants.chatId, chatId), eq(chatParticipants.agentId, humanAgentId)));
|
|
15744
|
-
await db.update(chatSubscriptions).set({
|
|
15801
|
+
await db.insert(chatUserState).values({
|
|
15802
|
+
chatId,
|
|
15803
|
+
agentId: humanAgentId,
|
|
15745
15804
|
lastReadAt: now,
|
|
15746
15805
|
unreadMentionCount: 0
|
|
15747
|
-
}).
|
|
15806
|
+
}).onConflictDoUpdate({
|
|
15807
|
+
target: [chatUserState.chatId, chatUserState.agentId],
|
|
15808
|
+
set: {
|
|
15809
|
+
lastReadAt: now,
|
|
15810
|
+
unreadMentionCount: 0
|
|
15811
|
+
}
|
|
15812
|
+
});
|
|
15748
15813
|
return {
|
|
15749
15814
|
chatId,
|
|
15750
15815
|
lastReadAt: now.toISOString(),
|
|
@@ -15770,7 +15835,7 @@ async function leaveMeChat(db, chatId, humanAgentId) {
|
|
|
15770
15835
|
async function chatRoutes(app) {
|
|
15771
15836
|
app.get("/:chatId", async (request) => {
|
|
15772
15837
|
const { chat, scope } = await requireChatAccess(request, app.db);
|
|
15773
|
-
const participants = await app.db.select().from(
|
|
15838
|
+
const participants = await app.db.select().from(chatMembership).where(and(eq(chatMembership.chatId, chat.id), eq(chatMembership.accessMode, "speaker")));
|
|
15774
15839
|
const firstMsgRows = await app.db.execute(sql`
|
|
15775
15840
|
SELECT content FROM messages
|
|
15776
15841
|
WHERE chat_id = ${chat.id}
|
|
@@ -15794,10 +15859,12 @@ async function chatRoutes(app) {
|
|
|
15794
15859
|
};
|
|
15795
15860
|
});
|
|
15796
15861
|
const title = resolveChatTitle(chat.topic, firstMessagePreview, participantsForTitle, scope.humanAgentId);
|
|
15862
|
+
const engagementStatus = await getCallerEngagement(app.db, chat.id, scope.humanAgentId);
|
|
15797
15863
|
return {
|
|
15798
15864
|
...chat,
|
|
15799
15865
|
title,
|
|
15800
15866
|
firstMessagePreview,
|
|
15867
|
+
engagementStatus,
|
|
15801
15868
|
createdAt: chat.createdAt.toISOString(),
|
|
15802
15869
|
updatedAt: chat.updatedAt.toISOString(),
|
|
15803
15870
|
participants: participants.map((p) => ({
|
|
@@ -15808,6 +15875,15 @@ async function chatRoutes(app) {
|
|
|
15808
15875
|
}))
|
|
15809
15876
|
};
|
|
15810
15877
|
});
|
|
15878
|
+
app.post("/:chatId/engagement", { config: { otelRecordBody: true } }, async (request, reply) => {
|
|
15879
|
+
const { scope } = await requireChatAccess(request, app.db);
|
|
15880
|
+
const body = patchChatEngagementSchema.parse(request.body);
|
|
15881
|
+
await setChatEngagement(app.db, request.params.chatId, scope.humanAgentId, body.status);
|
|
15882
|
+
return reply.status(200).send({
|
|
15883
|
+
chatId: request.params.chatId,
|
|
15884
|
+
engagementStatus: body.status
|
|
15885
|
+
});
|
|
15886
|
+
});
|
|
15811
15887
|
app.patch("/:chatId", { config: { otelRecordBody: true } }, async (request) => {
|
|
15812
15888
|
await requireChatAccess(request, app.db);
|
|
15813
15889
|
const body = updateChatSchema.parse(request.body);
|
|
@@ -15900,7 +15976,7 @@ async function chatRoutes(app) {
|
|
|
15900
15976
|
app.post("/:chatId/messages", async (request, reply) => {
|
|
15901
15977
|
const { scope } = await requireChatAccess(request, app.db);
|
|
15902
15978
|
const body = sendMessageSchema.parse(request.body);
|
|
15903
|
-
await ensureParticipant
|
|
15979
|
+
await ensureParticipant(app.db, request.params.chatId, scope.humanAgentId);
|
|
15904
15980
|
const prepared = await prepareImageOutbound(app.db, app.notifier, request.params.chatId, {
|
|
15905
15981
|
...body,
|
|
15906
15982
|
source: "hub_ui"
|
|
@@ -15927,7 +16003,7 @@ async function chatRoutes(app) {
|
|
|
15927
16003
|
app.post("/:chatId/questions/:correlationId/answer", { config: { otelRecordBody: false } }, async (request, reply) => {
|
|
15928
16004
|
const { scope } = await requireChatAccess(request, app.db);
|
|
15929
16005
|
const body = submitQuestionAnswerSchema.parse(request.body);
|
|
15930
|
-
await ensureParticipant
|
|
16006
|
+
await ensureParticipant(app.db, request.params.chatId, scope.humanAgentId);
|
|
15931
16007
|
const result = await submitAnswer(app.db, app.notifier, {
|
|
15932
16008
|
correlationId: request.params.correlationId,
|
|
15933
16009
|
chatId: request.params.chatId,
|
|
@@ -17324,7 +17400,7 @@ async function healthzRoutes(app) {
|
|
|
17324
17400
|
* `api/orgs/invitations.ts` (Class B, admin-gated).
|
|
17325
17401
|
*/
|
|
17326
17402
|
async function publicInvitationRoutes(app) {
|
|
17327
|
-
const { previewInvitation } = await import("./invitation-C299fxkP-
|
|
17403
|
+
const { previewInvitation } = await import("./invitation-C299fxkP-KKslbta2.mjs");
|
|
17328
17404
|
app.get("/:token/preview", async (request, reply) => {
|
|
17329
17405
|
if (!request.params.token) throw new UnauthorizedError("Token required");
|
|
17330
17406
|
const preview = await previewInvitation(app.db, request.params.token);
|
|
@@ -17590,7 +17666,7 @@ async function meRoutes(app) {
|
|
|
17590
17666
|
*/
|
|
17591
17667
|
app.get("/me/pinned-agents", async (request) => {
|
|
17592
17668
|
const { userId } = requireUser(request);
|
|
17593
|
-
const { listMyPinnedAgents } = await import("./client-
|
|
17669
|
+
const { listMyPinnedAgents } = await import("./client-DNEtPEBu-BtHkUya2.mjs");
|
|
17594
17670
|
return listMyPinnedAgents(app.db, { userId });
|
|
17595
17671
|
});
|
|
17596
17672
|
/**
|
|
@@ -18000,7 +18076,7 @@ async function orgChatRoutes(app) {
|
|
|
18000
18076
|
createdAt: chats.createdAt,
|
|
18001
18077
|
updatedAt: chats.updatedAt,
|
|
18002
18078
|
participantCount: sql`(
|
|
18003
|
-
SELECT count(*)::int FROM
|
|
18079
|
+
SELECT count(*)::int FROM chat_membership WHERE chat_id = ${chats.id} AND access_mode = 'speaker'
|
|
18004
18080
|
)`
|
|
18005
18081
|
}).from(chats).where(and(...conditions)).orderBy(desc(chats.createdAt)).limit(query.limit + 1);
|
|
18006
18082
|
const hasMore = rows.length > query.limit;
|
|
@@ -19526,8 +19602,8 @@ var schema_exports = /* @__PURE__ */ __exportAll({
|
|
|
19526
19602
|
agentPresence: () => agentPresence,
|
|
19527
19603
|
agents: () => agents,
|
|
19528
19604
|
authIdentities: () => authIdentities,
|
|
19529
|
-
|
|
19530
|
-
|
|
19605
|
+
chatMembership: () => chatMembership,
|
|
19606
|
+
chatUserState: () => chatUserState,
|
|
19531
19607
|
chats: () => chats,
|
|
19532
19608
|
clients: () => clients,
|
|
19533
19609
|
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-
|
|
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.
|