From fc048fb7b61e43c72ace367fbbdb09757a2e7ffd Mon Sep 17 00:00:00 2001 From: Arvin Ansari Date: Mon, 19 Oct 2026 16:39:14 +0200 Subject: [PATCH] feat(core): Add skill Assistant availability column (no-changelog) --- docs/generated/postgres-schema/README.md | 3 +- .../public.agent_skill_dependency.md | 1 + .../public.agent_version_skill_dependency.md | 1 + .../postgres-schema/public.agents.md | 1 + .../postgres-schema/public.project.md | 1 + .../generated/postgres-schema/public.skill.md | 3 + .../postgres-schema/public.skill_version.md | 1 + docs/generated/postgres-schema/public.user.md | 1 + docs/generated/sqlite-schema/README.md | 3 +- .../sqlite-schema/agent_skill_dependency.md | 1 + .../agent_version_skill_dependency.md | 1 + docs/generated/sqlite-schema/agents.md | 1 + docs/generated/sqlite-schema/project.md | 1 + docs/generated/sqlite-schema/skill.md | 4 +- docs/generated/sqlite-schema/skill_version.md | 1 + docs/generated/sqlite-schema/user.md | 1 + ...418677997-AddSkillAssistantAvailability.ts | 16 +++++ .../modules/agents/entities/skill.entity.ts | 3 + ...7-add-skill-assistant-availability.test.ts | 71 +++++++++++++++++++ 19 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 packages/@n8n/db/src/migrations/common/1792418677997-AddSkillAssistantAvailability.ts create mode 100644 packages/cli/test/migration/1792418677997-add-skill-assistant-availability.test.ts diff --git a/docs/generated/postgres-schema/README.md b/docs/generated/postgres-schema/README.md index 52084123841..54a72afdee0 100644 --- a/docs/generated/postgres-schema/README.md +++ b/docs/generated/postgres-schema/README.md @@ -135,7 +135,7 @@ Auto-generated from the PostgreSQL migrations in @n8n/db. Do not edit by hand. | [public.settings](public.settings.md) | 3 | | BASE TABLE | | [public.shared_credentials](public.shared_credentials.md) | 5 | | BASE TABLE | | [public.shared_workflow](public.shared_workflow.md) | 5 | | BASE TABLE | -| [public.skill](public.skill.md) | 7 | | BASE TABLE | +| [public.skill](public.skill.md) | 8 | | BASE TABLE | | [public.skill_file](public.skill_file.md) | 5 | | BASE TABLE | | [public.skill_version](public.skill_version.md) | 11 | | BASE TABLE | | [public.tag_entity](public.tag_entity.md) | 4 | | BASE TABLE | @@ -1713,6 +1713,7 @@ erDiagram } "public.skill" { varchar_36_ agentId FK + boolean availableToAssistant timestamp_3__with_time_zone createdAt uuid createdById FK varchar_36_ id diff --git a/docs/generated/postgres-schema/public.agent_skill_dependency.md b/docs/generated/postgres-schema/public.agent_skill_dependency.md index 74fe035f6a3..5dcf1edb866 100644 --- a/docs/generated/postgres-schema/public.agent_skill_dependency.md +++ b/docs/generated/postgres-schema/public.agent_skill_dependency.md @@ -62,6 +62,7 @@ erDiagram } "public.skill" { varchar_36_ agentId FK + boolean availableToAssistant timestamp_3__with_time_zone createdAt uuid createdById FK varchar_36_ id diff --git a/docs/generated/postgres-schema/public.agent_version_skill_dependency.md b/docs/generated/postgres-schema/public.agent_version_skill_dependency.md index 4135311d27d..1a65d809fb9 100644 --- a/docs/generated/postgres-schema/public.agent_version_skill_dependency.md +++ b/docs/generated/postgres-schema/public.agent_version_skill_dependency.md @@ -58,6 +58,7 @@ erDiagram } "public.skill" { varchar_36_ agentId FK + boolean availableToAssistant timestamp_3__with_time_zone createdAt uuid createdById FK varchar_36_ id diff --git a/docs/generated/postgres-schema/public.agents.md b/docs/generated/postgres-schema/public.agents.md index 3716bbeb0ca..4413e21bd4a 100644 --- a/docs/generated/postgres-schema/public.agents.md +++ b/docs/generated/postgres-schema/public.agents.md @@ -334,6 +334,7 @@ erDiagram } "public.skill" { varchar_36_ agentId FK + boolean availableToAssistant timestamp_3__with_time_zone createdAt uuid createdById FK varchar_36_ id diff --git a/docs/generated/postgres-schema/public.project.md b/docs/generated/postgres-schema/public.project.md index 7f114875982..e83e05438df 100644 --- a/docs/generated/postgres-schema/public.project.md +++ b/docs/generated/postgres-schema/public.project.md @@ -259,6 +259,7 @@ erDiagram } "public.skill" { varchar_36_ agentId FK + boolean availableToAssistant timestamp_3__with_time_zone createdAt uuid createdById FK varchar_36_ id diff --git a/docs/generated/postgres-schema/public.skill.md b/docs/generated/postgres-schema/public.skill.md index d644698182f..f7469a99fda 100644 --- a/docs/generated/postgres-schema/public.skill.md +++ b/docs/generated/postgres-schema/public.skill.md @@ -5,6 +5,7 @@ | Name | Type | Default | Nullable | Children | Parents | Comment | | ---- | ---- | ------- | -------- | -------- | ------- | ------- | | agentId | varchar(36) | | true | | [public.agents](public.agents.md) | Set when only this agent can use the skill. projectId is then NULL: the agent holds the project | +| availableToAssistant | boolean | false | false | | | | | createdAt | timestamp(3) with time zone | CURRENT_TIMESTAMP(3) | false | | | | | createdById | uuid | | true | | [public.user](public.user.md) | Author. NULL after the author is deleted | | id | varchar(36) | | false | [public.agent_skill_dependency](public.agent_skill_dependency.md) [public.agent_version_skill_dependency](public.agent_version_skill_dependency.md) [public.skill_version](public.skill_version.md) | | skill_\, the same format agents use | @@ -21,6 +22,7 @@ | FK_4de4501583cd1fcd6f5ea85f547 | FOREIGN KEY | FOREIGN KEY ("createdById") REFERENCES "user"(id) ON DELETE SET NULL | | FK_7d4fb6af107e507d9a86293cf9d | FOREIGN KEY | FOREIGN KEY ("agentId") REFERENCES agents(id) ON DELETE CASCADE | | PK_a0d33334424e64fb78dc3ce7196 | PRIMARY KEY | PRIMARY KEY (id) | +| skill_availableToAssistant_not_null | n | NOT NULL "availableToAssistant" | | skill_createdAt_not_null | n | NOT NULL "createdAt" | | skill_id_not_null | n | NOT NULL id | | skill_source_not_null | n | NOT NULL source | @@ -48,6 +50,7 @@ erDiagram "public.skill" { varchar_36_ agentId FK + boolean availableToAssistant timestamp_3__with_time_zone createdAt uuid createdById FK varchar_36_ id diff --git a/docs/generated/postgres-schema/public.skill_version.md b/docs/generated/postgres-schema/public.skill_version.md index 562a13ad867..e79aabb3669 100644 --- a/docs/generated/postgres-schema/public.skill_version.md +++ b/docs/generated/postgres-schema/public.skill_version.md @@ -104,6 +104,7 @@ erDiagram } "public.skill" { varchar_36_ agentId FK + boolean availableToAssistant timestamp_3__with_time_zone createdAt uuid createdById FK varchar_36_ id diff --git a/docs/generated/postgres-schema/public.user.md b/docs/generated/postgres-schema/public.user.md index 24f19c09860..d29a72f4516 100644 --- a/docs/generated/postgres-schema/public.user.md +++ b/docs/generated/postgres-schema/public.user.md @@ -427,6 +427,7 @@ erDiagram } "public.skill" { varchar_36_ agentId FK + boolean availableToAssistant timestamp_3__with_time_zone createdAt uuid createdById FK varchar_36_ id diff --git a/docs/generated/sqlite-schema/README.md b/docs/generated/sqlite-schema/README.md index af3996f06fa..7c9df21ee71 100644 --- a/docs/generated/sqlite-schema/README.md +++ b/docs/generated/sqlite-schema/README.md @@ -135,7 +135,7 @@ Auto-generated from the SQLite migrations in @n8n/db. Do not edit by hand. | [settings](settings.md) | 3 | | table | | [shared_credentials](shared_credentials.md) | 5 | | table | | [shared_workflow](shared_workflow.md) | 5 | | table | -| [skill](skill.md) | 7 | | table | +| [skill](skill.md) | 8 | | table | | [skill_file](skill_file.md) | 5 | | table | | [skill_version](skill_version.md) | 11 | | table | | [tag_entity](tag_entity.md) | 4 | | table | @@ -1699,6 +1699,7 @@ erDiagram } "skill" { varchar_36_ agentId FK + boolean availableToAssistant datetime_3_ createdAt varchar createdById FK varchar_36_ id PK diff --git a/docs/generated/sqlite-schema/agent_skill_dependency.md b/docs/generated/sqlite-schema/agent_skill_dependency.md index c341daa1073..56c171b7909 100644 --- a/docs/generated/sqlite-schema/agent_skill_dependency.md +++ b/docs/generated/sqlite-schema/agent_skill_dependency.md @@ -72,6 +72,7 @@ erDiagram } "skill" { varchar_36_ agentId FK + boolean availableToAssistant datetime_3_ createdAt varchar createdById FK varchar_36_ id PK diff --git a/docs/generated/sqlite-schema/agent_version_skill_dependency.md b/docs/generated/sqlite-schema/agent_version_skill_dependency.md index 9d8971a4724..cd4af4acd59 100644 --- a/docs/generated/sqlite-schema/agent_version_skill_dependency.md +++ b/docs/generated/sqlite-schema/agent_version_skill_dependency.md @@ -67,6 +67,7 @@ erDiagram } "skill" { varchar_36_ agentId FK + boolean availableToAssistant datetime_3_ createdAt varchar createdById FK varchar_36_ id PK diff --git a/docs/generated/sqlite-schema/agents.md b/docs/generated/sqlite-schema/agents.md index 3c29aeabe19..adfa08277ab 100644 --- a/docs/generated/sqlite-schema/agents.md +++ b/docs/generated/sqlite-schema/agents.md @@ -336,6 +336,7 @@ erDiagram } "skill" { varchar_36_ agentId FK + boolean availableToAssistant datetime_3_ createdAt varchar createdById FK varchar_36_ id PK diff --git a/docs/generated/sqlite-schema/project.md b/docs/generated/sqlite-schema/project.md index 6dd22d0d03f..35d739645f8 100644 --- a/docs/generated/sqlite-schema/project.md +++ b/docs/generated/sqlite-schema/project.md @@ -265,6 +265,7 @@ erDiagram } "skill" { varchar_36_ agentId FK + boolean availableToAssistant datetime_3_ createdAt varchar createdById FK varchar_36_ id PK diff --git a/docs/generated/sqlite-schema/skill.md b/docs/generated/sqlite-schema/skill.md index d26335ea399..cd6f8b0b4fd 100644 --- a/docs/generated/sqlite-schema/skill.md +++ b/docs/generated/sqlite-schema/skill.md @@ -6,7 +6,7 @@ Table Definition ```sql -CREATE TABLE "skill" ("id" varchar(36) PRIMARY KEY NOT NULL, "projectId" varchar(36), "agentId" varchar(36), "source" varchar(16) NOT NULL, "createdById" varchar, "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')), CONSTRAINT "CHK_skill_single_target" CHECK ("agentId" IS NULL OR "projectId" IS NULL), CONSTRAINT "FK_402ed37bff0d3134f6e782455ed" FOREIGN KEY ("projectId") REFERENCES "project" ("id") ON DELETE CASCADE, CONSTRAINT "FK_7d4fb6af107e507d9a86293cf9d" FOREIGN KEY ("agentId") REFERENCES "agents" ("id") ON DELETE CASCADE, CONSTRAINT "FK_4de4501583cd1fcd6f5ea85f547" FOREIGN KEY ("createdById") REFERENCES "user" ("id") ON DELETE SET NULL) +CREATE TABLE "skill" ("id" varchar(36) PRIMARY KEY NOT NULL, "projectId" varchar(36), "agentId" varchar(36), "source" varchar(16) NOT NULL, "createdById" varchar, "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')), "availableToAssistant" boolean NOT NULL DEFAULT false, CONSTRAINT "CHK_skill_single_target" CHECK ("agentId" IS NULL OR "projectId" IS NULL), CONSTRAINT "FK_402ed37bff0d3134f6e782455ed" FOREIGN KEY ("projectId") REFERENCES "project" ("id") ON DELETE CASCADE, CONSTRAINT "FK_7d4fb6af107e507d9a86293cf9d" FOREIGN KEY ("agentId") REFERENCES "agents" ("id") ON DELETE CASCADE, CONSTRAINT "FK_4de4501583cd1fcd6f5ea85f547" FOREIGN KEY ("createdById") REFERENCES "user" ("id") ON DELETE SET NULL) ``` @@ -16,6 +16,7 @@ CREATE TABLE "skill" ("id" varchar(36) PRIMARY KEY NOT NULL, "projectId" varchar | Name | Type | Default | Nullable | Children | Parents | Comment | | ---- | ---- | ------- | -------- | -------- | ------- | ------- | | agentId | varchar(36) | | true | | [agents](agents.md) | | +| availableToAssistant | boolean | false | false | | | | | createdAt | datetime(3) | STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') | false | | | | | createdById | varchar | | true | | [user](user.md) | | | id | varchar(36) | | false | [agent_skill_dependency](agent_skill_dependency.md) [agent_version_skill_dependency](agent_version_skill_dependency.md) [skill_version](skill_version.md) | | | @@ -56,6 +57,7 @@ erDiagram "skill" { varchar_36_ agentId FK + boolean availableToAssistant datetime_3_ createdAt varchar createdById FK varchar_36_ id PK diff --git a/docs/generated/sqlite-schema/skill_version.md b/docs/generated/sqlite-schema/skill_version.md index 23a5e245be9..46a9bf26ddc 100644 --- a/docs/generated/sqlite-schema/skill_version.md +++ b/docs/generated/sqlite-schema/skill_version.md @@ -107,6 +107,7 @@ erDiagram } "skill" { varchar_36_ agentId FK + boolean availableToAssistant datetime_3_ createdAt varchar createdById FK varchar_36_ id PK diff --git a/docs/generated/sqlite-schema/user.md b/docs/generated/sqlite-schema/user.md index 995ae1e8161..de1b53e1cba 100644 --- a/docs/generated/sqlite-schema/user.md +++ b/docs/generated/sqlite-schema/user.md @@ -433,6 +433,7 @@ erDiagram } "skill" { varchar_36_ agentId FK + boolean availableToAssistant datetime_3_ createdAt varchar createdById FK varchar_36_ id PK diff --git a/packages/@n8n/db/src/migrations/common/1792418677997-AddSkillAssistantAvailability.ts b/packages/@n8n/db/src/migrations/common/1792418677997-AddSkillAssistantAvailability.ts new file mode 100644 index 00000000000..2ec383d7b9f --- /dev/null +++ b/packages/@n8n/db/src/migrations/common/1792418677997-AddSkillAssistantAvailability.ts @@ -0,0 +1,16 @@ +import type { MigrationContext, ReversibleMigration } from '../migration-types'; + +export class AddSkillAssistantAvailability1792418677997 implements ReversibleMigration { + async up({ runQuery, escape }: MigrationContext) { + // ALTER preserves the skill versions and references on SQLite. + await runQuery( + `ALTER TABLE ${escape.tableName('skill')} ADD COLUMN ${escape.columnName('availableToAssistant')} boolean NOT NULL DEFAULT false`, + ); + } + + async down({ runQuery, escape }: MigrationContext) { + await runQuery( + `ALTER TABLE ${escape.tableName('skill')} DROP COLUMN ${escape.columnName('availableToAssistant')}`, + ); + } +} diff --git a/packages/cli/src/modules/agents/entities/skill.entity.ts b/packages/cli/src/modules/agents/entities/skill.entity.ts index d7e8d96560b..42b1777f95a 100644 --- a/packages/cli/src/modules/agents/entities/skill.entity.ts +++ b/packages/cli/src/modules/agents/entities/skill.entity.ts @@ -23,6 +23,9 @@ export class Skill extends WithTimestamps { @Column({ type: 'varchar', length: 36, nullable: true }) agentId: string | null; + @Column({ type: 'boolean', default: false }) + availableToAssistant: boolean; + @Column({ type: 'varchar', length: 16 }) source: SkillSource; diff --git a/packages/cli/test/migration/1792418677997-add-skill-assistant-availability.test.ts b/packages/cli/test/migration/1792418677997-add-skill-assistant-availability.test.ts new file mode 100644 index 00000000000..fc009f07fee --- /dev/null +++ b/packages/cli/test/migration/1792418677997-add-skill-assistant-availability.test.ts @@ -0,0 +1,71 @@ +import { + createTestMigrationContext, + initDbUpToMigration, + runSingleMigration, + type TestMigrationContext, +} from '@n8n/backend-test-utils'; +import { DbConnection } from '@n8n/db'; +import { Container } from '@n8n/di'; +import { DataSource } from '@n8n/typeorm'; + +const migration = 'AddSkillAssistantAvailability1792418677997'; + +describe('Skill Assistant availability migration', () => { + let context: TestMigrationContext; + + beforeAll(async () => { + await Container.get(DbConnection).init(); + context = createTestMigrationContext(Container.get(DataSource)); + await context.queryRunner.clearDatabase(); + await context.queryRunner.release(); + await initDbUpToMigration(migration); + context = createTestMigrationContext(Container.get(DataSource)); + }); + + afterAll(async () => { + await context?.queryRunner.release(); + await Container.get(DbConnection).close(); + }); + + it('defaults existing and new skills to false and preserves versions through up/down/up', async () => { + const table = context.escape.tableName('skill'); + const versions = context.escape.tableName('skill_version'); + const col = context.escape.columnName; + await context.runQuery( + `INSERT INTO ${table} (${col('id')}, ${col('source')}) VALUES ('existing', 'ui')`, + ); + await context.runQuery( + `INSERT INTO ${versions} (${col('id')}, ${col('skillId')}, ${col('version')}, ${col('name')}, ${col('description')}, ${col('instructions')}, ${col('contentHash')}) VALUES ('0199e890-7471-7000-8000-000000000001', 'existing', 1, 'Tone', 'Writing style', 'Be brief.', '${'a'.repeat(64)}')`, + ); + await context.queryRunner.release(); + await runSingleMigration(migration); + context = createTestMigrationContext(Container.get(DataSource)); + await context.runQuery( + `INSERT INTO ${table} (${col('id')}, ${col('source')}) VALUES ('new', 'agent')`, + ); + const readFlags = async () => + await context.runQuery>( + `SELECT ${col('availableToAssistant')} FROM ${table}`, + ); + expect((await readFlags()).map((row) => Boolean(row.availableToAssistant))).toEqual([ + false, + false, + ]); + await context.runQuery( + `UPDATE ${table} SET ${col('availableToAssistant')} = true WHERE ${col('id')} = 'existing'`, + ); + expect((await readFlags()).filter((row) => Boolean(row.availableToAssistant))).toHaveLength(1); + + await context.queryRunner.release(); + await Container.get(DataSource).undoLastMigration(); + await runSingleMigration(migration); + context = createTestMigrationContext(Container.get(DataSource)); + expect((await readFlags()).map((row) => Boolean(row.availableToAssistant))).toEqual([ + false, + false, + ]); + expect(await context.runQuery(`SELECT ${col('instructions')} FROM ${versions}`)).toEqual([ + { instructions: 'Be brief.' }, + ]); + }); +});