浏览代码

修复已停用电脑仍出现在 Bot 设备清单中

samien 1 月之前
父节点
当前提交
e591e40e06
共有 2 个文件被更改,包括 29 次插入 和 4 次删除
  1. 4 1
      src/server/desktopEnvironments.ts
  2. 25 3
      tests/server/desktopEnvironments.test.ts

+ 4 - 1
src/server/desktopEnvironments.ts

@@ -153,10 +153,13 @@ export class DesktopEnvironments {
  snapshot(owner:string,agent?:WorkspaceAgent,deviceHost?:string,policy?:{script:boolean;server:boolean;maxMiB:number}):DesktopEnvironmentSnapshot {
   const capturedAt=Date.now(),global=policy??{...this.computers(),maxMiB:(()=>{try{return readHostTools(this.store.home).fileTransferMaxMiB}catch{return 25}})()}
   const records=this.store.list<import('../shared/desktopHost.js').DesktopHostRecord>('_system','desktop-host')
+  const revoked=new Set(records.filter(record=>!record.enabled).map(record=>record.id))
   const states=[...this.sessions.values()]
   for(const record of records)if(!states.some(host=>host.key===record.id))states.push(newHostState(record.id,true))
   if(deviceHost&&!states.some(host=>host.key===deviceHost))states.push(newHostState(deviceHost,deviceHost!=='local'))
-  const hosts:BotDeviceSnapshot[]=states.map((s):BotDeviceSnapshot=>{
+  // Revoked pairings remain stored for reauthorization, not as offline computers.
+  // Filter after merging sessions and message origin so neither can resurrect them.
+  const hosts:BotDeviceSnapshot[]=states.filter(s=>!revoked.has(s.key)).map((s):BotDeviceSnapshot=>{
    const info=s.info,online=this.hostOnline(s),record=records.find(record=>record.id===s.key),open=(s.key==='local'?global.server:global.script)&&record?.enabled!==false
    const manual=this.current(s,'local'),waiting=manual?'human_control':s.paused.has('local')?'paused':'ready'
    const base:DeviceCapabilityStatus|undefined=agent&&(agent.archived||agent.remoteAgentId)?'agent_unavailable':!open?'disabled':!online?'offline':info?.platform!=='darwin'?'unsupported':undefined

+ 25 - 3
tests/server/desktopEnvironments.test.ts

@@ -392,20 +392,42 @@ it('snapshots negotiated host facts per owner without inventing paths for old cl
  expect(service.snapshot('owner',agent).hosts.find(host=>host.id==='local')?.metadata?.fileRoots).toEqual(['/Users/fixture'])
 })
 
-it('keeps offline and disabled registered computers visible with actionable reasons',()=>{
- store.put('_system','desktop-host',HOST_B,{id:HOST_B,name:'尚未连接',enabled:false,tokenHash:'private',createdAt:1})
+it('keeps enabled offline registered computers visible with actionable reasons',()=>{
+ store.put('_system','desktop-host',HOST_B,{id:HOST_B,name:'尚未连接',enabled:true,tokenHash:'private',createdAt:1})
  service.remoteExchange(HOST_A,{host:{...host,id:randomUUID(),name:'连接电脑',full:[ownerKey('owner')],environment:environmentMetadata},results:[]})
  const saved=service.snapshot('owner',agent,HOST_A)
  vi.useFakeTimers();vi.advanceTimersByTime(16000)
  const offline=service.snapshot('owner',agent,HOST_A)
  expect(offline.hosts.find(host=>host.id===HOST_A)).toMatchObject({source:true,online:false,capabilities:{shell:{enabled:false,status:'offline'}}})
  expect(offline.hosts.find(host=>host.id===HOST_A)?.metadata).toBeUndefined()
- expect(offline.hosts.find(host=>host.id===HOST_B)).toMatchObject({name:'尚未连接',open:false,capabilities:{view:{enabled:false,status:'disabled'}}})
+ expect(offline.hosts.find(host=>host.id===HOST_B)).toMatchObject({name:'尚未连接',online:false,open:true,capabilities:{view:{enabled:false,status:'offline'}}})
  expect(saved.hosts.find(host=>host.id===HOST_A)?.capabilities.shell.status).toBe('ready')
  expect(service.deviceContextLine('owner',HOST_A)).toContain('不能回退到服务器')
  expect(JSON.stringify(offline)).not.toContain('private')
 })
 
+it.each([false,true])('excludes revoked computers from Bot inventory even with a stale session: %s',staleSession=>{
+ const record={id:HOST_B,name:'旧配对电脑',enabled:false,tokenHash:'private',createdAt:1}
+ store.put('_system','desktop-host',HOST_B,record)
+ service.exchange({host,results:[]})
+ service.remoteExchange(HOST_A,{host:{...host,id:randomUUID(),name:'studio'},results:[]})
+ if(staleSession)service.remoteExchange(HOST_B,{host:{...host,id:randomUUID(),name:record.name},results:[]})
+ for(const source of [HOST_A,HOST_B]){
+  const snapshot=service.snapshot('owner',agent,source)
+  expect(snapshot.hosts.map(host=>host.id)).toEqual(['local',HOST_A])
+  expect(snapshot.sourceHost).toBe(source)
+  const inventory=service.onlineHostsLine('owner',source)
+  expect(inventory).toContain('host="server"')
+  expect(inventory).toContain(`host="${HOST_A}"`)
+  expect(inventory).not.toContain(HOST_B)
+  expect(inventory).not.toContain(record.name)
+ }
+ expect(service.deviceContextLine('owner',HOST_B)).toContain('不能回退到服务器')
+ expect(store.get('_system','desktop-host',HOST_B)).toEqual(record)
+ store.put('_system','desktop-host',HOST_B,{...record,enabled:true})
+ expect(service.snapshot('owner',agent).hosts.map(host=>host.id)).toEqual(['local',HOST_A,HOST_B])
+})
+
 it('distinguishes human takeover from grants and rechecks revocation after collecting a snapshot',async()=>{
  host.full=[ownerKey('owner')];drive()
  const ticket=await take(),snapshot=service.snapshot('owner',agent,'local'),server=snapshot.hosts[0]!