@agent-team-foundation/first-tree-hub 0.14.5 → 0.14.6
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/bootstrap-BmeaRhRp.mjs +3 -0
- package/dist/{bootstrap-CQQGgIx1.mjs → bootstrap-CmkHQsnS.mjs} +24 -16
- package/dist/cli/index.mjs +6 -94
- package/dist/{dist-CrdnqZjv.mjs → feishu-BE7QRxnE.mjs} +170 -379
- package/dist/feishu-De9_bA91.mjs +3 -0
- package/dist/index.mjs +5 -12
- package/dist/saas-connect-CNY9Ve5V.mjs +13748 -0
- package/package.json +4 -12
- package/dist/chunk-BSw8zbkd.mjs +0 -37
- package/dist/client-BPRIfrOT-CoV_2o7e.mjs +0 -4230
- package/dist/client-CEdYVnoj-BGiGcJbH.mjs +0 -7
- package/dist/dist-LgF7LHpE.mjs +0 -430
- package/dist/drizzle/0000_shocking_darkhawk.sql +0 -92
- package/dist/drizzle/0001_v2_schema_updates.sql +0 -26
- package/dist/drizzle/0002_adapter_tables.sql +0 -64
- package/dist/drizzle/0003_feishu_adapter.sql +0 -21
- package/dist/drizzle/0004_adapter_refactor.sql +0 -13
- package/dist/drizzle/0005_delegate_mention.sql +0 -1
- package/dist/drizzle/0006_agent_tree_path.sql +0 -1
- package/dist/drizzle/0007_decouple_context_tree.sql +0 -2
- package/dist/drizzle/0008_uuid_identity.sql +0 -12
- package/dist/drizzle/0009_agent_runtime_m1.sql +0 -31
- package/dist/drizzle/0010_cloud_multi_tenancy.sql +0 -34
- package/dist/drizzle/0011_org_uuid_pk.sql +0 -22
- package/dist/drizzle/0012_session_level_state.sql +0 -19
- package/dist/drizzle/0013_hub_tasks.sql +0 -38
- package/dist/drizzle/0014_drop_task_fks.sql +0 -9
- package/dist/drizzle/0015_member_system.sql +0 -34
- package/dist/drizzle/0016_strange_havok.sql +0 -25
- package/dist/drizzle/0017_session_outputs_unique.sql +0 -1
- package/dist/drizzle/0018_agent_visibility.sql +0 -13
- package/dist/drizzle/0019_agent_configs.sql +0 -30
- package/dist/drizzle/0020_unified_user_token.sql +0 -154
- package/dist/drizzle/0021_drop_agents_profile.sql +0 -10
- package/dist/drizzle/0022_session_events.sql +0 -32
- package/dist/drizzle/0023_clients_org_scoping.sql +0 -40
- package/dist/drizzle/0024_display_name_not_null.sql +0 -31
- package/dist/drizzle/0025_inbox_silent_entries.sql +0 -53
- package/dist/drizzle/0026_saas_onboarding.sql +0 -153
- package/dist/drizzle/0027_runtime_provider.sql +0 -10
- package/dist/drizzle/0028_auth_identity_user_github_unique.sql +0 -12
- package/dist/drizzle/0029_direct_agent_only_mention_only.sql +0 -28
- package/dist/drizzle/0030_chat_first_workspace.sql +0 -129
- package/dist/drizzle/0031_drop_system_configs.sql +0 -11
- package/dist/drizzle/0032_organization_settings.sql +0 -36
- package/dist/drizzle/0033_onboarding_dismissed_at.sql +0 -13
- package/dist/drizzle/0034_pending_questions.sql +0 -34
- package/dist/drizzle/0035_drop_hub_tasks.sql +0 -7
- package/dist/drizzle/0036_github_entity_chat_mappings.sql +0 -47
- package/dist/drizzle/0037_github_app_installations.sql +0 -52
- package/dist/drizzle/0038_chat_membership_user_state.sql +0 -223
- package/dist/drizzle/0039_drop_chat_participants_subscriptions.sql +0 -26
- package/dist/drizzle/0040_chat_user_state_engagement.sql +0 -24
- package/dist/drizzle/0041_notifications_dedup_key.sql +0 -29
- package/dist/drizzle/0042_notifications_drop_legacy_types.sql +0 -36
- package/dist/drizzle/0043_onboarding_completed_at.sql +0 -32
- package/dist/drizzle/0044_agent_avatar_color.sql +0 -11
- package/dist/drizzle/0045_agent_avatar_image.sql +0 -17
- package/dist/drizzle/meta/0000_snapshot.json +0 -687
- package/dist/drizzle/meta/0001_snapshot.json +0 -687
- package/dist/drizzle/meta/0012_snapshot.json +0 -1451
- package/dist/drizzle/meta/0013_snapshot.json +0 -1771
- package/dist/drizzle/meta/0014_snapshot.json +0 -1717
- package/dist/drizzle/meta/0016_snapshot.json +0 -1917
- package/dist/drizzle/meta/0018_snapshot.json +0 -1938
- package/dist/drizzle/meta/_journal.json +0 -328
- package/dist/esm-iadMkGbV.mjs +0 -1516
- package/dist/execAsync-DUfRkc4a.mjs +0 -10
- package/dist/execAsync-YbEZSOYd.mjs +0 -10
- package/dist/feishu-DNoBroKK.mjs +0 -53
- package/dist/from-DQ7eNRwu.mjs +0 -3840
- package/dist/getMachineId-bsd-BmasEOJr.mjs +0 -27
- package/dist/getMachineId-bsd-Dh3h0DDE.mjs +0 -27
- package/dist/getMachineId-darwin-CuhM3hfZ.mjs +0 -24
- package/dist/getMachineId-darwin-D9wR0SLj.mjs +0 -24
- package/dist/getMachineId-linux-CYfb0oxZ.mjs +0 -20
- package/dist/getMachineId-linux-D8ZaSjAC.mjs +0 -20
- package/dist/getMachineId-unsupported-Cu3iisaD.mjs +0 -15
- package/dist/getMachineId-unsupported-DZqI4ZT5.mjs +0 -15
- package/dist/getMachineId-win-8ZJbtrdf.mjs +0 -26
- package/dist/getMachineId-win-DT-hqwVp.mjs +0 -26
- package/dist/invitation-C9m2gQx4-C_4f5VTs.mjs +0 -4
- package/dist/invitation-D_ENPHyj-5ETiae5r.mjs +0 -167
- package/dist/logger-core-BTmvdflj-DjW8FM4T.mjs +0 -146
- package/dist/multipart-parser-QRu3OKK4.mjs +0 -294
- package/dist/observability-BAScT_5S-BcW9HgkG.mjs +0 -96129
- package/dist/observability-eLA9iNK_.mjs +0 -5
- package/dist/saas-connect-CYp9TOB5.mjs +0 -21918
- package/dist/src-DFlbpJfU.mjs +0 -1176
- package/dist/src-DNBS5Yjj.mjs +0 -735
- package/dist/uuid-DbS_4vFh-iFghv4zA.mjs +0 -129
- package/dist/web/assets/index-9wK0udbH.js +0 -416
- package/dist/web/assets/index-C7x7O7dG.js +0 -11
- package/dist/web/assets/index-DE7Q3QWE.css +0 -1
- package/dist/web/favicon.svg +0 -9
- package/dist/web/fonts/inter-latin-ext.woff2 +0 -0
- package/dist/web/fonts/inter-latin.woff2 +0 -0
- package/dist/web/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
- package/dist/web/fonts/jetbrains-mono-latin.woff2 +0 -0
- package/dist/web/index.html +0 -39
- /package/dist/{cli-fetch--tiwKm5S.mjs → cli-fetch-BGVItZxo.mjs} +0 -0
|
@@ -1,223 +0,0 @@
|
|
|
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 $$;
|
|
@@ -1,26 +0,0 @@
|
|
|
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";
|
|
@@ -1,24 +0,0 @@
|
|
|
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';
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
-- DB-backed deduplication for the notifications table.
|
|
2
|
-
--
|
|
3
|
-
-- Before this migration, the server kept an in-memory Map keyed by
|
|
4
|
-
-- `(agentId, notificationType)` to suppress duplicate notifications within a
|
|
5
|
-
-- five-minute window. That broke in two important ways:
|
|
6
|
-
--
|
|
7
|
-
-- 1. Multi-instance deployments: each server process owned its own Map, so
|
|
8
|
-
-- a notification produced on instance A passed instance B's dedupe gate
|
|
9
|
-
-- unchanged. Operators saw the same row inserted multiple times during
|
|
10
|
-
-- load-balancer failover or rolling restart.
|
|
11
|
-
-- 2. Process restart wiped the Map, re-opening the dedupe window for
|
|
12
|
-
-- whatever events had fired just before.
|
|
13
|
-
--
|
|
14
|
-
-- The new model is purely DB-driven. A producer optionally supplies a
|
|
15
|
-
-- `dedup_key` (e.g. `agent:{uuid}:error`, `chat:{uuid}:completed`). The
|
|
16
|
-
-- partial unique index below scopes uniqueness to `(organization_id,
|
|
17
|
-
-- dedup_key)` *while the prior row is still unread*. Re-emitting the same
|
|
18
|
-
-- key while the previous notification sits unread is a no-op (the
|
|
19
|
-
-- application uses `ON CONFLICT DO NOTHING`); after the user acknowledges
|
|
20
|
-
-- the prior row, a fresh notification can land again.
|
|
21
|
-
--
|
|
22
|
-
-- Producers without a `dedup_key` keep the legacy always-insert behaviour,
|
|
23
|
-
-- so this column is purely additive — no back-fill, no NOT NULL constraint.
|
|
24
|
-
|
|
25
|
-
ALTER TABLE "notifications" ADD COLUMN "dedup_key" text;
|
|
26
|
-
|
|
27
|
-
CREATE UNIQUE INDEX "uq_notifications_org_dedup_unread"
|
|
28
|
-
ON "notifications" ("organization_id", "dedup_key")
|
|
29
|
-
WHERE read = false AND dedup_key IS NOT NULL;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
-- Drop notification rows whose `type` is no longer in the shared schema.
|
|
2
|
-
--
|
|
3
|
-
-- Three types were removed in earlier work but their rows were left behind:
|
|
4
|
-
--
|
|
5
|
-
-- * `agent_disconnected` — producer deleted by PR #348 (the
|
|
6
|
-
-- systemctl-restart spam fix), schema enum entry deleted later. 37 rows
|
|
7
|
-
-- still in dev hub at the time of writing, with no UI label and no
|
|
8
|
-
-- click target.
|
|
9
|
-
-- * `session_completed` — removed end-to-end in this PR (was 56% of recent
|
|
10
|
-
-- notifications, duplicating the conversation list's "latest message"
|
|
11
|
-
-- signal). Without the cleanup, the bell would keep surfacing them
|
|
12
|
-
-- until each org's user marks them all read manually.
|
|
13
|
-
-- * `session_error` — long-dead type: schema entry + UI label existed,
|
|
14
|
-
-- but no producer ever wrote one. Cleanup is defensive — any stray
|
|
15
|
-
-- rows from a hand-written test or prod-DB experiment go.
|
|
16
|
-
--
|
|
17
|
-
-- `notifications.type` is a `text` column (not a PG enum), so this is a
|
|
18
|
-
-- pure data-cleanup migration — no schema change, no type-cast risk.
|
|
19
|
-
--
|
|
20
|
-
-- The unread-only branch handles the transition window for rows still on
|
|
21
|
-
-- the previous per-type dedup key (`agent:{id}:agent_error|blocked|stale`).
|
|
22
|
-
-- They co-exist with new `agent:{id}:fault` rows for the same agent until
|
|
23
|
-
-- one or the other is marked read, which is exactly the redundancy this PR
|
|
24
|
-
-- is fixing — so wipe them too. Read rows on those keys stay (history).
|
|
25
|
-
|
|
26
|
-
DELETE FROM "notifications"
|
|
27
|
-
WHERE "type" IN ('agent_disconnected', 'session_completed', 'session_error');
|
|
28
|
-
|
|
29
|
-
DELETE FROM "notifications"
|
|
30
|
-
WHERE "read" = false
|
|
31
|
-
AND "dedup_key" IN (
|
|
32
|
-
SELECT "dedup_key" FROM "notifications"
|
|
33
|
-
WHERE "dedup_key" LIKE 'agent:%:agent_error'
|
|
34
|
-
OR "dedup_key" LIKE 'agent:%:agent_blocked'
|
|
35
|
-
OR "dedup_key" LIKE 'agent:%:agent_stale'
|
|
36
|
-
);
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
-- Onboarding terminal-state stamp. Distinct from `onboarding_dismissed_at`:
|
|
2
|
-
--
|
|
3
|
-
-- * `onboarding_dismissed_at` means "the user clicked ✕ on the stepper" —
|
|
4
|
-
-- a UI hide, not a setup-complete signal. The user can resume the
|
|
5
|
-
-- wizard from Settings → Onboarding to land back on whichever step is
|
|
6
|
-
-- still incomplete.
|
|
7
|
-
--
|
|
8
|
-
-- * `onboarding_completed_at` means "the user actually walked Step 3 to
|
|
9
|
-
-- success" (admin Continue, invitee Confirm/Continue). Once set, the
|
|
10
|
-
-- Settings → Onboarding entry point and Resume button disappear
|
|
11
|
-
-- permanently. Subsequent tree / source-repo edits go through Settings
|
|
12
|
-
-- → Team and /agents/:uuid.
|
|
13
|
-
--
|
|
14
|
-
-- The two stamps are intentionally orthogonal: `inferOnboardingStep()`
|
|
15
|
-
-- keeps its existing "infer from current resources" semantics and never
|
|
16
|
-
-- consults this column. This field is UI-gate only.
|
|
17
|
-
--
|
|
18
|
-
-- Backfill: every already-dismissed user is treated as completed. The pre-
|
|
19
|
-
-- column population (mid-2025) had no terminal-state concept, so the only
|
|
20
|
-
-- signal we have is "they hid the stepper" — and the alternative (showing
|
|
21
|
-
-- the Onboarding sidebar entry to every legacy user forever) is worse than
|
|
22
|
-
-- the off-by-one risk that a few users dismissed-without-finishing and
|
|
23
|
-
-- will lose their Resume affordance.
|
|
24
|
-
|
|
25
|
-
ALTER TABLE "users"
|
|
26
|
-
ADD COLUMN IF NOT EXISTS "onboarding_completed_at" timestamp with time zone;
|
|
27
|
-
|
|
28
|
-
--> statement-breakpoint
|
|
29
|
-
UPDATE "users"
|
|
30
|
-
SET "onboarding_completed_at" = "onboarding_dismissed_at"
|
|
31
|
-
WHERE "onboarding_dismissed_at" IS NOT NULL
|
|
32
|
-
AND "onboarding_completed_at" IS NULL;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
-- Add avatar_color_token to agents.
|
|
2
|
-
--
|
|
3
|
-
-- Manager-selected avatar fill color override for the web client. One of
|
|
4
|
-
-- "hue-0".."hue-7", matching the `--avatar-hue-*` CSS tokens in
|
|
5
|
-
-- `packages/web/src/index.css`. NULL means "auto" — the renderer falls
|
|
6
|
-
-- back to the deterministic djb2 hash of `uuid` (the previous default).
|
|
7
|
-
--
|
|
8
|
-
-- Nullable, no default, no backfill: existing rows continue to render the
|
|
9
|
-
-- hashed color until their manager picks something explicit. This is a
|
|
10
|
-
-- pure additive change; no NOT NULL constraint, no enum, no FK.
|
|
11
|
-
ALTER TABLE "agents" ADD COLUMN "avatar_color_token" text;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
-- Add inline avatar image storage to agents.
|
|
2
|
-
--
|
|
3
|
-
-- The PRD chose PG bytea over object storage for the first cut: clients
|
|
4
|
-
-- always pre-resize to 256×256 WEBP (typically < 50 KB), and a few KB ×
|
|
5
|
-
-- N agents per org sits well within row-size budgets. Switching to S3/R2
|
|
6
|
-
-- later is a follow-up migration; the column shape stays the same except
|
|
7
|
-
-- avatar_image_data flips to NULL and avatar_image_url moves to a real
|
|
8
|
-
-- external URL.
|
|
9
|
-
--
|
|
10
|
-
-- All three columns are nullable and move together — either all three
|
|
11
|
-
-- carry an image (data + mime + updated_at) or all are NULL. The service
|
|
12
|
-
-- layer enforces that invariant on writes; SQL keeps it loose so a partial
|
|
13
|
-
-- backfill / restore doesn't break inserts.
|
|
14
|
-
ALTER TABLE "agents"
|
|
15
|
-
ADD COLUMN "avatar_image_data" bytea,
|
|
16
|
-
ADD COLUMN "avatar_image_mime" text,
|
|
17
|
-
ADD COLUMN "avatar_image_updated_at" timestamp with time zone;
|