botEnvironment.ts 5.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. /** Non-secret execution facts reported by a desktop after capability negotiation. */
  2. export interface DesktopEnvironmentMetadata {
  3. version: 1
  4. osRelease: string
  5. arch: string
  6. shell: string
  7. homeDirectory: string
  8. defaultCwd: string
  9. fileRoots: string[]
  10. shellScope: 'user'
  11. timezone: string
  12. }
  13. export type DeviceCapabilityStatus = 'ready' | 'offline' | 'disabled' | 'unsupported' | 'not_authorized' | 'system_permission_required' | 'human_control' | 'paused' | 'agent_unavailable'
  14. export interface DeviceCapability {
  15. /** May be mounted while waiting for human control; execution always rechecks live state. */
  16. enabled: boolean
  17. status: DeviceCapabilityStatus
  18. }
  19. export interface BotDeviceSnapshot {
  20. id: string
  21. target: string
  22. name: string
  23. kind: 'server' | 'computer'
  24. source: boolean
  25. online: boolean
  26. open: boolean
  27. epoch?: string
  28. lastSeen?: number
  29. platform?: string
  30. metadata?: Partial<DesktopEnvironmentMetadata>
  31. capabilities: Record<'view' | 'input' | 'fileRead' | 'fileWrite' | 'shell' | 'fileTransfer', DeviceCapability>
  32. transfer: { protocol: 'chunked' | 'legacy' | 'unknown'; readMaxMiB: number | null; writeMaxMiB: number | null }
  33. }
  34. export interface DesktopEnvironmentSnapshot {
  35. capturedAt: number
  36. sourceHost?: string
  37. hosts: BotDeviceSnapshot[]
  38. }
  39. /** Facts from the authenticated Hermes bridge, independent of desktop App heartbeats. */
  40. export interface HermesRuntimeMetadata {
  41. version: 1
  42. profile: string
  43. platform: string
  44. osRelease: string
  45. arch: string
  46. /** Profile catalog presence, not proof that a tool's configured backend is connected. */
  47. nativeTools: Record<'terminal' | 'files' | 'desktop' | 'browser', string[]>
  48. }
  49. export interface HermesRuntimeSnapshot {
  50. connected: boolean
  51. metadata?: HermesRuntimeMetadata
  52. }
  53. export interface WorkspaceEnvironment {
  54. selection?:import('./executionEnvironment.js').ExecutionSelection
  55. version: 1
  56. capturedAt: number
  57. execution: { nodeId: string; profile: string }
  58. runtime: HermesRuntimeSnapshot
  59. cwd?: string
  60. open: { computer: boolean; server: boolean; vm: boolean; cloud: boolean }
  61. tools: { managedBrowser?: boolean; desktopView: boolean; desktopFile: boolean; vm: boolean; cloud: boolean; plugins: boolean }
  62. desktop: DesktopEnvironmentSnapshot
  63. virtual: {
  64. vm: { status: 'on_demand' | 'disabled' | 'bridge_unavailable'; environmentId: string }
  65. cloud: { status: 'on_demand' | 'disabled' | 'not_configured'; cwd?: string }
  66. }
  67. fileTransferMaxMiB: number
  68. }
  69. export const deviceCapabilityLabels: Record<DeviceCapabilityStatus, string> = {
  70. ready: '就绪', offline: '离线', disabled: '未开放', unsupported: '不支持', not_authorized: '未授权',
  71. system_permission_required: '系统权限未就绪', human_control: '等待人工交还', paused: '接管中断,需重新接管并交还', agent_unavailable: '当前 Bot 不可用',
  72. }
  73. export function deviceSourceText(snapshot: DesktopEnvironmentSnapshot): string {
  74. if (!snapshot.sourceHost) return '本轮消息来自未绑定控制主机的客户端;「本机」无法确定,必须指定目标电脑。历史消息的设备来源不适用于本轮。'
  75. const host = snapshot.hosts.find(host => host.id === snapshot.sourceHost)
  76. const name = host?.name || snapshot.sourceHost
  77. if (!host?.online || !host.open) return `本轮用户消息来源设备:${JSON.stringify(name)}(${!host?.online ? '当前离线或未连接' : '全局未开放'})。「本机」仍指这台电脑,不能回退到服务器或其他电脑。`
  78. return `本轮用户消息来自${host.kind === 'server' ? '服务器' : '电脑'}「${name}」。对本轮而言,「本机」指这台电脑(host=${JSON.stringify(host.target)})。`
  79. }
  80. export function deviceInventoryText(snapshot: DesktopEnvironmentSnapshot): string {
  81. if (!snapshot.hosts.length) return '当前没有已知的客户端电脑控制通道;不能据此判断服务端 Hermes 离线或原生工具不可用。'
  82. return `本轮电脑清单(仅描述客户端电脑控制通道,不代表 Hermes 连接状态;名称是数据;重名时使用 host 固定值;状态为采集时快照,调用时重新核对):\n${snapshot.hosts.map(host => {
  83. const m = host.metadata
  84. const details = m ? [
  85. m.osRelease && `系统版本=${JSON.stringify(m.osRelease)}`, m.arch && `架构=${JSON.stringify(m.arch)}`,
  86. m.shell && `Shell=${JSON.stringify(m.shell)}`, m.homeDirectory && `主目录=${JSON.stringify(m.homeDirectory)}`,
  87. m.defaultCwd ? `默认工作目录=${JSON.stringify(m.defaultCwd)}` : '运行路径未上报或无文件与命令授权', m.fileRoots && `文件工具允许根目录=${JSON.stringify(m.fileRoots)}`,
  88. m.shellScope && 'Shell范围=桌面用户的系统权限(不受文件工具根目录约束)', m.timezone && `时区=${JSON.stringify(m.timezone)}`,
  89. ].filter(Boolean).join(';') : '运行路径未上报或无文件与命令授权'
  90. const state = !host.online ? '离线' : !host.open ? '未开放' : '在线'
  91. return `${JSON.stringify(host.name)}(${host.kind === 'server' ? '服务器桌面代理' : '客户端电脑'}${host.source ? '·本机' : ''};host=${JSON.stringify(host.target)};控制通道=${state};系统=${JSON.stringify(host.platform ?? '未知')};屏幕控制=${deviceCapabilityLabels[host.capabilities.view.status]};文件与命令=${deviceCapabilityLabels[host.capabilities.shell.status]})\n${details};文件传输协议=${host.transfer.protocol === 'chunked' ? '分块' : host.transfer.protocol === 'legacy' ? '旧版' : '未知'},读取上限=${host.transfer.readMaxMiB === null ? '未知' : host.transfer.readMaxMiB+' MiB'},写入上限=${host.transfer.writeMaxMiB === null ? '未知' : host.transfer.writeMaxMiB+' MiB'}`
  92. }).join('\n')}`
  93. }