Skip to content

Commit 53420ff

Browse files
committed
Update skills
1 parent 2f58bb9 commit 53420ff

9 files changed

Lines changed: 362 additions & 81 deletions

File tree

apps/sim/app/api/skills/route.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
1111
import { generateRequestId } from '@/lib/core/utils/request'
1212
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1313
import { captureServerEvent } from '@/lib/posthog/server'
14+
import { isBuiltinSkillId } from '@/lib/workflows/skills/builtin-skills'
1415
import { deleteSkill, listSkills, upsertSkills } from '@/lib/workflows/skills/operations'
1516
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
1617

@@ -47,8 +48,9 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
4748
}
4849

4950
const result = await listSkills({ workspaceId })
51+
const data = result.map((s) => ({ ...s, readOnly: isBuiltinSkillId(s.id) }))
5052

51-
return NextResponse.json({ data: result }, { status: 200 })
53+
return NextResponse.json({ data }, { status: 200 })
5254
} catch (error) {
5355
logger.error(`[${requestId}] Error fetching skills:`, error)
5456
return NextResponse.json({ error: 'Failed to fetch skills' }, { status: 500 })

apps/sim/app/workspace/[workspaceId]/skills/components/skill-modal/skill-modal.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ export function SkillModal({
144144
)
145145

146146
const isEditing = !!initialValues
147+
const readOnly = !!initialValues?.readOnly
147148
const showFooter = activeTab === 'create' || isEditing
148149

149150
return (
@@ -249,15 +250,25 @@ export function SkillModal({
249250
{showFooter && (
250251
<ChipModalFooter className={isEditing && onDelete ? 'justify-between' : undefined}>
251252
{isEditing && onDelete && (
252-
<Chip variant='destructive' flush onClick={() => onDelete(initialValues.id)}>
253+
<Chip
254+
variant='destructive'
255+
flush
256+
onClick={() => onDelete(initialValues.id)}
257+
disabled={readOnly}
258+
>
253259
Delete
254260
</Chip>
255261
)}
256262
<div className='flex gap-2'>
257263
<Chip variant='filled' flush onClick={() => onOpenChange(false)}>
258264
Cancel
259265
</Chip>
260-
<Chip variant='primary' flush onClick={handleSave} disabled={saving || !hasChanges}>
266+
<Chip
267+
variant='primary'
268+
flush
269+
onClick={handleSave}
270+
disabled={readOnly || saving || !hasChanges}
271+
>
261272
{saving ? 'Saving...' : isEditing ? 'Update' : 'Create'}
262273
</Chip>
263274
</div>

apps/sim/app/workspace/[workspaceId]/skills/fixtures/sample-skills.ts

Lines changed: 0 additions & 57 deletions
This file was deleted.

apps/sim/app/workspace/[workspaceId]/skills/skills.tsx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useMemo, useState } from 'react'
3+
import { useState } from 'react'
44
import { createLogger } from '@sim/logger'
55
import { getErrorMessage } from '@sim/utils/errors'
66
import { ArrowRight, Plus, Search } from 'lucide-react'
@@ -10,10 +10,6 @@ import { AgentSkillsIcon } from '@/components/icons'
1010
import { IntegrationTabsHeader } from '@/app/workspace/[workspaceId]/integrations/components/integration-tabs-header'
1111
import { ShowcaseWithExplore } from '@/app/workspace/[workspaceId]/integrations/components/showcase-with-explore'
1212
import { SkillModal } from '@/app/workspace/[workspaceId]/skills/components/skill-modal'
13-
import {
14-
getSampleSkills,
15-
PREVIEW_SKILLS_WITH_SAMPLES,
16-
} from '@/app/workspace/[workspaceId]/skills/fixtures/sample-skills'
1713
import type { SkillDefinition } from '@/hooks/queries/skills'
1814
import { useDeleteSkill, useSkills } from '@/hooks/queries/skills'
1915

@@ -86,12 +82,7 @@ export function Skills() {
8682
const [skillToDelete, setSkillToDelete] = useState<{ id: string; name: string } | null>(null)
8783
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
8884

89-
const allSkills = useMemo(() => {
90-
if (!PREVIEW_SKILLS_WITH_SAMPLES) return skills
91-
return [...getSampleSkills(workspaceId), ...skills]
92-
}, [skills, workspaceId])
93-
94-
const filteredSkills = allSkills.filter((s) => {
85+
const filteredSkills = skills.filter((s) => {
9586
if (!searchTerm.trim()) return true
9687
const searchLower = searchTerm.toLowerCase()
9788
return (
@@ -101,7 +92,7 @@ export function Skills() {
10192
})
10293

10394
const handleDeleteClick = (skillId: string) => {
104-
const s = allSkills.find((sk) => sk.id === skillId)
95+
const s = skills.find((sk) => sk.id === skillId)
10596
if (!s) return
10697

10798
setSkillToDelete({ id: skillId, name: s.name })

apps/sim/executor/handlers/agent/skills-resolver.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { skill } from '@sim/db/schema'
33
import { createLogger } from '@sim/logger'
44
import { and, eq, inArray } from 'drizzle-orm'
55
import type { SkillInput } from '@/executor/handlers/agent/types'
6+
import {
7+
getBuiltinSkillById,
8+
getBuiltinSkillByName,
9+
} from '@/lib/workflows/skills/builtin-skills'
610

711
const logger = createLogger('SkillsResolver')
812

@@ -29,18 +33,29 @@ export async function resolveSkillMetadata(
2933
): Promise<SkillMetadata[]> {
3034
if (!skillInputs.length || !workspaceId) return []
3135

32-
const skillIds = skillInputs.map((s) => s.skillId)
36+
const metadata: SkillMetadata[] = []
37+
const dbSkillIds: string[] = []
38+
for (const input of skillInputs) {
39+
const builtin = getBuiltinSkillById(input.skillId)
40+
if (builtin) {
41+
metadata.push({ name: builtin.name, description: builtin.description })
42+
} else {
43+
dbSkillIds.push(input.skillId)
44+
}
45+
}
46+
47+
if (dbSkillIds.length === 0) return metadata
3348

3449
try {
3550
const rows = await db
3651
.select({ name: skill.name, description: skill.description })
3752
.from(skill)
38-
.where(and(eq(skill.workspaceId, workspaceId), inArray(skill.id, skillIds)))
53+
.where(and(eq(skill.workspaceId, workspaceId), inArray(skill.id, dbSkillIds)))
3954

40-
return rows
55+
return [...metadata, ...rows]
4156
} catch (error) {
42-
logger.error('Failed to resolve skill metadata', { error, skillIds, workspaceId })
43-
return []
57+
logger.error('Failed to resolve skill metadata', { error, dbSkillIds, workspaceId })
58+
return metadata
4459
}
4560
}
4661

@@ -54,6 +69,9 @@ export async function resolveSkillContent(
5469
): Promise<string | null> {
5570
if (!skillName || !workspaceId) return null
5671

72+
const builtin = getBuiltinSkillByName(skillName)
73+
if (builtin) return builtin.content
74+
5775
try {
5876
const rows = await db
5977
.select({ content: skill.content, name: skill.name })
@@ -79,6 +97,9 @@ export async function resolveSkillContentById(
7997
): Promise<{ name: string; content: string } | null> {
8098
if (!skillId || !workspaceId) return null
8199

100+
const builtin = getBuiltinSkillById(skillId)
101+
if (builtin) return { name: builtin.name, content: builtin.content }
102+
82103
try {
83104
const rows = await db
84105
.select({ content: skill.content, name: skill.name })

apps/sim/lib/api/contracts/skills.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export const skillSchema = z.object({
1010
content: z.string(),
1111
createdAt: z.string(),
1212
updatedAt: z.string(),
13+
/** True for built-in template skills, which are read-only and not stored in the DB. */
14+
readOnly: z.boolean().optional(),
1315
})
1416

1517
export type Skill = z.output<typeof skillSchema>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { describe, expect, it } from 'vitest'
5+
import {
6+
BUILTIN_SKILLS,
7+
getBuiltinSkillById,
8+
getBuiltinSkillByName,
9+
isBuiltinSkillId,
10+
} from './builtin-skills'
11+
12+
describe('builtin skills', () => {
13+
it('ships the four template skills with stable ids and valid fields', () => {
14+
expect(BUILTIN_SKILLS.map((s) => s.id)).toEqual([
15+
'builtin-connect-integration',
16+
'builtin-research',
17+
'builtin-create-table',
18+
'builtin-deploy-workflow',
19+
])
20+
for (const s of BUILTIN_SKILLS) {
21+
expect(s.name).toMatch(/^[a-z0-9]+(-[a-z0-9]+)*$/)
22+
expect(s.description.length).toBeGreaterThan(0)
23+
expect(s.content.length).toBeGreaterThan(0)
24+
}
25+
})
26+
27+
it('resolves by id and name (case-insensitive) and reports membership', () => {
28+
expect(getBuiltinSkillById('builtin-research')?.name).toBe('research')
29+
expect(getBuiltinSkillByName('RESEARCH')?.id).toBe('builtin-research')
30+
expect(isBuiltinSkillId('builtin-deploy-workflow')).toBe(true)
31+
expect(isBuiltinSkillId('sk-some-db-id')).toBe(false)
32+
expect(getBuiltinSkillById('does-not-exist')).toBeUndefined()
33+
})
34+
})

0 commit comments

Comments
 (0)