mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
feat(core): Add the scope column to the agents table (no-changelog) (#41165)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
aadbb7c498
commit
aba5258930
5
docs/generated/postgres-schema/README.md
generated
5
docs/generated/postgres-schema/README.md
generated
|
|
@ -36,7 +36,7 @@ Auto-generated from the PostgreSQL migrations in @n8n/db. Do not edit by hand.
|
|||
| [public.agent_thread_grants](public.agent_thread_grants.md) | 4 | | BASE TABLE |
|
||||
| [public.agent_version_skill_dependency](public.agent_version_skill_dependency.md) | 4 | | BASE TABLE |
|
||||
| [public.agent_workflow_dependency](public.agent_workflow_dependency.md) | 3 | | BASE TABLE |
|
||||
| [public.agents](public.agents.md) | 14 | | BASE TABLE |
|
||||
| [public.agents](public.agents.md) | 15 | | BASE TABLE |
|
||||
| [public.agents_memory_entries](public.agents_memory_entries.md) | 13 | | BASE TABLE |
|
||||
| [public.agents_memory_entry_candidates](public.agents_memory_entry_candidates.md) | 14 | | BASE TABLE |
|
||||
| [public.agents_memory_entry_locks](public.agents_memory_entry_locks.md) | 6 | | BASE TABLE |
|
||||
|
|
@ -240,7 +240,7 @@ erDiagram
|
|||
"public.agent_version_skill_dependency" }o--|| "public.skill_version" : "FOREIGN KEY (#quot;skillVersionId#quot;) REFERENCES skill_version(id)"
|
||||
"public.agent_workflow_dependency" }o--|| "public.workflow_entity" : "FOREIGN KEY (#quot;workflowId#quot;) REFERENCES workflow_entity(id) ON DELETE CASCADE"
|
||||
"public.agent_workflow_dependency" }o--|| "public.agents" : "FOREIGN KEY (#quot;agentId#quot;) REFERENCES agents(id) ON DELETE CASCADE"
|
||||
"public.agents" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
|
||||
"public.agents" }o--o| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
|
||||
"public.agents" }o--o| "public.agent_history" : "FOREIGN KEY (#quot;activeVersionId#quot;) REFERENCES agent_history(#quot;versionId#quot;) ON DELETE SET NULL"
|
||||
"public.agents_memory_entries" }o--|| "public.agents" : "FOREIGN KEY (#quot;agentId#quot;) REFERENCES agents(id) ON DELETE CASCADE"
|
||||
"public.agents_memory_entries" }o--|| "public.agents_resources" : "FOREIGN KEY (#quot;resourceId#quot;) REFERENCES agents_resources(id) ON DELETE CASCADE"
|
||||
|
|
@ -758,6 +758,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
10
docs/generated/postgres-schema/public.agents.md
generated
10
docs/generated/postgres-schema/public.agents.md
generated
|
|
@ -10,9 +10,10 @@
|
|||
| id | varchar(36) | | false | [public.agent_background_job](public.agent_background_job.md) [public.agent_channel_status](public.agent_channel_status.md) [public.agent_chat_attachments](public.agent_chat_attachments.md) [public.agent_chat_subscriptions](public.agent_chat_subscriptions.md) [public.agent_checkpoints](public.agent_checkpoints.md) [public.agent_credential_dependency](public.agent_credential_dependency.md) [public.agent_eval_dataset](public.agent_eval_dataset.md) [public.agent_execution_threads](public.agent_execution_threads.md) [public.agent_files](public.agent_files.md) [public.agent_history](public.agent_history.md) [public.agent_skill_dependency](public.agent_skill_dependency.md) [public.agent_task_definition](public.agent_task_definition.md) [public.agent_task_run_lock](public.agent_task_run_lock.md) [public.agent_workflow_dependency](public.agent_workflow_dependency.md) [public.agents_memory_entries](public.agents_memory_entries.md) [public.agents_memory_entry_candidates](public.agents_memory_entry_candidates.md) [public.agents_memory_entry_locks](public.agents_memory_entry_locks.md) [public.agents_memory_entry_sources](public.agents_memory_entry_sources.md) [public.agents_observation_cursors](public.agents_observation_cursors.md) [public.agents_observation_locks](public.agents_observation_locks.md) [public.agents_observations](public.agents_observations.md) [public.skill](public.skill.md) | | |
|
||||
| integrations | json | '[]'::json | false | | | |
|
||||
| name | varchar(128) | | false | | | |
|
||||
| projectId | varchar(255) | | false | | [public.project](public.project.md) | |
|
||||
| projectId | varchar(255) | | true | | [public.project](public.project.md) | |
|
||||
| revision | integer | 0 | false | | | |
|
||||
| schema | json | | true | | | |
|
||||
| scope | varchar(16) | 'project'::character varying | false | | | project: belongs to projectId; instance: code-defined, belongs to no project (projectId is null) |
|
||||
| setupCompletedAt | timestamp(3) with time zone | | true | | | When this agent first reached a complete, publishable setup |
|
||||
| skills | json | '{}'::json | false | | | |
|
||||
| tools | json | '{}'::json | false | | | |
|
||||
|
|
@ -23,6 +24,8 @@
|
|||
|
||||
| Name | Type | Definition |
|
||||
| ---- | ---- | ---------- |
|
||||
| CHK_agents_scope | CHECK | CHECK (((scope)::text = ANY ((ARRAY['project'::character varying, 'instance'::character varying])::text[]))) |
|
||||
| CHK_agents_scope_project | CHECK | CHECK ((((scope)::text = 'instance'::text) = ("projectId" IS NULL))) |
|
||||
| FK_940597dfe9753375309ce6aeea0 | FOREIGN KEY | FOREIGN KEY ("activeVersionId") REFERENCES agent_history("versionId") ON DELETE SET NULL |
|
||||
| FK_a30d560207c4071d98aa03c179c | FOREIGN KEY | FOREIGN KEY ("projectId") REFERENCES project(id) ON DELETE CASCADE |
|
||||
| PK_9c653f28ae19c5884d5baf6a1d9 | PRIMARY KEY | PRIMARY KEY (id) |
|
||||
|
|
@ -31,8 +34,8 @@
|
|||
| agents_id_not_null | n | NOT NULL id |
|
||||
| agents_integrations_not_null | n | NOT NULL integrations |
|
||||
| agents_name_not_null | n | NOT NULL name |
|
||||
| agents_projectId_not_null | n | NOT NULL "projectId" |
|
||||
| agents_revision_not_null | n | NOT NULL revision |
|
||||
| agents_scope_not_null | n | NOT NULL scope |
|
||||
| agents_skills_not_null | n | NOT NULL skills |
|
||||
| agents_tools_not_null | n | NOT NULL tools |
|
||||
| agents_updatedAt_not_null | n | NOT NULL "updatedAt" |
|
||||
|
|
@ -73,7 +76,7 @@ erDiagram
|
|||
"public.agents_observation_locks" }o--|| "public.agents" : "FOREIGN KEY (#quot;agentId#quot;) REFERENCES agents(id) ON DELETE CASCADE"
|
||||
"public.agents_observations" }o--|| "public.agents" : "FOREIGN KEY (#quot;agentId#quot;) REFERENCES agents(id) ON DELETE CASCADE"
|
||||
"public.skill" }o--o| "public.agents" : "FOREIGN KEY (#quot;agentId#quot;) REFERENCES agents(id) ON DELETE CASCADE"
|
||||
"public.agents" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
|
||||
"public.agents" }o--o| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
|
||||
|
||||
"public.agents" {
|
||||
varchar_36_ activeVersionId FK
|
||||
|
|
@ -85,6 +88,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
3
docs/generated/postgres-schema/public.project.md
generated
3
docs/generated/postgres-schema/public.project.md
generated
|
|
@ -42,7 +42,7 @@ erDiagram
|
|||
"public.activity_event" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
|
||||
"public.agent_chat_attachments" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
|
||||
"public.agent_execution_threads" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
|
||||
"public.agents" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
|
||||
"public.agents" }o--o| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
|
||||
"public.ai_preference" }o--o| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
|
||||
"public.data_table" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
|
||||
"public.folder" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
|
||||
|
|
@ -148,6 +148,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
1
docs/generated/postgres-schema/public.skill.md
generated
1
docs/generated/postgres-schema/public.skill.md
generated
|
|
@ -65,6 +65,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
integer revision
|
||||
json schema
|
||||
varchar_16_ scope
|
||||
timestamp_3__with_time_zone setupCompletedAt
|
||||
json skills
|
||||
json tools
|
||||
|
|
|
|||
5
docs/generated/sqlite-schema/README.md
generated
5
docs/generated/sqlite-schema/README.md
generated
|
|
@ -36,7 +36,7 @@ Auto-generated from the SQLite migrations in @n8n/db. Do not edit by hand.
|
|||
| [agent_thread_grants](agent_thread_grants.md) | 4 | | table |
|
||||
| [agent_version_skill_dependency](agent_version_skill_dependency.md) | 4 | | table |
|
||||
| [agent_workflow_dependency](agent_workflow_dependency.md) | 3 | | table |
|
||||
| [agents](agents.md) | 14 | | table |
|
||||
| [agents](agents.md) | 15 | | table |
|
||||
| [agents_memory_entries](agents_memory_entries.md) | 13 | | table |
|
||||
| [agents_memory_entry_candidates](agents_memory_entry_candidates.md) | 14 | | table |
|
||||
| [agents_memory_entry_locks](agents_memory_entry_locks.md) | 6 | | table |
|
||||
|
|
@ -222,7 +222,7 @@ erDiagram
|
|||
"agent_version_skill_dependency" |o--|| "agent_history" : "FOREIGN KEY (agentVersionId) REFERENCES agent_history (versionId) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"agent_workflow_dependency" |o--|| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"agent_workflow_dependency" |o--|| "agents" : "FOREIGN KEY (agentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"agents" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"agents" }o--o| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"agents" }o--o| "agent_history" : "FOREIGN KEY (activeVersionId) REFERENCES agent_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
|
||||
"agents_memory_entries" }o--o| "agents_memory_entries" : "FOREIGN KEY (supersededBy) REFERENCES agents_memory_entries (id) ON UPDATE NO ACTION ON DELETE NO ACTION MATCH NONE"
|
||||
"agents_memory_entries" }o--|| "agents_resources" : "FOREIGN KEY (resourceId) REFERENCES agents_resources (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
|
|
@ -744,6 +744,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
1
docs/generated/sqlite-schema/agent_files.md
generated
1
docs/generated/sqlite-schema/agent_files.md
generated
|
|
@ -73,6 +73,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
1
docs/generated/sqlite-schema/agent_history.md
generated
1
docs/generated/sqlite-schema/agent_history.md
generated
|
|
@ -74,6 +74,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
10
docs/generated/sqlite-schema/agents.md
generated
10
docs/generated/sqlite-schema/agents.md
generated
|
|
@ -6,7 +6,7 @@
|
|||
<summary><strong>Table Definition</strong></summary>
|
||||
|
||||
```sql
|
||||
CREATE TABLE "agents" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" varchar(128) NOT NULL, "projectId" varchar(255) NOT NULL, "integrations" text NOT NULL DEFAULT ('[]'), "schema" text, "tools" text NOT NULL DEFAULT ('{}'), "skills" text NOT NULL DEFAULT ('{}'), "versionId" varchar(36), "createdAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "updatedAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "activeVersionId" varchar(36), "availableInMCP" boolean NOT NULL DEFAULT (false), "setupCompletedAt" datetime(3), "revision" integer NOT NULL DEFAULT 0, CONSTRAINT "FK_940597dfe9753375309ce6aeea0" FOREIGN KEY ("activeVersionId") REFERENCES "agent_history" ("versionId") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_a30d560207c4071d98aa03c179c" FOREIGN KEY ("projectId") REFERENCES "project" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)
|
||||
CREATE TABLE "agents" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" varchar(128) NOT NULL, "projectId" varchar(255), "integrations" text NOT NULL DEFAULT ('[]'), "schema" text, "tools" text NOT NULL DEFAULT ('{}'), "skills" text NOT NULL DEFAULT ('{}'), "versionId" varchar(36), "createdAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "updatedAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "activeVersionId" varchar(36), "availableInMCP" boolean NOT NULL DEFAULT (false), "setupCompletedAt" datetime(3), "revision" integer NOT NULL DEFAULT (0), "scope" varchar(16) NOT NULL DEFAULT ('project'), CONSTRAINT "CHK_agents_scope" CHECK (("scope" IN ('project', 'instance'))), CONSTRAINT "CHK_agents_scope_project" CHECK (("scope" = 'instance') = ("projectId" IS NULL)), CONSTRAINT "FK_940597dfe9753375309ce6aeea0" FOREIGN KEY ("activeVersionId") REFERENCES "agent_history" ("versionId") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_a30d560207c4071d98aa03c179c" FOREIGN KEY ("projectId") REFERENCES "project" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
|
@ -21,9 +21,10 @@ CREATE TABLE "agents" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" varchar(128
|
|||
| id | varchar(36) | | false | [agent_background_job](agent_background_job.md) [agent_channel_status](agent_channel_status.md) [agent_chat_attachments](agent_chat_attachments.md) [agent_chat_subscriptions](agent_chat_subscriptions.md) [agent_checkpoints](agent_checkpoints.md) [agent_credential_dependency](agent_credential_dependency.md) [agent_eval_dataset](agent_eval_dataset.md) [agent_execution_threads](agent_execution_threads.md) [agent_files](agent_files.md) [agent_history](agent_history.md) [agent_skill_dependency](agent_skill_dependency.md) [agent_task_definition](agent_task_definition.md) [agent_task_run_lock](agent_task_run_lock.md) [agent_workflow_dependency](agent_workflow_dependency.md) [agents_memory_entries](agents_memory_entries.md) [agents_memory_entry_candidates](agents_memory_entry_candidates.md) [agents_memory_entry_locks](agents_memory_entry_locks.md) [agents_memory_entry_sources](agents_memory_entry_sources.md) [agents_observation_cursors](agents_observation_cursors.md) [agents_observation_locks](agents_observation_locks.md) [agents_observations](agents_observations.md) [skill](skill.md) | | |
|
||||
| integrations | TEXT | '[]' | false | | | |
|
||||
| name | varchar(128) | | false | | | |
|
||||
| projectId | varchar(255) | | false | | [project](project.md) | |
|
||||
| projectId | varchar(255) | | true | | [project](project.md) | |
|
||||
| revision | INTEGER | 0 | false | | | |
|
||||
| schema | TEXT | | true | | | |
|
||||
| scope | varchar(16) | 'project' | false | | | |
|
||||
| setupCompletedAt | datetime(3) | | true | | | |
|
||||
| skills | TEXT | '{}' | false | | | |
|
||||
| tools | TEXT | '{}' | false | | | |
|
||||
|
|
@ -34,6 +35,8 @@ CREATE TABLE "agents" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" varchar(128
|
|||
|
||||
| Name | Type | Definition |
|
||||
| ---- | ---- | ---------- |
|
||||
| - | CHECK | CHECK (("scope" IN ('project', 'instance'))) |
|
||||
| - | CHECK | CHECK (("scope" = 'instance') = ("projectId" IS NULL)) |
|
||||
| - (Foreign key ID: 0) | FOREIGN KEY | FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE |
|
||||
| - (Foreign key ID: 1) | FOREIGN KEY | FOREIGN KEY (activeVersionId) REFERENCES agent_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE |
|
||||
| id | PRIMARY KEY | PRIMARY KEY (id) |
|
||||
|
|
@ -75,7 +78,7 @@ erDiagram
|
|||
"agents_observation_locks" |o--|| "agents" : "FOREIGN KEY (agentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"agents_observations" }o--|| "agents" : "FOREIGN KEY (agentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"skill" }o--o| "agents" : "FOREIGN KEY (agentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"agents" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"agents" }o--o| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
|
||||
"agents" {
|
||||
varchar_36_ activeVersionId FK
|
||||
|
|
@ -87,6 +90,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
3
docs/generated/sqlite-schema/project.md
generated
3
docs/generated/sqlite-schema/project.md
generated
|
|
@ -48,7 +48,7 @@ erDiagram
|
|||
"activity_event" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"agent_chat_attachments" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"agent_execution_threads" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"agents" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"agents" }o--o| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"ai_preference" }o--o| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"data_table" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
"folder" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
|
||||
|
|
@ -154,6 +154,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
1
docs/generated/sqlite-schema/skill.md
generated
1
docs/generated/sqlite-schema/skill.md
generated
|
|
@ -73,6 +73,7 @@ erDiagram
|
|||
varchar_255_ projectId FK
|
||||
INTEGER revision
|
||||
TEXT schema
|
||||
varchar_16_ scope
|
||||
datetime_3_ setupCompletedAt
|
||||
TEXT skills
|
||||
TEXT tools
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
import { TableCheck } from '@n8n/typeorm';
|
||||
|
||||
import type { MigrationContext, ReversibleMigration } from '../migration-types';
|
||||
|
||||
const table = 'agents';
|
||||
const scopeCheck = 'agents_scope_project';
|
||||
|
||||
/**
|
||||
* Adds instance-scoped agents. An instance agent belongs to no project, so
|
||||
* its `projectId` is null. Its runtime comes from code, and each of its
|
||||
* threads carries its own working project. A CHECK constraint makes sure that
|
||||
* `projectId` is null exactly for instance agents.
|
||||
*/
|
||||
export class AddInstanceScopeToAgents1792051765225 implements ReversibleMigration {
|
||||
async up(context: MigrationContext) {
|
||||
const {
|
||||
schemaBuilder: { addColumns, column, dropNotNull },
|
||||
} = context;
|
||||
await addColumns(
|
||||
table,
|
||||
[
|
||||
column('scope')
|
||||
.varchar(16)
|
||||
.notNull.default("'project'")
|
||||
.withEnumCheck(['project', 'instance'])
|
||||
.comment(
|
||||
'project: belongs to projectId; instance: code-defined, belongs to no project (projectId is null)',
|
||||
),
|
||||
],
|
||||
{ recreatesOnSqlite: true },
|
||||
);
|
||||
await dropNotNull(table, 'projectId', { recreatesOnSqlite: true });
|
||||
await this.addScopeCheck(context);
|
||||
}
|
||||
|
||||
async down(context: MigrationContext) {
|
||||
const {
|
||||
schemaBuilder: { addNotNull, dropColumns, dropEnumCheck },
|
||||
} = context;
|
||||
// The old schema cannot store an agent without a project.
|
||||
// `DeleteInstanceAgentsOnRevert` already deleted these agents and their
|
||||
// dependent rows with foreign keys on. This delete is a fallback.
|
||||
await this.deleteInstanceAgents(context);
|
||||
await this.dropScopeCheck(context);
|
||||
await addNotNull(table, 'projectId', { recreatesOnSqlite: true });
|
||||
await dropEnumCheck(table, 'scope', { recreatesOnSqlite: true });
|
||||
await dropColumns(table, ['scope'], { recreatesOnSqlite: true });
|
||||
}
|
||||
|
||||
private async addScopeCheck({ escape, queryRunner, tablePrefix }: MigrationContext) {
|
||||
const scope = escape.columnName('scope');
|
||||
const projectId = escape.columnName('projectId');
|
||||
await queryRunner.createCheckConstraint(
|
||||
`${tablePrefix}${table}`,
|
||||
new TableCheck({
|
||||
name: `CHK_${tablePrefix}${scopeCheck}`,
|
||||
expression: `(${scope} = 'instance') = (${projectId} IS NULL)`,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private async dropScopeCheck({ queryRunner, tablePrefix }: MigrationContext) {
|
||||
await queryRunner.dropCheckConstraint(
|
||||
`${tablePrefix}${table}`,
|
||||
`CHK_${tablePrefix}${scopeCheck}`,
|
||||
);
|
||||
}
|
||||
|
||||
private async deleteInstanceAgents({ escape, runQuery }: MigrationContext) {
|
||||
await runQuery(
|
||||
`DELETE FROM ${escape.tableName(table)} WHERE ${escape.columnName('projectId')} IS NULL`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import type { MigrationContext, ReversibleMigration } from '../migration-types';
|
||||
|
||||
/**
|
||||
* Runs before `AddInstanceScopeToAgents` on revert. That migration recreates
|
||||
* `agents` with foreign keys off on SQLite, so deleting instance agents there
|
||||
* does not cascade. This migration keeps foreign keys on and deletes them
|
||||
* first, so the cascades also delete the rows that depend on them.
|
||||
*/
|
||||
export class DeleteInstanceAgentsOnRevert1792139680416 implements ReversibleMigration {
|
||||
async up() {
|
||||
// Nothing to change on upgrade. This migration only does work on revert.
|
||||
}
|
||||
|
||||
async down({ escape, runQuery }: MigrationContext) {
|
||||
await runQuery(
|
||||
`DELETE FROM ${escape.tableName('agents')} WHERE ${escape.columnName('scope')} = 'instance'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { AddInstanceScopeToAgents1792051765225 as BaseMigration } from '../common/1792051765225-AddInstanceScopeToAgents';
|
||||
|
||||
/**
|
||||
* `agents` has incoming cascading foreign keys. Recreating it must not delete
|
||||
* their rows, so foreign keys are off for this migration. On revert,
|
||||
* `DeleteInstanceAgentsOnRevert` deletes instance agents and their dependent
|
||||
* rows first, with foreign keys on.
|
||||
*/
|
||||
export class AddInstanceScopeToAgents1792051765225 extends BaseMigration {
|
||||
withFKsDisabled = true as const;
|
||||
}
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
import {
|
||||
createTestMigrationContext,
|
||||
initDbUpToMigration,
|
||||
runSingleMigration,
|
||||
undoLastSingleMigration,
|
||||
type TestMigrationContext,
|
||||
} from '@n8n/backend-test-utils';
|
||||
import { DbConnection } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import { DataSource } from '@n8n/typeorm';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
const migrationName = 'AddInstanceScopeToAgents1792051765225';
|
||||
|
||||
describe('AddInstanceScopeToAgents migration', () => {
|
||||
let dataSource: DataSource;
|
||||
|
||||
beforeAll(async () => {
|
||||
await Container.get(DbConnection).init();
|
||||
dataSource = Container.get(DataSource);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await withContext(async ({ queryRunner }) => await queryRunner.clearDatabase());
|
||||
await initDbUpToMigration(migrationName);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await Container.get(DbConnection).close();
|
||||
});
|
||||
|
||||
async function withContext<T>(run: (context: TestMigrationContext) => Promise<T>): Promise<T> {
|
||||
const context = createTestMigrationContext(dataSource);
|
||||
try {
|
||||
return await run(context);
|
||||
} finally {
|
||||
await context.queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
function columns(context: TestMigrationContext, names: string[]) {
|
||||
return names.map((name) => context.escape.columnName(name)).join(', ');
|
||||
}
|
||||
|
||||
async function insertProject(context: TestMigrationContext) {
|
||||
const id = randomUUID();
|
||||
await context.runQuery(
|
||||
`INSERT INTO ${context.escape.tableName('project')} (${columns(context, ['id', 'name', 'type', 'createdAt', 'updatedAt'])})
|
||||
VALUES (:id, 'Agents project', 'team', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
|
||||
{ id },
|
||||
);
|
||||
return id;
|
||||
}
|
||||
|
||||
async function insertAgent(
|
||||
context: TestMigrationContext,
|
||||
projectId: string | null,
|
||||
scope?: string,
|
||||
) {
|
||||
const id = randomUUID();
|
||||
const names = ['id', 'name', 'projectId', 'createdAt', 'updatedAt'];
|
||||
const values = [':id', "'Agent'", ':projectId', 'CURRENT_TIMESTAMP', 'CURRENT_TIMESTAMP'];
|
||||
if (scope !== undefined) {
|
||||
names.push('scope');
|
||||
values.push(':scope');
|
||||
}
|
||||
await context.runQuery(
|
||||
`INSERT INTO ${context.escape.tableName('agents')} (${columns(context, names)}) VALUES (${values.join(', ')})`,
|
||||
{ id, projectId, scope },
|
||||
);
|
||||
return id;
|
||||
}
|
||||
|
||||
/** A thread with one execution: rows that depend on the agent at two levels. */
|
||||
async function insertThreadWithExecution(
|
||||
context: TestMigrationContext,
|
||||
agentId: string,
|
||||
projectId: string,
|
||||
) {
|
||||
const threadId = randomUUID();
|
||||
await context.runQuery(
|
||||
`INSERT INTO ${context.escape.tableName('agent_execution_threads')}
|
||||
(${columns(context, ['id', 'agentId', 'agentName', 'projectId', 'createdAt', 'updatedAt'])})
|
||||
VALUES (:threadId, :agentId, 'Agent', :projectId, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
|
||||
{ threadId, agentId, projectId },
|
||||
);
|
||||
await context.runQuery(
|
||||
`INSERT INTO ${context.escape.tableName('agent_execution')}
|
||||
(${columns(context, ['id', 'threadId', 'status', 'createdAt', 'updatedAt'])})
|
||||
VALUES (:id, :threadId, 'success', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
|
||||
{ id: randomUUID(), threadId },
|
||||
);
|
||||
return threadId;
|
||||
}
|
||||
|
||||
async function readIds(context: TestMigrationContext, table: string) {
|
||||
const rows = await context.runQuery<Array<{ id: string }>>(
|
||||
`SELECT ${context.escape.columnName('id')} AS id FROM ${context.escape.tableName(table)}`,
|
||||
);
|
||||
return rows.map((row) => row.id).sort();
|
||||
}
|
||||
|
||||
async function readAgent(context: TestMigrationContext, id: string) {
|
||||
const [row] = await context.runQuery<Array<{ scope: string; projectId: string | null }>>(
|
||||
`SELECT ${columns(context, ['scope', 'projectId'])} FROM ${context.escape.tableName('agents')} WHERE ${context.escape.columnName('id')} = :id`,
|
||||
{ id },
|
||||
);
|
||||
return row;
|
||||
}
|
||||
|
||||
it('marks existing agents as project agents and keeps their dependent rows', async () => {
|
||||
const { agentId, threadId } = await withContext(async (context) => {
|
||||
const projectId = await insertProject(context);
|
||||
const agentId = await insertAgent(context, projectId);
|
||||
const threadId = await insertThreadWithExecution(context, agentId, projectId);
|
||||
return { agentId, threadId };
|
||||
});
|
||||
|
||||
await runSingleMigration(migrationName);
|
||||
|
||||
await withContext(async (context) => {
|
||||
expect(await readAgent(context, agentId)).toMatchObject({ scope: 'project' });
|
||||
expect(await readIds(context, 'agent_execution_threads')).toEqual([threadId]);
|
||||
expect(await readIds(context, 'agent_execution')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('stores an instance agent without a project and rejects an unknown scope', async () => {
|
||||
await runSingleMigration(migrationName);
|
||||
|
||||
await withContext(async (context) => {
|
||||
const agentId = await insertAgent(context, null, 'instance');
|
||||
expect(await readAgent(context, agentId)).toEqual({ scope: 'instance', projectId: null });
|
||||
|
||||
const projectId = await insertProject(context);
|
||||
await expect(insertAgent(context, projectId, 'global')).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects a scope that does not match the project', async () => {
|
||||
await runSingleMigration(migrationName);
|
||||
|
||||
await withContext(async (context) => {
|
||||
const projectId = await insertProject(context);
|
||||
// A project agent needs a project.
|
||||
await expect(insertAgent(context, null)).rejects.toThrow();
|
||||
await expect(insertAgent(context, null, 'project')).rejects.toThrow();
|
||||
// An instance agent belongs to no project.
|
||||
await expect(insertAgent(context, projectId, 'instance')).rejects.toThrow();
|
||||
expect(await readIds(context, 'agents')).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects an agent without a project before the migration', async () => {
|
||||
await withContext(async (context) => {
|
||||
await expect(insertAgent(context, null)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes instance agents on revert, and keeps project agents with their rows', async () => {
|
||||
await runSingleMigration(migrationName);
|
||||
const kept = await withContext(async (context) => {
|
||||
const projectId = await insertProject(context);
|
||||
const projectAgentId = await insertAgent(context, projectId);
|
||||
const projectThreadId = await insertThreadWithExecution(context, projectAgentId, projectId);
|
||||
// DeleteInstanceAgentsOnRevert deletes the rows that depend on instance agents first.
|
||||
await insertAgent(context, null, 'instance');
|
||||
return { projectAgentId, projectThreadId };
|
||||
});
|
||||
|
||||
await undoLastSingleMigration();
|
||||
|
||||
await withContext(async (context) => {
|
||||
expect(await readIds(context, 'agents')).toEqual([kept.projectAgentId]);
|
||||
expect(await readIds(context, 'agent_execution_threads')).toEqual([kept.projectThreadId]);
|
||||
expect(await readIds(context, 'agent_execution')).toHaveLength(1);
|
||||
if (context.isSqlite) {
|
||||
expect(await context.runQuery('PRAGMA foreign_key_check')).toEqual([]);
|
||||
}
|
||||
await expect(insertAgent(context, null)).rejects.toThrow();
|
||||
// The foreign keys still hold after the table was recreated.
|
||||
await expect(
|
||||
insertThreadWithExecution(context, randomUUID(), await insertProject(context)),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
it('can apply again after a revert', async () => {
|
||||
const agentId = await withContext(async (context) => {
|
||||
return await insertAgent(context, await insertProject(context));
|
||||
});
|
||||
|
||||
await runSingleMigration(migrationName);
|
||||
await undoLastSingleMigration();
|
||||
await runSingleMigration(migrationName);
|
||||
|
||||
await withContext(async (context) => {
|
||||
expect(await readAgent(context, agentId)).toMatchObject({ scope: 'project' });
|
||||
await expect(insertAgent(context, null, 'instance')).resolves.toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
import {
|
||||
createTestMigrationContext,
|
||||
initDbUpToMigration,
|
||||
runSingleMigration,
|
||||
undoLastSingleMigration,
|
||||
type TestMigrationContext,
|
||||
} from '@n8n/backend-test-utils';
|
||||
import { DbConnection } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import { DataSource } from '@n8n/typeorm';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
const migrationName = 'DeleteInstanceAgentsOnRevert1792139680416';
|
||||
|
||||
describe('DeleteInstanceAgentsOnRevert migration', () => {
|
||||
let dataSource: DataSource;
|
||||
|
||||
beforeAll(async () => {
|
||||
await Container.get(DbConnection).init();
|
||||
dataSource = Container.get(DataSource);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await withContext(async ({ queryRunner }) => await queryRunner.clearDatabase());
|
||||
await initDbUpToMigration(migrationName);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await Container.get(DbConnection).close();
|
||||
});
|
||||
|
||||
async function withContext<T>(run: (context: TestMigrationContext) => Promise<T>): Promise<T> {
|
||||
const context = createTestMigrationContext(dataSource);
|
||||
try {
|
||||
return await run(context);
|
||||
} finally {
|
||||
await context.queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
function columns(context: TestMigrationContext, names: string[]) {
|
||||
return names.map((name) => context.escape.columnName(name)).join(', ');
|
||||
}
|
||||
|
||||
async function insertProject(context: TestMigrationContext) {
|
||||
const id = randomUUID();
|
||||
await context.runQuery(
|
||||
`INSERT INTO ${context.escape.tableName('project')} (${columns(context, ['id', 'name', 'type', 'createdAt', 'updatedAt'])})
|
||||
VALUES (:id, 'Agents project', 'team', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
|
||||
{ id },
|
||||
);
|
||||
return id;
|
||||
}
|
||||
|
||||
async function insertAgent(context: TestMigrationContext, projectId: string | null) {
|
||||
const id = randomUUID();
|
||||
const scope = projectId === null ? 'instance' : 'project';
|
||||
await context.runQuery(
|
||||
`INSERT INTO ${context.escape.tableName('agents')} (${columns(context, ['id', 'name', 'projectId', 'scope', 'createdAt', 'updatedAt'])})
|
||||
VALUES (:id, 'Agent', :projectId, :scope, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
|
||||
{ id, projectId, scope },
|
||||
);
|
||||
return id;
|
||||
}
|
||||
|
||||
/** A thread with one execution: rows that depend on the agent at two levels. */
|
||||
async function insertThreadWithExecution(
|
||||
context: TestMigrationContext,
|
||||
agentId: string,
|
||||
projectId: string,
|
||||
) {
|
||||
const threadId = randomUUID();
|
||||
const executionId = randomUUID();
|
||||
await context.runQuery(
|
||||
`INSERT INTO ${context.escape.tableName('agent_execution_threads')}
|
||||
(${columns(context, ['id', 'agentId', 'agentName', 'projectId', 'createdAt', 'updatedAt'])})
|
||||
VALUES (:threadId, :agentId, 'Agent', :projectId, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
|
||||
{ threadId, agentId, projectId },
|
||||
);
|
||||
await context.runQuery(
|
||||
`INSERT INTO ${context.escape.tableName('agent_execution')}
|
||||
(${columns(context, ['id', 'threadId', 'status', 'createdAt', 'updatedAt'])})
|
||||
VALUES (:executionId, :threadId, 'success', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
|
||||
{ executionId, threadId },
|
||||
);
|
||||
return { threadId, executionId };
|
||||
}
|
||||
|
||||
async function readIds(context: TestMigrationContext, table: string) {
|
||||
const rows = await context.runQuery<Array<{ id: string }>>(
|
||||
`SELECT ${context.escape.columnName('id')} AS id FROM ${context.escape.tableName(table)}`,
|
||||
);
|
||||
return rows.map((row) => row.id).sort();
|
||||
}
|
||||
|
||||
it('deletes instance agents and their dependent rows on revert', async () => {
|
||||
await runSingleMigration(migrationName);
|
||||
const kept = await withContext(async (context) => {
|
||||
const projectId = await insertProject(context);
|
||||
const projectAgentId = await insertAgent(context, projectId);
|
||||
const projectRows = await insertThreadWithExecution(context, projectAgentId, projectId);
|
||||
const instanceAgentId = await insertAgent(context, null);
|
||||
await insertThreadWithExecution(context, instanceAgentId, projectId);
|
||||
return { projectAgentId, ...projectRows };
|
||||
});
|
||||
|
||||
await undoLastSingleMigration();
|
||||
|
||||
await withContext(async (context) => {
|
||||
expect(await readIds(context, 'agents')).toEqual([kept.projectAgentId]);
|
||||
expect(await readIds(context, 'agent_execution_threads')).toEqual([kept.threadId]);
|
||||
expect(await readIds(context, 'agent_execution')).toEqual([kept.executionId]);
|
||||
if (context.isSqlite) {
|
||||
expect(await context.runQuery('PRAGMA foreign_key_check')).toEqual([]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('leaves no orphan rows after both migrations are reverted', async () => {
|
||||
await runSingleMigration(migrationName);
|
||||
await withContext(async (context) => {
|
||||
const projectId = await insertProject(context);
|
||||
await insertThreadWithExecution(context, await insertAgent(context, null), projectId);
|
||||
});
|
||||
|
||||
await undoLastSingleMigration();
|
||||
await undoLastSingleMigration();
|
||||
|
||||
await withContext(async (context) => {
|
||||
expect(await readIds(context, 'agents')).toEqual([]);
|
||||
expect(await readIds(context, 'agent_execution_threads')).toEqual([]);
|
||||
expect(await readIds(context, 'agent_execution')).toEqual([]);
|
||||
if (context.isSqlite) {
|
||||
expect(await context.runQuery('PRAGMA foreign_key_check')).toEqual([]);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user