feat: add persistent workbar browser tabs and scoped MCP routing
This commit is contained in:
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
WORKBAR_APP_DEFINITIONS,
|
||||
WORKBAR_LIMITS,
|
||||
normalizeWorkbarLayoutPreferences,
|
||||
workbarAppDefinitionSchema,
|
||||
workbarLayoutPreferencesSchema,
|
||||
workbarTabInstanceSchema
|
||||
@@ -14,20 +15,28 @@ const projectId = '00000000-0000-4000-8000-000000000201'
|
||||
describe('workbar contracts', () => {
|
||||
it('freezes the first application catalog and instance policies', () => {
|
||||
expect(
|
||||
WORKBAR_APP_DEFINITIONS.map(({ id, instancePolicy, defaultOpen }) => ({
|
||||
WORKBAR_APP_DEFINITIONS.map(({ id, instancePolicy, defaultContext, defaultOpen, required, closable, reorderable }) => ({
|
||||
id,
|
||||
instancePolicy,
|
||||
defaultOpen
|
||||
defaultContext,
|
||||
defaultOpen,
|
||||
required,
|
||||
closable,
|
||||
reorderable
|
||||
}))
|
||||
).toEqual([
|
||||
{ id: 'tasks', instancePolicy: 'single', defaultOpen: true },
|
||||
{ id: 'workspace', instancePolicy: 'single', defaultOpen: true },
|
||||
{ id: 'browser', instancePolicy: 'single', defaultOpen: true },
|
||||
{ id: 'results', instancePolicy: 'single', defaultOpen: true },
|
||||
{ id: 'tasks', instancePolicy: 'single', defaultContext: 'current-project', defaultOpen: true, required: true, closable: false, reorderable: true },
|
||||
{ id: 'workspace', instancePolicy: 'single', defaultContext: 'current-project', defaultOpen: true, required: true, closable: false, reorderable: true },
|
||||
{ id: 'browser', instancePolicy: 'multiple', defaultContext: 'current-conversation', defaultOpen: true, required: false, closable: true, reorderable: true },
|
||||
{ id: 'results', instancePolicy: 'single', defaultContext: 'current-project', defaultOpen: true, required: false, closable: true, reorderable: true },
|
||||
{
|
||||
id: 'terminal',
|
||||
instancePolicy: 'multiple',
|
||||
defaultOpen: false
|
||||
defaultContext: 'current-project',
|
||||
defaultOpen: false,
|
||||
required: false,
|
||||
closable: true,
|
||||
reorderable: true
|
||||
}
|
||||
])
|
||||
for (const definition of WORKBAR_APP_DEFINITIONS) {
|
||||
@@ -51,12 +60,52 @@ describe('workbar contracts', () => {
|
||||
activeInstanceId: terminalId,
|
||||
expanded: true,
|
||||
dock: 'right',
|
||||
widthRatio: 0.35
|
||||
widthRatio: 0.35,
|
||||
taskScope: 'current-project'
|
||||
} as const
|
||||
|
||||
expect(workbarLayoutPreferencesSchema.parse(layout)).toEqual(layout)
|
||||
})
|
||||
|
||||
it('persists a browser conversation binding without a runtime tab identifier', () => {
|
||||
expect(
|
||||
workbarTabInstanceSchema.parse({
|
||||
id: '00000000-0000-4000-8000-000000000103',
|
||||
appId: 'browser',
|
||||
title: '浏览器 · 会话 A',
|
||||
targetRef: { type: 'conversation', conversationId: 'conversation-a' }
|
||||
})
|
||||
).toEqual({
|
||||
id: '00000000-0000-4000-8000-000000000103',
|
||||
appId: 'browser',
|
||||
title: '浏览器 · 会话 A',
|
||||
targetRef: { type: 'conversation', conversationId: 'conversation-a' }
|
||||
})
|
||||
expect(
|
||||
workbarTabInstanceSchema.safeParse({
|
||||
id: '00000000-0000-4000-8000-000000000103',
|
||||
appId: 'browser',
|
||||
title: '浏览器',
|
||||
targetRef: { type: 'conversation', conversationId: 'conversation-a' },
|
||||
tabId: '00000000-0000-4000-8000-000000000999'
|
||||
}).success
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('explicitly keeps every existing app visible across context switches', () => {
|
||||
for (const definition of WORKBAR_APP_DEFINITIONS) {
|
||||
expect(definition.visibleAcrossContextSwitches).toBe(true)
|
||||
const { visibleAcrossContextSwitches, ...withoutVisibility } = definition
|
||||
expect(workbarAppDefinitionSchema.safeParse(withoutVisibility).success).toBe(false)
|
||||
expect(
|
||||
workbarAppDefinitionSchema.parse({
|
||||
...withoutVisibility,
|
||||
visibleAcrossContextSwitches
|
||||
}).visibleAcrossContextSwitches
|
||||
).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects unknown and sensitive target parameters', () => {
|
||||
for (const targetRef of [
|
||||
{ type: 'local', cwd: 'C:\\secret' },
|
||||
@@ -102,7 +151,8 @@ describe('workbar contracts', () => {
|
||||
activeInstanceId: taskId,
|
||||
expanded: true,
|
||||
dock: 'right',
|
||||
widthRatio: 0.4
|
||||
widthRatio: 0.4,
|
||||
taskScope: 'current-project'
|
||||
}
|
||||
expect(
|
||||
workbarLayoutPreferencesSchema.safeParse({
|
||||
@@ -131,4 +181,61 @@ describe('workbar contracts', () => {
|
||||
}).success
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('normalizes duplicate policies, fixed defaults, and stale active state', () => {
|
||||
const browserId = '00000000-0000-4000-8000-000000000103'
|
||||
const secondBrowserId = '00000000-0000-4000-8000-000000000104'
|
||||
const workspaceId = '00000000-0000-4000-8000-000000000105'
|
||||
const normalized = normalizeWorkbarLayoutPreferences(
|
||||
{
|
||||
instances: [
|
||||
{ id: browserId, appId: 'browser', title: '浏览器' },
|
||||
{ id: secondBrowserId, appId: 'browser', title: '浏览器 2' },
|
||||
{ id: workspaceId, appId: 'workspace', title: '工作区' },
|
||||
{ id: terminalId, appId: 'workspace', title: '重复工作区' }
|
||||
],
|
||||
activeInstanceId: terminalId,
|
||||
expanded: true,
|
||||
dock: 'right',
|
||||
widthRatio: 0.4,
|
||||
taskScope: 'all-projects'
|
||||
},
|
||||
[
|
||||
{ id: taskId, appId: 'tasks', title: '任务中心' },
|
||||
{ id: workspaceId, appId: 'workspace', title: '工作区' }
|
||||
]
|
||||
)
|
||||
|
||||
expect(normalized?.instances.map(({ id, appId }) => ({ id, appId })))
|
||||
.toEqual([
|
||||
{ id: taskId, appId: 'tasks' },
|
||||
{ id: browserId, appId: 'browser' },
|
||||
{ id: secondBrowserId, appId: 'browser' },
|
||||
{ id: workspaceId, appId: 'workspace' }
|
||||
])
|
||||
expect(normalized?.activeInstanceId).toBe(taskId)
|
||||
expect(normalized?.taskScope).toBe('all-projects')
|
||||
})
|
||||
|
||||
it('preserves a valid empty layout with a null active instance', () => {
|
||||
expect(
|
||||
normalizeWorkbarLayoutPreferences(
|
||||
{
|
||||
instances: [],
|
||||
activeInstanceId: null,
|
||||
expanded: false,
|
||||
dock: 'right',
|
||||
widthRatio: 0.4
|
||||
},
|
||||
[]
|
||||
)
|
||||
).toEqual({
|
||||
instances: [],
|
||||
activeInstanceId: null,
|
||||
expanded: false,
|
||||
dock: 'right',
|
||||
widthRatio: 0.4,
|
||||
taskScope: 'current-project'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user