@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
|
@@ -45,9 +45,9 @@ function scanMentionTokens(content) {
|
|
|
45
45
|
return tokens;
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
|
-
* Derive the `
|
|
49
|
-
* given the chat's `type` and the joining agent's `type`. This is the
|
|
50
|
-
* authoritative rule for the invariant
|
|
48
|
+
* Derive the `chat_membership.mode` that a freshly inserted speaker row MUST
|
|
49
|
+
* get, given the chat's `type` and the joining agent's `type`. This is the
|
|
50
|
+
* single authoritative rule for the invariant
|
|
51
51
|
*
|
|
52
52
|
* `(chat.type === 'group' && agent.type !== 'human') ⇒ mode === 'mention_only'`
|
|
53
53
|
*
|
|
@@ -68,9 +68,9 @@ function scanMentionTokens(content) {
|
|
|
68
68
|
* agent should listen to every message in this 1:1 line)
|
|
69
69
|
*
|
|
70
70
|
* `peerAgentTypes` is read only in the `direct` branch; callers may pass
|
|
71
|
-
* an empty array (or omit it) for `group` chats — it's ignored. Watcher
|
|
72
|
-
*
|
|
73
|
-
* only governs the "speaking" mode column.
|
|
71
|
+
* an empty array (or omit it) for `group` chats — it's ignored. Watcher
|
|
72
|
+
* rows (`chat_membership.access_mode = 'watcher'`) are unaffected; the
|
|
73
|
+
* helper only governs the "speaking" mode column.
|
|
74
74
|
*/
|
|
75
75
|
function defaultParticipantMode(chatType, agentType, peerAgentTypes = []) {
|
|
76
76
|
if (agentType === "human") return "full";
|
|
@@ -632,6 +632,27 @@ const chatTypeSchema = z.enum([
|
|
|
632
632
|
"group",
|
|
633
633
|
"thread"
|
|
634
634
|
]);
|
|
635
|
+
/**
|
|
636
|
+
* Per-(chat, user) engagement state. Stored on `chat_user_state` so each
|
|
637
|
+
* user manages their own view independently of structural membership.
|
|
638
|
+
*
|
|
639
|
+
* active — default; chat is in the user's active conversation list.
|
|
640
|
+
* archived — user-snoozed; auto-revives to `active` when a new message
|
|
641
|
+
* lands in the chat (see `services/chat-projection.ts`).
|
|
642
|
+
* deleted — user-removed; never auto-revives. Restorable only by the
|
|
643
|
+
* user from the chat detail page.
|
|
644
|
+
*/
|
|
645
|
+
const CHAT_ENGAGEMENT_STATUSES = {
|
|
646
|
+
ACTIVE: "active",
|
|
647
|
+
ARCHIVED: "archived",
|
|
648
|
+
DELETED: "deleted"
|
|
649
|
+
};
|
|
650
|
+
const chatEngagementStatusSchema = z.enum([
|
|
651
|
+
"active",
|
|
652
|
+
"archived",
|
|
653
|
+
"deleted"
|
|
654
|
+
]);
|
|
655
|
+
const patchChatEngagementSchema = z.object({ status: chatEngagementStatusSchema });
|
|
635
656
|
const createChatSchema = z.object({
|
|
636
657
|
type: chatTypeSchema,
|
|
637
658
|
topic: z.string().max(500).optional(),
|
|
@@ -661,7 +682,8 @@ z.object({
|
|
|
661
682
|
}).extend({
|
|
662
683
|
participants: z.array(chatParticipantSchema),
|
|
663
684
|
title: z.string(),
|
|
664
|
-
firstMessagePreview: z.string().nullable()
|
|
685
|
+
firstMessagePreview: z.string().nullable(),
|
|
686
|
+
engagementStatus: chatEngagementStatusSchema
|
|
665
687
|
});
|
|
666
688
|
const updateChatSchema = z.object({ topic: z.string().trim().max(500).nullable() });
|
|
667
689
|
/**
|
|
@@ -1129,10 +1151,16 @@ const meChatFilterSchema = z.enum([
|
|
|
1129
1151
|
"watching"
|
|
1130
1152
|
]);
|
|
1131
1153
|
const meChatMembershipKindSchema = z.enum(["participant", "watching"]);
|
|
1154
|
+
const chatEngagementViewSchema = z.enum([
|
|
1155
|
+
"active",
|
|
1156
|
+
"archived",
|
|
1157
|
+
"all"
|
|
1158
|
+
]);
|
|
1132
1159
|
const listMeChatsQuerySchema = z.object({
|
|
1133
1160
|
cursor: z.string().optional(),
|
|
1134
1161
|
limit: z.coerce.number().int().min(1).max(200).default(50),
|
|
1135
|
-
filter: meChatFilterSchema.default("all")
|
|
1162
|
+
filter: meChatFilterSchema.default("all"),
|
|
1163
|
+
engagement: chatEngagementViewSchema.default("active")
|
|
1136
1164
|
});
|
|
1137
1165
|
const meChatParticipantSchema = z.object({
|
|
1138
1166
|
agentId: z.string(),
|
|
@@ -1150,7 +1178,8 @@ const meChatRowSchema = z.object({
|
|
|
1150
1178
|
lastMessageAt: z.string().nullable(),
|
|
1151
1179
|
lastMessagePreview: z.string().nullable(),
|
|
1152
1180
|
unreadMentionCount: z.number().int(),
|
|
1153
|
-
canReply: z.boolean()
|
|
1181
|
+
canReply: z.boolean(),
|
|
1182
|
+
engagementStatus: chatEngagementStatusSchema
|
|
1154
1183
|
});
|
|
1155
1184
|
z.object({
|
|
1156
1185
|
rows: z.array(meChatRowSchema),
|
|
@@ -1725,4 +1754,4 @@ z.object({
|
|
|
1725
1754
|
capabilities: serverCapabilitiesSchema.optional()
|
|
1726
1755
|
}).passthrough();
|
|
1727
1756
|
//#endregion
|
|
1728
|
-
export {
|
|
1757
|
+
export { patchOnboardingSchema as $, defaultRuntimeConfigPayload as A, inboxDeliverFrameSchema as B, createAdapterMappingSchema as C, updateOrganizationSchema as Ct, createMemberSchema as D, createMeChatSchema as E, githubCallbackQuerySchema as F, joinByInvitationSchema as G, isOrgSettingNamespace as H, githubDevCallbackQuerySchema as I, messageSourceSchema as J, listMeChatsQuerySchema as K, githubStartQuerySchema as L, dryRunAgentRuntimeConfigSchema as M, extractMentions as N, createOrgFromMeSchema as O, githubAppInstallationClaimBodySchema as P, patchChatEngagementSchema as Q, imageInlineContentSchema as R, createAdapterConfigSchema as S, updateMemberSchema as St, createChatSchema as T, isRedactedEnvValue as U, inboxPollQuerySchema as V, isReservedAgentName as W, onboardingEventSchema as X, notificationQuerySchema as Y, paginationQuerySchema as Z, chatMetadataSchema as _, updateAdapterConfigSchema as _t, AGENT_VISIBILITY as a, safeRedirectPath as at, connectTokenExchangeSchema as b, updateChatSchema as bt, MENTION_REGEX as c, sendMessageSchema as ct, addMeChatParticipantsSchema as d, sessionEventMessageSchema as dt, questionAnswerMessageContentSchema as et, addParticipantSchema as f, sessionEventSchema as ft, agentTypeSchema as g, submitQuestionAnswerSchema as gt, agentRuntimeConfigPayloadSchema as h, stripCode as ht, AGENT_STATUSES as i, runtimeStateMessageSchema as it, delegateFeishuUserSchema as j, defaultParticipantMode as k, ORG_SETTINGS_NAMESPACES as l, sendToAgentSchema as lt, agentPinnedMessageSchema as m, sessionStateMessageSchema as mt, AGENT_NAME_REGEX as n, rebindAgentSchema as nt, CHAT_ENGAGEMENT_STATUSES as o, scanMentionTokens as ot, agentBindRequestSchema as p, sessionReconcileRequestSchema as pt, loginSchema as q, AGENT_SELECTOR_HEADER as r, refreshTokenSchema as rt, DEFAULT_RUNTIME_PROVIDER as s, selfServiceFeishuBotSchema as st, AGENT_BIND_REJECT_REASONS as t, questionMessageContentSchema as tt, WS_AUTH_FRAME_TIMEOUT_MS as u, sessionCompletionMessageSchema as ut, clientCapabilitiesSchema as v, updateAgentRuntimeConfigSchema as vt, createAgentSchema as w, wsAuthFrameSchema as wt, contextTreeSnapshotSchema as x, updateClientCapabilitiesSchema as xt, clientRegisterSchema as y, updateAgentSchema as yt, inboxAckFrameSchema as z };
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
-- Chat data model restructure — Step 2 (migration N).
|
|
2
|
+
-- See proposals/chat-data-model-restructure.20260512.md §8 (schema)
|
|
3
|
+
-- and §9 (migration path).
|
|
4
|
+
--
|
|
5
|
+
-- Replaces the chat_participants / chat_subscriptions split with a
|
|
6
|
+
-- three-layer model: chats (entity) + chat_membership (structure) +
|
|
7
|
+
-- chat_user_state (per-user private state). This migration creates
|
|
8
|
+
-- the two new tables and back-fills them from the legacy two; the
|
|
9
|
+
-- legacy tables stay in place. A follow-up migration (0038, separate
|
|
10
|
+
-- PR) drops them once the new code stabilises ≥1 workday in prod —
|
|
11
|
+
-- see §9.2 step 6 for why those are split.
|
|
12
|
+
--
|
|
13
|
+
-- Pre-flight collision probe (§9.1) MUST be run against staging and
|
|
14
|
+
-- prod read-replicas before this PR is opened. If
|
|
15
|
+
-- SELECT chat_id, agent_id
|
|
16
|
+
-- FROM chat_participants p JOIN chat_subscriptions s USING (chat_id, agent_id)
|
|
17
|
+
-- returns any rows, the cutover preference (speaker row wins) applies
|
|
18
|
+
-- automatically via the insert ordering below, but the surprise should
|
|
19
|
+
-- be investigated first.
|
|
20
|
+
--
|
|
21
|
+
-- Service-layer integrity (no FK / CHECK / trigger): consistent with
|
|
22
|
+
-- messages / inbox_entries / notifications. The DB-level
|
|
23
|
+
-- `ON DELETE CASCADE` from chats.id is intentionally NOT carried over
|
|
24
|
+
-- — chat hard-delete paths must explicitly clean these tables (see
|
|
25
|
+
-- §8.5, §11.7 chat-delete integration test).
|
|
26
|
+
--
|
|
27
|
+
-- Migration 0037 is hand-written. drizzle-kit generate refuses to
|
|
28
|
+
-- diff against the pre-0019 snapshot; we have followed this convention
|
|
29
|
+
-- for every migration since 0019 (see 0036's header).
|
|
30
|
+
|
|
31
|
+
CREATE TABLE IF NOT EXISTS "chat_membership" (
|
|
32
|
+
"chat_id" text NOT NULL,
|
|
33
|
+
"agent_id" text NOT NULL,
|
|
34
|
+
"role" text NOT NULL DEFAULT 'member',
|
|
35
|
+
"access_mode" text NOT NULL,
|
|
36
|
+
"mode" text NOT NULL DEFAULT 'full',
|
|
37
|
+
"source" text NOT NULL DEFAULT 'manual',
|
|
38
|
+
"joined_at" timestamp with time zone NOT NULL DEFAULT now(),
|
|
39
|
+
CONSTRAINT "chat_membership_pkey" PRIMARY KEY ("chat_id", "agent_id")
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
--> statement-breakpoint
|
|
43
|
+
CREATE INDEX IF NOT EXISTS "idx_membership_agent"
|
|
44
|
+
ON "chat_membership" ("agent_id");
|
|
45
|
+
|
|
46
|
+
--> statement-breakpoint
|
|
47
|
+
-- Index name matches §8.2 of the proposal (`idx_membership_chat_role`).
|
|
48
|
+
-- The trailing token is `role` (the legacy speaker/watcher *role* concept)
|
|
49
|
+
-- rather than `access_mode` (the literal column name) to align with the
|
|
50
|
+
-- design checklist.
|
|
51
|
+
CREATE INDEX IF NOT EXISTS "idx_membership_chat_role"
|
|
52
|
+
ON "chat_membership" ("chat_id", "access_mode");
|
|
53
|
+
|
|
54
|
+
--> statement-breakpoint
|
|
55
|
+
CREATE TABLE IF NOT EXISTS "chat_user_state" (
|
|
56
|
+
"chat_id" text NOT NULL,
|
|
57
|
+
"agent_id" text NOT NULL,
|
|
58
|
+
"last_read_at" timestamp with time zone,
|
|
59
|
+
"unread_mention_count" integer NOT NULL DEFAULT 0,
|
|
60
|
+
CONSTRAINT "chat_user_state_pkey" PRIMARY KEY ("chat_id", "agent_id")
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
--> statement-breakpoint
|
|
64
|
+
CREATE INDEX IF NOT EXISTS "idx_user_state_agent"
|
|
65
|
+
ON "chat_user_state" ("agent_id");
|
|
66
|
+
|
|
67
|
+
--> statement-breakpoint
|
|
68
|
+
-- Partial index for the unread-badge / ?filter=unread lookup. Most rows
|
|
69
|
+
-- have unread_mention_count = 0; bounding the index by the actual
|
|
70
|
+
-- unread row count keeps the scan cheap regardless of table size.
|
|
71
|
+
CREATE INDEX IF NOT EXISTS "idx_user_state_unread"
|
|
72
|
+
ON "chat_user_state" ("agent_id")
|
|
73
|
+
WHERE unread_mention_count > 0;
|
|
74
|
+
|
|
75
|
+
--> statement-breakpoint
|
|
76
|
+
-- Back-fill chat_membership from chat_participants. These are the
|
|
77
|
+
-- "speaker" rows — they retain their owner/member role from the legacy
|
|
78
|
+
-- table and gain access_mode = 'speaker'. `joined_at` carries over
|
|
79
|
+
-- verbatim. `source = 'manual'` is the conservative default; we do not
|
|
80
|
+
-- attempt to reconstruct the original add-path retroactively.
|
|
81
|
+
-- Speaker-wins merge policy per proposal §9.2 step 3 and §11.7 checklist.
|
|
82
|
+
-- Since `chat_participants` is loaded BEFORE `chat_subscriptions`, the
|
|
83
|
+
-- INSERT below cannot produce a conflict in a clean cutover. The
|
|
84
|
+
-- ON CONFLICT DO UPDATE is the explicit double-protection requested by
|
|
85
|
+
-- the proposal: if a dirty write between the §9.1 probe and the
|
|
86
|
+
-- maintenance window has somehow planted a watcher row first (e.g. a
|
|
87
|
+
-- partial / aborted prior migration), upgrade it in place to speaker
|
|
88
|
+
-- rather than silently leaving it as a watcher (which DO NOTHING would
|
|
89
|
+
-- have done). `joined_at` keeps the earliest known timestamp so the
|
|
90
|
+
-- audit trail is not flattened.
|
|
91
|
+
INSERT INTO "chat_membership"
|
|
92
|
+
("chat_id", "agent_id", "role", "access_mode", "mode", "source", "joined_at")
|
|
93
|
+
SELECT
|
|
94
|
+
cp."chat_id",
|
|
95
|
+
cp."agent_id",
|
|
96
|
+
COALESCE(cp."role", 'member'),
|
|
97
|
+
'speaker',
|
|
98
|
+
COALESCE(cp."mode", 'full'),
|
|
99
|
+
'manual',
|
|
100
|
+
COALESCE(cp."joined_at", now())
|
|
101
|
+
FROM "chat_participants" cp
|
|
102
|
+
ON CONFLICT ("chat_id", "agent_id") DO UPDATE SET
|
|
103
|
+
"access_mode" = 'speaker',
|
|
104
|
+
"role" = EXCLUDED."role",
|
|
105
|
+
"mode" = EXCLUDED."mode",
|
|
106
|
+
"source" = EXCLUDED."source",
|
|
107
|
+
"joined_at" = LEAST("chat_membership"."joined_at", EXCLUDED."joined_at");
|
|
108
|
+
|
|
109
|
+
--> statement-breakpoint
|
|
110
|
+
-- Back-fill chat_membership from chat_subscriptions. These are the
|
|
111
|
+
-- "watcher" rows.
|
|
112
|
+
--
|
|
113
|
+
-- Speaker-wins on collision: if a row already exists for this
|
|
114
|
+
-- (chat, agent) pair it is by construction a speaker (the previous
|
|
115
|
+
-- INSERT just wrote it). The guarded UPDATE makes this a no-op for
|
|
116
|
+
-- existing speaker rows (the `WHERE chat_membership.access_mode =
|
|
117
|
+
-- 'watcher'` guard is never satisfied), so the speaker is preserved
|
|
118
|
+
-- untouched. We use ON CONFLICT DO UPDATE rather than DO NOTHING per
|
|
119
|
+
-- proposal §9.2 step 3 — the merge policy lives in the SQL itself,
|
|
120
|
+
-- not implicitly in the INSERT ordering.
|
|
121
|
+
--
|
|
122
|
+
-- source = 'auto_manager' captures that watcher rows historically came
|
|
123
|
+
-- from recomputeChatWatchers' anchor-based set rebuild. This default is
|
|
124
|
+
-- harmless even for the rare manually-attached watcher rows.
|
|
125
|
+
INSERT INTO "chat_membership"
|
|
126
|
+
("chat_id", "agent_id", "role", "access_mode", "mode", "source", "joined_at")
|
|
127
|
+
SELECT
|
|
128
|
+
cs."chat_id",
|
|
129
|
+
cs."agent_id",
|
|
130
|
+
'member',
|
|
131
|
+
'watcher',
|
|
132
|
+
'full',
|
|
133
|
+
'auto_manager',
|
|
134
|
+
COALESCE(cs."created_at", now())
|
|
135
|
+
FROM "chat_subscriptions" cs
|
|
136
|
+
ON CONFLICT ("chat_id", "agent_id") DO UPDATE SET
|
|
137
|
+
"joined_at" = LEAST("chat_membership"."joined_at", EXCLUDED."joined_at")
|
|
138
|
+
WHERE "chat_membership"."access_mode" = 'watcher';
|
|
139
|
+
|
|
140
|
+
--> statement-breakpoint
|
|
141
|
+
-- Back-fill chat_user_state from chat_participants. Only rows whose
|
|
142
|
+
-- read state was actually touched (lastReadAt non-null OR
|
|
143
|
+
-- unreadMentionCount > 0) are materialised — the rest can be served
|
|
144
|
+
-- via COALESCE-defaults at read time and would only bloat the table.
|
|
145
|
+
--
|
|
146
|
+
-- Speaker-wins: no prior row can exist for this (chat, agent) pair in
|
|
147
|
+
-- a clean cutover. The ON CONFLICT DO UPDATE is defensive against a
|
|
148
|
+
-- partial / aborted prior migration leaving a stale row behind — we
|
|
149
|
+
-- overwrite with the authoritative speaker-side read state per §9.2.
|
|
150
|
+
INSERT INTO "chat_user_state"
|
|
151
|
+
("chat_id", "agent_id", "last_read_at", "unread_mention_count")
|
|
152
|
+
SELECT
|
|
153
|
+
cp."chat_id",
|
|
154
|
+
cp."agent_id",
|
|
155
|
+
cp."last_read_at",
|
|
156
|
+
cp."unread_mention_count"
|
|
157
|
+
FROM "chat_participants" cp
|
|
158
|
+
WHERE cp."last_read_at" IS NOT NULL
|
|
159
|
+
OR cp."unread_mention_count" > 0
|
|
160
|
+
ON CONFLICT ("chat_id", "agent_id") DO UPDATE SET
|
|
161
|
+
"last_read_at" = EXCLUDED."last_read_at",
|
|
162
|
+
"unread_mention_count" = EXCLUDED."unread_mention_count";
|
|
163
|
+
|
|
164
|
+
--> statement-breakpoint
|
|
165
|
+
-- Same back-fill from chat_subscriptions. Speaker-wins: if a row was
|
|
166
|
+
-- written above from chat_participants, that participant-side row
|
|
167
|
+
-- represents the speaker's authoritative read state and must not be
|
|
168
|
+
-- clobbered. The guarded UPDATE has WHERE FALSE so the conflict path
|
|
169
|
+
-- is a true no-op; we use DO UPDATE (over DO NOTHING) to keep the
|
|
170
|
+
-- merge policy explicit in the SQL itself per §9.2 step 3.
|
|
171
|
+
INSERT INTO "chat_user_state"
|
|
172
|
+
("chat_id", "agent_id", "last_read_at", "unread_mention_count")
|
|
173
|
+
SELECT
|
|
174
|
+
cs."chat_id",
|
|
175
|
+
cs."agent_id",
|
|
176
|
+
cs."last_read_at",
|
|
177
|
+
cs."unread_mention_count"
|
|
178
|
+
FROM "chat_subscriptions" cs
|
|
179
|
+
WHERE cs."last_read_at" IS NOT NULL
|
|
180
|
+
OR cs."unread_mention_count" > 0
|
|
181
|
+
ON CONFLICT ("chat_id", "agent_id") DO UPDATE SET
|
|
182
|
+
"last_read_at" = "chat_user_state"."last_read_at"
|
|
183
|
+
WHERE FALSE;
|
|
184
|
+
|
|
185
|
+
--> statement-breakpoint
|
|
186
|
+
-- Row-count assertions. Fail the migration loudly if the back-fills
|
|
187
|
+
-- did not materialise the expected number of rows. UNION (deduping by
|
|
188
|
+
-- (chat_id, agent_id)) matches the speaker-wins merge policy above.
|
|
189
|
+
DO $$
|
|
190
|
+
DECLARE
|
|
191
|
+
expected_membership int;
|
|
192
|
+
actual_membership int;
|
|
193
|
+
expected_state int;
|
|
194
|
+
actual_state int;
|
|
195
|
+
BEGIN
|
|
196
|
+
SELECT COUNT(*) INTO expected_membership FROM (
|
|
197
|
+
SELECT "chat_id", "agent_id" FROM "chat_participants"
|
|
198
|
+
UNION
|
|
199
|
+
SELECT "chat_id", "agent_id" FROM "chat_subscriptions"
|
|
200
|
+
) sub;
|
|
201
|
+
|
|
202
|
+
SELECT COUNT(*) INTO actual_membership FROM "chat_membership";
|
|
203
|
+
|
|
204
|
+
IF expected_membership <> actual_membership THEN
|
|
205
|
+
RAISE EXCEPTION 'chat_membership row count mismatch: expected % got %',
|
|
206
|
+
expected_membership, actual_membership;
|
|
207
|
+
END IF;
|
|
208
|
+
|
|
209
|
+
SELECT COUNT(*) INTO expected_state FROM (
|
|
210
|
+
SELECT "chat_id", "agent_id" FROM "chat_participants"
|
|
211
|
+
WHERE "last_read_at" IS NOT NULL OR "unread_mention_count" > 0
|
|
212
|
+
UNION
|
|
213
|
+
SELECT "chat_id", "agent_id" FROM "chat_subscriptions"
|
|
214
|
+
WHERE "last_read_at" IS NOT NULL OR "unread_mention_count" > 0
|
|
215
|
+
) sub;
|
|
216
|
+
|
|
217
|
+
SELECT COUNT(*) INTO actual_state FROM "chat_user_state";
|
|
218
|
+
|
|
219
|
+
IF expected_state <> actual_state THEN
|
|
220
|
+
RAISE EXCEPTION 'chat_user_state row count mismatch: expected % got %',
|
|
221
|
+
expected_state, actual_state;
|
|
222
|
+
END IF;
|
|
223
|
+
END $$;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
-- Chat data model restructure — Step 3 (drop legacy).
|
|
2
|
+
-- See proposals/chat-data-model-restructure.20260512.md §9.2 step 6
|
|
3
|
+
-- and §12.2 (catastrophic rollback boundary).
|
|
4
|
+
--
|
|
5
|
+
-- PR-B of the two-PR cutover. PR-A (#325, migration 0038) created
|
|
6
|
+
-- `chat_membership` + `chat_user_state` and back-filled them from
|
|
7
|
+
-- `chat_participants` + `chat_subscriptions`. The service layer has
|
|
8
|
+
-- been cutover to read/write only the new tables since PR-A. This
|
|
9
|
+
-- migration drops the legacy tables now that ≥1 workday of post-deploy
|
|
10
|
+
-- observation has passed without anomalies.
|
|
11
|
+
--
|
|
12
|
+
-- ROLLBACK is catastrophic post-this-migration: the legacy tables are
|
|
13
|
+
-- gone and require a backup-restore + re-run of 0038 to recover. This
|
|
14
|
+
-- is the explicit reason PR-A and PR-B were split — PR-A's reverse
|
|
15
|
+
-- migration is loss-free, PR-B's is not (§12.2). Do NOT merge this
|
|
16
|
+
-- before ops confirms PR-A stability.
|
|
17
|
+
--
|
|
18
|
+
-- Service-layer dependency check (run before merging):
|
|
19
|
+
-- git grep 'chat_participants\|chat_subscriptions' packages/server/src/
|
|
20
|
+
-- should return only doc / comment hits — any live query reference is
|
|
21
|
+
-- a blocker.
|
|
22
|
+
|
|
23
|
+
DROP TABLE IF EXISTS "chat_subscriptions";
|
|
24
|
+
|
|
25
|
+
--> statement-breakpoint
|
|
26
|
+
DROP TABLE IF EXISTS "chat_participants";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
-- Per-(chat, user) engagement state on `chat_user_state`.
|
|
2
|
+
-- Replaces the design of closed PR #316, which originally tried to add
|
|
3
|
+
-- this column to both `chat_participants` and `chat_subscriptions` and
|
|
4
|
+
-- was rejected for forcing double-writes + state-carry across the
|
|
5
|
+
-- speaker/watcher boundary. After the data-model restructure
|
|
6
|
+
-- (proposals/chat-data-model-restructure.20260512.md, migrations
|
|
7
|
+
-- 0038/0039), the natural home is `chat_user_state` — sitting next to
|
|
8
|
+
-- the other per-user private columns (`last_read_at`,
|
|
9
|
+
-- `unread_mention_count`).
|
|
10
|
+
--
|
|
11
|
+
-- Values: 'active' (default) | 'archived' | 'deleted'. Auto-revive
|
|
12
|
+
-- archived → active happens on new message in `applyAfterFanOut`;
|
|
13
|
+
-- `deleted` is sticky and reachable only via the chat detail page +
|
|
14
|
+
-- Restore button.
|
|
15
|
+
--
|
|
16
|
+
-- `chat_user_state` rows are lazy-materialised (row only created on
|
|
17
|
+
-- first markRead / mention / engagement write). The service layer
|
|
18
|
+
-- reads via `COALESCE(engagement_status, 'active')`, so existing rows
|
|
19
|
+
-- without an explicit value (and rows that don't yet exist) both
|
|
20
|
+
-- resolve to `'active'` — no back-fill needed and the NOT NULL DEFAULT
|
|
21
|
+
-- handles new INSERTs.
|
|
22
|
+
|
|
23
|
+
ALTER TABLE "chat_user_state"
|
|
24
|
+
ADD COLUMN "engagement_status" text NOT NULL DEFAULT 'active';
|
|
@@ -267,6 +267,27 @@
|
|
|
267
267
|
"when": 1778803200000,
|
|
268
268
|
"tag": "0037_github_app_installations",
|
|
269
269
|
"breakpoints": true
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"idx": 38,
|
|
273
|
+
"version": "7",
|
|
274
|
+
"when": 1778889600000,
|
|
275
|
+
"tag": "0038_chat_membership_user_state",
|
|
276
|
+
"breakpoints": true
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"idx": 39,
|
|
280
|
+
"version": "7",
|
|
281
|
+
"when": 1779148800000,
|
|
282
|
+
"tag": "0039_drop_chat_participants_subscriptions",
|
|
283
|
+
"breakpoints": true
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
"idx": 40,
|
|
287
|
+
"version": "7",
|
|
288
|
+
"when": 1779235200000,
|
|
289
|
+
"tag": "0040_chat_user_state_engagement",
|
|
290
|
+
"breakpoints": true
|
|
270
291
|
}
|
|
271
292
|
]
|
|
272
293
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { r as __exportAll } from "./chunk-BSw8zbkd.mjs";
|
|
2
2
|
import { t as cliFetch } from "./cli-fetch--tiwKm5S.mjs";
|
|
3
|
-
import { r as AGENT_SELECTOR_HEADER } from "./dist-
|
|
3
|
+
import { r as AGENT_SELECTOR_HEADER } from "./dist-CnjqakXS.mjs";
|
|
4
4
|
//#region src/core/feishu.ts
|
|
5
5
|
var feishu_exports = /* @__PURE__ */ __exportAll({
|
|
6
6
|
bindFeishuBot: () => bindFeishuBot,
|
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import "./observability-BAScT_5S-BcW9HgkG.mjs";
|
|
2
|
-
import { A as checkDocker, B as isServiceSupported, E as checkAgentConfigs, F as checkWebSocket, G as uninstallClientService, H as restartClientService, I as printResults, J as stopPostgres, K as ensurePostgres, M as checkServerConfig, N as checkServerHealth, O as checkClientConfig, P as checkServerReachable, R as getClientServiceStatus, S as runHomeMigration, T as runMigrations, U as startClientService, V as resolveCliInvocation, W as stopClientService, X as handleClientOrgMismatch, Y as ClientRuntime, Z as rotateClientIdWithBackup, _ as formatCheckReport, b as onboardCreate, d as startServer, g as promptMissingFields, h as promptAddAgent, j as checkNodeVersion, k as checkDatabase, lt as FirstTreeHubSDK, m as isInteractive, n as deriveHubUrlFromToken, nt as hasUser, q as isDockerAvailable, t as HubUrlDerivationError, tt as createOwner, ut as SdkError, y as onboardCheck, z as installClientService } from "./saas-connect-
|
|
2
|
+
import { A as checkDocker, B as isServiceSupported, E as checkAgentConfigs, F as checkWebSocket, G as uninstallClientService, H as restartClientService, I as printResults, J as stopPostgres, K as ensurePostgres, M as checkServerConfig, N as checkServerHealth, O as checkClientConfig, P as checkServerReachable, R as getClientServiceStatus, S as runHomeMigration, T as runMigrations, U as startClientService, V as resolveCliInvocation, W as stopClientService, X as handleClientOrgMismatch, Y as ClientRuntime, Z as rotateClientIdWithBackup, _ as formatCheckReport, b as onboardCreate, d as startServer, g as promptMissingFields, h as promptAddAgent, j as checkNodeVersion, k as checkDatabase, lt as FirstTreeHubSDK, m as isInteractive, n as deriveHubUrlFromToken, nt as hasUser, q as isDockerAvailable, t as HubUrlDerivationError, tt as createOwner, ut as SdkError, y as onboardCheck, z as installClientService } from "./saas-connect-CLcon-De.mjs";
|
|
3
3
|
import "./logger-core-BTmvdflj-DjW8FM4T.mjs";
|
|
4
4
|
import { a as ensureFreshAdminToken, c as resolveServerUrl, i as ensureFreshAccessToken, n as AuthRefreshRateLimitedError, s as resolveAccessToken, t as AuthRefreshFailedError } from "./bootstrap-BCZC1ki6.mjs";
|
|
5
5
|
import { i as blank, s as status } from "./cli-fetch--tiwKm5S.mjs";
|
|
6
|
-
import "./dist-
|
|
7
|
-
import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-
|
|
6
|
+
import "./dist-CnjqakXS.mjs";
|
|
7
|
+
import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-DrnBbl8T.mjs";
|
|
8
8
|
import "./errors-CF5evtJt-B0NTIVPt.mjs";
|
|
9
9
|
import "./src-DNBS5Yjj.mjs";
|
|
10
|
-
import "./client-
|
|
10
|
+
import "./client-bR8nwHaV-OxnjyKOk.mjs";
|
|
11
11
|
import "./invitation-Bg0TRiyx-BsZH4GCS.mjs";
|
|
12
12
|
export { AuthRefreshFailedError, AuthRefreshRateLimitedError, ClientRuntime, FirstTreeHubSDK, HubUrlDerivationError, SdkError, bindFeishuBot, bindFeishuUser, blank, checkAgentConfigs, checkClientConfig, checkDatabase, checkDocker, checkNodeVersion, checkServerConfig, checkServerHealth, checkServerReachable, checkWebSocket, createOwner, deriveHubUrlFromToken, ensureFreshAccessToken, ensureFreshAdminToken, ensurePostgres, formatCheckReport, getClientServiceStatus, handleClientOrgMismatch, hasUser, installClientService, isDockerAvailable, isInteractive, isServiceSupported, onboardCheck, onboardCreate, printResults, promptAddAgent, promptMissingFields, resolveAccessToken, resolveCliInvocation, resolveServerUrl, restartClientService, rotateClientIdWithBackup, runHomeMigration, runMigrations, startClientService, startServer, status, stopClientService, stopPostgres, uninstallClientService };
|