From 23f9b0efd138e5031991b4c7d371a94fc63b890e Mon Sep 17 00:00:00 2001
From: Jack Minnetian <270441393+BlueBottleLatte@users.noreply.github.com>
Date: Mon, 8 Jun 2026 20:02:00 -0700
Subject: [PATCH 01/25] Update schema to support the foundation for skills,
both personal and for the organization
---
.../migration.sql | 38 +++++++++++++++++++
packages/db/prisma/schema.prisma | 34 +++++++++++++++++
packages/db/src/index.ts | 14 ++++++-
3 files changed, 85 insertions(+), 1 deletion(-)
create mode 100644 packages/db/prisma/migrations/20260609000000_add_agent_skills/migration.sql
diff --git a/packages/db/prisma/migrations/20260609000000_add_agent_skills/migration.sql b/packages/db/prisma/migrations/20260609000000_add_agent_skills/migration.sql
new file mode 100644
index 000000000..cdb8ab4ec
--- /dev/null
+++ b/packages/db/prisma/migrations/20260609000000_add_agent_skills/migration.sql
@@ -0,0 +1,38 @@
+-- CreateEnum
+CREATE TYPE "AgentSkillScope" AS ENUM ('PERSONAL', 'ORG');
+
+-- CreateTable
+CREATE TABLE "AgentSkill" (
+ "id" TEXT NOT NULL,
+ "scope" "AgentSkillScope" NOT NULL,
+ "namespaceKey" TEXT NOT NULL,
+ "slug" TEXT NOT NULL,
+ "name" TEXT NOT NULL,
+ "description" TEXT NOT NULL,
+ "instructions" TEXT NOT NULL,
+ "createdById" TEXT NOT NULL,
+ "orgId" INTEGER,
+ "enabled" BOOLEAN NOT NULL DEFAULT true,
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" TIMESTAMP(3) NOT NULL,
+
+ CONSTRAINT "AgentSkill_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "AgentSkill_namespaceKey_slug_key" ON "AgentSkill"("namespaceKey", "slug");
+
+-- CreateIndex
+CREATE INDEX "AgentSkill_createdById_idx" ON "AgentSkill"("createdById");
+
+-- CreateIndex
+CREATE INDEX "AgentSkill_orgId_idx" ON "AgentSkill"("orgId");
+
+-- CreateIndex
+CREATE INDEX "AgentSkill_scope_idx" ON "AgentSkill"("scope");
+
+-- AddForeignKey
+ALTER TABLE "AgentSkill" ADD CONSTRAINT "AgentSkill_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "AgentSkill" ADD CONSTRAINT "AgentSkill_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Org"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma
index f18889a5f..5c44a2994 100644
--- a/packages/db/prisma/schema.prisma
+++ b/packages/db/prisma/schema.prisma
@@ -334,6 +334,7 @@ model Org {
repoVisits RepoVisit[]
mcpServers McpServer[]
+ agentSkills AgentSkill[]
license License?
servicePingEvents ServicePingEvent[]
@@ -402,6 +403,11 @@ enum McpServerToolPermission {
DISABLED
}
+enum AgentSkillScope {
+ PERSONAL
+ ORG
+}
+
model UserToOrg {
joinedAt DateTime @default(now())
@@ -519,6 +525,7 @@ model User {
sessionVersion Int @default(0)
userMcpServers UserMcpServer[]
+ createdAgentSkills AgentSkill[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -741,6 +748,33 @@ model ChatAccess {
@@unique([chatId, userId])
}
+model AgentSkill {
+ id String @id @default(cuid())
+
+ scope AgentSkillScope
+ namespaceKey String
+ slug String
+
+ name String
+ description String
+ instructions String
+
+ createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade)
+ createdById String
+
+ org Org? @relation(fields: [orgId], references: [id], onDelete: Cascade)
+ orgId Int?
+
+ enabled Boolean @default(true)
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+
+ @@unique([namespaceKey, slug])
+ @@index([createdById])
+ @@index([orgId])
+ @@index([scope])
+}
+
// OAuth2 Authorization Server models
// @see: https://datatracker.ietf.org/doc/html/rfc6749
diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts
index 245206d9d..9815f91d9 100644
--- a/packages/db/src/index.ts
+++ b/packages/db/src/index.ts
@@ -1,3 +1,15 @@
import type { User, Account } from ".prisma/client";
export type UserWithAccounts = User & { accounts: Account[] };
-export * from ".prisma/client";
\ No newline at end of file
+export * from ".prisma/client";
+
+type AgentSkillNamespaceInput =
+ | { scope: "PERSONAL"; userId: string }
+ | { scope: "ORG"; orgId: number };
+
+export const getAgentSkillNamespaceKey = (input: AgentSkillNamespaceInput) => {
+ if (input.scope === "PERSONAL") {
+ return `user:${input.userId}`;
+ }
+
+ return `org:${input.orgId}`;
+};
From 05ae0bd73310155797728451537da9716df024e7 Mon Sep 17 00:00:00 2001
From: Jack Minnetian <270441393+BlueBottleLatte@users.noreply.github.com>
Date: Mon, 8 Jun 2026 21:27:48 -0700
Subject: [PATCH 02/25] Add initial UI and actions to support personal skills
---
.../migration.sql | 7 -
packages/db/prisma/schema.prisma | 9 +-
packages/db/src/index.ts | 8 +
packages/web/package.json | 1 +
.../(app)/settings/accountAskAgent/page.tsx | 8 +
.../accountAskAgent/skills/[skillId]/page.tsx | 34 ++
.../accountAskAgent/skills/new/page.tsx | 14 +
.../components/settingsContentFrame.tsx | 27 ++
.../web/src/app/(app)/settings/layout.tsx | 1 +
.../mcp/components/accountAskAgentPage.tsx | 199 +++++++-
.../src/ee/features/chat/mcp/mcpToolSets.ts | 5 +-
.../src/ee/features/chat/skills/actions.ts | 188 ++++++++
.../components/personalSkillEditorPage.tsx | 449 ++++++++++++++++++
.../src/ee/features/chat/skills/types.test.ts | 146 ++++++
.../web/src/ee/features/chat/skills/types.ts | 145 ++++++
packages/web/src/ee/features/oauth/server.ts | 4 +-
packages/web/src/lib/errorCodes.ts | 2 +
packages/web/src/lib/prismaErrors.ts | 21 +
yarn.lock | 1 +
19 files changed, 1249 insertions(+), 20 deletions(-)
create mode 100644 packages/web/src/app/(app)/settings/accountAskAgent/skills/[skillId]/page.tsx
create mode 100644 packages/web/src/app/(app)/settings/accountAskAgent/skills/new/page.tsx
create mode 100644 packages/web/src/app/(app)/settings/components/settingsContentFrame.tsx
create mode 100644 packages/web/src/ee/features/chat/skills/actions.ts
create mode 100644 packages/web/src/ee/features/chat/skills/components/personalSkillEditorPage.tsx
create mode 100644 packages/web/src/ee/features/chat/skills/types.test.ts
create mode 100644 packages/web/src/ee/features/chat/skills/types.ts
create mode 100644 packages/web/src/lib/prismaErrors.ts
diff --git a/packages/db/prisma/migrations/20260609000000_add_agent_skills/migration.sql b/packages/db/prisma/migrations/20260609000000_add_agent_skills/migration.sql
index cdb8ab4ec..0581017b7 100644
--- a/packages/db/prisma/migrations/20260609000000_add_agent_skills/migration.sql
+++ b/packages/db/prisma/migrations/20260609000000_add_agent_skills/migration.sql
@@ -1,10 +1,6 @@
--- CreateEnum
-CREATE TYPE "AgentSkillScope" AS ENUM ('PERSONAL', 'ORG');
-
-- CreateTable
CREATE TABLE "AgentSkill" (
"id" TEXT NOT NULL,
- "scope" "AgentSkillScope" NOT NULL,
"namespaceKey" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"name" TEXT NOT NULL,
@@ -28,9 +24,6 @@ CREATE INDEX "AgentSkill_createdById_idx" ON "AgentSkill"("createdById");
-- CreateIndex
CREATE INDEX "AgentSkill_orgId_idx" ON "AgentSkill"("orgId");
--- CreateIndex
-CREATE INDEX "AgentSkill_scope_idx" ON "AgentSkill"("scope");
-
-- AddForeignKey
ALTER TABLE "AgentSkill" ADD CONSTRAINT "AgentSkill_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma
index 5c44a2994..67bb3467d 100644
--- a/packages/db/prisma/schema.prisma
+++ b/packages/db/prisma/schema.prisma
@@ -403,11 +403,6 @@ enum McpServerToolPermission {
DISABLED
}
-enum AgentSkillScope {
- PERSONAL
- ORG
-}
-
model UserToOrg {
joinedAt DateTime @default(now())
@@ -751,7 +746,8 @@ model ChatAccess {
model AgentSkill {
id String @id @default(cuid())
- scope AgentSkillScope
+ // Encodes both the scope and owner of the skill, e.g. "user: {skill.name}
+ {skill.description}
+
+ No skills yet
+
+ Create a personal slash command for prompts you reuse in Ask Sourcebot.
+
- Front matter can fill name, command, and description on import. + Front matter can fill name, command, arguments, and description on import.
{skill.name}
- - /{skill.slug} - +
@@ -93,6 +122,17 @@ function PersonalSkillCard({
Edit
+ {skill.name}
+ {skill.description}
+
+ No workspace skills yet
+
+ Publish a personal skill to share it with this workspace.
+
+ Workspace +
++ {orgSkills.length} {pluralize(orgSkills.length, "skill")} +
+{skill.name}
++ {skill.description} +
+ )} ++ Manage workspace slash-command behavior for Ask Sourcebot. +
++ {orgSkills.length} {pluralize(orgSkills.length, "skill")} +
+@@ -241,42 +194,69 @@ function OrgSkillCatalogCard({ )}
- No workspace skills yet -
-- Publish a personal skill to share it with this workspace. -
+ No workspace skills yet +
++ {description} +
+- No workspace skills yet + No shared skills yet
{description}
@@ -117,7 +117,7 @@ export function DeleteWorkspaceSkillDialog({
{skill.name}
- {skill.description}
- {skill.name}
- {skill.description}
-
- No skills yet
-
- Create a personal slash command for prompts you reuse in Ask Sourcebot.
-
- Manage personal slash-command workflows for Ask Sourcebot.
-
- Personal
-
- {personalSkills.length} {pluralize(personalSkills.length, "skill")}
-
- Shared
-
- {orgSkills.length} {pluralize(orgSkills.length, "skill")}
- Skills
-
{name}
+ {badge} +/{slug}
+{label}
+{count}
+{emptyLabel}
+ ) : ( + children + )} +No skill selected
++ Select a skill to view its details, or create a personal slash-command workflow for Ask Sourcebot. +
+{skill.description}
+ )} +{label}
+{children}
++ On this page +
+