@lobu/cli 6.0.0 → 6.1.1
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/README.md +20 -27
- package/dist/bundled-skills/lobu/SKILL.md +12 -12
- package/dist/commands/_lib/apply/apply-cmd.d.ts +2 -0
- package/dist/commands/_lib/apply/apply-cmd.d.ts.map +1 -1
- package/dist/commands/_lib/apply/apply-cmd.js +26 -0
- package/dist/commands/_lib/apply/apply-cmd.js.map +1 -1
- package/dist/commands/_lib/apply/client.d.ts +1 -1
- package/dist/commands/_lib/apply/client.d.ts.map +1 -1
- package/dist/commands/_lib/apply/desired-state.js +6 -6
- package/dist/commands/_lib/apply/desired-state.js.map +1 -1
- package/dist/commands/agent.d.ts +7 -0
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +65 -1
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/chat.d.ts +12 -9
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +117 -56
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/dev.d.ts +15 -7
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +79 -44
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +136 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/eval.d.ts +8 -0
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +56 -1
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/init.d.ts +20 -5
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +332 -183
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/link.d.ts +11 -0
- package/dist/commands/link.d.ts.map +1 -0
- package/dist/commands/link.js +28 -0
- package/dist/commands/link.js.map +1 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +14 -2
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/memory/_lib/browser-auth-cmd.d.ts.map +1 -1
- package/dist/commands/memory/_lib/browser-auth-cmd.js +4 -4
- package/dist/commands/memory/_lib/browser-auth-cmd.js.map +1 -1
- package/dist/commands/memory/_lib/install-targets.d.ts.map +1 -1
- package/dist/commands/memory/_lib/install-targets.js +1 -5
- package/dist/commands/memory/_lib/install-targets.js.map +1 -1
- package/dist/commands/memory/_lib/mcp.d.ts +2 -2
- package/dist/commands/memory/_lib/mcp.d.ts.map +1 -1
- package/dist/commands/memory/_lib/mcp.js +24 -12
- package/dist/commands/memory/_lib/mcp.js.map +1 -1
- package/dist/commands/memory/_lib/openclaw-auth.d.ts +1 -0
- package/dist/commands/memory/_lib/openclaw-auth.d.ts.map +1 -1
- package/dist/commands/memory/_lib/openclaw-auth.js +14 -3
- package/dist/commands/memory/_lib/openclaw-auth.js.map +1 -1
- package/dist/commands/memory/_lib/openclaw-cmd.js +1 -1
- package/dist/commands/memory/_lib/openclaw-cmd.js.map +1 -1
- package/dist/commands/memory/_lib/schema.d.ts +2 -2
- package/dist/commands/memory/_lib/schema.d.ts.map +1 -1
- package/dist/commands/memory/_lib/schema.js +3 -3
- package/dist/commands/memory/_lib/schema.js.map +1 -1
- package/dist/commands/memory/_lib/seed-cmd.d.ts.map +1 -1
- package/dist/commands/memory/_lib/seed-cmd.js +5 -6
- package/dist/commands/memory/_lib/seed-cmd.js.map +1 -1
- package/dist/commands/memory/run.d.ts.map +1 -1
- package/dist/commands/memory/run.js +2 -2
- package/dist/commands/memory/run.js.map +1 -1
- package/dist/commands/platforms/platform-prompts.d.ts +0 -1
- package/dist/commands/platforms/platform-prompts.d.ts.map +1 -1
- package/dist/commands/platforms/platform-prompts.js +54 -8
- package/dist/commands/platforms/platform-prompts.js.map +1 -1
- package/dist/commands/telemetry.d.ts +10 -0
- package/dist/commands/telemetry.d.ts.map +1 -0
- package/dist/commands/telemetry.js +68 -0
- package/dist/commands/telemetry.js.map +1 -0
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +1 -1
- package/dist/commands/whoami.js.map +1 -1
- package/dist/connectors/README.md +534 -0
- package/dist/connectors/__tests__/browser-scraper-utils.test.ts +186 -0
- package/dist/connectors/browser-scraper-utils.ts +214 -0
- package/dist/connectors/capterra.ts +273 -0
- package/dist/connectors/g2.ts +286 -0
- package/dist/connectors/github.ts +1553 -0
- package/dist/connectors/glassdoor.ts +291 -0
- package/dist/connectors/gmaps.ts +197 -0
- package/dist/connectors/google_calendar.ts +631 -0
- package/dist/connectors/google_gmail.ts +751 -0
- package/dist/connectors/google_photos.ts +776 -0
- package/dist/connectors/google_play.ts +342 -0
- package/dist/connectors/hackernews.ts +471 -0
- package/dist/connectors/index.ts +23 -0
- package/dist/connectors/ios_appstore.ts +226 -0
- package/dist/connectors/linkedin.ts +471 -0
- package/dist/connectors/microsoft_outlook.ts +410 -0
- package/dist/connectors/producthunt.ts +471 -0
- package/dist/connectors/reddit.ts +600 -0
- package/dist/connectors/rss.ts +448 -0
- package/dist/connectors/spotify.ts +590 -0
- package/dist/connectors/trustpilot.ts +199 -0
- package/dist/connectors/website.ts +629 -0
- package/dist/connectors/whatsapp.ts +1073 -0
- package/dist/connectors/x.ts +526 -0
- package/dist/connectors/youtube.ts +666 -0
- package/dist/db/migrations/00000000000000_baseline.sql +4867 -0
- package/dist/db/migrations/20260405193000_add_mcp_sessions.sql +33 -0
- package/dist/db/migrations/20260408120000_remove_system_connectors.sql +48 -0
- package/dist/db/migrations/20260408120001_optional_compiled_code.sql +6 -0
- package/dist/db/migrations/20260409110000_add_active_watcher_run_index.sql +9 -0
- package/dist/db/migrations/20260409130000_connector_default_config.sql +5 -0
- package/dist/db/migrations/20260410120000_add_agent_secrets.sql +25 -0
- package/dist/db/migrations/20260413170000_add_watcher_group_id.sql +67 -0
- package/dist/db/migrations/20260416120000_add_entity_wa_jid_index.sql +14 -0
- package/dist/db/migrations/20260417100000_add_entity_identities.sql +77 -0
- package/dist/db/migrations/20260418100000_add_auth_runs.sql +83 -0
- package/dist/db/migrations/20260418110000_add_runs_created_by_user.sql +18 -0
- package/dist/db/migrations/20260419120000_add_event_identity_indexes.sql +56 -0
- package/dist/db/migrations/20260420120000_extend_reserved_org_slugs.sql +56 -0
- package/dist/db/migrations/20260424030000_add_watcher_run_correlation.sql +52 -0
- package/dist/db/migrations/20260424130000_relax_events_client_id_fk.sql +47 -0
- package/dist/db/migrations/20260425100000_normalize_watcher_feedback.sql +91 -0
- package/dist/db/migrations/20260425120000_add_run_diagnostics.sql +20 -0
- package/dist/db/migrations/20260425130000_add_repair_agent_plumbing.sql +46 -0
- package/dist/db/migrations/20260426120000_entities_entity_type_fk.sql +101 -0
- package/dist/db/migrations/20260426130000_db_integrity_cleanup.sql +104 -0
- package/dist/db/migrations/20260426130001_db_integrity_cleanup_concurrent.sql +187 -0
- package/dist/db/migrations/20260427133000_events_created_by_nullable.sql +74 -0
- package/dist/db/migrations/20260427140000_identity_engine_indexes.sql +140 -0
- package/dist/db/migrations/20260427150000_drop_events_source_id.sql +177 -0
- package/dist/db/migrations/20260427160000_drop_dead_schema.sql +76 -0
- package/dist/db/migrations/20260427170000_market_founder_to_member.sql +364 -0
- package/dist/db/migrations/20260428040000_cascade_events_watchers_org_fk.sql +66 -0
- package/dist/db/migrations/20260428050000_add_runs_approved_input.sql +9 -0
- package/dist/db/migrations/20260429010000_auth_profile_tenant_scoped_fk.sql +79 -0
- package/dist/db/migrations/20260429060000_extend_runs_for_lobu_queue.sql +108 -0
- package/dist/db/migrations/20260429120000_agent_changed_notify.sql +97 -0
- package/dist/db/migrations/20260429120100_user_auth_profiles_and_model_prefs.sql +36 -0
- package/dist/db/migrations/20260429120200_fix_notify_old_keys.sql +130 -0
- package/dist/db/migrations/20260429130000_oauth_states_cli_sessions_rate_limits.sql +83 -0
- package/dist/db/migrations/20260429140000_phase8_grants_chat_connections_mcp_sessions.sql +84 -0
- package/dist/db/migrations/20260429140100_runs_priority_expires_at_retry_delay.sql +44 -0
- package/dist/db/migrations/20260429180000_drop_invalidatable_cache_triggers.sql +25 -0
- package/dist/db/migrations/20260430005614_agents_apply_fields.sql +21 -0
- package/dist/db/migrations/20260430022231_fix_connection_config_encryption.sql +69 -0
- package/dist/db/migrations/20260430151215_add_task_run_type.sql +77 -0
- package/dist/db/migrations/20260501000000_drop_cli_sessions.sql +27 -0
- package/dist/db/migrations/20260501133000_lobu_memory_mcp_id.sql +117 -0
- package/dist/db/migrations/20260502000000_drop_chat_connections.sql +60 -0
- package/dist/db/migrations/20260503000000_agent_secrets_org_scope.sql +56 -0
- package/dist/db/migrations/20260504000000_flatten_agents_drop_sandbox_model.sql +48 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +147 -23
- package/dist/index.js.map +1 -1
- package/dist/internal/api-client.d.ts +4 -8
- package/dist/internal/api-client.d.ts.map +1 -1
- package/dist/internal/api-client.js +1 -1
- package/dist/internal/api-client.js.map +1 -1
- package/dist/internal/context.js +2 -2
- package/dist/internal/context.js.map +1 -1
- package/dist/internal/credentials.d.ts.map +1 -1
- package/dist/internal/credentials.js +6 -1
- package/dist/internal/credentials.js.map +1 -1
- package/dist/internal/index.d.ts +2 -3
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +2 -2
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/oauth.d.ts +7 -6
- package/dist/internal/oauth.d.ts.map +1 -1
- package/dist/internal/oauth.js +3 -3
- package/dist/internal/project-link.d.ts +10 -0
- package/dist/internal/project-link.d.ts.map +1 -0
- package/dist/internal/project-link.js +48 -0
- package/dist/internal/project-link.js.map +1 -0
- package/dist/providers.json +2 -2
- package/dist/server.bundle.mjs +3173 -4404
- package/dist/start-local.bundle.mjs +71481 -0
- package/dist/templates/README.md.tmpl +10 -11
- package/package.json +14 -12
- package/dist/__tests__/chat.integration.test.d.ts +0 -2
- package/dist/__tests__/chat.integration.test.d.ts.map +0 -1
- package/dist/__tests__/chat.integration.test.js +0 -337
- package/dist/__tests__/chat.integration.test.js.map +0 -1
- package/dist/__tests__/dev.test.d.ts +0 -2
- package/dist/__tests__/dev.test.d.ts.map +0 -1
- package/dist/__tests__/dev.test.js +0 -25
- package/dist/__tests__/dev.test.js.map +0 -1
- package/dist/__tests__/init-memory.test.d.ts +0 -2
- package/dist/__tests__/init-memory.test.d.ts.map +0 -1
- package/dist/__tests__/init-memory.test.js +0 -45
- package/dist/__tests__/init-memory.test.js.map +0 -1
- package/dist/__tests__/token.test.d.ts +0 -2
- package/dist/__tests__/token.test.d.ts.map +0 -1
- package/dist/__tests__/token.test.js +0 -52
- package/dist/__tests__/token.test.js.map +0 -1
- package/dist/commands/_lib/apply/__tests__/client.test.d.ts +0 -2
- package/dist/commands/_lib/apply/__tests__/client.test.d.ts.map +0 -1
- package/dist/commands/_lib/apply/__tests__/client.test.js +0 -23
- package/dist/commands/_lib/apply/__tests__/client.test.js.map +0 -1
- package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts +0 -2
- package/dist/commands/_lib/apply/__tests__/desired-state.test.d.ts.map +0 -1
- package/dist/commands/_lib/apply/__tests__/desired-state.test.js +0 -140
- package/dist/commands/_lib/apply/__tests__/desired-state.test.js.map +0 -1
- package/dist/commands/_lib/apply/__tests__/diff.test.d.ts +0 -2
- package/dist/commands/_lib/apply/__tests__/diff.test.d.ts.map +0 -1
- package/dist/commands/_lib/apply/__tests__/diff.test.js +0 -378
- package/dist/commands/_lib/apply/__tests__/diff.test.js.map +0 -1
- package/dist/commands/apply.d.ts +0 -3
- package/dist/commands/apply.d.ts.map +0 -1
- package/dist/commands/apply.js +0 -5
- package/dist/commands/apply.js.map +0 -1
- package/dist/commands/memory/_lib/openclaw-auth.test.d.ts +0 -2
- package/dist/commands/memory/_lib/openclaw-auth.test.d.ts.map +0 -1
- package/dist/commands/memory/_lib/openclaw-auth.test.js +0 -9
- package/dist/commands/memory/_lib/openclaw-auth.test.js.map +0 -1
- package/dist/internal/__tests__/api-client.test.d.ts +0 -2
- package/dist/internal/__tests__/api-client.test.d.ts.map +0 -1
- package/dist/internal/__tests__/api-client.test.js +0 -95
- package/dist/internal/__tests__/api-client.test.js.map +0 -1
- package/dist/internal/__tests__/context.test.d.ts +0 -2
- package/dist/internal/__tests__/context.test.d.ts.map +0 -1
- package/dist/internal/__tests__/context.test.js +0 -77
- package/dist/internal/__tests__/context.test.js.map +0 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Phase 6: NOTIFY trigger for invalidatableCache invalidation.
|
|
4
|
+
--
|
|
5
|
+
-- The agent-related runtime caches (formerly Redis-backed) read agents,
|
|
6
|
+
-- channel bindings, user-agent associations, and per-(user,agent) auth
|
|
7
|
+
-- profiles directly from Postgres. Each gateway process keeps a small
|
|
8
|
+
-- read-through cache invalidated by `pg_notify('agent_changed', <key>)`.
|
|
9
|
+
--
|
|
10
|
+
-- We emit a single channel name and let the cache implementation match
|
|
11
|
+
-- the payload against the cached key. Channels are deliberately coarse
|
|
12
|
+
-- (one per logical resource family) to keep the postmaster's notification
|
|
13
|
+
-- table small — see invalidatable-cache.ts.
|
|
14
|
+
|
|
15
|
+
CREATE OR REPLACE FUNCTION public.notify_agent_changed()
|
|
16
|
+
RETURNS trigger
|
|
17
|
+
LANGUAGE plpgsql AS $$
|
|
18
|
+
BEGIN
|
|
19
|
+
PERFORM pg_notify('agent_changed', COALESCE(NEW.id, OLD.id));
|
|
20
|
+
RETURN COALESCE(NEW, OLD);
|
|
21
|
+
END;
|
|
22
|
+
$$;
|
|
23
|
+
|
|
24
|
+
DROP TRIGGER IF EXISTS agents_changed_notify ON public.agents;
|
|
25
|
+
CREATE TRIGGER agents_changed_notify
|
|
26
|
+
AFTER INSERT OR UPDATE OR DELETE ON public.agents
|
|
27
|
+
FOR EACH ROW EXECUTE FUNCTION public.notify_agent_changed();
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
-- Channel bindings: payload is "<platform>:<teamId|->:<channelId>" so the
|
|
31
|
+
-- in-process cache (keyed identically) can drop just the affected entry.
|
|
32
|
+
CREATE OR REPLACE FUNCTION public.notify_channel_binding_changed()
|
|
33
|
+
RETURNS trigger
|
|
34
|
+
LANGUAGE plpgsql AS $$
|
|
35
|
+
DECLARE
|
|
36
|
+
rec record;
|
|
37
|
+
payload text;
|
|
38
|
+
BEGIN
|
|
39
|
+
IF TG_OP = 'DELETE' THEN
|
|
40
|
+
rec := OLD;
|
|
41
|
+
ELSE
|
|
42
|
+
rec := NEW;
|
|
43
|
+
END IF;
|
|
44
|
+
payload := format(
|
|
45
|
+
'%s:%s:%s',
|
|
46
|
+
rec.platform,
|
|
47
|
+
COALESCE(rec.team_id, '-'),
|
|
48
|
+
rec.channel_id
|
|
49
|
+
);
|
|
50
|
+
PERFORM pg_notify('channel_binding_changed', payload);
|
|
51
|
+
RETURN COALESCE(NEW, OLD);
|
|
52
|
+
END;
|
|
53
|
+
$$;
|
|
54
|
+
|
|
55
|
+
DROP TRIGGER IF EXISTS agent_channel_bindings_changed_notify ON public.agent_channel_bindings;
|
|
56
|
+
CREATE TRIGGER agent_channel_bindings_changed_notify
|
|
57
|
+
AFTER INSERT OR UPDATE OR DELETE ON public.agent_channel_bindings
|
|
58
|
+
FOR EACH ROW EXECUTE FUNCTION public.notify_channel_binding_changed();
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
-- User-agent associations: payload is "<platform>:<userId>" so the
|
|
62
|
+
-- agent listing cache for a single user can be dropped without affecting
|
|
63
|
+
-- other users.
|
|
64
|
+
CREATE OR REPLACE FUNCTION public.notify_agent_users_changed()
|
|
65
|
+
RETURNS trigger
|
|
66
|
+
LANGUAGE plpgsql AS $$
|
|
67
|
+
DECLARE
|
|
68
|
+
rec record;
|
|
69
|
+
BEGIN
|
|
70
|
+
IF TG_OP = 'DELETE' THEN
|
|
71
|
+
rec := OLD;
|
|
72
|
+
ELSE
|
|
73
|
+
rec := NEW;
|
|
74
|
+
END IF;
|
|
75
|
+
PERFORM pg_notify(
|
|
76
|
+
'agent_users_changed',
|
|
77
|
+
format('%s:%s', rec.platform, rec.user_id)
|
|
78
|
+
);
|
|
79
|
+
RETURN COALESCE(NEW, OLD);
|
|
80
|
+
END;
|
|
81
|
+
$$;
|
|
82
|
+
|
|
83
|
+
DROP TRIGGER IF EXISTS agent_users_changed_notify ON public.agent_users;
|
|
84
|
+
CREATE TRIGGER agent_users_changed_notify
|
|
85
|
+
AFTER INSERT OR UPDATE OR DELETE ON public.agent_users
|
|
86
|
+
FOR EACH ROW EXECUTE FUNCTION public.notify_agent_users_changed();
|
|
87
|
+
|
|
88
|
+
-- migrate:down
|
|
89
|
+
|
|
90
|
+
DROP TRIGGER IF EXISTS agent_users_changed_notify ON public.agent_users;
|
|
91
|
+
DROP FUNCTION IF EXISTS public.notify_agent_users_changed();
|
|
92
|
+
|
|
93
|
+
DROP TRIGGER IF EXISTS agent_channel_bindings_changed_notify ON public.agent_channel_bindings;
|
|
94
|
+
DROP FUNCTION IF EXISTS public.notify_channel_binding_changed();
|
|
95
|
+
|
|
96
|
+
DROP TRIGGER IF EXISTS agents_changed_notify ON public.agents;
|
|
97
|
+
DROP FUNCTION IF EXISTS public.notify_agent_changed();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Phase 6: PG-backed storage for per-user runtime state previously held in Redis.
|
|
4
|
+
--
|
|
5
|
+
-- These tables replace Redis-keyed structures:
|
|
6
|
+
-- user:auth-profiles:{userId}:{agentId} → user_auth_profiles
|
|
7
|
+
-- {providerId}:model_preference:{userId} → user_model_preferences
|
|
8
|
+
--
|
|
9
|
+
-- The previous Redis layout kept the credential/refreshToken in the secret
|
|
10
|
+
-- store and persisted only refs in the cached JSON; we keep that contract
|
|
11
|
+
-- and store the same JSON document in `profiles` here.
|
|
12
|
+
|
|
13
|
+
CREATE TABLE IF NOT EXISTS public.user_auth_profiles (
|
|
14
|
+
user_id text NOT NULL,
|
|
15
|
+
agent_id text NOT NULL,
|
|
16
|
+
profiles jsonb DEFAULT '[]'::jsonb NOT NULL,
|
|
17
|
+
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
18
|
+
PRIMARY KEY (user_id, agent_id)
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
CREATE INDEX IF NOT EXISTS user_auth_profiles_agent_id_idx
|
|
22
|
+
ON public.user_auth_profiles (agent_id);
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
CREATE TABLE IF NOT EXISTS public.user_model_preferences (
|
|
26
|
+
user_id text NOT NULL,
|
|
27
|
+
provider_id text NOT NULL,
|
|
28
|
+
model text NOT NULL,
|
|
29
|
+
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
|
30
|
+
PRIMARY KEY (user_id, provider_id)
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
-- migrate:down
|
|
34
|
+
|
|
35
|
+
DROP TABLE IF EXISTS public.user_model_preferences;
|
|
36
|
+
DROP TABLE IF EXISTS public.user_auth_profiles;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Phase 6 follow-up: emit BOTH the OLD and NEW cache keys when a row's key
|
|
4
|
+
-- columns change in an UPDATE. The Phase-6 triggers only emitted the NEW
|
|
5
|
+
-- key, so a process that had the OLD key cached would miss the invalidation
|
|
6
|
+
-- and serve stale data until the TTL expired.
|
|
7
|
+
|
|
8
|
+
-- Phase 6 follow-up: add a partial unique index that treats NULL team_id as
|
|
9
|
+
-- a single equivalence class. Postgres unique constraints treat NULLs as
|
|
10
|
+
-- distinct, so the existing UNIQUE (platform, channel_id, team_id) lets
|
|
11
|
+
-- repeated upserts of (platform, channel_id, NULL) insert duplicate rows;
|
|
12
|
+
-- the matching ON CONFLICT clause never fires and a subsequent getBinding()
|
|
13
|
+
-- reads an arbitrary row.
|
|
14
|
+
--
|
|
15
|
+
-- Using a partial index lets the existing platform/channel_id/team_id
|
|
16
|
+
-- constraint stay in place for the team_id-set rows; the new index covers
|
|
17
|
+
-- the team_id-null branch.
|
|
18
|
+
CREATE UNIQUE INDEX IF NOT EXISTS agent_channel_bindings_no_team_unique
|
|
19
|
+
ON public.agent_channel_bindings (platform, channel_id)
|
|
20
|
+
WHERE team_id IS NULL;
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
CREATE OR REPLACE FUNCTION public.notify_channel_binding_changed()
|
|
24
|
+
RETURNS trigger
|
|
25
|
+
LANGUAGE plpgsql AS $$
|
|
26
|
+
DECLARE
|
|
27
|
+
new_payload text;
|
|
28
|
+
old_payload text;
|
|
29
|
+
BEGIN
|
|
30
|
+
IF TG_OP = 'DELETE' THEN
|
|
31
|
+
PERFORM pg_notify(
|
|
32
|
+
'channel_binding_changed',
|
|
33
|
+
format('%s:%s:%s', OLD.platform, COALESCE(OLD.team_id, '-'), OLD.channel_id)
|
|
34
|
+
);
|
|
35
|
+
RETURN OLD;
|
|
36
|
+
END IF;
|
|
37
|
+
|
|
38
|
+
new_payload := format(
|
|
39
|
+
'%s:%s:%s', NEW.platform, COALESCE(NEW.team_id, '-'), NEW.channel_id
|
|
40
|
+
);
|
|
41
|
+
PERFORM pg_notify('channel_binding_changed', new_payload);
|
|
42
|
+
|
|
43
|
+
IF TG_OP = 'UPDATE' THEN
|
|
44
|
+
old_payload := format(
|
|
45
|
+
'%s:%s:%s', OLD.platform, COALESCE(OLD.team_id, '-'), OLD.channel_id
|
|
46
|
+
);
|
|
47
|
+
IF old_payload <> new_payload THEN
|
|
48
|
+
PERFORM pg_notify('channel_binding_changed', old_payload);
|
|
49
|
+
END IF;
|
|
50
|
+
END IF;
|
|
51
|
+
|
|
52
|
+
RETURN NEW;
|
|
53
|
+
END;
|
|
54
|
+
$$;
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
CREATE OR REPLACE FUNCTION public.notify_agent_users_changed()
|
|
58
|
+
RETURNS trigger
|
|
59
|
+
LANGUAGE plpgsql AS $$
|
|
60
|
+
DECLARE
|
|
61
|
+
new_payload text;
|
|
62
|
+
old_payload text;
|
|
63
|
+
BEGIN
|
|
64
|
+
IF TG_OP = 'DELETE' THEN
|
|
65
|
+
PERFORM pg_notify(
|
|
66
|
+
'agent_users_changed', format('%s:%s', OLD.platform, OLD.user_id)
|
|
67
|
+
);
|
|
68
|
+
RETURN OLD;
|
|
69
|
+
END IF;
|
|
70
|
+
|
|
71
|
+
new_payload := format('%s:%s', NEW.platform, NEW.user_id);
|
|
72
|
+
PERFORM pg_notify('agent_users_changed', new_payload);
|
|
73
|
+
|
|
74
|
+
IF TG_OP = 'UPDATE' THEN
|
|
75
|
+
old_payload := format('%s:%s', OLD.platform, OLD.user_id);
|
|
76
|
+
IF old_payload <> new_payload THEN
|
|
77
|
+
PERFORM pg_notify('agent_users_changed', old_payload);
|
|
78
|
+
END IF;
|
|
79
|
+
END IF;
|
|
80
|
+
|
|
81
|
+
RETURN NEW;
|
|
82
|
+
END;
|
|
83
|
+
$$;
|
|
84
|
+
|
|
85
|
+
-- migrate:down
|
|
86
|
+
|
|
87
|
+
DROP INDEX IF EXISTS public.agent_channel_bindings_no_team_unique;
|
|
88
|
+
|
|
89
|
+
-- Restore the Phase-6 (pre-fix) function bodies, which only emitted the NEW key.
|
|
90
|
+
CREATE OR REPLACE FUNCTION public.notify_channel_binding_changed()
|
|
91
|
+
RETURNS trigger
|
|
92
|
+
LANGUAGE plpgsql AS $$
|
|
93
|
+
DECLARE
|
|
94
|
+
rec record;
|
|
95
|
+
payload text;
|
|
96
|
+
BEGIN
|
|
97
|
+
IF TG_OP = 'DELETE' THEN
|
|
98
|
+
rec := OLD;
|
|
99
|
+
ELSE
|
|
100
|
+
rec := NEW;
|
|
101
|
+
END IF;
|
|
102
|
+
payload := format(
|
|
103
|
+
'%s:%s:%s',
|
|
104
|
+
rec.platform,
|
|
105
|
+
COALESCE(rec.team_id, '-'),
|
|
106
|
+
rec.channel_id
|
|
107
|
+
);
|
|
108
|
+
PERFORM pg_notify('channel_binding_changed', payload);
|
|
109
|
+
RETURN COALESCE(NEW, OLD);
|
|
110
|
+
END;
|
|
111
|
+
$$;
|
|
112
|
+
|
|
113
|
+
CREATE OR REPLACE FUNCTION public.notify_agent_users_changed()
|
|
114
|
+
RETURNS trigger
|
|
115
|
+
LANGUAGE plpgsql AS $$
|
|
116
|
+
DECLARE
|
|
117
|
+
rec record;
|
|
118
|
+
BEGIN
|
|
119
|
+
IF TG_OP = 'DELETE' THEN
|
|
120
|
+
rec := OLD;
|
|
121
|
+
ELSE
|
|
122
|
+
rec := NEW;
|
|
123
|
+
END IF;
|
|
124
|
+
PERFORM pg_notify(
|
|
125
|
+
'agent_users_changed',
|
|
126
|
+
format('%s:%s', rec.platform, rec.user_id)
|
|
127
|
+
);
|
|
128
|
+
RETURN COALESCE(NEW, OLD);
|
|
129
|
+
END;
|
|
130
|
+
$$;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Phase 7 of Redis -> Postgres migration: replace the three remaining
|
|
4
|
+
-- ephemeral-key Redis stores (OAuth state CSRF nonces, CLI auth sessions,
|
|
5
|
+
-- and the fixed-window rate limiter) with thin Postgres tables. None of
|
|
6
|
+
-- these need cross-process pub/sub or pipelining; they're cheap row-level
|
|
7
|
+
-- reads/writes with a TTL column for lazy cleanup on read plus a periodic
|
|
8
|
+
-- vacuum task.
|
|
9
|
+
--
|
|
10
|
+
-- Design notes:
|
|
11
|
+
-- - All three tables key on a text id and store an explicit `expires_at`.
|
|
12
|
+
-- Lazy reads filter `expires_at > now()`; a background sweeper deletes
|
|
13
|
+
-- stale rows in bulk.
|
|
14
|
+
-- - `payload` is jsonb so the OAuth state stores can keep their schema
|
|
15
|
+
-- flexible (PKCE verifier, redirect URI, MCP discovery context, etc.)
|
|
16
|
+
-- without churning migrations.
|
|
17
|
+
-- - The rate_limits table is a single counter per (key, window_started_at)
|
|
18
|
+
-- instead of one row per request — the existing Redis impl is a fixed
|
|
19
|
+
-- window with INCR + EXPIRE, so a counter row matches the semantics
|
|
20
|
+
-- exactly.
|
|
21
|
+
|
|
22
|
+
-- OAuth state nonces: provider PKCE flows, MCP OAuth flow, Slack install,
|
|
23
|
+
-- CLI browser/device handoff. `scope` mirrors the Redis key-prefix
|
|
24
|
+
-- (e.g. `claude:oauth_state`, `mcp-oauth:state`, `slack:oauth:state`,
|
|
25
|
+
-- `cli:auth:request`) so a single lookup tagged by scope+id replaces the
|
|
26
|
+
-- previous prefix+id Redis lookup.
|
|
27
|
+
CREATE TABLE IF NOT EXISTS public.oauth_states (
|
|
28
|
+
id text PRIMARY KEY,
|
|
29
|
+
scope text NOT NULL,
|
|
30
|
+
payload jsonb NOT NULL,
|
|
31
|
+
expires_at timestamptz NOT NULL,
|
|
32
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
CREATE INDEX IF NOT EXISTS oauth_states_scope_idx
|
|
36
|
+
ON public.oauth_states (scope);
|
|
37
|
+
|
|
38
|
+
CREATE INDEX IF NOT EXISTS oauth_states_expires_at_idx
|
|
39
|
+
ON public.oauth_states (expires_at);
|
|
40
|
+
|
|
41
|
+
-- CLI auth sessions for the `lobu` CLI. Each row is a long-lived (30 day)
|
|
42
|
+
-- refresh-token-anchored session; the access token is JWT-shaped and
|
|
43
|
+
-- carries `sessionId` so verifyAccessToken can re-check the row exists
|
|
44
|
+
-- and hasn't been revoked.
|
|
45
|
+
CREATE TABLE IF NOT EXISTS public.cli_sessions (
|
|
46
|
+
session_id text PRIMARY KEY,
|
|
47
|
+
user_id text NOT NULL,
|
|
48
|
+
email text,
|
|
49
|
+
name text,
|
|
50
|
+
refresh_token_id text NOT NULL,
|
|
51
|
+
expires_at timestamptz NOT NULL,
|
|
52
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
CREATE INDEX IF NOT EXISTS cli_sessions_user_id_idx
|
|
56
|
+
ON public.cli_sessions (user_id);
|
|
57
|
+
|
|
58
|
+
CREATE INDEX IF NOT EXISTS cli_sessions_expires_at_idx
|
|
59
|
+
ON public.cli_sessions (expires_at);
|
|
60
|
+
|
|
61
|
+
-- Fixed-window rate limit counters. One row per (key, window_started_at);
|
|
62
|
+
-- a successful consume() does an UPSERT that increments `count`, sets the
|
|
63
|
+
-- window if missing, and returns the new count. The window expires when
|
|
64
|
+
-- `expires_at <= now()`.
|
|
65
|
+
--
|
|
66
|
+
-- `key` is the same string the Redis impl used (`rate-limit:cli:admin-login:<ip>`,
|
|
67
|
+
-- etc) so callers don't have to translate.
|
|
68
|
+
CREATE TABLE IF NOT EXISTS public.rate_limits (
|
|
69
|
+
key text PRIMARY KEY,
|
|
70
|
+
count integer NOT NULL DEFAULT 0,
|
|
71
|
+
window_started_at timestamptz NOT NULL,
|
|
72
|
+
expires_at timestamptz NOT NULL,
|
|
73
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
CREATE INDEX IF NOT EXISTS rate_limits_expires_at_idx
|
|
77
|
+
ON public.rate_limits (expires_at);
|
|
78
|
+
|
|
79
|
+
-- migrate:down
|
|
80
|
+
|
|
81
|
+
DROP TABLE IF EXISTS public.rate_limits;
|
|
82
|
+
DROP TABLE IF EXISTS public.cli_sessions;
|
|
83
|
+
DROP TABLE IF EXISTS public.oauth_states;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Phase 8 of Redis -> Postgres migration: replace the remaining Redis-only
|
|
4
|
+
-- substrates that don't fit cleanly into the existing tables with proper
|
|
5
|
+
-- typed Postgres tables.
|
|
6
|
+
--
|
|
7
|
+
-- 1. `grants` — per-(agent, kind, pattern) grant rows. Replaces the
|
|
8
|
+
-- `grant:<agentId>:<pattern>` Redis key prefix and SCAN-by-prefix list.
|
|
9
|
+
-- Wildcard expansion happens in the application layer.
|
|
10
|
+
-- 2. `chat_connections` — chat-platform (Telegram/Slack/Discord/...) connection
|
|
11
|
+
-- rows. Replaces the `connection:<id>`, `connections:all`, and
|
|
12
|
+
-- `connections:agent:<id>` Redis keys used by ChatInstanceManager. The
|
|
13
|
+
-- existing `public.connections` table is for Lobu product connectors,
|
|
14
|
+
-- not chat platforms.
|
|
15
|
+
-- 3. `mcp_proxy_sessions` — short-lived MCP server session-id mappings used
|
|
16
|
+
-- by the MCP proxy. The existing `public.mcp_sessions` table is the
|
|
17
|
+
-- inbound MCP-server-as-server session table; this is the outbound
|
|
18
|
+
-- upstream-MCP session-id cache.
|
|
19
|
+
|
|
20
|
+
-- ============================================================================
|
|
21
|
+
-- grants
|
|
22
|
+
-- ============================================================================
|
|
23
|
+
|
|
24
|
+
CREATE TABLE IF NOT EXISTS public.grants (
|
|
25
|
+
agent_id text NOT NULL REFERENCES public.agents(id) ON DELETE CASCADE,
|
|
26
|
+
kind text NOT NULL,
|
|
27
|
+
pattern text NOT NULL,
|
|
28
|
+
expires_at timestamptz,
|
|
29
|
+
granted_at timestamptz NOT NULL DEFAULT now(),
|
|
30
|
+
denied boolean NOT NULL DEFAULT false,
|
|
31
|
+
PRIMARY KEY (agent_id, kind, pattern)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
CREATE INDEX IF NOT EXISTS grants_agent_id_idx
|
|
35
|
+
ON public.grants (agent_id);
|
|
36
|
+
|
|
37
|
+
CREATE INDEX IF NOT EXISTS grants_expires_at_idx
|
|
38
|
+
ON public.grants (expires_at)
|
|
39
|
+
WHERE expires_at IS NOT NULL;
|
|
40
|
+
|
|
41
|
+
-- ============================================================================
|
|
42
|
+
-- chat_connections
|
|
43
|
+
-- ============================================================================
|
|
44
|
+
|
|
45
|
+
CREATE TABLE IF NOT EXISTS public.chat_connections (
|
|
46
|
+
id text PRIMARY KEY,
|
|
47
|
+
platform text NOT NULL,
|
|
48
|
+
template_agent_id text REFERENCES public.agents(id) ON DELETE CASCADE,
|
|
49
|
+
config jsonb NOT NULL,
|
|
50
|
+
settings jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
51
|
+
metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
52
|
+
status text NOT NULL DEFAULT 'active',
|
|
53
|
+
error_message text,
|
|
54
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
55
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
CREATE INDEX IF NOT EXISTS chat_connections_template_agent_id_idx
|
|
59
|
+
ON public.chat_connections (template_agent_id)
|
|
60
|
+
WHERE template_agent_id IS NOT NULL;
|
|
61
|
+
|
|
62
|
+
CREATE INDEX IF NOT EXISTS chat_connections_platform_idx
|
|
63
|
+
ON public.chat_connections (platform);
|
|
64
|
+
|
|
65
|
+
-- ============================================================================
|
|
66
|
+
-- mcp_proxy_sessions (NOT to be confused with public.mcp_sessions which is
|
|
67
|
+
-- the inbound MCP server's session table)
|
|
68
|
+
-- ============================================================================
|
|
69
|
+
|
|
70
|
+
CREATE TABLE IF NOT EXISTS public.mcp_proxy_sessions (
|
|
71
|
+
session_key text PRIMARY KEY,
|
|
72
|
+
upstream_session_id text NOT NULL,
|
|
73
|
+
expires_at timestamptz NOT NULL,
|
|
74
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
CREATE INDEX IF NOT EXISTS mcp_proxy_sessions_expires_at_idx
|
|
78
|
+
ON public.mcp_proxy_sessions (expires_at);
|
|
79
|
+
|
|
80
|
+
-- migrate:down
|
|
81
|
+
|
|
82
|
+
DROP TABLE IF EXISTS public.mcp_proxy_sessions;
|
|
83
|
+
DROP TABLE IF EXISTS public.chat_connections;
|
|
84
|
+
DROP TABLE IF EXISTS public.grants;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Phase 10 of Redis -> Postgres migration: honor the caller-supplied
|
|
4
|
+
-- queue options (priority, expireInSeconds, retryDelay) that RunsQueue
|
|
5
|
+
-- previously dropped on the floor.
|
|
6
|
+
--
|
|
7
|
+
-- 1. priority: int, default 0; claim ORDER BY priority DESC, run_at ASC, id ASC.
|
|
8
|
+
-- 2. expires_at: row-level TTL. Claim filter excludes expired rows; the
|
|
9
|
+
-- periodic cleanup task deletes them.
|
|
10
|
+
-- 3. retry_delay_seconds: when set, scheduleRetry uses fixed-delay backoff
|
|
11
|
+
-- instead of exponential. NULL falls back to the existing exponential
|
|
12
|
+
-- cap-300s curve.
|
|
13
|
+
|
|
14
|
+
ALTER TABLE public.runs
|
|
15
|
+
ADD COLUMN IF NOT EXISTS priority integer NOT NULL DEFAULT 0,
|
|
16
|
+
ADD COLUMN IF NOT EXISTS expires_at timestamptz,
|
|
17
|
+
ADD COLUMN IF NOT EXISTS retry_delay_seconds integer;
|
|
18
|
+
|
|
19
|
+
-- Refresh the lobu-claim index so priority + run_at decide claim order.
|
|
20
|
+
DROP INDEX IF EXISTS public.runs_lobu_claim_idx;
|
|
21
|
+
|
|
22
|
+
CREATE INDEX IF NOT EXISTS runs_lobu_claim_idx
|
|
23
|
+
ON public.runs (run_type, queue_name, priority DESC, run_at ASC, id ASC)
|
|
24
|
+
WHERE status = 'pending'
|
|
25
|
+
AND run_type IN ('chat_message', 'schedule', 'agent_run', 'internal');
|
|
26
|
+
|
|
27
|
+
CREATE INDEX IF NOT EXISTS runs_expires_at_idx
|
|
28
|
+
ON public.runs (expires_at)
|
|
29
|
+
WHERE expires_at IS NOT NULL;
|
|
30
|
+
|
|
31
|
+
-- migrate:down
|
|
32
|
+
|
|
33
|
+
DROP INDEX IF EXISTS public.runs_expires_at_idx;
|
|
34
|
+
DROP INDEX IF EXISTS public.runs_lobu_claim_idx;
|
|
35
|
+
|
|
36
|
+
CREATE INDEX IF NOT EXISTS runs_lobu_claim_idx
|
|
37
|
+
ON public.runs (run_type, queue_name, run_at)
|
|
38
|
+
WHERE status = 'pending'
|
|
39
|
+
AND run_type IN ('chat_message', 'schedule', 'agent_run', 'internal');
|
|
40
|
+
|
|
41
|
+
ALTER TABLE public.runs
|
|
42
|
+
DROP COLUMN IF EXISTS retry_delay_seconds,
|
|
43
|
+
DROP COLUMN IF EXISTS expires_at,
|
|
44
|
+
DROP COLUMN IF EXISTS priority;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Drop the NOTIFY triggers + functions that powered the InvalidatableCache.
|
|
4
|
+
-- The cache layer was removed: stores read-through to PG directly. Reads sit
|
|
5
|
+
-- at ~7 SELECTs per chat dispatch — well within PG capacity at current scale.
|
|
6
|
+
-- The runs-queue's pg_notify('runs_lobu:<queue>', ...) wakeup path is
|
|
7
|
+
-- unaffected (different channel, different trigger).
|
|
8
|
+
|
|
9
|
+
DROP TRIGGER IF EXISTS agent_users_changed_notify ON public.agent_users;
|
|
10
|
+
DROP FUNCTION IF EXISTS public.notify_agent_users_changed();
|
|
11
|
+
|
|
12
|
+
DROP TRIGGER IF EXISTS agent_channel_bindings_changed_notify ON public.agent_channel_bindings;
|
|
13
|
+
DROP FUNCTION IF EXISTS public.notify_channel_binding_changed();
|
|
14
|
+
|
|
15
|
+
DROP TRIGGER IF EXISTS agents_changed_notify ON public.agents;
|
|
16
|
+
DROP FUNCTION IF EXISTS public.notify_agent_changed();
|
|
17
|
+
|
|
18
|
+
-- migrate:down
|
|
19
|
+
|
|
20
|
+
-- Restoration mirrors 20260429120000_agent_changed_notify.sql +
|
|
21
|
+
-- 20260429120200_fix_notify_old_keys.sql; if you need to roll back, replay
|
|
22
|
+
-- those by hand. We don't reproduce them here because the cache that consumed
|
|
23
|
+
-- these channels no longer exists — a rollback to the old behavior requires
|
|
24
|
+
-- restoring the cache code too.
|
|
25
|
+
SELECT 1;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Persist three agent settings fields that the file-loader produces from
|
|
4
|
+
-- lobu.toml but the postgres-backed AgentConfigStore had nowhere to put:
|
|
5
|
+
-- * egress_config -> AgentSettings.egressConfig
|
|
6
|
+
-- * pre_approved_tools -> AgentSettings.preApprovedTools
|
|
7
|
+
-- * guardrails -> AgentSettings.guardrails
|
|
8
|
+
-- Without these columns, `lobu apply` would silently drop the values on
|
|
9
|
+
-- every push, producing perpetual drift between local and cloud.
|
|
10
|
+
|
|
11
|
+
ALTER TABLE public.agents
|
|
12
|
+
ADD COLUMN egress_config jsonb DEFAULT '{}'::jsonb,
|
|
13
|
+
ADD COLUMN pre_approved_tools jsonb DEFAULT '[]'::jsonb,
|
|
14
|
+
ADD COLUMN guardrails jsonb DEFAULT '[]'::jsonb;
|
|
15
|
+
|
|
16
|
+
-- migrate:down
|
|
17
|
+
|
|
18
|
+
ALTER TABLE public.agents
|
|
19
|
+
DROP COLUMN egress_config,
|
|
20
|
+
DROP COLUMN pre_approved_tools,
|
|
21
|
+
DROP COLUMN guardrails;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
-- migrate:up
|
|
2
|
+
|
|
3
|
+
-- Fix connection-config encryption asymmetry in agent_connections.config.
|
|
4
|
+
--
|
|
5
|
+
-- encryptConfig() in postgres-stores.ts historically returned raw
|
|
6
|
+
-- "iv:tag:ciphertext" output from @lobu/core's `encrypt()`, but
|
|
7
|
+
-- decryptConfig() only decrypts strings that start with "enc:v1:". So any
|
|
8
|
+
-- secret-named field that hit encryptConfig was stored as prefixless
|
|
9
|
+
-- ciphertext and round-tripped as that ciphertext literal on read.
|
|
10
|
+
--
|
|
11
|
+
-- This migration backfills existing prefixless rows by re-prefixing them so
|
|
12
|
+
-- the now-aligned decryptConfig path can decrypt them.
|
|
13
|
+
--
|
|
14
|
+
-- Identification: AES-GCM in @lobu/core uses a 12-byte IV (24 hex chars)
|
|
15
|
+
-- and a 16-byte auth tag (32 hex chars), joined with the ciphertext as
|
|
16
|
+
-- `iv:tag:ciphertext`. We match exactly that shape to avoid touching
|
|
17
|
+
-- arbitrary `:` separated values.
|
|
18
|
+
--
|
|
19
|
+
-- Idempotent: jsonb_object_agg only rewrites string values that match the
|
|
20
|
+
-- prefixless shape AND lack the prefix. Re-running the migration is a noop.
|
|
21
|
+
|
|
22
|
+
UPDATE public.agent_connections AS ac
|
|
23
|
+
SET config = sub.fixed_config
|
|
24
|
+
FROM (
|
|
25
|
+
SELECT
|
|
26
|
+
id,
|
|
27
|
+
jsonb_object_agg(
|
|
28
|
+
key,
|
|
29
|
+
CASE
|
|
30
|
+
WHEN jsonb_typeof(value) = 'string'
|
|
31
|
+
AND value #>> '{}' ~ '^[0-9a-f]{24}:[0-9a-f]{32}:[0-9a-f]+$'
|
|
32
|
+
AND value #>> '{}' NOT LIKE 'enc:v1:%'
|
|
33
|
+
THEN to_jsonb('enc:v1:' || (value #>> '{}'))
|
|
34
|
+
ELSE value
|
|
35
|
+
END
|
|
36
|
+
) AS fixed_config
|
|
37
|
+
FROM public.agent_connections,
|
|
38
|
+
LATERAL jsonb_each(config)
|
|
39
|
+
GROUP BY id
|
|
40
|
+
) AS sub
|
|
41
|
+
WHERE ac.id = sub.id
|
|
42
|
+
AND ac.config IS DISTINCT FROM sub.fixed_config;
|
|
43
|
+
|
|
44
|
+
-- migrate:down
|
|
45
|
+
|
|
46
|
+
-- Strip the "enc:v1:" prefix to restore the prefixless ciphertext shape.
|
|
47
|
+
-- Same regex: only touch strings whose remainder is `iv:tag:ciphertext`.
|
|
48
|
+
|
|
49
|
+
UPDATE public.agent_connections AS ac
|
|
50
|
+
SET config = sub.fixed_config
|
|
51
|
+
FROM (
|
|
52
|
+
SELECT
|
|
53
|
+
id,
|
|
54
|
+
jsonb_object_agg(
|
|
55
|
+
key,
|
|
56
|
+
CASE
|
|
57
|
+
WHEN jsonb_typeof(value) = 'string'
|
|
58
|
+
AND value #>> '{}' LIKE 'enc:v1:%'
|
|
59
|
+
AND substring(value #>> '{}' FROM 8) ~ '^[0-9a-f]{24}:[0-9a-f]{32}:[0-9a-f]+$'
|
|
60
|
+
THEN to_jsonb(substring(value #>> '{}' FROM 8))
|
|
61
|
+
ELSE value
|
|
62
|
+
END
|
|
63
|
+
) AS fixed_config
|
|
64
|
+
FROM public.agent_connections,
|
|
65
|
+
LATERAL jsonb_each(config)
|
|
66
|
+
GROUP BY id
|
|
67
|
+
) AS sub
|
|
68
|
+
WHERE ac.id = sub.id
|
|
69
|
+
AND ac.config IS DISTINCT FROM sub.fixed_config;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
-- migrate:up transaction:false
|
|
2
|
+
|
|
3
|
+
-- Add 'task' to the runs_run_type_check + extend runs_lobu_claim_idx.
|
|
4
|
+
--
|
|
5
|
+
-- Background: the lobu-queue lanes (chat_message, schedule, agent_run,
|
|
6
|
+
-- internal) are claimed in-process by the gateway's RunsQueue. The 'task'
|
|
7
|
+
-- lane extends that pattern for platform-side periodic + lazy work
|
|
8
|
+
-- (token refresh, classification reconciliation, embed backfill, watcher
|
|
9
|
+
-- maintenance, etc.) that previously ran from a single setInterval-driven
|
|
10
|
+
-- maintenance scheduler.
|
|
11
|
+
--
|
|
12
|
+
-- Lock-safety: this migration runs `transaction:false` so that
|
|
13
|
+
-- CREATE INDEX CONCURRENTLY and VALIDATE CONSTRAINT release locks between
|
|
14
|
+
-- statements. Without it, dbmate's per-migration transaction would force
|
|
15
|
+
-- ACCESS EXCLUSIVE on the runs table for the duration of a constraint
|
|
16
|
+
-- validation or index build — visible downtime for a hot queue table.
|
|
17
|
+
SET lock_timeout = '5s';
|
|
18
|
+
|
|
19
|
+
-- 1. Widen the run_type CHECK constraint without scanning the table.
|
|
20
|
+
-- NOT VALID adds the catalog row under a brief ACCESS EXCLUSIVE.
|
|
21
|
+
-- VALIDATE takes only SHARE UPDATE EXCLUSIVE so concurrent reads/writes
|
|
22
|
+
-- are unaffected. Idempotent: re-runs safely if a prior run died midway.
|
|
23
|
+
DO $$
|
|
24
|
+
BEGIN
|
|
25
|
+
IF NOT EXISTS (
|
|
26
|
+
SELECT 1
|
|
27
|
+
FROM pg_constraint
|
|
28
|
+
WHERE conrelid = 'public.runs'::regclass
|
|
29
|
+
AND conname = 'runs_run_type_check_v2'
|
|
30
|
+
) THEN
|
|
31
|
+
ALTER TABLE public.runs
|
|
32
|
+
ADD CONSTRAINT runs_run_type_check_v2 CHECK (run_type = ANY (ARRAY[
|
|
33
|
+
'sync'::text,
|
|
34
|
+
'action'::text,
|
|
35
|
+
'embed_backfill'::text,
|
|
36
|
+
'watcher'::text,
|
|
37
|
+
'auth'::text,
|
|
38
|
+
'chat_message'::text,
|
|
39
|
+
'schedule'::text,
|
|
40
|
+
'agent_run'::text,
|
|
41
|
+
'internal'::text,
|
|
42
|
+
'task'::text
|
|
43
|
+
])) NOT VALID;
|
|
44
|
+
END IF;
|
|
45
|
+
END$$;
|
|
46
|
+
|
|
47
|
+
ALTER TABLE public.runs VALIDATE CONSTRAINT runs_run_type_check_v2;
|
|
48
|
+
|
|
49
|
+
ALTER TABLE public.runs DROP CONSTRAINT IF EXISTS runs_run_type_check;
|
|
50
|
+
ALTER TABLE public.runs RENAME CONSTRAINT runs_run_type_check_v2 TO runs_run_type_check;
|
|
51
|
+
|
|
52
|
+
-- 2. Replace the lobu claim index. Originally written with CONCURRENTLY,
|
|
53
|
+
-- but dbmate's transaction wrapper still presents these to PG as
|
|
54
|
+
-- in-transaction even with `transaction:false`, which breaks
|
|
55
|
+
-- CREATE/DROP INDEX CONCURRENTLY (see comments in
|
|
56
|
+
-- 20260426130001_db_integrity_cleanup_concurrent.sql for the same
|
|
57
|
+
-- workaround). The partial index only covers `status = 'pending'`
|
|
58
|
+
-- rows in the lobu lanes — typically a small set since pending rows
|
|
59
|
+
-- are claimed within milliseconds — so the ACCESS EXCLUSIVE held
|
|
60
|
+
-- during the non-concurrent build is sub-second in practice.
|
|
61
|
+
DROP INDEX IF EXISTS public.runs_lobu_claim_idx;
|
|
62
|
+
|
|
63
|
+
CREATE INDEX runs_lobu_claim_idx
|
|
64
|
+
ON public.runs (run_type, queue_name, priority DESC, run_at ASC, id ASC)
|
|
65
|
+
WHERE status = 'pending'
|
|
66
|
+
AND run_type IN ('chat_message', 'schedule', 'agent_run', 'internal', 'task');
|
|
67
|
+
|
|
68
|
+
-- migrate:down
|
|
69
|
+
|
|
70
|
+
-- This migration is forward-only.
|
|
71
|
+
--
|
|
72
|
+
-- Reverting would require either deleting all rows with run_type='task'
|
|
73
|
+
-- (data loss — both pending tasks and historical run records) or leaving
|
|
74
|
+
-- the constraint widened (the failure mode the up migration was avoiding).
|
|
75
|
+
-- Neither is safe to do automatically. If you genuinely need to revert,
|
|
76
|
+
-- write a follow-up migration that explicitly handles the data first.
|
|
77
|
+
SELECT 1;
|