@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,153 +0,0 @@
1
- -- SaaS onboarding milestone — adds the data-model surface needed for
2
- -- public GitHub-OAuth signup, per-org invitation links, and "leave team"
3
- -- soft-delete. See proposals/hub-saas-onboarding.20260428.md for the full
4
- -- design contract.
5
- --
6
- -- Three independent additions, no destructive changes to existing tables:
7
- --
8
- -- 1. `auth_identities` — third-party / local auth identities for a user.
9
- -- Models the "how does this user prove who they are" boundary.
10
- -- `(provider, identifier)` is globally unique. v1 stores the credential
11
- -- payload (password hash, webauthn pubkey) on the same row;
12
- -- v2 splits it into `auth_credentials` if multi-factor is needed
13
- -- (the migration is sketched in the schema file's header comment).
14
- --
15
- -- 2. `invitations` + `invitation_redemptions` — org-level share links.
16
- -- The "one active link per org" rule is enforced by a partial UNIQUE
17
- -- index (Drizzle's TS DSL doesn't model partial uniques yet, so we
18
- -- add it directly here). Rotation = revoke prior + insert new in a
19
- -- single transaction. Redemptions are recorded for audit.
20
- --
21
- -- 3. `members.status` — "active" | "left" soft-delete marker for the
22
- -- "leave team" flow. Existing rows backfill to "active" via the
23
- -- column DEFAULT. The auth middleware rejects tokens that resolve to
24
- -- a "left" member; join-by-invite flips a "left" row back to "active".
25
- --
26
- -- All three changes are append-only (new tables + new column with DEFAULT).
27
- -- ALTER TABLE on `members` takes a brief ACCESS EXCLUSIVE lock, which is
28
- -- safe on a v1 SaaS members table (small) but should be benchmarked on a
29
- -- large multi-tenant install before rolling.
30
- --
31
- -- See 0020_unified_user_token.sql header for why this file does NOT wrap in
32
- -- BEGIN;/COMMIT; — Drizzle migrator already runs every pending migration
33
- -- inside a single outer transaction.
34
-
35
- -- ---------------------------------------------------------------------------
36
- -- 1. auth_identities
37
- -- ---------------------------------------------------------------------------
38
- CREATE TABLE IF NOT EXISTS "auth_identities" (
39
- "id" text PRIMARY KEY NOT NULL,
40
- "user_id" text NOT NULL,
41
- "provider" text NOT NULL,
42
- "identifier" text NOT NULL,
43
- "email" text,
44
- "verified_at" timestamp with time zone,
45
- "credential_type" text,
46
- "credential_payload" jsonb,
47
- "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
48
- "created_at" timestamp with time zone DEFAULT now() NOT NULL,
49
- "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
50
- CONSTRAINT "uq_auth_identities_provider_identifier" UNIQUE ("provider", "identifier")
51
- );
52
-
53
- --> statement-breakpoint
54
- ALTER TABLE "auth_identities"
55
- ADD CONSTRAINT "auth_identities_user_id_users_id_fk"
56
- FOREIGN KEY ("user_id") REFERENCES "users"("id")
57
- ON DELETE cascade ON UPDATE no action;
58
-
59
- --> statement-breakpoint
60
- CREATE INDEX IF NOT EXISTS "idx_auth_identities_user" ON "auth_identities" ("user_id");
61
- --> statement-breakpoint
62
- CREATE INDEX IF NOT EXISTS "idx_auth_identities_email" ON "auth_identities" ("email");
63
-
64
- -- ---------------------------------------------------------------------------
65
- -- 2. invitations + invitation_redemptions
66
- -- ---------------------------------------------------------------------------
67
- --> statement-breakpoint
68
- CREATE TABLE IF NOT EXISTS "invitations" (
69
- "id" text PRIMARY KEY NOT NULL,
70
- "organization_id" text NOT NULL,
71
- "token" text NOT NULL UNIQUE,
72
- "role" text DEFAULT 'member' NOT NULL,
73
- "expires_at" timestamp with time zone,
74
- "revoked_at" timestamp with time zone,
75
- "created_by" text NOT NULL,
76
- "created_at" timestamp with time zone DEFAULT now() NOT NULL
77
- );
78
-
79
- --> statement-breakpoint
80
- ALTER TABLE "invitations"
81
- ADD CONSTRAINT "invitations_organization_id_organizations_id_fk"
82
- FOREIGN KEY ("organization_id") REFERENCES "organizations"("id")
83
- ON DELETE cascade ON UPDATE no action;
84
- --> statement-breakpoint
85
- ALTER TABLE "invitations"
86
- ADD CONSTRAINT "invitations_created_by_users_id_fk"
87
- FOREIGN KEY ("created_by") REFERENCES "users"("id")
88
- ON DELETE no action ON UPDATE no action;
89
-
90
- --> statement-breakpoint
91
- CREATE INDEX IF NOT EXISTS "idx_invitations_token" ON "invitations" ("token");
92
- --> statement-breakpoint
93
- CREATE INDEX IF NOT EXISTS "idx_invitations_org" ON "invitations" ("organization_id");
94
-
95
- --> statement-breakpoint
96
- -- v1 enforced rule: each org may have at most one non-revoked invitation.
97
- -- The predicate is intentionally `revoked_at IS NULL` only — Postgres rejects
98
- -- `now()` in an index predicate (must be IMMUTABLE), and conflating "expired"
99
- -- with "no longer the active link" matches the v1 service contract anyway.
100
- -- The runtime "is this still usable" filter (which DOES check `expires_at`)
101
- -- lives in services/invitation.ts. Future "multiple links per org" relaxes by
102
- -- dropping this index.
103
- CREATE UNIQUE INDEX IF NOT EXISTS "uq_invitations_active_per_org"
104
- ON "invitations" ("organization_id")
105
- WHERE "revoked_at" IS NULL;
106
-
107
- --> statement-breakpoint
108
- CREATE TABLE IF NOT EXISTS "invitation_redemptions" (
109
- "id" text PRIMARY KEY NOT NULL,
110
- "invitation_id" text NOT NULL,
111
- "user_id" text NOT NULL,
112
- "redeemed_at" timestamp with time zone DEFAULT now() NOT NULL,
113
- "ip" text,
114
- "user_agent" text
115
- );
116
-
117
- --> statement-breakpoint
118
- ALTER TABLE "invitation_redemptions"
119
- ADD CONSTRAINT "invitation_redemptions_invitation_id_invitations_id_fk"
120
- FOREIGN KEY ("invitation_id") REFERENCES "invitations"("id")
121
- ON DELETE cascade ON UPDATE no action;
122
- --> statement-breakpoint
123
- ALTER TABLE "invitation_redemptions"
124
- ADD CONSTRAINT "invitation_redemptions_user_id_users_id_fk"
125
- FOREIGN KEY ("user_id") REFERENCES "users"("id")
126
- ON DELETE cascade ON UPDATE no action;
127
-
128
- --> statement-breakpoint
129
- CREATE INDEX IF NOT EXISTS "idx_invitation_redemptions_invitation"
130
- ON "invitation_redemptions" ("invitation_id");
131
- --> statement-breakpoint
132
- CREATE INDEX IF NOT EXISTS "idx_invitation_redemptions_user"
133
- ON "invitation_redemptions" ("user_id");
134
-
135
- -- ---------------------------------------------------------------------------
136
- -- 3. members.status — soft-delete marker for "leave team"
137
- -- ---------------------------------------------------------------------------
138
- --
139
- -- No partial index on `status='active'` is created in v1. Filter sites are:
140
- -- - middleware/member-auth.ts: lookup by members.id (already PK-indexed)
141
- -- - services/auth.ts (password login): WHERE user_id = ? AND status='active'
142
- -- - services/membership.ts (listActiveMemberships): same filter
143
- -- The existing `idx_members_user (user_id)` already collapses each user's
144
- -- members rows to ~1-5 typical, so the in-page status check is essentially
145
- -- free at the v1 SaaS scale (low six-digit users × low single-digit teams).
146
- -- A partial unique-eligible index becomes worth adding when:
147
- -- - members > ~100k rows AND
148
- -- - average rows-per-user > ~50
149
- -- whichever comes first. At that point the migration is a single
150
- -- `CREATE INDEX CONCURRENTLY ... WHERE status='active'`.
151
- --> statement-breakpoint
152
- ALTER TABLE "members"
153
- ADD COLUMN IF NOT EXISTS "status" text DEFAULT 'active' NOT NULL;
@@ -1,10 +0,0 @@
1
- -- Add runtime_provider to agents.
2
- --
3
- -- Tags each agent with the runtime that drives it (e.g. "claude-code", "codex").
4
- -- DEFAULT 'claude-code' backfills every existing row so the NOT NULL constraint
5
- -- is safe to land in a single step. Hub deploys are stop-migrate-restart, not
6
- -- rolling, so we don't need a two-phase add (nullable → backfill → not null).
7
- --
8
- -- Capabilities reporting reuses the existing `clients.metadata` jsonb column
9
- -- under the `capabilities` subkey (Option C); no SQL change for clients.
10
- ALTER TABLE "agents" ADD COLUMN "runtime_provider" text DEFAULT 'claude-code' NOT NULL;
@@ -1,12 +0,0 @@
1
- -- Partial unique index: each user can hold at most one github identity.
2
- --
3
- -- Defense-in-depth. The (provider, identifier) UNIQUE catches duplicates
4
- -- of the SAME githubId, but does not stop a single user from collecting
5
- -- multiple DIFFERENT githubIds (e.g. a future "merge accounts" or
6
- -- "rebind" flow that misfires, or a one-off SQL migration that errs).
7
- -- This index makes any such double-bind fail atomically with a
8
- -- unique-violation at the storage layer.
9
-
10
- CREATE UNIQUE INDEX IF NOT EXISTS "uq_auth_identities_user_github"
11
- ON "auth_identities" ("user_id")
12
- WHERE "provider" = 'github';
@@ -1,28 +0,0 @@
1
- -- Backfill: in any direct chat with no human participant, flip both agents to
2
- -- `mention_only`. New direct chats created by `findOrCreateDirectChat` and
3
- -- `createChat` already encode this rule at insert time; this migration patches
4
- -- chats created before the rule existed.
5
- --
6
- -- Why: `full` mode in agent↔agent direct chats causes a reply loop. Every
7
- -- message wakes the other party unconditionally, so a courtesy "thanks" or
8
- -- a duplicated "已回复" tool echo gets treated as a fresh prompt and the two
9
- -- agents chat forever. `mention_only` makes engagement opt-in via `@` so
10
- -- conversations naturally end. Human↔agent direct stays `full` because in a
11
- -- 1:1 with a person the agent must respond on every turn — the human
12
- -- participant is what flips the rule off.
13
- --
14
- -- Group chats are intentionally untouched. Existing rule
15
- -- (`maybeUpgradeDirectToGroup`) already enforces mention_only for non-human
16
- -- participants there.
17
-
18
- UPDATE "chat_participants"
19
- SET "mode" = 'mention_only'
20
- WHERE "chat_id" IN (
21
- SELECT c."id" FROM "chats" c
22
- WHERE c."type" = 'direct'
23
- AND NOT EXISTS (
24
- SELECT 1 FROM "chat_participants" cp
25
- INNER JOIN "agents" a ON a."uuid" = cp."agent_id"
26
- WHERE cp."chat_id" = c."id" AND a."type" = 'human'
27
- )
28
- );
@@ -1,129 +0,0 @@
1
- -- Chat-first workspace foundation. See docs/chat-first-workspace-product-design.md
2
- -- for the contract this migration implements.
3
- --
4
- -- Three structural changes + one data backfill:
5
- -- 1. chats: add last_message_at + last_message_preview projection columns
6
- -- and (organization_id, last_message_at DESC) index. Powers GET /me/chats
7
- -- cursor pagination + sort.
8
- -- 2. chat_participants: add last_read_at + unread_mention_count columns.
9
- -- The chat-first workspace per-user read cursor and red-dot counter
10
- -- live with the participation row that owns them; no separate read-state
11
- -- table.
12
- -- 3. chat_subscriptions (NEW): non-speaking observers ("watchers"). Stays
13
- -- strictly disjoint from chat_participants — invariant 1 in the design.
14
- -- ON DELETE CASCADE so dropping a chat tears down its watchers too.
15
- -- 4. Backfill (single statement each):
16
- -- - chats projection from messages, using DISTINCT ON to avoid the
17
- -- per-row correlated subquery that would lock messages for minutes
18
- -- on large tables.
19
- -- - chat_subscriptions for every active manager whose managed non-human
20
- -- agent already participates in a chat the manager themselves does
21
- -- not speak in. Exactly the rows recomputeChatWatchers would create
22
- -- on first run, but in one bulk INSERT.
23
- --
24
- -- chat_participants.last_read_at + unread_mention_count default to NULL/0,
25
- -- which is the desired "treat all existing chats as already read" behavior
26
- -- on the workspace upgrade.
27
-
28
- ALTER TABLE "chats"
29
- ADD COLUMN IF NOT EXISTS "last_message_at" timestamp with time zone,
30
- ADD COLUMN IF NOT EXISTS "last_message_preview" text;
31
-
32
- --> statement-breakpoint
33
- CREATE INDEX IF NOT EXISTS "idx_chats_org_last_message"
34
- ON "chats" ("organization_id", "last_message_at" DESC);
35
-
36
- --> statement-breakpoint
37
- ALTER TABLE "chat_participants"
38
- ADD COLUMN IF NOT EXISTS "last_read_at" timestamp with time zone,
39
- ADD COLUMN IF NOT EXISTS "unread_mention_count" integer NOT NULL DEFAULT 0;
40
-
41
- --> statement-breakpoint
42
- CREATE TABLE IF NOT EXISTS "chat_subscriptions" (
43
- "chat_id" text NOT NULL,
44
- "agent_id" text NOT NULL,
45
- "kind" text NOT NULL DEFAULT 'watching',
46
- "last_read_at" timestamp with time zone,
47
- "unread_mention_count" integer NOT NULL DEFAULT 0,
48
- "created_at" timestamp with time zone NOT NULL DEFAULT now(),
49
- CONSTRAINT "chat_subscriptions_chat_id_fkey"
50
- FOREIGN KEY ("chat_id") REFERENCES "chats"("id") ON DELETE CASCADE,
51
- -- Intentionally NO ON DELETE clause on the agent FK. `services/agent.ts:
52
- -- deleteAgent` is soft-only (UPDATE status='deleted', name=NULL — never
53
- -- DELETE), so the row stays. Adding CASCADE would be dead code today and
54
- -- silently legitimise a future hard-delete path; default RESTRICT instead
55
- -- pins the soft-delete convention at the schema layer — any future caller
56
- -- that tries a hard DELETE FROM agents will be forced to clean up
57
- -- subscriptions explicitly. (asymmetry with chat_id FK is intentional —
58
- -- chats can be hard-deleted by admin, agents cannot.)
59
- CONSTRAINT "chat_subscriptions_agent_id_fkey"
60
- FOREIGN KEY ("agent_id") REFERENCES "agents"("uuid"),
61
- CONSTRAINT "chat_subscriptions_pkey"
62
- PRIMARY KEY ("chat_id", "agent_id")
63
- );
64
-
65
- --> statement-breakpoint
66
- CREATE INDEX IF NOT EXISTS "idx_chat_subscriptions_agent"
67
- ON "chat_subscriptions" ("agent_id");
68
-
69
- --> statement-breakpoint
70
- -- Backfill projection: one INSERT-shaped UPDATE driven by a DISTINCT ON
71
- -- subquery so messages is touched once. Avoids the correlated subquery
72
- -- variant that runs two scans per chat row.
73
- --
74
- -- Preview must match `chat-projection.ts:applyAfterFanOut`'s live-write
75
- -- semantics: a clean unquoted string for text messages, NULL for
76
- -- structured content (file / image / etc.). `content::text` would
77
- -- serialize JSONB to wire form (with quotes for strings, `{...}` for
78
- -- objects), producing visible inconsistency between backfilled and
79
- -- live-written rows. `jsonb_typeof` + `#>> '{}'` extracts the bare
80
- -- string only when content is a JSON string; otherwise NULL (B3 in
81
- -- PR review). `trim()` mirrors `outboundContent.trim()` in
82
- -- `chat-projection.ts:applyAfterFanOut` so leading whitespace doesn't
83
- -- visually jump on the first live overwrite.
84
- WITH last_msg AS (
85
- SELECT DISTINCT ON ("chat_id")
86
- "chat_id",
87
- "created_at",
88
- CASE
89
- WHEN jsonb_typeof("content") = 'string'
90
- THEN LEFT(trim("content" #>> '{}'), 200)
91
- ELSE NULL
92
- END AS "preview"
93
- FROM "messages"
94
- ORDER BY "chat_id", "created_at" DESC
95
- )
96
- UPDATE "chats" c
97
- SET "last_message_at" = lm."created_at",
98
- "last_message_preview" = lm."preview"
99
- FROM last_msg lm
100
- WHERE c."id" = lm."chat_id";
101
-
102
- --> statement-breakpoint
103
- -- Watcher backfill: every active member whose managed (non-human) agent
104
- -- participates in a chat where the member's own human agent is NOT a
105
- -- speaking participant. Idempotent via ON CONFLICT.
106
- --
107
- -- The explicit NULL casts are required: PostgreSQL infers a bare NULL in a
108
- -- VALUES/SELECT list as `text`, which then fails to coerce to the target
109
- -- columns (timestamptz / integer respectively).
110
- INSERT INTO "chat_subscriptions"
111
- ("chat_id", "agent_id", "kind", "last_read_at", "unread_mention_count", "created_at")
112
- SELECT DISTINCT
113
- cp."chat_id",
114
- m."agent_id",
115
- 'watching',
116
- NULL::timestamp with time zone,
117
- 0,
118
- now()
119
- FROM "chat_participants" cp
120
- JOIN "agents" a ON a."uuid" = cp."agent_id"
121
- JOIN "members" m ON m."id" = a."manager_id"
122
- WHERE m."status" = 'active'
123
- AND a."type" <> 'human'
124
- AND NOT EXISTS (
125
- SELECT 1 FROM "chat_participants" cp2
126
- WHERE cp2."chat_id" = cp."chat_id"
127
- AND cp2."agent_id" = m."agent_id"
128
- )
129
- ON CONFLICT ("chat_id", "agent_id") DO NOTHING;
@@ -1,11 +0,0 @@
1
- -- Drop the `system_configs` table — replaced by deployment-level env
2
- -- vars (FIRST_TREE_HUB_INBOX_TIMEOUT_SECONDS, FIRST_TREE_HUB_MAX_RETRY_COUNT,
3
- -- FIRST_TREE_HUB_POLLING_INTERVAL_SECONDS, FIRST_TREE_HUB_PRESENCE_CLEANUP_SECONDS,
4
- -- FIRST_TREE_HUB_NOTIFICATION_WEBHOOK_URL).
5
- --
6
- -- See proposals/hub-strip-jwt-ambient-scope.20260508.md §3.5 + §6.3.
7
- -- The table held tunables that were never customer-configurable; promoting
8
- -- them to env vars closes the multi-tenant security gap where any org admin
9
- -- could mutate cross-org runtime behavior.
10
-
11
- DROP TABLE IF EXISTS "system_configs";
@@ -1,36 +0,0 @@
1
- -- Per-organization settings, keyed by (organization_id, namespace).
2
- --
3
- -- Each row holds an entire group of related config as a JSONB blob; the
4
- -- schema for each namespace lives in @agent-team-foundation/first-tree-hub-shared
5
- -- (ORG_SETTINGS_NAMESPACES) and is enforced by the service layer on every
6
- -- read/write. Adding a new config group means registering a new namespace +
7
- -- Zod schema in shared — the DB does not change.
8
- --
9
- -- `version` is reserved for future optimistic locking (PUT with If-Match).
10
- -- We keep the column from day one so tightening to compare-and-swap later
11
- -- is a code-only change with no migration.
12
- --
13
- -- Sensitive fields inside `value` (e.g. github_integration.webhookSecret)
14
- -- are AES-256-GCM-encrypted at the service layer using crypto.ts's
15
- -- encryptValue / decryptValue — same pattern as adapter_configs.
16
- --
17
- -- ON DELETE CASCADE on organization_id: settings have no independent
18
- -- lifecycle, deleting an org must drop them. updated_by is SET NULL so a
19
- -- user deletion does not cascade-clobber unrelated config rows.
20
-
21
- CREATE TABLE IF NOT EXISTS "organization_settings" (
22
- "organization_id" text NOT NULL,
23
- "namespace" text NOT NULL,
24
- "value" jsonb NOT NULL DEFAULT '{}'::jsonb,
25
- "version" integer NOT NULL DEFAULT 0,
26
- "updated_by" text,
27
- "updated_at" timestamp with time zone NOT NULL DEFAULT now(),
28
- CONSTRAINT "organization_settings_pkey" PRIMARY KEY ("organization_id", "namespace"),
29
- CONSTRAINT "organization_settings_organization_id_organizations_id_fk"
30
- FOREIGN KEY ("organization_id") REFERENCES "organizations"("id") ON DELETE CASCADE,
31
- CONSTRAINT "organization_settings_updated_by_users_id_fk"
32
- FOREIGN KEY ("updated_by") REFERENCES "users"("id") ON DELETE SET NULL
33
- );
34
-
35
- CREATE INDEX IF NOT EXISTS "idx_org_settings_namespace"
36
- ON "organization_settings" ("namespace");
@@ -1,13 +0,0 @@
1
- -- Onboarding stepper dismissal flag. Decoupled from the server-side
2
- -- `onboardingStep` enum so the stepper keeps rendering across all three
3
- -- UI steps (server-side onboardingStep flips to `completed` at the end of
4
- -- Step 2; Step 3 is purely client-driven and the stepper must keep
5
- -- showing during the tree-init chat).
6
- --
7
- -- See docs/new-user-onboarding-design.md §8.
8
- --
9
- -- NULL → stepper renders
10
- -- value → user clicked the `✕`; stepper unmounts. Irreversible from UI v1.
11
-
12
- ALTER TABLE "users"
13
- ADD COLUMN IF NOT EXISTS "onboarding_dismissed_at" timestamp with time zone;
@@ -1,34 +0,0 @@
1
- -- Pending ask-user question lifecycle. See packages/server/src/db/schema/pending-questions.ts
2
- -- and services/questions.ts for the read / write paths.
3
- --
4
- -- One row per `format=question` message. Rows are written inside the same
5
- -- transaction as the message INSERT (services/message.ts step 3b) so a
6
- -- rollback drops both. Status flips to `answered` when the user posts an
7
- -- answer, or to `superseded` when the chat session is archived
8
- -- (services/session.ts archiveSession) or the owning client is claimed
9
- -- away (services/client.ts claimClient).
10
- --
11
- -- Per the team's "integrity in service layer" convention, NO foreign-key
12
- -- constraints — referential integrity is enforced by the question service.
13
- -- A correlationId reuses the SDK `tool_use_id` so a single id flows from
14
- -- the Claude Agent SDK callback through to the answer message.
15
-
16
- CREATE TABLE IF NOT EXISTS "pending_questions" (
17
- "id" text PRIMARY KEY NOT NULL,
18
- "agent_id" text NOT NULL,
19
- "chat_id" text NOT NULL,
20
- "message_id" text NOT NULL,
21
- "status" text NOT NULL DEFAULT 'pending',
22
- "created_at" timestamp with time zone NOT NULL DEFAULT now(),
23
- "answered_at" timestamp with time zone,
24
- "superseded_at" timestamp with time zone,
25
- "superseded_reason" text
26
- );
27
-
28
- --> statement-breakpoint
29
- CREATE INDEX IF NOT EXISTS "idx_pending_questions_agent_status"
30
- ON "pending_questions" ("agent_id", "status");
31
-
32
- --> statement-breakpoint
33
- CREATE INDEX IF NOT EXISTS "idx_pending_questions_chat_status"
34
- ON "pending_questions" ("chat_id", "status");
@@ -1,7 +0,0 @@
1
- -- Drop the Hub Task subsystem. The product no longer uses tasks; the
2
- -- chat-first workspace covers every flow that previously needed task rows.
3
- -- Order matches the historical service-layer dependency; DB-level FKs were
4
- -- already removed in 0014_drop_task_fks so either order would technically work.
5
-
6
- DROP TABLE IF EXISTS "task_chats";--> statement-breakpoint
7
- DROP TABLE IF EXISTS "tasks";
@@ -1,47 +0,0 @@
1
- -- GitHub webhook → chat clustering (Phase 0).
2
- -- Maps every (organization, human_agent, delegate_agent, entity) tuple to a
3
- -- single chat. Replaces the legacy "one (human, delegate) chat absorbs every
4
- -- GitHub event" behaviour — see docs webhook-routing-design.md §4 for the
5
- -- background and §4.3 for the data-model decision.
6
- --
7
- -- The composite primary key is also the uniqueness constraint we rely on for
8
- -- concurrent webhook safety: two near-simultaneous events for a brand-new
9
- -- entity hit ON CONFLICT DO NOTHING and the second deliverer falls back to a
10
- -- re-SELECT, so the pair never spawns duplicate chats.
11
- --
12
- -- ON DELETE CASCADE on agent / chat columns: a deleted agent or chat must
13
- -- drop its mapping rows. We do NOT cascade from organizations because that
14
- -- relationship is enforced via the agent FKs already.
15
- --
16
- -- This table is GitHub-specific. Future external sources (Linear, Slack
17
- -- channel events, …) get their own table — their entity models differ
18
- -- enough that a generic table would slide back into untyped jsonb.
19
- --
20
- -- Migration 0036 is hand-written to match the team's recent migration
21
- -- workflow — drizzle-kit generate's snapshot metadata is incomplete pre-0019
22
- -- and refuses to diff (same constraint that 0032's commit message called out).
23
-
24
- CREATE TABLE IF NOT EXISTS "github_entity_chat_mappings" (
25
- "organization_id" text NOT NULL,
26
- "human_agent_id" text NOT NULL,
27
- "delegate_agent_id" text NOT NULL,
28
- "entity_type" text NOT NULL,
29
- "entity_key" text NOT NULL,
30
- "chat_id" text NOT NULL,
31
- "bound_at" timestamp with time zone NOT NULL DEFAULT now(),
32
- "bound_via" text NOT NULL,
33
- CONSTRAINT "github_entity_chat_mappings_pkey"
34
- PRIMARY KEY ("organization_id", "human_agent_id", "delegate_agent_id", "entity_type", "entity_key"),
35
- CONSTRAINT "github_entity_chat_mappings_organization_id_organizations_id_fk"
36
- FOREIGN KEY ("organization_id") REFERENCES "organizations"("id"),
37
- CONSTRAINT "github_entity_chat_mappings_human_agent_id_agents_uuid_fk"
38
- FOREIGN KEY ("human_agent_id") REFERENCES "agents"("uuid") ON DELETE CASCADE,
39
- CONSTRAINT "github_entity_chat_mappings_delegate_agent_id_agents_uuid_fk"
40
- FOREIGN KEY ("delegate_agent_id") REFERENCES "agents"("uuid") ON DELETE CASCADE,
41
- CONSTRAINT "github_entity_chat_mappings_chat_id_chats_id_fk"
42
- FOREIGN KEY ("chat_id") REFERENCES "chats"("id") ON DELETE CASCADE
43
- );
44
-
45
- --> statement-breakpoint
46
- CREATE INDEX IF NOT EXISTS "idx_github_entity_chat_mappings_chat"
47
- ON "github_entity_chat_mappings" ("chat_id");
@@ -1,52 +0,0 @@
1
- -- GitHub App installation registry. See packages/server/src/db/schema/github-app-installations.ts
2
- -- for the Drizzle types, and docs/github-app-design-zh.md for the design rationale.
3
- --
4
- -- One row per (GitHub account ↔ Hub team) binding. Replaces the per-repo
5
- -- OAuth + webhook-secret model that lived in
6
- -- `organization_settings.github_integration.webhookSecretCipher` — both
7
- -- coexist during the transition, the old path is dropped in a later PR
8
- -- (D3 hard cut, design doc §7 step 7).
9
- --
10
- -- Per the team's "integrity in service layer" convention, NO foreign-key
11
- -- constraints on hub_organization_id beyond the optional reference — the
12
- -- 1:1 binding (D2 / §8 Q1) is enforced by a UNIQUE INDEX rather than by
13
- -- ON DELETE CASCADE so deleting a Hub org doesn't tombstone the
14
- -- GitHub-side record (which still exists upstream).
15
-
16
- CREATE TABLE IF NOT EXISTS "github_app_installations" (
17
- "id" text PRIMARY KEY NOT NULL,
18
- "installation_id" bigint NOT NULL,
19
- "account_type" text NOT NULL,
20
- "account_login" text NOT NULL,
21
- "account_github_id" bigint NOT NULL,
22
- "hub_organization_id" text,
23
- "permissions" jsonb NOT NULL,
24
- "events" jsonb NOT NULL,
25
- "suspended_at" timestamp with time zone,
26
- "created_at" timestamp with time zone NOT NULL DEFAULT now(),
27
- "updated_at" timestamp with time zone NOT NULL DEFAULT now(),
28
- CONSTRAINT "ck_github_app_installations_account_type"
29
- CHECK ("account_type" IN ('User', 'Organization'))
30
- );
31
-
32
- --> statement-breakpoint
33
- DO $$ BEGIN
34
- ALTER TABLE "github_app_installations"
35
- ADD CONSTRAINT "github_app_installations_hub_organization_id_organizations_id_fk"
36
- FOREIGN KEY ("hub_organization_id") REFERENCES "organizations"("id")
37
- ON DELETE SET NULL ON UPDATE NO ACTION;
38
- EXCEPTION
39
- WHEN duplicate_object THEN null;
40
- END $$;
41
-
42
- --> statement-breakpoint
43
- CREATE UNIQUE INDEX IF NOT EXISTS "uq_github_app_installations_installation_id"
44
- ON "github_app_installations" ("installation_id");
45
-
46
- --> statement-breakpoint
47
- CREATE UNIQUE INDEX IF NOT EXISTS "uq_github_app_installations_hub_org"
48
- ON "github_app_installations" ("hub_organization_id");
49
-
50
- --> statement-breakpoint
51
- CREATE INDEX IF NOT EXISTS "idx_github_app_installations_account"
52
- ON "github_app_installations" ("account_github_id");