|
@@ -6,7 +6,8 @@ import {existsSync} from 'node:fs'
|
|
|
import type {DatabaseSync} from 'node:sqlite'
|
|
import type {DatabaseSync} from 'node:sqlite'
|
|
|
import {z} from 'zod'
|
|
import {z} from 'zod'
|
|
|
import {HttpError} from '../../server/errors.js'
|
|
import {HttpError} from '../../server/errors.js'
|
|
|
-import type {GatewayFrame} from '../../server/workspaceGateway.js'
|
|
|
|
|
|
|
+import type {GatewayFrame,GatewayTarget} from '../../server/workspaceGateway.js'
|
|
|
|
|
+import {ProfileComputerSession} from './profileSession.js'
|
|
|
import {UNCONFIGURED_COMPUTER_IMAGE} from '../../shared/runner.js'
|
|
import {UNCONFIGURED_COMPUTER_IMAGE} from '../../shared/runner.js'
|
|
|
import type {RunnerConfiguration} from '../../shared/runner.js'
|
|
import type {RunnerConfiguration} from '../../shared/runner.js'
|
|
|
import {ContainerComputerProvider,COMPUTER_WORKSPACE,CUA_DRIVER,CUA_SOCKET,type ComputerSpecification} from '../computers/container.js'
|
|
import {ContainerComputerProvider,COMPUTER_WORKSPACE,CUA_DRIVER,CUA_SOCKET,type ComputerSpecification} from '../computers/container.js'
|
|
@@ -19,8 +20,8 @@ import {HermesWorkerProcess,type WorkerModel,type WorkerProxyEnvironment,type Wo
|
|
|
import {HOST_TOOLS,hostTool,hostPath,readHostFile,writeHostFile} from './hostTools.js'
|
|
import {HOST_TOOLS,hostTool,hostPath,readHostFile,writeHostFile} from './hostTools.js'
|
|
|
import {homedir,platform} from 'node:os'
|
|
import {homedir,platform} from 'node:os'
|
|
|
|
|
|
|
|
-export interface ComputerTarget {environmentId:string;ownerKey:string;agentId:string;hostAccess?:boolean}
|
|
|
|
|
-export interface WorkerSession {agentId?:string;id:string;profile:string;ownerKey:string;environmentId:string;cwd:string;configuredCwd:string;history:any[];outcome?:'complete'|'failed'|'uncertain'}
|
|
|
|
|
|
|
+export interface ComputerTarget {environmentId:string;ownerKey:string;agentId:string;hostAccess?:boolean;profileSession?:boolean}
|
|
|
|
|
+export interface WorkerSession {agentId?:string;id:string;profile:string;ownerKey:string;environmentId:string;cwd:string;configuredCwd:string;history:any[];outcome?:'complete'|'failed'|'uncertain';vmExecution?:'profile';profileSessionId?:string}
|
|
|
interface Live {workMarker?:string;paused?:boolean;pauseJob?:Promise<ComputerLease>;segment?:number;toolCalls:Set<Promise<unknown>>;journal:Map<string,{name:string;result:unknown}>;proxy?:ComputerPublicProxy;session:WorkerSession;model:WorkerModel;contextConfig?:WorkerContextConfiguration;proxyEnv?:WorkerProxyEnvironment;lease:ComputerLease;controller:AbortController;worker?:HermesWorkerProcess;finishing?:Promise<void>;stopping?:Promise<void>;releasing?:Promise<void>;images:any[];running:boolean;received:boolean}
|
|
interface Live {workMarker?:string;paused?:boolean;pauseJob?:Promise<ComputerLease>;segment?:number;toolCalls:Set<Promise<unknown>>;journal:Map<string,{name:string;result:unknown}>;proxy?:ComputerPublicProxy;session:WorkerSession;model:WorkerModel;contextConfig?:WorkerContextConfiguration;proxyEnv?:WorkerProxyEnvironment;lease:ComputerLease;controller:AbortController;worker?:HermesWorkerProcess;finishing?:Promise<void>;stopping?:Promise<void>;releasing?:Promise<void>;images:any[];running:boolean;received:boolean}
|
|
|
const object=(properties:Record<string,unknown>,required:string[])=>({type:'object',properties,required,additionalProperties:false})
|
|
const object=(properties:Record<string,unknown>,required:string[])=>({type:'object',properties,required,additionalProperties:false})
|
|
|
const text={type:'string'}
|
|
const text={type:'string'}
|
|
@@ -33,6 +34,7 @@ const TOOLS:WorkerTool[]=[
|
|
|
{name:'computer_action',description:'在隔离电脑内调用 CUA 动作;先查看桌面并使用可用动作。',inputSchema:object({name:text,arguments:{type:'object'}},['name','arguments'])},
|
|
{name:'computer_action',description:'在隔离电脑内调用 CUA 动作;先查看桌面并使用可用动作。',inputSchema:object({name:text,arguments:{type:'object'}},['name','arguments'])},
|
|
|
]
|
|
]
|
|
|
const pathField=z.string().min(1).max(4096).refine(value=>!value.includes('\0'))
|
|
const pathField=z.string().min(1).max(4096).refine(value=>!value.includes('\0'))
|
|
|
|
|
+const PROFILE_COMPUTER_RULES='当前使用本机协作模式:沿用基础 Profile 的本机工具、配置和上下文,本地虚拟机是独立的 Linux 电脑。computer_shell/read_file/write_file/desktop_state/action/export 只操作虚拟机;本机文件、终端和程序使用 Profile 已授权的原生工具。computer_copy_file 在两个环境间显式复制文件。调用前明确目标环境,文件路径和浏览器登录互不通用;需要回传虚拟机文件时使用 computer_export。人工接管期间停止操作,交还后先检查当前状态。'
|
|
|
export class ComputerRuntime {
|
|
export class ComputerRuntime {
|
|
|
retainDesktops=false
|
|
retainDesktops=false
|
|
|
readonly controls:ComputerControls
|
|
readonly controls:ComputerControls
|
|
@@ -90,6 +92,7 @@ export class ComputerRuntime {
|
|
|
const row=this.db.prepare('SELECT value FROM computer_sessions WHERE id=?').get(id) as {value:string}|undefined
|
|
const row=this.db.prepare('SELECT value FROM computer_sessions WHERE id=?').get(id) as {value:string}|undefined
|
|
|
const session:WorkerSession|undefined=row?JSON.parse(row.value):undefined
|
|
const session:WorkerSession|undefined=row?JSON.parse(row.value):undefined
|
|
|
if(!session||(session.agentId??session.environmentId)!==meta.agentId||session.ownerKey!==meta.ownerKey||session.environmentId!==meta.environmentId||session.profile!==profile)throw new HttpError(404,'电脑会话不存在或不属于当前任务','computer_session_missing')
|
|
if(!session||(session.agentId??session.environmentId)!==meta.agentId||session.ownerKey!==meta.ownerKey||session.environmentId!==meta.environmentId||session.profile!==profile)throw new HttpError(404,'电脑会话不存在或不属于当前任务','computer_session_missing')
|
|
|
|
|
+ if((session.vmExecution==='profile')!==(meta.profileSession===true))throw new HttpError(409,'虚拟机执行方式已改变,请使用新会话','computer_session_mode_changed')
|
|
|
return session
|
|
return session
|
|
|
}
|
|
}
|
|
|
async resolve(profile:string,signal?:AbortSignal){
|
|
async resolve(profile:string,signal?:AbortSignal){
|
|
@@ -127,10 +130,11 @@ export class ComputerGateway {
|
|
|
private timer?:ReturnType<typeof setInterval>
|
|
private timer?:ReturnType<typeof setInterval>
|
|
|
private checking=false
|
|
private checking=false
|
|
|
private hostCwd=homedir()
|
|
private hostCwd=homedir()
|
|
|
|
|
+ private profileSession?:ProfileComputerSession
|
|
|
private team?:{id:string;catalog:WorkerTool[];call(name:string,args:Record<string,unknown>,callId:string):Promise<unknown>}
|
|
private team?:{id:string;catalog:WorkerTool[];call(name:string,args:Record<string,unknown>,callId:string):Promise<unknown>}
|
|
|
- constructor(readonly runtime:ComputerRuntime,readonly meta:ComputerTarget,readonly workId:string,readonly check:()=>Promise<void>,readonly publish:(body:Record<string,unknown>)=>Promise<any>){ }
|
|
|
|
|
|
|
+ constructor(readonly runtime:ComputerRuntime,readonly meta:ComputerTarget,readonly workId:string,readonly check:()=>Promise<void>,readonly publish:(body:Record<string,unknown>)=>Promise<any>,readonly profileTarget?:GatewayTarget){ }
|
|
|
async connect(){this.runtime.assertTarget(this.meta);await this.runtime.ready;await this.authorized()}
|
|
async connect(){this.runtime.assertTarget(this.meta);await this.runtime.ready;await this.authorized()}
|
|
|
- private async authorized(){try{await this.check();this.guard()}catch(error){await this.interrupt().catch(()=>{});if(!this.closed)this.onDisconnect();throw error}}
|
|
|
|
|
|
|
+ private async authorized(){try{await this.check();this.guard()}catch(error){const stopping=this.interrupt().catch(()=>{});if(!this.profileSession)await stopping;if(!this.closed)this.onDisconnect();throw error}}
|
|
|
private guard(){if(this.closed||this.live?.controller.signal.aborted)throw new HttpError(410,'电脑任务授权已结束','computer_cancelled')}
|
|
private guard(){if(this.closed||this.live?.controller.signal.aborted)throw new HttpError(410,'电脑任务授权已结束','computer_cancelled')}
|
|
|
private event(type:string,payload:Record<string,unknown>){if(!this.closed&&this.live)this.onEvent({type,session_id:this.live.session.id,payload})}
|
|
private event(type:string,payload:Record<string,unknown>){if(!this.closed&&this.live)this.onEvent({type,session_id:this.live.session.id,payload})}
|
|
|
private spec(session:WorkerSession):ComputerSpecification{return {id:this.meta.environmentId,ownerKey:this.meta.ownerKey,imageId:this.runtime.imageFor(this.meta),cwd:session.cwd,network:this.runtime.config.network??'none'}}
|
|
private spec(session:WorkerSession):ComputerSpecification{return {id:this.meta.environmentId,ownerKey:this.meta.ownerKey,imageId:this.runtime.imageFor(this.meta),cwd:session.cwd,network:this.runtime.config.network??'none'}}
|
|
@@ -141,12 +145,13 @@ export class ComputerGateway {
|
|
|
if(method==='session.create'||method==='session.resume'){
|
|
if(method==='session.create'||method==='session.resume'){
|
|
|
if(this.live)throw new HttpError(409,'电脑通道已有会话','computer_session_busy')
|
|
if(this.live)throw new HttpError(409,'电脑通道已有会话','computer_session_busy')
|
|
|
await this.authorized()
|
|
await this.authorized()
|
|
|
- if(method==='session.resume'&¶ms.recoverOnly===true){const session=this.runtime.session(String(params.session_id),this.meta,String(params.profile));this.recoverySession=session;const resource=this.runtime.pool.status(this.meta.ownerKey).find(item=>item.environmentId===this.meta.environmentId);if(resource?.holderId===this.workId)await this.runtime.pool.stopHolder(this.meta.ownerKey,this.meta.environmentId,this.workId);return {session_id:session.id,stored_session_id:session.id,running:false,info:{profile_name:session.profile}}}
|
|
|
|
|
|
|
+ if(method==='session.resume'&¶ms.recoverOnly===true){const session=this.runtime.session(String(params.session_id),this.meta,String(params.profile));this.recoverySession=session;if(this.meta.profileSession){await this.openProfile(session,params);await this.profileSession!.stop()}const resource=this.runtime.pool.status(this.meta.ownerKey).find(item=>item.environmentId===this.meta.environmentId);if(resource?.holderId===this.workId)await this.runtime.pool.stopHolder(this.meta.ownerKey,this.meta.environmentId,this.workId);return {session_id:session.id,stored_session_id:session.id,running:false,info:{profile_name:session.profile}}}
|
|
|
if(this.runtime.imageFor(this.meta)===UNCONFIGURED_COMPUTER_IMAGE)throw new HttpError(409,'请先在应用设置的本地虚拟机页面完成准备','computer_image_required')
|
|
if(this.runtime.imageFor(this.meta)===UNCONFIGURED_COMPUTER_IMAGE)throw new HttpError(409,'请先在应用设置的本地虚拟机页面完成准备','computer_image_required')
|
|
|
- const profile=String(params.profile),resolved=await this.runtime.resolve(profile,this.controller.signal)
|
|
|
|
|
|
|
+ const profile=String(params.profile),resolved=await (this.meta.profileSession?this.runtime.resolveWorkspace(profile,this.controller.signal):this.runtime.resolve(profile,this.controller.signal))
|
|
|
if(this.meta.hostAccess){const configured=String(resolved.configuredCwd??'.');this.hostCwd=['.','auto','cwd'].includes(configured)?homedir():hostPath(homedir(),configured)}
|
|
if(this.meta.hostAccess){const configured=String(resolved.configuredCwd??'.');this.hostCwd=['.','auto','cwd'].includes(configured)?homedir():hostPath(homedir(),configured)}
|
|
|
await this.authorized();this.guard()
|
|
await this.authorized();this.guard()
|
|
|
const session:WorkerSession=method==='session.resume'?this.runtime.session(String(params.session_id),this.meta,profile):{id:randomUUID(),agentId:this.meta.agentId,ownerKey:this.meta.ownerKey,environmentId:this.meta.environmentId,profile,cwd:resolved.cwd,configuredCwd:resolved.configuredCwd,history:[]}
|
|
const session:WorkerSession=method==='session.resume'?this.runtime.session(String(params.session_id),this.meta,profile):{id:randomUUID(),agentId:this.meta.agentId,ownerKey:this.meta.ownerKey,environmentId:this.meta.environmentId,profile,cwd:resolved.cwd,configuredCwd:resolved.configuredCwd,history:[]}
|
|
|
|
|
+ if(this.meta.profileSession)session.vmExecution='profile'
|
|
|
session.cwd=this.meta.environmentId!==this.meta.agentId?(this.runtime.pool.definition(this.meta.ownerKey,this.meta.environmentId)?.cwd??COMPUTER_WORKSPACE):resolved.cwd;session.configuredCwd=resolved.configuredCwd
|
|
session.cwd=this.meta.environmentId!==this.meta.agentId?(this.runtime.pool.definition(this.meta.ownerKey,this.meta.environmentId)?.cwd??COMPUTER_WORKSPACE):resolved.cwd;session.configuredCwd=resolved.configuredCwd
|
|
|
await this.runtime.pool.configure(this.spec(session),()=>this.guard())
|
|
await this.runtime.pool.configure(this.spec(session),()=>this.guard())
|
|
|
await this.authorized()
|
|
await this.authorized()
|
|
@@ -160,11 +165,28 @@ export class ComputerGateway {
|
|
|
catch(error){await this.interrupt();throw error}
|
|
catch(error){await this.interrupt();throw error}
|
|
|
}
|
|
}
|
|
|
this.timer=setInterval(()=>{if(this.checking)return;this.checking=true;void this.authorized().then(()=>this.runtime.pool.renew(this.live!.lease)).catch(async()=>{await this.interrupt().catch(()=>{});if(!this.closed)this.onDisconnect()}).finally(()=>{this.checking=false})},5000);this.timer.unref()
|
|
this.timer=setInterval(()=>{if(this.checking)return;this.checking=true;void this.authorized().then(()=>this.runtime.pool.renew(this.live!.lease)).catch(async()=>{await this.interrupt().catch(()=>{});if(!this.closed)this.onDisconnect()}).finally(()=>{this.checking=false})},5000);this.timer.unref()
|
|
|
|
|
+ if(this.meta.profileSession){
|
|
|
|
|
+ try{const opened=await this.openProfile(session,params);return {...opened,session_id:session.id,stored_session_id:session.id}}
|
|
|
|
|
+ catch(error){await this.interrupt();throw error}
|
|
|
|
|
+ }
|
|
|
return {session_id:session.id,stored_session_id:session.id,running:false,info:{profile_name:profile}}
|
|
return {session_id:session.id,stored_session_id:session.id,running:false,info:{profile_name:profile}}
|
|
|
}
|
|
}
|
|
|
const live=this.live
|
|
const live=this.live
|
|
|
if(!live||params.session_id!==live.session.id)throw new HttpError(403,'电脑会话不属于当前通道','computer_session_forbidden')
|
|
if(!live||params.session_id!==live.session.id)throw new HttpError(403,'电脑会话不属于当前通道','computer_session_forbidden')
|
|
|
if(method==='session.interrupt'||method==='session.close'){await this.interrupt();return {status:'interrupted'}}
|
|
if(method==='session.interrupt'||method==='session.close'){await this.interrupt();return {status:'interrupted'}}
|
|
|
|
|
+ if(this.profileSession){
|
|
|
|
|
+ if(method==='session.usage')return this.profileSession.rpc(method,params)
|
|
|
|
|
+ if(live.paused)throw new HttpError(409,'电脑正在等待人工操作','computer_paused')
|
|
|
|
|
+ await this.authorized()
|
|
|
|
|
+ if(method==='prompt.submit'){
|
|
|
|
|
+ if(live.running||live.received)throw new HttpError(409,'电脑会话已经提交过本轮','computer_session_busy')
|
|
|
|
|
+ if(typeof params.workMarker==='string'&&/^\[yaoyao-run:[0-9a-f-]{36}:[0-9a-f-]{36}\]$/.test(params.workMarker))live.workMarker=params.workMarker
|
|
|
|
|
+ await this.profileSession.bind();this.guard()
|
|
|
|
|
+ live.running=true;live.session.outcome='uncertain';this.runtime.save(live.session)
|
|
|
|
|
+ return this.profileSession.rpc(method,{...params,text:`${PROFILE_COMPUTER_RULES}\n${String(params.text??'')}`})
|
|
|
|
|
+ }
|
|
|
|
|
+ return this.profileSession.rpc(method,params)
|
|
|
|
|
+ }
|
|
|
if(method==='session.usage')return {}
|
|
if(method==='session.usage')return {}
|
|
|
if(method==='session.cwd.set'){
|
|
if(method==='session.cwd.set'){
|
|
|
if(params.cwd!==live.session.cwd&¶ms.cwd!==live.session.configuredCwd)throw new HttpError(403,'工作目录必须采用基础 Profile 的配置','computer_cwd_forbidden')
|
|
if(params.cwd!==live.session.cwd&¶ms.cwd!==live.session.configuredCwd)throw new HttpError(403,'工作目录必须采用基础 Profile 的配置','computer_cwd_forbidden')
|
|
@@ -195,6 +217,40 @@ export class ComputerGateway {
|
|
|
}
|
|
}
|
|
|
throw new HttpError(409,'当前隔离 Worker 不支持此交互','computer_method_unavailable')
|
|
throw new HttpError(409,'当前隔离 Worker 不支持此交互','computer_method_unavailable')
|
|
|
}
|
|
}
|
|
|
|
|
+ private async openProfile(session:WorkerSession,params:Record<string,unknown>){
|
|
|
|
|
+ if(!this.profileTarget)throw new HttpError(409,'缺少本机 Profile 连接,请更新执行节点','computer_profile_unavailable')
|
|
|
|
|
+ if(params.session_id&&!session.profileSessionId)throw new HttpError(409,'本机 Profile 会话尚未建立,请新建会话','computer_profile_session_invalid')
|
|
|
|
|
+ const native=new ProfileComputerSession(this.profileTarget,session.profile,this.workId,()=>this.guard(),
|
|
|
|
|
+ ()=>[...TOOLS,...HOST_TOOLS.filter(tool=>tool.name==='computer_copy_file'),...(this.team?.catalog??[])],
|
|
|
|
|
+ async(name,args)=>{
|
|
|
|
|
+ const live=this.live
|
|
|
|
|
+ if(!live||!this.active||live.paused)throw new HttpError(410,'本轮电脑工具已结束或暂停','computer_cancelled')
|
|
|
|
|
+ const call=this.tool(live,name,args,randomUUID())
|
|
|
|
|
+ live.toolCalls.add(call);void call.finally(()=>live.toolCalls.delete(call)).catch(()=>{})
|
|
|
|
|
+ return call
|
|
|
|
|
+ })
|
|
|
|
|
+ this.profileSession=native
|
|
|
|
|
+ native.onStoredId=id=>{session.profileSessionId=id;this.runtime.save(session)}
|
|
|
|
|
+ native.onDisconnect=()=>{void this.interrupt().finally(()=>{if(!this.closed)this.onDisconnect()}).catch(()=>{})}
|
|
|
|
|
+ native.onEvent=frame=>{
|
|
|
|
|
+ const live=this.live
|
|
|
|
|
+ if(!live||this.closed||live.controller.signal.aborted||live.paused||live.received)return
|
|
|
|
|
+ if(['message.complete','run.completed','run.failed','message.error','error'].includes(frame.type)){
|
|
|
|
|
+ live.received=true
|
|
|
|
|
+ live.finishing=this.finishProfile(live,frame)
|
|
|
|
|
+ void live.finishing.catch(()=>this.lost(live))
|
|
|
|
|
+ }else this.event(frame.type,frame.type==='session.info'?{...frame.payload,stored_session_id:session.id,session_key:session.id}:frame.payload??{})
|
|
|
|
|
+ }
|
|
|
|
|
+ return native.open(session.profileSessionId,params)
|
|
|
|
|
+ }
|
|
|
|
|
+ private async finishProfile(live:Live,frame:GatewayFrame){
|
|
|
|
|
+ await Promise.allSettled([...live.toolCalls]);await this.authorized()
|
|
|
|
|
+ const payload=frame.payload??{},success=['message.complete','run.completed'].includes(frame.type)&&!payload.error&&!['interrupted','failed','error'].includes(String(payload.status))
|
|
|
|
|
+ live.session.outcome=success?'complete':'failed';this.runtime.save(live.session)
|
|
|
|
|
+ await this.release(live,this.runtime.retainDesktops&&success);live.running=false
|
|
|
|
|
+ if(this.closed||live.controller.signal.aborted)return
|
|
|
|
|
+ this.event(frame.type,{...payload,...(payload.status==='error'?{status:'failed'}:{})})
|
|
|
|
|
+ }
|
|
|
private async startModel(live:Live,prompt:string,history:any[]){
|
|
private async startModel(live:Live,prompt:string,history:any[]){
|
|
|
live.received=false;live.running=true;live.session.outcome='uncertain';live.session.history.push({role:'user',content:prompt});this.runtime.save(live.session)
|
|
live.received=false;live.running=true;live.session.outcome='uncertain';live.session.history.push({role:'user',content:prompt});this.runtime.save(live.session)
|
|
|
const directory=join(this.runtime.home,'workers',live.session.id,this.workId,String(live.segment=(live.segment??0)+1))
|
|
const directory=join(this.runtime.home,'workers',live.session.id,this.workId,String(live.segment=(live.segment??0)+1))
|
|
@@ -339,6 +395,7 @@ export class ComputerGateway {
|
|
|
if(live.pauseJob)return live.pauseJob
|
|
if(live.pauseJob)return live.pauseJob
|
|
|
live.paused=true;this.event('computer.paused',{stage:'pausing'})
|
|
live.paused=true;this.event('computer.paused',{stage:'pausing'})
|
|
|
live.pauseJob=(async()=>{
|
|
live.pauseJob=(async()=>{
|
|
|
|
|
+ await this.profileSession?.stop()
|
|
|
await live.worker?.close()
|
|
await live.worker?.close()
|
|
|
await Promise.allSettled([...live.toolCalls])
|
|
await Promise.allSettled([...live.toolCalls])
|
|
|
authorize();this.guard()
|
|
authorize();this.guard()
|
|
@@ -362,6 +419,11 @@ export class ComputerGateway {
|
|
|
live.lease=await this.runtime.pool.transfer(live.lease,this.workId,()=>this.guard())
|
|
live.lease=await this.runtime.pool.transfer(live.lease,this.workId,()=>this.guard())
|
|
|
live.paused=false;live.pauseJob=undefined;live.journal.clear()
|
|
live.paused=false;live.pauseJob=undefined;live.journal.clear()
|
|
|
this.event('computer.resumed',{generation:live.lease.generation,notes})
|
|
this.event('computer.resumed',{generation:live.lease.generation,notes})
|
|
|
|
|
+ if(this.profileSession){
|
|
|
|
|
+ await this.profileSession.bind();this.guard()
|
|
|
|
|
+ await this.profileSession.rpc('prompt.submit',{text:`${PROFILE_COMPUTER_RULES}\n用户已交还电脑控制权。请先检查当前状态,避免重复已完成的操作。交还说明:${notes||'无额外说明'}\n${live.workMarker??''}`})
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
await this.startModel(live,`用户已完成电脑接管并明确交还控制。请先检查当前电脑状态,再继续原任务;不要重复已完成的副作用。用户说明:${notes||'无额外说明'}\n${live.workMarker??''}`,structuredClone(live.session.history))
|
|
await this.startModel(live,`用户已完成电脑接管并明确交还控制。请先检查当前电脑状态,再继续原任务;不要重复已完成的副作用。用户说明:${notes||'无额外说明'}\n${live.workMarker??''}`,structuredClone(live.session.history))
|
|
|
}
|
|
}
|
|
|
async interrupt(){
|
|
async interrupt(){
|
|
@@ -369,12 +431,15 @@ export class ComputerGateway {
|
|
|
if(live.stopping)return live.stopping
|
|
if(live.stopping)return live.stopping
|
|
|
live.stopping=(async()=>{
|
|
live.stopping=(async()=>{
|
|
|
live.controller.abort();clearInterval(this.timer)
|
|
live.controller.abort();clearInterval(this.timer)
|
|
|
- await live.worker?.close()
|
|
|
|
|
- try{await this.release(live)}catch(error){if((error as any).code!=='computer_lease_stale')throw error}
|
|
|
|
|
- live.running=false
|
|
|
|
|
|
|
+ try{await this.profileSession?.stop()}
|
|
|
|
|
+ finally{
|
|
|
|
|
+ await this.profileSession?.close();await live.worker?.close()
|
|
|
|
|
+ try{await this.release(live)}catch(error){if((error as any).code!=='computer_lease_stale')throw error}
|
|
|
|
|
+ live.running=false
|
|
|
|
|
+ }
|
|
|
})()
|
|
})()
|
|
|
return live.stopping
|
|
return live.stopping
|
|
|
}
|
|
}
|
|
|
async stopControl(){await this.interrupt();this.event('message.complete',{text:'电脑控制已结束,本轮已停止',status:'interrupted'})}
|
|
async stopControl(){await this.interrupt();this.event('message.complete',{text:'电脑控制已结束,本轮已停止',status:'interrupted'})}
|
|
|
- close(){if(this.runtime.gateways.get(this.meta.environmentId)===this)this.runtime.gateways.delete(this.meta.environmentId);this.closed=true;this.controller.abort();this.team=undefined;clearInterval(this.timer);return this.interrupt()}
|
|
|
|
|
|
|
+ async close(){if(this.runtime.gateways.get(this.meta.environmentId)===this)this.runtime.gateways.delete(this.meta.environmentId);this.closed=true;this.controller.abort();this.team=undefined;clearInterval(this.timer);try{if(this.live?.running||this.live&&!this.live.releasing)await this.interrupt()}finally{await this.profileSession?.close()}}
|
|
|
}
|
|
}
|