feat: add tool status management and localization for skill installation

- Updated chat message types to include tool statuses.
- Enhanced localization files for English, Thai, and Chinese to support new tool status messages.
- Modified HomePage and SkillsPage components to handle tool statuses in chat messages.
- Implemented tool status merging and updating logic in the chat store.
- Added handling for tool status events in the gateway event processing.
- Created tests for chat message rendering with tool statuses and skill installation shortcuts.
- Improved gateway event dispatching for tool lifecycle events.
This commit is contained in:
duanshuwen
2026-04-23 20:27:54 +08:00
parent 979fb0a0f6
commit df600272d6
29 changed files with 2041 additions and 384 deletions

View File

@@ -0,0 +1,174 @@
// @vitest-environment node
import { beforeEach, describe, expect, it, vi } from 'vitest';
const mocks = vi.hoisted(() => ({
appendMessage: vi.fn(),
setActiveRun: vi.fn(),
clearActiveRun: vi.fn(),
handleSkillsInstall: vi.fn(),
parseGitHubSkillUrl: vi.fn((url: string) => ({
owner: 'MiniMax-AI',
repo: 'skills',
ref: 'main',
skillPath: 'skills/minimax-xlsx',
defaultSlug: 'minimax-xlsx',
archiveUrl: 'https://api.github.com/repos/MiniMax-AI/skills/zipball/main',
repositoryUrl: 'https://github.com/MiniMax-AI/skills.git',
originalUrl: url,
})),
appendTranscriptLine: vi.fn(),
logger: {
error: vi.fn(),
},
}));
vi.mock('../electron/gateway/session-store', () => ({
sessionStore: {
appendMessage: mocks.appendMessage,
setActiveRun: mocks.setActiveRun,
clearActiveRun: mocks.clearActiveRun,
},
}));
vi.mock('../electron/gateway/handlers/skills', () => ({
handleSkillsInstall: mocks.handleSkillsInstall,
}));
vi.mock('@electron/service/skill-install-service', () => ({
parseGitHubSkillUrl: mocks.parseGitHubSkillUrl,
}));
vi.mock('@electron/utils/token-usage-writer', () => ({
appendTranscriptLine: mocks.appendTranscriptLine,
}));
vi.mock('@electron/service/logger', () => ({
default: mocks.logger,
}));
function flushAsyncTasks(): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, 0));
}
describe('gateway skill install shortcut', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('extracts a github-url install intent from an explicit install request', async () => {
const { extractSkillInstallIntent } = await import('../electron/gateway/skill-install-shortcut');
expect(
extractSkillInstallIntent(
'https://github.com/MiniMax-AI/skills/blob/main/skills/minimax-xlsx/SKILL.md帮我安装这个skill',
),
).toEqual({
request: {
kind: 'github-url',
url: 'https://github.com/MiniMax-AI/skills/blob/main/skills/minimax-xlsx/SKILL.md',
},
description: 'minimax-xlsx',
});
});
it('extracts a marketplace install intent when the user explicitly names a skill slug', async () => {
const { extractSkillInstallIntent } = await import('../electron/gateway/skill-install-shortcut');
expect(
extractSkillInstallIntent('帮我安装 minimax-xlsx 这个 skill'),
).toEqual({
request: {
kind: 'marketplace',
slug: 'minimax-xlsx',
},
description: 'minimax-xlsx',
});
});
it('runs a skill install shortcut and emits tool status before the final assistant message', async () => {
mocks.handleSkillsInstall.mockResolvedValue({
success: true,
slug: 'minimax-xlsx',
baseDir: '/tmp/minimax-xlsx',
source: 'github-url',
enabled: true,
});
const { maybeHandleSkillInstallMessage } = await import('../electron/gateway/skill-install-shortcut');
const broadcast = vi.fn();
const handled = maybeHandleSkillInstallMessage(
'agent:test:main',
'run-1',
{
role: 'user',
content: 'https://github.com/MiniMax-AI/skills/blob/main/skills/minimax-xlsx/SKILL.md帮我安装这个skill',
},
broadcast,
);
expect(handled).toBe(true);
expect(mocks.setActiveRun).toHaveBeenCalledWith(
'agent:test:main',
'run-1',
expect.any(AbortController),
);
await flushAsyncTasks();
expect(mocks.handleSkillsInstall).toHaveBeenCalledWith(
{
kind: 'github-url',
url: 'https://github.com/MiniMax-AI/skills/blob/main/skills/minimax-xlsx/SKILL.md',
},
broadcast,
);
expect(broadcast).toHaveBeenNthCalledWith(1, expect.objectContaining({
type: 'tool:status',
runId: 'run-1',
toolName: 'skills.install',
status: 'running',
}));
expect(broadcast).toHaveBeenNthCalledWith(2, expect.objectContaining({
type: 'tool:status',
runId: 'run-1',
toolName: 'skills.install',
status: 'completed',
}));
expect(mocks.appendMessage).toHaveBeenCalledWith(
'agent:test:main',
expect.objectContaining({
role: 'assistant',
content: '已安装并启用 skill minimax-xlsxgithub-url。位置/tmp/minimax-xlsx',
_toolStatuses: [
expect.objectContaining({
name: 'skills.install',
status: 'completed',
input: {
kind: 'github-url',
url: 'https://github.com/MiniMax-AI/skills/blob/main/skills/minimax-xlsx/SKILL.md',
},
result: expect.objectContaining({
slug: 'minimax-xlsx',
baseDir: '/tmp/minimax-xlsx',
source: 'github-url',
}),
}),
],
}),
);
expect(broadcast).toHaveBeenNthCalledWith(3, expect.objectContaining({
type: 'chat:final',
runId: 'run-1',
message: expect.objectContaining({
_toolStatuses: [
expect.objectContaining({
name: 'skills.install',
status: 'completed',
}),
],
}),
}));
});
});