From ddce51eaba65bf89ee07c07331197deca7160802 Mon Sep 17 00:00:00 2001 From: Arvin Xu Date: Mon, 23 Feb 2026 00:28:10 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20fix=20skill=20search=20no?= =?UTF-8?q?t=20found=20(#12432)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix skill issue * fix skills * fix skills search query --- package.json | 8 +- .../src/ExecutionRuntime/index.test.ts | 159 ++++++++++++++++++ .../src/ExecutionRuntime/index.ts | 10 +- packages/builtin-tool-skills/src/manifest.ts | 19 ++- packages/builtin-tool-skills/src/types.ts | 16 +- .../builtin-tool-skills/vitest.config.mts | 7 + scripts/vercelIgnoredBuildStep.js | 7 +- src/server/routers/lambda/market/skill.ts | 13 +- src/server/services/market/index.ts | 12 +- src/services/marketApi.ts | 14 +- 10 files changed, 239 insertions(+), 26 deletions(-) create mode 100644 packages/builtin-tool-skills/src/ExecutionRuntime/index.test.ts create mode 100644 packages/builtin-tool-skills/vitest.config.mts diff --git a/package.json b/package.json index 16428d225b..1301dc34e3 100644 --- a/package.json +++ b/package.json @@ -201,8 +201,6 @@ "@lobechat/builtin-tool-web-browsing": "workspace:*", "@lobechat/business-config": "workspace:*", "@lobechat/business-const": "workspace:*", - "@lobechat/eval-dataset-parser": "workspace:*", - "@lobechat/eval-rubric": "workspace:*", "@lobechat/config": "workspace:*", "@lobechat/const": "workspace:*", "@lobechat/context-engine": "workspace:*", @@ -213,6 +211,8 @@ "@lobechat/editor-runtime": "workspace:*", "@lobechat/electron-client-ipc": "workspace:*", "@lobechat/electron-server-ipc": "workspace:*", + "@lobechat/eval-dataset-parser": "workspace:*", + "@lobechat/eval-rubric": "workspace:*", "@lobechat/fetch-sse": "workspace:*", "@lobechat/file-loaders": "workspace:*", "@lobechat/memory-user-memory": "workspace:*", @@ -230,7 +230,7 @@ "@lobehub/desktop-ipc-typings": "workspace:*", "@lobehub/editor": "^3.16.1", "@lobehub/icons": "^4.1.0", - "@lobehub/market-sdk": "0.29.3", + "@lobehub/market-sdk": "^0.30.3", "@lobehub/tts": "^4.0.2", "@lobehub/ui": "^4.38.1", "@modelcontextprotocol/sdk": "^1.25.3", @@ -491,4 +491,4 @@ "drizzle-orm": "0.44.7" } } -} \ No newline at end of file +} diff --git a/packages/builtin-tool-skills/src/ExecutionRuntime/index.test.ts b/packages/builtin-tool-skills/src/ExecutionRuntime/index.test.ts new file mode 100644 index 0000000000..cb7d26aa08 --- /dev/null +++ b/packages/builtin-tool-skills/src/ExecutionRuntime/index.test.ts @@ -0,0 +1,159 @@ +import { describe, expect, it, vi } from 'vitest'; + +import type { CommandResult } from '../types'; +import { SkillsExecutionRuntime, type SkillRuntimeService } from './index'; + +const createMockService = (overrides?: Partial): SkillRuntimeService => ({ + findAll: vi.fn().mockResolvedValue({ data: [], total: 0 }), + findById: vi.fn().mockResolvedValue(undefined), + findByName: vi.fn().mockResolvedValue(undefined), + importFromGitHub: vi.fn(), + importFromUrl: vi.fn(), + importFromZipUrl: vi.fn(), + readResource: vi.fn(), + ...overrides, +}); + +describe('SkillsExecutionRuntime', () => { + describe('execScript', () => { + const args = { command: 'echo hello', description: 'test command' }; + + describe('via execScript service method', () => { + it('should return success: true when script succeeds', async () => { + const service = createMockService({ + execScript: vi.fn().mockResolvedValue({ + exitCode: 0, + output: 'hello', + success: true, + } satisfies CommandResult), + }); + const runtime = new SkillsExecutionRuntime({ service }); + + const result = await runtime.execScript(args); + + expect(result.success).toBe(true); + expect(result.content).toBe('hello'); + expect(result.state).toEqual({ command: 'echo hello', exitCode: 0, success: true }); + }); + + it('should return success: false when script fails with non-zero exit code', async () => { + const service = createMockService({ + execScript: vi.fn().mockResolvedValue({ + exitCode: 1, + output: '', + stderr: 'command not found', + success: false, + } satisfies CommandResult), + }); + const runtime = new SkillsExecutionRuntime({ service }); + + const result = await runtime.execScript(args); + + expect(result.success).toBe(false); + expect(result.content).toBe('command not found'); + expect(result.state).toEqual({ command: 'echo hello', exitCode: 1, success: false }); + }); + + it('should combine output and stderr', async () => { + const service = createMockService({ + execScript: vi.fn().mockResolvedValue({ + exitCode: 0, + output: 'stdout line', + stderr: 'stderr line', + success: true, + } satisfies CommandResult), + }); + const runtime = new SkillsExecutionRuntime({ service }); + + const result = await runtime.execScript(args); + + expect(result.content).toBe('stdout line\nstderr line'); + }); + + it('should return "(no output)" when output is empty', async () => { + const service = createMockService({ + execScript: vi.fn().mockResolvedValue({ + exitCode: 0, + output: '', + success: true, + } satisfies CommandResult), + }); + const runtime = new SkillsExecutionRuntime({ service }); + + const result = await runtime.execScript(args); + + expect(result.content).toBe('(no output)'); + }); + + it('should return success: false when execScript throws', async () => { + const service = createMockService({ + execScript: vi.fn().mockRejectedValue(new Error('sandbox timeout')), + }); + const runtime = new SkillsExecutionRuntime({ service }); + + const result = await runtime.execScript(args); + + expect(result.success).toBe(false); + expect(result.content).toBe('Failed to execute command: sandbox timeout'); + }); + }); + + describe('via runCommand fallback', () => { + it('should return success: true when command succeeds', async () => { + const service = createMockService({ + runCommand: vi.fn().mockResolvedValue({ + exitCode: 0, + output: 'ok', + success: true, + } satisfies CommandResult), + }); + const runtime = new SkillsExecutionRuntime({ service }); + + const result = await runtime.execScript(args); + + expect(result.success).toBe(true); + expect(result.content).toBe('ok'); + }); + + it('should return success: false when command fails with non-zero exit code', async () => { + const service = createMockService({ + runCommand: vi.fn().mockResolvedValue({ + exitCode: 127, + output: '', + stderr: 'not found', + success: false, + } satisfies CommandResult), + }); + const runtime = new SkillsExecutionRuntime({ service }); + + const result = await runtime.execScript(args); + + expect(result.success).toBe(false); + expect(result.content).toBe('not found'); + expect(result.state).toEqual({ command: 'echo hello', exitCode: 127, success: false }); + }); + + it('should return success: false when runCommand throws', async () => { + const service = createMockService({ + runCommand: vi.fn().mockRejectedValue(new Error('connection lost')), + }); + const runtime = new SkillsExecutionRuntime({ service }); + + const result = await runtime.execScript(args); + + expect(result.success).toBe(false); + expect(result.content).toBe('Failed to execute command: connection lost'); + }); + + it('should return success: false when neither execScript nor runCommand is available', async () => { + const service = createMockService(); + const runtime = new SkillsExecutionRuntime({ service }); + + const result = await runtime.execScript(args); + + expect(result.success).toBe(false); + expect(result.content).toBe('Command execution is not available in this environment.'); + }); + }); + }); +}); diff --git a/packages/builtin-tool-skills/src/ExecutionRuntime/index.ts b/packages/builtin-tool-skills/src/ExecutionRuntime/index.ts index 911efbef46..5f8f85a6ef 100644 --- a/packages/builtin-tool-skills/src/ExecutionRuntime/index.ts +++ b/packages/builtin-tool-skills/src/ExecutionRuntime/index.ts @@ -137,7 +137,7 @@ export class SkillsExecutionRuntime { exitCode: result.exitCode, success: result.success, }, - success: true, + success: result.success, }; } catch (e) { return { @@ -167,7 +167,7 @@ export class SkillsExecutionRuntime { exitCode: result.exitCode, success: result.success, }, - success: true, + success: result.success, }; } catch (e) { return { @@ -322,8 +322,8 @@ export class SkillsExecutionRuntime { if (result.items.length === 0) { return { - content: args.search - ? `No skills found matching "${args.search}"` + content: args.q + ? `No skills found matching "${args.q}"` : 'No skills found in the market', state: result, success: true, @@ -334,7 +334,7 @@ export class SkillsExecutionRuntime { const skillsList = result.items .map( (skill, index) => - `${index + 1}. **${skill.name}** (${skill.identifier})\n ${skill.description}${skill.summary ? `\n Summary: ${skill.summary}` : ''}${skill.repository ? `\n Repository: ${skill.repository}` : ''}${skill.downloadCount ? `\n Downloads: ${skill.downloadCount}` : ''}`, + `${index + 1}. **${skill.name}** (${skill.identifier})\n ${skill.description}${skill.summary ? `\n Summary: ${skill.summary}` : ''}${skill.repository ? `\n Repository: ${skill.repository}` : ''}${skill.installCount ? `\n Installs: ${skill.installCount}` : ''}`, ) .join('\n\n'); diff --git a/packages/builtin-tool-skills/src/manifest.ts b/packages/builtin-tool-skills/src/manifest.ts index 7d65e46e2d..e6b4ae005e 100644 --- a/packages/builtin-tool-skills/src/manifest.ts +++ b/packages/builtin-tool-skills/src/manifest.ts @@ -1,4 +1,4 @@ -import { BuiltinToolManifest } from '@lobechat/types'; +import type { BuiltinToolManifest } from '@lobechat/types'; import { isDesktop } from './const'; import { systemPrompt } from './systemRole'; @@ -134,7 +134,7 @@ export const SkillsManifest: BuiltinToolManifest = { }, { description: - 'Search for skills in the LobeHub Market. Use this to discover available skills that can extend Claude\'s capabilities. Search across skill names, descriptions, and summaries. Results can be filtered and sorted by various criteria (stars, downloads, etc).', + "Search for skills in the LobeHub Market. Use this to discover available skills that can extend Claude's capabilities. Search across skill names, descriptions, and summaries. Results can be filtered and sorted by various criteria (stars, downloads, etc).", name: SkillsApiName.searchSkill, parameters: { properties: { @@ -155,15 +155,24 @@ export const SkillsManifest: BuiltinToolManifest = { description: 'Number of results per page. Default: 20.', type: 'number', }, - search: { + q: { description: 'Search query to filter skills. Searches across skill name, description, and summary. Optional.', type: 'string', }, sort: { description: - 'Field to sort by. Options: createdAt (creation date), downloadCount (downloads), forks (GitHub forks), name (alphabetical), stars (GitHub stars), updatedAt (last update), watchers (GitHub watchers). Default: "updatedAt".', - enum: ['createdAt', 'downloadCount', 'forks', 'name', 'stars', 'updatedAt', 'watchers'], + 'Field to sort by. Options: createdAt (creation date), installCount (installs), forks (GitHub forks), name (alphabetical), relevance (search relevance), stars (GitHub stars), updatedAt (last update), watchers (GitHub watchers). Default: "updatedAt".', + enum: [ + 'createdAt', + 'forks', + 'installCount', + 'name', + 'relevance', + 'stars', + 'updatedAt', + 'watchers', + ], type: 'string', }, }, diff --git a/packages/builtin-tool-skills/src/types.ts b/packages/builtin-tool-skills/src/types.ts index c88a9d5c08..6152323bc5 100644 --- a/packages/builtin-tool-skills/src/types.ts +++ b/packages/builtin-tool-skills/src/types.ts @@ -131,19 +131,27 @@ export interface SearchSkillParams { /** * Search query (searches name, description, summary) */ - search?: string; + q?: string; /** - * Sort field: createdAt | downloadCount | forks | name | stars | updatedAt | watchers + * Sort field: createdAt | installCount | forks | name | relevance | stars | updatedAt | watchers */ - sort?: 'createdAt' | 'downloadCount' | 'forks' | 'name' | 'stars' | 'updatedAt' | 'watchers'; + sort?: + | 'createdAt' + | 'forks' + | 'installCount' + | 'name' + | 'relevance' + | 'stars' + | 'updatedAt' + | 'watchers'; } export interface MarketSkillItem { category?: string; createdAt: string; description: string; - downloadCount: number; identifier: string; + installCount: number; name: string; repository?: string; sourceUrl?: string; diff --git a/packages/builtin-tool-skills/vitest.config.mts b/packages/builtin-tool-skills/vitest.config.mts new file mode 100644 index 0000000000..4ac6027d57 --- /dev/null +++ b/packages/builtin-tool-skills/vitest.config.mts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + }, +}); diff --git a/scripts/vercelIgnoredBuildStep.js b/scripts/vercelIgnoredBuildStep.js index 411e22b304..1d85c9c780 100644 --- a/scripts/vercelIgnoredBuildStep.js +++ b/scripts/vercelIgnoredBuildStep.js @@ -5,7 +5,12 @@ const branchName = process.env.VERCEL_GIT_COMMIT_REF || ''; function shouldProceedBuild() { // 如果是 lighthouse 分支或以 testgru 开头的分支,取消构建 - if (branchName === 'lighthouse' || branchName.startsWith('gru/')) { + if ( + branchName === 'lighthouse' || + ['gru', 'automatic', 'reproduction'].some((item) => + branchName.startsWith(`${item.toLowerCase()}/`), + ) + ) { return false; } diff --git a/src/server/routers/lambda/market/skill.ts b/src/server/routers/lambda/market/skill.ts index 2f712f27ad..6cff353d9a 100644 --- a/src/server/routers/lambda/market/skill.ts +++ b/src/server/routers/lambda/market/skill.ts @@ -31,9 +31,18 @@ export const skillRouter = router({ order: z.enum(['asc', 'desc']).optional(), page: z.number().optional(), pageSize: z.number().optional(), - search: z.string().optional(), + q: z.string().optional(), sort: z - .enum(['createdAt', 'downloadCount', 'forks', 'name', 'stars', 'updatedAt', 'watchers']) + .enum([ + 'createdAt', + 'forks', + 'installCount', + 'name', + 'relevance', + 'stars', + 'updatedAt', + 'watchers', + ]) .optional(), }), ) diff --git a/src/server/services/market/index.ts b/src/server/services/market/index.ts index 1411a83b83..58a053c096 100644 --- a/src/server/services/market/index.ts +++ b/src/server/services/market/index.ts @@ -396,8 +396,16 @@ export class MarketService { order?: 'asc' | 'desc'; page?: number; pageSize?: number; - search?: string; - sort?: 'createdAt' | 'downloadCount' | 'forks' | 'name' | 'stars' | 'updatedAt' | 'watchers'; + q?: string; + sort?: + | 'createdAt' + | 'forks' + | 'installCount' + | 'name' + | 'relevance' + | 'stars' + | 'updatedAt' + | 'watchers'; }) { log('searchSkill: %O', params); diff --git a/src/services/marketApi.ts b/src/services/marketApi.ts index 126b316eb9..09a884b78d 100644 --- a/src/services/marketApi.ts +++ b/src/services/marketApi.ts @@ -213,14 +213,22 @@ export class MarketApiService { order?: 'asc' | 'desc'; page?: number; pageSize?: number; - search?: string; - sort?: 'createdAt' | 'downloadCount' | 'forks' | 'name' | 'stars' | 'updatedAt' | 'watchers'; + q?: string; + sort?: + | 'createdAt' + | 'forks' + | 'installCount' + | 'name' + | 'relevance' + | 'stars' + | 'updatedAt' + | 'watchers'; }): Promise<{ items: Array<{ category?: string; createdAt: string; description: string; - downloadCount: number; + installCount: number; identifier: string; name: string; repository?: string;