@steerable/agent-shell-web 0.6.18 → 0.6.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +15 -8
- package/src/lib/chat-transport.ts +1 -2
- package/src/lib/electron-bridge.ts +2 -2
- package/src/lib/http-bridge.ts +18 -4
- package/vite.base.d.ts +7 -0
- package/vite.base.js +83 -0
- package/src/brand.test.ts +0 -22
- package/src/components/AgentSidebar.test.tsx +0 -791
- package/src/components/ChatHeader.test.tsx +0 -230
- package/src/components/LocalLlmSettingsModal.test.tsx +0 -372
- package/src/components/chat/ApprovalModal.test.tsx +0 -83
- package/src/components/chat/AskUserModalHost.test.tsx +0 -176
- package/src/components/chat/AskUserQuestionMenu.test.tsx +0 -190
- package/src/components/chat/AssistantMessage.test.tsx +0 -68
- package/src/components/chat/ChatInput.test.tsx +0 -609
- package/src/components/chat/ExecPolicyPicker.test.tsx +0 -26
- package/src/components/chat/Markdown.test.tsx +0 -170
- package/src/components/chat/ModelPicker.test.tsx +0 -149
- package/src/components/chat/SessionTreeModal.test.tsx +0 -161
- package/src/components/chat/TaskOutcomeCards.test.tsx +0 -97
- package/src/components/chat/TaskPanelModal.test.tsx +0 -198
- package/src/components/chat/TaskProcessPanel.test.tsx +0 -111
- package/src/components/chat/TurnProcessGroup.test.tsx +0 -161
- package/src/components/chat/elapsed.test.ts +0 -42
- package/src/components/chat/executed-actions-model.test.ts +0 -134
- package/src/components/chat/orchestration-children-model.test.ts +0 -115
- package/src/components/chat/turn-timeline.test.ts +0 -97
- package/src/components/chat/useChatTasks.test.ts +0 -136
- package/src/components/settings/AgentsSettingsPanel.test.tsx +0 -229
- package/src/components/settings/InsightsSettingsPanel.test.tsx +0 -132
- package/src/components/settings/LlmSettingsPanel.test.tsx +0 -461
- package/src/components/settings/ModelIdCombobox.test.tsx +0 -88
- package/src/components/settings/SecuritySettingsPanel.test.tsx +0 -122
- package/src/components/settings/SkillsSettingsPanel.test.tsx +0 -62
- package/src/components/settings/WebSearchSettingsPanel.test.tsx +0 -90
- package/src/components/settings/compat-flags-model.test.ts +0 -71
- package/src/components/settings/llm-vendors.test.ts +0 -171
- package/src/components/settings/model-capabilities.test.ts +0 -88
- package/src/components/settings/preset-choice-model.test.ts +0 -96
- package/src/hooks/useChatsAndAgents.test.ts +0 -165
- package/src/lib/attachments.test.ts +0 -127
- package/src/lib/chat-title.test.ts +0 -53
- package/src/lib/chat-transport.test.ts +0 -277
- package/src/lib/date-groups.test.ts +0 -62
- package/src/lib/demo-flag.test.ts +0 -17
- package/src/lib/electron-bridge.test.ts +0 -54
- package/src/lib/exec-policy.test.ts +0 -71
- package/src/lib/http-bridge.test.ts +0 -246
- package/src/lib/insights.test.ts +0 -56
- package/src/lib/local-api.test.ts +0 -353
- package/src/lib/pending-first-message.test.ts +0 -17
- package/src/lib/slash-sources.test.ts +0 -161
- package/src/pages/AgentPage.test.tsx +0 -627
- package/src/pages/SettingsPage.test.tsx +0 -102
- package/vite.base.ts +0 -94
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* slash-sources:`/` 令牌背后的技能 + MCP 工具目录。
|
|
3
|
-
* 锁定:隐藏技能的三种命中方式(内建名 / 目录 id / 包注册表)、
|
|
4
|
-
* SLASH_TOKEN_PATTERN 的真实匹配边界(行首或空白后、不含内层斜杠——
|
|
5
|
-
* 路径排除发生在 resolve 阶段而不是正则阶段)、resolveSlashTool 的
|
|
6
|
-
* 优先级(MCP 精确 token > mcp__ 前缀直通 > 技能名 / displayName,
|
|
7
|
-
* 隐藏技能永不解析)、refreshSlashSources 的降级(MCP 失败不影响技能)。
|
|
8
|
-
*/
|
|
9
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
10
|
-
import { registerPackRenderer } from '../packs/registry';
|
|
11
|
-
import {
|
|
12
|
-
isHiddenSlashSkill,
|
|
13
|
-
refreshSlashSources,
|
|
14
|
-
resolveSlashTool,
|
|
15
|
-
SLASH_TOKEN_PATTERN,
|
|
16
|
-
type McpToolItem,
|
|
17
|
-
type SkillItem,
|
|
18
|
-
} from './slash-sources';
|
|
19
|
-
|
|
20
|
-
afterEach(() => {
|
|
21
|
-
delete (window as { electron?: unknown }).electron;
|
|
22
|
-
vi.restoreAllMocks();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
describe('isHiddenSlashSkill', () => {
|
|
26
|
-
it('内建技能按 name 或目录 id 命中', () => {
|
|
27
|
-
expect(isHiddenSlashSkill({ name: 'plan-mode', description: '' })).toBe(true);
|
|
28
|
-
expect(isHiddenSlashSkill({ name: 'identity', description: '' })).toBe(true);
|
|
29
|
-
expect(
|
|
30
|
-
isHiddenSlashSkill({ name: '计划', id: '70-plan-mode', description: '' }),
|
|
31
|
-
).toBe(true);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('业务技能不隐藏', () => {
|
|
35
|
-
expect(isHiddenSlashSkill({ name: 'commit', description: '' })).toBe(false);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('包渲染层声明的隐藏技能也命中', () => {
|
|
39
|
-
registerPackRenderer('test-pack-slash', { hiddenSlashSkills: ['pack-secret'] });
|
|
40
|
-
expect(isHiddenSlashSkill({ name: 'pack-secret', description: '' })).toBe(true);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('SLASH_TOKEN_PATTERN 匹配边界', () => {
|
|
45
|
-
/** 消费方用的是捕获组 1(完整匹配带前导空白),这里同样取捕获组。 */
|
|
46
|
-
const tokens = (s: string) => [...s.matchAll(SLASH_TOKEN_PATTERN)].map((m) => m[1]);
|
|
47
|
-
|
|
48
|
-
it('匹配行首或空白后的 /token', () => {
|
|
49
|
-
expect(tokens('hello /foo world')).toEqual(['/foo']);
|
|
50
|
-
expect(tokens('/foo /bar')).toEqual(['/foo', '/bar']);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('紧跟在非空白后的斜杠不匹配(分数、相对路径、URL)', () => {
|
|
54
|
-
expect(tokens('a/b 1/2')).toEqual([]);
|
|
55
|
-
expect(tokens('https://a.com/b')).toEqual([]);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('绝对路径里的内层斜杠会截断 token——路径排除靠 resolve 阶段', () => {
|
|
59
|
-
// 正则只保证「空白后 + 无内层斜杠」;/mnt/c 被截成 /mnt,
|
|
60
|
-
// 再由 resolveSlashTool 查目录落空来排除。
|
|
61
|
-
expect(tokens('see /mnt/c')).toEqual(['/mnt']);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe('resolveSlashTool', () => {
|
|
66
|
-
const skills: SkillItem[] = [
|
|
67
|
-
{ name: 'commit', description: '提交' },
|
|
68
|
-
{ name: 'x', displayName: '提交', description: '' },
|
|
69
|
-
{ name: 'plan-mode', description: '' },
|
|
70
|
-
];
|
|
71
|
-
const mcpTools: McpToolItem[] = [
|
|
72
|
-
{ token: 'mcp__fs__read', toolName: 'read', serverKey: 'fs', serverName: 'FS', description: '' },
|
|
73
|
-
];
|
|
74
|
-
|
|
75
|
-
it('MCP 精确 token 命中(大小写不敏感),label 保留用户输入', () => {
|
|
76
|
-
expect(resolveSlashTool('mcp__fs__read', skills, mcpTools)).toEqual({
|
|
77
|
-
type: 'mcp',
|
|
78
|
-
id: 'mcp__fs__read',
|
|
79
|
-
label: 'mcp__fs__read',
|
|
80
|
-
});
|
|
81
|
-
expect(resolveSlashTool('MCP__FS__READ', skills, mcpTools)?.id).toBe('mcp__fs__read');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('mcp__ 前缀即使目录里没有也直通(服务器可能暂时断开)', () => {
|
|
85
|
-
expect(resolveSlashTool('mcp__down__tool', [], [])).toEqual({
|
|
86
|
-
type: 'mcp',
|
|
87
|
-
id: 'mcp__down__tool',
|
|
88
|
-
label: 'mcp__down__tool',
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('技能按 name 或 displayName 命中,大小写不敏感', () => {
|
|
93
|
-
expect(resolveSlashTool('Commit', skills, mcpTools)).toEqual({
|
|
94
|
-
type: 'skill',
|
|
95
|
-
id: 'commit',
|
|
96
|
-
label: 'Commit',
|
|
97
|
-
});
|
|
98
|
-
expect(resolveSlashTool('提交', skills, mcpTools)).toEqual({
|
|
99
|
-
type: 'skill',
|
|
100
|
-
id: 'x',
|
|
101
|
-
label: '提交',
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('隐藏技能即使在目录里也不解析', () => {
|
|
106
|
-
expect(resolveSlashTool('plan-mode', skills, mcpTools)).toBeNull();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('什么都不匹配返回 null', () => {
|
|
110
|
-
expect(resolveSlashTool('nosuch', skills, mcpTools)).toBeNull();
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
describe('refreshSlashSources', () => {
|
|
115
|
-
it('非 Electron 环境直接返回当前缓存,不发请求', async () => {
|
|
116
|
-
const sources = await refreshSlashSources();
|
|
117
|
-
expect(sources).toEqual({ skills: [], mcpTools: [] });
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('拉取两个端点并过滤隐藏技能', async () => {
|
|
121
|
-
const request = vi.fn((input: { path: string }) => {
|
|
122
|
-
if (input.path === '/api/v2/chat-agents/skills') {
|
|
123
|
-
return Promise.resolve({
|
|
124
|
-
skills: [
|
|
125
|
-
{ name: 'commit', description: '提交' },
|
|
126
|
-
{ name: 'plan-mode', description: '' },
|
|
127
|
-
],
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
return Promise.resolve({ mcpTools: [{ token: 'mcp__fs__read' }] });
|
|
131
|
-
});
|
|
132
|
-
(window as { electron?: unknown }).electron = { localBackend: { request } };
|
|
133
|
-
const sources = await refreshSlashSources();
|
|
134
|
-
expect(sources.skills).toEqual([{ name: 'commit', description: '提交' }]);
|
|
135
|
-
expect(sources.mcpTools).toEqual([{ token: 'mcp__fs__read' }]);
|
|
136
|
-
expect(request).toHaveBeenCalledWith({
|
|
137
|
-
method: 'GET',
|
|
138
|
-
path: '/api/v2/chat-agents/skills',
|
|
139
|
-
});
|
|
140
|
-
expect(request).toHaveBeenCalledWith({
|
|
141
|
-
method: 'GET',
|
|
142
|
-
path: '/api/v2/chat-agents/mcp-tools',
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('MCP 端点失败时保留旧缓存、技能结果照常更新', async () => {
|
|
147
|
-
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
148
|
-
const request = vi.fn((input: { path: string }) => {
|
|
149
|
-
if (input.path === '/api/v2/chat-agents/skills') {
|
|
150
|
-
return Promise.resolve({ skills: [{ name: 'lint', description: '' }] });
|
|
151
|
-
}
|
|
152
|
-
return Promise.reject(new Error('mcp down'));
|
|
153
|
-
});
|
|
154
|
-
(window as { electron?: unknown }).electron = { localBackend: { request } };
|
|
155
|
-
const sources = await refreshSlashSources();
|
|
156
|
-
expect(sources.skills).toEqual([{ name: 'lint', description: '' }]);
|
|
157
|
-
// 模块级缓存的语义是「保留最近一次成功」:上一用例拉到的 MCP 工具还在。
|
|
158
|
-
expect(sources.mcpTools).toEqual([{ token: 'mcp__fs__read' }]);
|
|
159
|
-
expect(errorSpy).toHaveBeenCalled();
|
|
160
|
-
});
|
|
161
|
-
});
|