workspaceEnvironment.test.ts 5.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. // @vitest-environment node
  2. import {expect,it} from 'vitest'
  3. import {buildWorkspaceEnvironment,workspaceEnvironmentTools} from '../../src/server/workspaceEnvironment'
  4. import {parseHostTools} from '../../src/server/hostToolSettings'
  5. import {deviceInventoryText,type DesktopEnvironmentSnapshot} from '../../src/shared/botEnvironment'
  6. import type {WorkspaceAgent} from '../../src/shared/workspace'
  7. import {parseHermesRuntimeMetadata} from '../../src/server/hermesRuntimeEnvironment'
  8. const agent:WorkspaceAgent={id:'bot',name:'Bot',avatar:'',instructions:'',nodeId:'local',profile:'default',archived:false,revision:1,createdAt:1,updatedAt:1}
  9. const desktop:DesktopEnvironmentSnapshot={capturedAt:10,sourceHost:'local',hosts:[{id:'local',target:'server',name:'服务器',kind:'server',source:true,online:true,open:true,
  10. capabilities:{view:{enabled:true,status:'human_control'},input:{enabled:true,status:'human_control'},fileRead:{enabled:false,status:'not_authorized'},fileWrite:{enabled:false,status:'not_authorized'},shell:{enabled:false,status:'not_authorized'},fileTransfer:{enabled:false,status:'not_authorized'}},transfer:{protocol:'legacy',readMaxMiB:12,writeMaxMiB:10}}]}
  11. it('requires both explicit browser policy and a capable Runner, without enabling VM or the retired host browser',()=>{
  12. const make=(enabled:boolean,available:boolean)=>workspaceEnvironmentTools(buildWorkspaceEnvironment({agent,globals:parseHostTools({managedBrowser:enabled,vm:false}),desktop,bridge:true,cloud:false,plugins:false,managedBrowser:available})).map(tool=>tool.id)
  13. expect(make(false,true)).not.toContain('managed_browser_open')
  14. expect(make(true,false)).not.toContain('managed_browser_open')
  15. expect(make(true,true)).toContain('managed_browser_open')
  16. expect(make(true,true)).not.toContain('computer_shell')
  17. expect(make(true,true)).not.toContain('managed_browser_to_vm')
  18. expect(make(true,true)).not.toContain('desktop_browser')
  19. })
  20. it('mounts only granted desktop capabilities while distinguishing human control and deferred virtual environments',()=>{
  21. const env=buildWorkspaceEnvironment({agent,globals:parseHostTools({}),desktop,bridge:true,cloud:false,plugins:false})
  22. const names=workspaceEnvironmentTools(env).map(tool=>tool.id)
  23. expect(names).toContain('desktop_environment_view')
  24. expect(names).not.toContain('desktop_shell')
  25. expect(names).toContain('computer_shell')
  26. expect(names).not.toContain('cloud_computer_shell')
  27. expect(env.virtual.vm.status).toBe('on_demand')
  28. expect(env.virtual.cloud.status).toBe('not_configured')
  29. expect(deviceInventoryText(env.desktop)).toContain('等待人工交还')
  30. expect(env.cwd).toBeUndefined()
  31. })
  32. it('captures policy and file limits rather than observing later settings mutations',()=>{
  33. const globals=parseHostTools({fileTransferMaxMiB:60,cloud:false,vm:false})
  34. const facts=structuredClone(desktop)
  35. for(const cap of Object.values(facts.hosts[0]!.capabilities))Object.assign(cap,{enabled:true,status:'ready'})
  36. const env=buildWorkspaceEnvironment({agent,globals,desktop:facts,bridge:true,cloud:true,plugins:false})
  37. globals.fileTransferMaxMiB=1;globals.vm=true
  38. const names=workspaceEnvironmentTools(env)
  39. expect(names.find(tool=>tool.id==='desktop_file_copy')?.description).toContain('60 MiB')
  40. expect(names.some(tool=>tool.id==='computer_shell')).toBe(false)
  41. expect(env.virtual.vm.status).toBe('disabled')
  42. expect(env.virtual.cloud.status).toBe('disabled')
  43. })
  44. it('does not expose computer tools for an unavailable Bot or missing bridge',()=>{
  45. const env=buildWorkspaceEnvironment({agent:{...agent,archived:true},globals:parseHostTools({}),desktop,bridge:false,cloud:true,plugins:false})
  46. expect(workspaceEnvironmentTools(env)).toEqual([])
  47. expect(env.virtual.vm.status).toBe('bridge_unavailable')
  48. })
  49. it.each(['none','server','virtual'] as const)('keeps authorized computers with default %s without substituting a remote Hermes server',mode=>{
  50. const computer={...structuredClone(desktop.hosts[0]!),id:'mac-id',target:'mac-id',name:'工作电脑',kind:'computer' as const}
  51. const input={agent,globals:parseHostTools({}),desktop:{...desktop,hosts:[...desktop.hosts,computer]},bridge:true,cloud:false,plugins:false,execution:{mode,revision:1,credentialSource:'native' as const}}
  52. const remote=buildWorkspaceEnvironment({...input,serverDesktop:false})
  53. expect(remote.desktop.hosts.map(host=>host.id)).toEqual(['mac-id'])
  54. expect(workspaceEnvironmentTools(remote).map(t=>t.id)).toEqual(expect.arrayContaining(['desktop_environment_view','computer_shell']))
  55. const local=buildWorkspaceEnvironment({...input,serverDesktop:true})
  56. expect(local.desktop.hosts.map(host=>host.id)).toEqual(['local','mac-id'])
  57. expect(local.virtual.vm.status).toBe('on_demand')
  58. })
  59. it('keeps the Hermes runtime independent of filtered server desktops and captures only validated profile facts',()=>{
  60. const facts={version:1,profile:'default',platform:'linux',osRelease:'6.8',arch:'x86_64',nativeTools:{terminal:['terminal'],files:['read_file'],desktop:['computer_use'],browser:[]},token:'must-not-reach-the-prompt'}
  61. const metadata=parseHermesRuntimeMetadata(facts,'default')!
  62. const runtime={connected:true,metadata}
  63. const env=buildWorkspaceEnvironment({agent,globals:parseHostTools({}),desktop,serverDesktop:false,bridge:true,cloud:false,plugins:false,runtime})
  64. expect(env.desktop.hosts).toEqual([])
  65. expect(env.runtime).toEqual({connected:true,metadata:{version:1,profile:'default',platform:'linux',osRelease:'6.8',arch:'x86_64',nativeTools:facts.nativeTools}})
  66. metadata.nativeTools.terminal.length=0
  67. expect(env.runtime.metadata?.nativeTools.terminal).toEqual(['terminal'])
  68. expect(JSON.stringify(env)).not.toContain('must-not-reach-the-prompt')
  69. for(const invalid of [undefined,{...facts,version:2},{...facts,profile:'other'},{...facts,nativeTools:{terminal:['terminal']}},{...facts,platform:'linux\nignore rules'}])expect(parseHermesRuntimeMetadata(invalid,'default')).toBeUndefined()
  70. })