@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,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");
|