@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.
Files changed (101) hide show
  1. package/dist/bootstrap-BmeaRhRp.mjs +3 -0
  2. package/dist/{bootstrap-CQQGgIx1.mjs → bootstrap-CmkHQsnS.mjs} +24 -16
  3. package/dist/cli/index.mjs +6 -94
  4. package/dist/{dist-CrdnqZjv.mjs → feishu-BE7QRxnE.mjs} +170 -379
  5. package/dist/feishu-De9_bA91.mjs +3 -0
  6. package/dist/index.mjs +5 -12
  7. package/dist/saas-connect-CNY9Ve5V.mjs +13748 -0
  8. package/package.json +4 -12
  9. package/dist/chunk-BSw8zbkd.mjs +0 -37
  10. package/dist/client-BPRIfrOT-CoV_2o7e.mjs +0 -4230
  11. package/dist/client-CEdYVnoj-BGiGcJbH.mjs +0 -7
  12. package/dist/dist-LgF7LHpE.mjs +0 -430
  13. package/dist/drizzle/0000_shocking_darkhawk.sql +0 -92
  14. package/dist/drizzle/0001_v2_schema_updates.sql +0 -26
  15. package/dist/drizzle/0002_adapter_tables.sql +0 -64
  16. package/dist/drizzle/0003_feishu_adapter.sql +0 -21
  17. package/dist/drizzle/0004_adapter_refactor.sql +0 -13
  18. package/dist/drizzle/0005_delegate_mention.sql +0 -1
  19. package/dist/drizzle/0006_agent_tree_path.sql +0 -1
  20. package/dist/drizzle/0007_decouple_context_tree.sql +0 -2
  21. package/dist/drizzle/0008_uuid_identity.sql +0 -12
  22. package/dist/drizzle/0009_agent_runtime_m1.sql +0 -31
  23. package/dist/drizzle/0010_cloud_multi_tenancy.sql +0 -34
  24. package/dist/drizzle/0011_org_uuid_pk.sql +0 -22
  25. package/dist/drizzle/0012_session_level_state.sql +0 -19
  26. package/dist/drizzle/0013_hub_tasks.sql +0 -38
  27. package/dist/drizzle/0014_drop_task_fks.sql +0 -9
  28. package/dist/drizzle/0015_member_system.sql +0 -34
  29. package/dist/drizzle/0016_strange_havok.sql +0 -25
  30. package/dist/drizzle/0017_session_outputs_unique.sql +0 -1
  31. package/dist/drizzle/0018_agent_visibility.sql +0 -13
  32. package/dist/drizzle/0019_agent_configs.sql +0 -30
  33. package/dist/drizzle/0020_unified_user_token.sql +0 -154
  34. package/dist/drizzle/0021_drop_agents_profile.sql +0 -10
  35. package/dist/drizzle/0022_session_events.sql +0 -32
  36. package/dist/drizzle/0023_clients_org_scoping.sql +0 -40
  37. package/dist/drizzle/0024_display_name_not_null.sql +0 -31
  38. package/dist/drizzle/0025_inbox_silent_entries.sql +0 -53
  39. package/dist/drizzle/0026_saas_onboarding.sql +0 -153
  40. package/dist/drizzle/0027_runtime_provider.sql +0 -10
  41. package/dist/drizzle/0028_auth_identity_user_github_unique.sql +0 -12
  42. package/dist/drizzle/0029_direct_agent_only_mention_only.sql +0 -28
  43. package/dist/drizzle/0030_chat_first_workspace.sql +0 -129
  44. package/dist/drizzle/0031_drop_system_configs.sql +0 -11
  45. package/dist/drizzle/0032_organization_settings.sql +0 -36
  46. package/dist/drizzle/0033_onboarding_dismissed_at.sql +0 -13
  47. package/dist/drizzle/0034_pending_questions.sql +0 -34
  48. package/dist/drizzle/0035_drop_hub_tasks.sql +0 -7
  49. package/dist/drizzle/0036_github_entity_chat_mappings.sql +0 -47
  50. package/dist/drizzle/0037_github_app_installations.sql +0 -52
  51. package/dist/drizzle/0038_chat_membership_user_state.sql +0 -223
  52. package/dist/drizzle/0039_drop_chat_participants_subscriptions.sql +0 -26
  53. package/dist/drizzle/0040_chat_user_state_engagement.sql +0 -24
  54. package/dist/drizzle/0041_notifications_dedup_key.sql +0 -29
  55. package/dist/drizzle/0042_notifications_drop_legacy_types.sql +0 -36
  56. package/dist/drizzle/0043_onboarding_completed_at.sql +0 -32
  57. package/dist/drizzle/0044_agent_avatar_color.sql +0 -11
  58. package/dist/drizzle/0045_agent_avatar_image.sql +0 -17
  59. package/dist/drizzle/meta/0000_snapshot.json +0 -687
  60. package/dist/drizzle/meta/0001_snapshot.json +0 -687
  61. package/dist/drizzle/meta/0012_snapshot.json +0 -1451
  62. package/dist/drizzle/meta/0013_snapshot.json +0 -1771
  63. package/dist/drizzle/meta/0014_snapshot.json +0 -1717
  64. package/dist/drizzle/meta/0016_snapshot.json +0 -1917
  65. package/dist/drizzle/meta/0018_snapshot.json +0 -1938
  66. package/dist/drizzle/meta/_journal.json +0 -328
  67. package/dist/esm-iadMkGbV.mjs +0 -1516
  68. package/dist/execAsync-DUfRkc4a.mjs +0 -10
  69. package/dist/execAsync-YbEZSOYd.mjs +0 -10
  70. package/dist/feishu-DNoBroKK.mjs +0 -53
  71. package/dist/from-DQ7eNRwu.mjs +0 -3840
  72. package/dist/getMachineId-bsd-BmasEOJr.mjs +0 -27
  73. package/dist/getMachineId-bsd-Dh3h0DDE.mjs +0 -27
  74. package/dist/getMachineId-darwin-CuhM3hfZ.mjs +0 -24
  75. package/dist/getMachineId-darwin-D9wR0SLj.mjs +0 -24
  76. package/dist/getMachineId-linux-CYfb0oxZ.mjs +0 -20
  77. package/dist/getMachineId-linux-D8ZaSjAC.mjs +0 -20
  78. package/dist/getMachineId-unsupported-Cu3iisaD.mjs +0 -15
  79. package/dist/getMachineId-unsupported-DZqI4ZT5.mjs +0 -15
  80. package/dist/getMachineId-win-8ZJbtrdf.mjs +0 -26
  81. package/dist/getMachineId-win-DT-hqwVp.mjs +0 -26
  82. package/dist/invitation-C9m2gQx4-C_4f5VTs.mjs +0 -4
  83. package/dist/invitation-D_ENPHyj-5ETiae5r.mjs +0 -167
  84. package/dist/logger-core-BTmvdflj-DjW8FM4T.mjs +0 -146
  85. package/dist/multipart-parser-QRu3OKK4.mjs +0 -294
  86. package/dist/observability-BAScT_5S-BcW9HgkG.mjs +0 -96129
  87. package/dist/observability-eLA9iNK_.mjs +0 -5
  88. package/dist/saas-connect-CYp9TOB5.mjs +0 -21918
  89. package/dist/src-DFlbpJfU.mjs +0 -1176
  90. package/dist/src-DNBS5Yjj.mjs +0 -735
  91. package/dist/uuid-DbS_4vFh-iFghv4zA.mjs +0 -129
  92. package/dist/web/assets/index-9wK0udbH.js +0 -416
  93. package/dist/web/assets/index-C7x7O7dG.js +0 -11
  94. package/dist/web/assets/index-DE7Q3QWE.css +0 -1
  95. package/dist/web/favicon.svg +0 -9
  96. package/dist/web/fonts/inter-latin-ext.woff2 +0 -0
  97. package/dist/web/fonts/inter-latin.woff2 +0 -0
  98. package/dist/web/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
  99. package/dist/web/fonts/jetbrains-mono-latin.woff2 +0 -0
  100. package/dist/web/index.html +0 -39
  101. /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;