Przeglądaj źródła

feat(envs): 云端与桌面并行注入,VM 按执行节点可用性降级

grokCloud.selected 对 envs 机器人只看 cloud 勾选与连接配置,
不再受桌面优先互斥;workspaceRuntime 增加 computerAvailable
(接线到 runnerHub.computerRunner):envs 的 VM 轮次在执行节点
离线或缺能力时自动改走本机会话,绑定与消息执行标记跟随实际
执行方式,桌面/云端工具照常注入。
Samien 1 miesiąc temu
rodzic
commit
e21ca798e7

+ 1 - 0
src/server/app.ts

@@ -259,6 +259,7 @@ export function createApplication(options: ApplicationOptions = {}): Application
   workspaceRuntime.desktopEnvironments=desktopEnvironments
   grokCloud.preferDesktop=(owner,agent)=>!!desktopEnvironments.selected(owner,agent)
   grokCloud.beforeSelection=(owner,id)=>desktopEnvironments.assertIdle(owner,id)
+  workspaceRuntime.computerAvailable=(owner,agent)=>{try{runners.computerRunner(owner,agent);return true}catch{return false}}
   workspaceRuntime.cloud=grokCloud
   const workspaceInspector=new WorkspaceInspector(workspace,auth)
   workspaceRuntime.inspector=workspaceInspector

+ 1 - 1
src/server/grokCloud.ts

@@ -67,7 +67,7 @@ export class GrokCloud {
   preferDesktop?:(owner:string,agent:WorkspaceAgent)=>boolean
   beforeSelection?:(owner:string,agentId:string)=>void
   configured(owner:string){return !!this.store.get(owner,'grok-cloud','connection')}
-  selected(owner:string,agent:WorkspaceAgent){return agent.computer==='cloud'||(agent.computer==='auto'&&agent.execution!=='computer'&&!this.preferDesktop?.(owner,agent)&&this.configured(owner))}
+  selected(owner:string,agent:WorkspaceAgent){if(agent.envs)return agentEnvs(agent).cloud&&this.configured(owner);return agent.computer==='cloud'||(agent.computer==='auto'&&agent.execution!=='computer'&&!this.preferDesktop?.(owner,agent)&&this.configured(owner))}
   private credential(owner:string):Credential {const row=this.store.get<{sealed:string}>(owner,'grok-cloud','connection');if(!row)throw new HttpError(409,'请先连接 Grok Bot 云端账号','grok_cloud_not_configured');return this.nodes.open<Credential>(row.sealed)}
   private agent(owner:string,id:string){const agent=this.store.require<WorkspaceAgent>(owner,'agent',id);if(agent.archived||agent.remoteAgentId||agent.temporaryGoalId)throw new HttpError(409,'此机器人不能连接云端电脑','grok_agent_unavailable');this.nodes.requireSource(owner,agent);return agent}
   private authorize(owner:string,agentId:string){const agent=this.agent(owner,agentId);if(!this.selected(owner,agent))throw new HttpError(410,'机器人已切换电脑','computer_control_expired');return agent}

+ 7 - 4
src/server/workspaceRuntime.ts

@@ -87,6 +87,8 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
   }
   desktopEnvironments?:DesktopEnvironments
   cloud?:GrokCloud
+  /** Wired to the runner hub: false degrades envs VM turns to profile sessions. */
+  computerAvailable?:(owner:string,agent:Agent)=>boolean
   inspector?:import('./workspaceInspector.js').WorkspaceInspector
   private live = new Map<string, LiveTurn>()
   readonly teamTools: WorkspaceTeamTools
@@ -271,7 +273,8 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
     this.nodes.requireSource(owner, agent)
     if (agent.remoteAgentId) agent = await this.nodes.refreshRemoteAgent(owner,agent)
     const key = this.bindingKey(c.id, run.conversationTaskId, agent.id),
-      target = agent.remoteAgentId || agent.execution==='computer' ? this.nodes.targetForAgent(owner,agent) : this.nodes.target(owner, agent.nodeId)
+      computerExecution = !!(agent.remoteAgentId || (agent.execution==='computer' && (!agent.envs || this.computerAvailable?.(owner,agent) !== false))),
+      target = computerExecution ? this.nodes.targetForAgent(owner,agent) : this.nodes.target(owner, agent.nodeId)
     const gateway = new WorkspaceGateway(target,{workId:run.id,publishArtifact:async(name,bytes)=>{
       this.requireAuthorization(owner,run.runId);this.nodes.requireSource(owner,agent)
       const file=this.publishArtifact(owner,resultMessage,agent,name,bytes)
@@ -317,7 +320,7 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
         conversationTaskId: run.conversationTaskId,
         seq: 0,
         role: 'assistant',
-        execution:agent.execution??'profile',
+        execution:computerExecution?'computer':(agent.execution??'profile'),
         agentId: agent.id,
         agentName: agent.name,
         content: '',
@@ -623,8 +626,8 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
       binding = {
         memoryVersion: recovering ? binding?.memoryVersion : memory.version,
         runnerId: target.runner?.id,
-        execution:agent.execution,
-        vmExecution:agent.vmExecution,
+        execution:computerExecution?'computer':'profile',
+        vmExecution:computerExecution?agent.vmExecution:undefined,
         hermesComputer:target.runner?.hermesComputer,
         computerEnvironmentId:agent.computerEnvironmentId,
         id: key,

+ 9 - 0
tests/server/agentEnvs.test.ts

@@ -53,3 +53,12 @@ it('raw computer writes on an envs agent only toggle the VM leg',async()=>{
   store.updateAgent('owner',agent.id,{computer:'vm'})
   expect(store.require<any>('owner','agent',agent.id)).toMatchObject({envs:{vm:true,cloud:true,desktop:true},computer:'vm',execution:'computer'})
 })
+it('selects the cloud environment in parallel regardless of desktop readiness',async()=>{
+  const cloud=new GrokCloud(store,auth,nodes,{assertLocalVmIdle:()=>{}} as any,vi.fn(async()=>new Response('{}')))
+  const agent=store.createAgent('owner',{name:'云桌面并列',profile:'default',computer:'off',envs:{vm:true,cloud:true,desktop:true}})
+  expect(cloud.selected('owner',agent)).toBe(false)
+  store.put('owner','grok-cloud','connection',{sealed:'x'})
+  expect(cloud.selected('owner',agent)).toBe(true)
+  cloud.preferDesktop=()=>true
+  expect(cloud.selected('owner',agent)).toBe(true)
+})