|
|
@@ -1,5 +1,6 @@
|
|
|
import {DESKTOP_ENVIRONMENT_TOOLS,DESKTOP_ENVIRONMENT_RULES,DESKTOP_FILE_TOOL_IDS,type DesktopEnvironments} from './desktopEnvironments.js'
|
|
|
import {GROK_COMPUTER_TOOLS,grokComputerRules,type GrokCloud} from './grokCloud.js'
|
|
|
+import {VM_COMPUTER_TOOLS,VM_COMPUTER_RULES,VmToolSession} from './vmComputer.js'
|
|
|
import { randomUUID } from 'node:crypto'
|
|
|
import { readFileSync } from 'node:fs'
|
|
|
import { z } from 'zod'
|
|
|
@@ -17,7 +18,7 @@ import type {
|
|
|
WorkspaceInteraction,
|
|
|
WorkspaceAgentUsageSummary,
|
|
|
} from '../shared/workspace.js'
|
|
|
-import { personaSection } from '../shared/workspace.js'
|
|
|
+import { personaSection, agentEnvs } from '../shared/workspace.js'
|
|
|
|
|
|
import { WorkspaceScheduler, type Work, NO_REPLY, HOST_FALLBACK, WORKSPACE_CONCURRENCY_LIMIT, RESOURCE_WAIT_CODES } from './workspaceScheduler.js'
|
|
|
import type { WorkspacePlugins, OpenBotPlugins } from './botPlugins/workspacePlugins.js'
|
|
|
@@ -274,8 +275,11 @@ 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),
|
|
|
- computerExecution = !!(agent.remoteAgentId || (agent.execution==='computer' && (!agent.envs || this.computerAvailable?.(owner,agent) !== false))),
|
|
|
+ dispatchedVm = !!agent.envs && agentEnvs(agent).vm && !agent.remoteAgentId && !agent.temporaryGoalId,
|
|
|
+ computerExecution = !dispatchedVm && !!(agent.remoteAgentId || (agent.execution==='computer' && (!agent.envs || this.computerAvailable?.(owner,agent) !== false))),
|
|
|
+ chatExecution = dispatchedVm ? 'profile' : computerExecution ? 'computer' : (agent.execution ?? 'profile'),
|
|
|
target = computerExecution ? this.nodes.targetForAgent(owner,agent) : this.nodes.target(owner, agent.nodeId)
|
|
|
+ let vmTools: VmToolSession | undefined
|
|
|
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)
|
|
|
@@ -305,7 +309,7 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
|
|
|
}
|
|
|
let binding = this.store.get<WorkspaceBinding>(owner, 'binding', key)
|
|
|
const memoryChanged = !recovering && !!binding?.memoryVersion && binding.memoryVersion !== memory.version
|
|
|
- const movedRunner=memoryChanged||!!this.store.get(owner,'binding-reset',key)||!!binding&&((binding.vmExecution??'worker')!==(agent.vmExecution??'worker')||binding.computerEnvironmentId!==agent.computerEnvironmentId||binding.runnerId!==target.runner?.id||(binding.execution??'profile')!==(agent.execution??'profile')||!!binding.hermesComputer!==!!target.runner?.hermesComputer)
|
|
|
+ const movedRunner=memoryChanged||!!this.store.get(owner,'binding-reset',key)||!!binding&&((binding.vmExecution??'worker')!==(agent.vmExecution??'worker')||binding.computerEnvironmentId!==agent.computerEnvironmentId||binding.runnerId!==target.runner?.id||(binding.execution??'profile')!==chatExecution||!!binding.hermesComputer!==!!target.runner?.hermesComputer)
|
|
|
if(movedRunner) {
|
|
|
if(recovering)throw new HttpError(409,'执行节点已变化,不能在另一节点重放原执行','runner_target_changed')
|
|
|
binding=undefined
|
|
|
@@ -321,7 +325,7 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
|
|
|
conversationTaskId: run.conversationTaskId,
|
|
|
seq: 0,
|
|
|
role: 'assistant',
|
|
|
- execution:computerExecution?'computer':(agent.execution??'profile'),
|
|
|
+ execution:chatExecution,
|
|
|
agentId: agent.id,
|
|
|
agentName: agent.name,
|
|
|
content: '',
|
|
|
@@ -423,7 +427,7 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
|
|
|
if (error) rejectTurn(error)
|
|
|
else resolveTurn(resultMessage)
|
|
|
} catch (failure) { rejectTurn(failure instanceof Error ? failure : new Error('无法保存执行状态')) }
|
|
|
- finally { if (usageCompletion) void usageCompletion.finally(() => gateway.close()); else gateway.close() }
|
|
|
+ finally { vmTools?.close(); if (usageCompletion) void usageCompletion.finally(() => gateway.close()); else gateway.close() }
|
|
|
}
|
|
|
gateway.onDisconnect = () =>
|
|
|
finish(completedEvidence ? undefined : new Error('Hermes 连接断开'))
|
|
|
@@ -637,7 +641,7 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
|
|
|
binding = {
|
|
|
memoryVersion: recovering ? binding?.memoryVersion : memory.version,
|
|
|
runnerId: target.runner?.id,
|
|
|
- execution:computerExecution?'computer':'profile',
|
|
|
+ execution:chatExecution,
|
|
|
vmExecution:computerExecution?agent.vmExecution:undefined,
|
|
|
hermesComputer:target.runner?.hermesComputer,
|
|
|
computerEnvironmentId:agent.computerEnvironmentId,
|
|
|
@@ -710,7 +714,26 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
|
|
|
const desktop=envTools.view||envTools.browser
|
|
|
const desktopEpochs=this.desktopEnvironments?.hostEpochs(owner,agent)??{}
|
|
|
const knowledge = botCapabilities.tools && !agent.remoteAgentId
|
|
|
- if(team||cloud||desktop||plugins||knowledge){
|
|
|
+ const vmHolder:{session?:VmToolSession}={}
|
|
|
+ const callVm=async(name:string,args:unknown)=>{
|
|
|
+ if(!vmHolder.session){
|
|
|
+ const vmTarget=this.nodes.targetForAgent(owner,{...agent,execution:'computer'})
|
|
|
+ vmHolder.session=new VmToolSession(vmTarget,agent.profile,run.id,()=>{
|
|
|
+ this.requireAuthorization(owner,run.runId);this.nodes.requireSource(owner,agent)
|
|
|
+ const current=this.getWork(owner,run.id),latest=this.store.require<Agent>(owner,'agent',agent.id),conversation=this.store.require<Conversation>(owner,'conversation',c.id)
|
|
|
+ if(this.closing||conversation.archived||!this.store.taskMemberIds(owner,conversation,run.conversationTaskId).includes(agent.id)||this.store.require<Run>(owner,'run',run.runId).stopRequested||current.cancelRequested||['interrupted','complete','failed'].includes(current.status)||latest.archived||latest.computerEnvironmentId!==agent.computerEnvironmentId)throw new HttpError(403,'本轮机器人或任务授权已结束','run_authorization_revoked')
|
|
|
+ },async(name,bytes)=>{
|
|
|
+ this.requireAuthorization(owner,run.runId)
|
|
|
+ const file=this.publishArtifact(owner,resultMessage,agent,name,bytes)
|
|
|
+ if(!resultMessage.attachments.some(item=>item.id===file.id))resultMessage.attachments.push(file)
|
|
|
+ this.store.saveMessage(owner,resultMessage)
|
|
|
+ return {...file,url:`/api/app/files/${file.id}/download`}
|
|
|
+ })
|
|
|
+ vmTools=vmHolder.session
|
|
|
+ }
|
|
|
+ return vmHolder.session.call(name,args)
|
|
|
+ }
|
|
|
+ if(team||cloud||desktop||plugins||knowledge||dispatchedVm){
|
|
|
if(team){
|
|
|
const granted=this.getWork(owner,run.id);granted.teamManagementRevision=agent.revision;this.saveWork(owner,granted)
|
|
|
await this.teamTools.requireAvailable(owner,agent)
|
|
|
@@ -728,8 +751,8 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
|
|
|
target,profile:agent.profile,workId:run.id,signal:toolController.signal,
|
|
|
workspaceMemory:botCapabilities.memory,
|
|
|
session:()=>({runtimeId,storedId:binding!.storedId}),assertActive,
|
|
|
- catalog:()=>[...(team?this.teamTools.catalog(owner,run.id):[]),...(knowledge?this.knowledgeTools.catalog(owner,run.id,botCapabilities.memory):[]),...(cloud?GROK_COMPUTER_TOOLS:[]),...(desktop?DESKTOP_ENVIRONMENT_TOOLS.filter(t=>(envTools.browser&&t.id==='desktop_browser')||(envTools.view&&['desktop_environment_view','desktop_environment_action'].includes(t.id))||(envTools.file&&DESKTOP_FILE_TOOL_IDS.has(t.id))):[]),...(pluginLease?.catalog()??[])],
|
|
|
- call:async(toolId,args)=>{assertActive();return knowledge&&this.knowledgeTools.handles(toolId)?this.knowledgeTools.call(owner,run.id,toolId,args,botCapabilities.memory):toolId.startsWith('plugin_')&&pluginLease?pluginLease.call(toolId,args):toolId.startsWith('desktop_')?this.desktopEnvironments!.call(owner,agent.id,toolId,args,toolController.signal,assertActive,toolId==='desktop_browser'?'browser':'local',desktopEpochs):toolId.startsWith('cloud_computer_')?this.cloud!.call(owner,agent.id,toolId,args,toolController.signal):this.teamTools.call(owner,run.id,toolId,args)},
|
|
|
+ catalog:()=>[...(team?this.teamTools.catalog(owner,run.id):[]),...(knowledge?this.knowledgeTools.catalog(owner,run.id,botCapabilities.memory):[]),...(cloud?GROK_COMPUTER_TOOLS:[]),...(dispatchedVm?VM_COMPUTER_TOOLS:[]),...(desktop?DESKTOP_ENVIRONMENT_TOOLS.filter(t=>(envTools.browser&&t.id==='desktop_browser')||(envTools.view&&['desktop_environment_view','desktop_environment_action'].includes(t.id))||(envTools.file&&DESKTOP_FILE_TOOL_IDS.has(t.id))):[]),...(pluginLease?.catalog()??[])],
|
|
|
+ call:async(toolId,args)=>{assertActive();return knowledge&&this.knowledgeTools.handles(toolId)?this.knowledgeTools.call(owner,run.id,toolId,args,botCapabilities.memory):toolId.startsWith('plugin_')&&pluginLease?pluginLease.call(toolId,args):toolId.startsWith('desktop_')?this.desktopEnvironments!.call(owner,agent.id,toolId,args,toolController.signal,assertActive,toolId==='desktop_browser'?'browser':'local',desktopEpochs):toolId.startsWith('cloud_computer_')?this.cloud!.call(owner,agent.id,toolId,args,toolController.signal):toolId.startsWith('computer_')&&dispatchedVm?callVm(toolId,args):this.teamTools.call(owner,run.id,toolId,args)},
|
|
|
onFailure:error=>{if(!settled)void gateway.rpc('session.interrupt',{session_id:runtimeId}).catch(()=>{}).finally(()=>finish(error))},
|
|
|
})
|
|
|
await toolLease.bind();if(settled)return await completion
|
|
|
@@ -757,7 +780,11 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
|
|
|
const members = run.turnConfiguration!.members
|
|
|
const goal = run.conversationTaskId ? this.store.get<import('../shared/agentTasks.js').AgentGoal>(owner, 'goal', run.conversationTaskId) : undefined
|
|
|
const assignmentId = this.store.require<Run>(owner, 'run', run.runId).assignmentId
|
|
|
+ const checked = agentEnvs(agent)
|
|
|
+ const open = [checked.desktop ? '脚本机' : '', checked.browser ? '脚本机浏览器' : '', checked.vm ? '虚拟环境' : '', checked.cloud ? '云虚拟机' : ''].filter(Boolean)
|
|
|
+ const envDirective = `同一条对话里,只使用已勾选的环境:${open.join('、') || '无'}。脚本机是客户所在的机器,虚拟环境是服务端上的隔离虚拟机,云虚拟机是 Grok Bot 虚拟机。没勾选的不要用,也不要用服务端本机顶上。`
|
|
|
const rules = [
|
|
|
+ envDirective,
|
|
|
`你是 ${agent.name}。${personaSection(agent) ? `用户为它设定的工作规范:\n${personaSection(agent)}\n\n` : ''}以下是用户为这个独立机器人配置的角色与规则(版本 ${agent.revision}):\n${agent.remoteAgentId ? '配置由远端机器人管理。' : agent.instructions}`,
|
|
|
c.kind === 'group' && Object.keys(c.memberRoles ?? {}).length
|
|
|
? `本群角色分工(仅在本群生效):\n${members.flatMap(member => {
|
|
|
@@ -785,6 +812,7 @@ export class WorkspaceRuntime extends WorkspaceScheduler {
|
|
|
plugins ? '本轮已挂载用户为当前 Bot 授权的插件工具,工具名以 plugin_ 开头,说明中包含实际服务和操作。仅按用户当前任务使用;连接或重新授权应用请让用户打开 Bot 模式的工具 → 已连接应用。不要索取 API Key 或在回复中展示凭据。' : '',
|
|
|
desktop ? DESKTOP_ENVIRONMENT_RULES+(this.desktopEnvironments?.onlineHostsLine(owner)??'') : '',
|
|
|
cloud ? grokComputerRules() : '',
|
|
|
+ dispatchedVm ? VM_COMPUTER_RULES : '',
|
|
|
`[yaoyao-run:${run.runId}:${resultMessage.id}]`,
|
|
|
]
|
|
|
.filter(Boolean)
|