From 4c6190285c212ad973dab1eee26ca94727871eec Mon Sep 17 00:00:00 2001 From: jkooe <62494645+jkooe@users.noreply.github.com> Date: Thu, 1 Oct 2026 21:11:59 +0800 Subject: [PATCH] =?UTF-8?q?refactor(protocol):=20=E6=8B=86=E5=88=86=201281?= =?UTF-8?q?=20=E8=A1=8C=E7=9A=84=20capabilities.ts=EF=BC=88=E7=BA=AF?= =?UTF-8?q?=E6=90=AC=E7=A7=BB=EF=BC=8C=E5=85=AC=E5=85=B1=20API=20=E4=B8=8D?= =?UTF-8?q?=E5=8F=98=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 结果 capabilities.ts 1281 → 12 行(仅门面:re-export + findCapability) capabilities/names.ts 55 行 标准能力名 CapabilityNames + 类型 capabilities/schemas.ts 11 行 通用 schema 片段(filterSchema) capabilities/manifest/system.ts 303 行 系统面 9 项 capabilities/manifest/graphics.ts 413 行 图形 12 项(screen/window/input) capabilities/manifest/files.ts 121 行 文件 4 项 capabilities/manifest/software.ts 62 行 软件 2 项 capabilities/manifest/automation.ts 329 行 自动化 12 项(task/clip/event/net/macro/group) capabilities/manifest/index.ts 20 行 聚合为单一 CAPABILITY_MANIFEST ## 做法(可复用的纯搬移流程) 1. 先扫「清单数组区间内是否夹带顶层声明」→ 无 ✓(否则按函数切分会漏) 2. 用花括号配对切出 39 个描述符(断言数量必须一致,避免静默丢项) 3. 按**能力名字符串前缀**分组(不是枚举成员名——成员名与字符串前缀并不一致, 例如 AuditList→system.audit.list、AgentRestart→system.agent.restart) 4. 聚合文件保持对外单一 CAPABILITY_MANIFEST;facade 保持全部导出不变 ## 副作用(正面) 清单顺序由「历史上陆续追加的混杂顺序」变为**按域分组**(系统→软件→图形→文件→自动化), CLI/MCP 的能力展示更易读。已真机确认无功能影响。 ## 验证 - 构建 6 包全过;单测 124/124(含清单守护:数量=39、逐项 name/schema 校验);e2e 26/26 - 真机:部署后能力 39 项 ✓、CLI 能力矩阵正常打印(含风险图标)✓ --- packages/protocol/src/capabilities.ts | 1284 +---------------- .../src/capabilities/manifest/automation.ts | 329 +++++ .../src/capabilities/manifest/files.ts | 121 ++ .../src/capabilities/manifest/graphics.ts | 413 ++++++ .../src/capabilities/manifest/index.ts | 20 + .../src/capabilities/manifest/software.ts | 62 + .../src/capabilities/manifest/system.ts | 303 ++++ packages/protocol/src/capabilities/names.ts | 55 + packages/protocol/src/capabilities/schemas.ts | 11 + 9 files changed, 1322 insertions(+), 1276 deletions(-) create mode 100644 packages/protocol/src/capabilities/manifest/automation.ts create mode 100644 packages/protocol/src/capabilities/manifest/files.ts create mode 100644 packages/protocol/src/capabilities/manifest/graphics.ts create mode 100644 packages/protocol/src/capabilities/manifest/index.ts create mode 100644 packages/protocol/src/capabilities/manifest/software.ts create mode 100644 packages/protocol/src/capabilities/manifest/system.ts create mode 100644 packages/protocol/src/capabilities/names.ts create mode 100644 packages/protocol/src/capabilities/schemas.ts diff --git a/packages/protocol/src/capabilities.ts b/packages/protocol/src/capabilities.ts index 3e9ec1d..50d31d4 100644 --- a/packages/protocol/src/capabilities.ts +++ b/packages/protocol/src/capabilities.ts @@ -1,1280 +1,12 @@ -import type { CapabilityDescriptor, JsonSchema } from './messages.js'; +// 能力清单的对外门面(实现已按组拆分到 ./capabilities/ 下,公共 API 保持不变) +export { CapabilityNames } from './capabilities/names.js'; +export type { CapabilityName } from './capabilities/names.js'; +export { filterSchema } from './capabilities/schemas.js'; +export { CAPABILITY_MANIFEST } from './capabilities/manifest/index.js'; -/** nodeagent v1 标准能力名。 */ -export const CapabilityNames = { - SystemInfo: 'system.info', - SystemStatus: 'system.status', - ProcessList: 'system.process.list', - ServiceList: 'system.service.list', - ShellExec: 'system.shell.exec', - AppList: 'app.list', - AppInstall: 'app.install', - // v2 图形接管 - ScreenInfo: 'screen.info', - ScreenCapture: 'screen.capture', - MouseMove: 'input.mouse.move', - MouseClick: 'input.mouse.click', - MouseScroll: 'input.mouse.scroll', - MouseDrag: 'input.mouse.drag', - KeyType: 'input.key.type', - KeyPress: 'input.key.press', - // v3+ 审计 - AuditList: 'system.audit.list', - // v5 文件传输 - FsList: 'fs.list', - FsStat: 'fs.stat', - FsRead: 'fs.read', - FsWrite: 'fs.write', - // v7 自持能力(受控重启) - AgentRestart: 'system.agent.restart', - // v8 GUI 语义(窗口与元素定位) - WindowList: 'window.list', - WindowFocus: 'window.focus', - ScreenFind: 'screen.find', - ScreenRecord: 'screen.record', - // v10 异步任务与剪贴板 - TaskList: 'system.task.list', - TaskGet: 'system.task.get', - TaskKill: 'system.task.kill', - ClipGet: 'clip.get', - ClipSet: 'clip.set', - // v11 安全加固 - AuditVerify: 'system.audit.verify', - Metrics: 'system.metrics', - // v16 网络变更两阶段提交 - NetApply: 'system.net.apply', - NetConfirm: 'system.net.confirm', - NetStatus: 'system.net.status', - // v12 事件订阅与监控 - EventWatch: 'event.watch', - EventUnwatch: 'event.unwatch', - EventList: 'event.list', - EventPoll: 'event.poll', -} as const; +import { CAPABILITY_MANIFEST as MANIFEST } from './capabilities/manifest/index.js'; +import type { CapabilityDescriptor } from './messages.js'; -export type CapabilityName = (typeof CapabilityNames)[keyof typeof CapabilityNames]; - -// ---------- 通用 schema 片段 ---------- - -const filterSchema: JsonSchema = { - type: 'object', - properties: { - name_pattern: { type: 'string', description: '名称匹配(子串,不区分大小写)' }, - }, - additionalProperties: false, -}; - -// ---------- 能力清单 ---------- - -/** - * v1 标准能力清单。 - * 被控端按此声明并在 auth_ok 中返回;控制端据此展示与校验。 - */ -export const CAPABILITY_MANIFEST: CapabilityDescriptor[] = [ - { - name: CapabilityNames.SystemInfo, - version: '1.0', - description: '获取 Windows 系统基本信息(主机名、OS、CPU、内存、开机时长)', - risk: 'low', - params_schema: { - type: 'object', - properties: { - fields: { - type: 'array', - description: '可选:只返回指定字段', - items: { type: 'string' }, - }, - }, - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - hostname: { type: 'string' }, - os: { type: 'string' }, - os_version: { type: 'string' }, - arch: { type: 'string' }, - cpu_model: { type: 'string' }, - cpu_cores: { type: 'integer' }, - memory_total: { type: 'integer', description: '字节' }, - uptime_sec: { type: 'integer' }, - is_admin: { type: 'boolean' }, - pid: { type: 'integer', description: '被控端进程 PID(v11)' }, - agent_script: { type: 'string', description: '被控端入口脚本路径,供一键升级定位(v11)' }, - agent_home: { type: 'string', description: '被控端数据目录 NODEAGENT_HOME(v11)' }, - node_path: { type: 'string', description: '被控端 Node 可执行文件路径(v11)' }, - ps_helper: { - type: 'object', - description: 'PowerShell 常驻助手状态(v15):spawned/hits/failures/consecutiveFailures/avg_ms 等', - }, - }, - }, - }, - { - name: CapabilityNames.SystemStatus, - version: '1.0', - description: '获取资源状态:CPU 占用、内存、磁盘、网络适配器', - risk: 'low', - params_schema: { type: 'object', properties: {}, additionalProperties: false }, - returns_schema: { - type: 'object', - properties: { - cpu_pct: { type: 'number' }, - memory_used: { type: 'integer' }, - memory_total: { type: 'integer' }, - memory_pct: { type: 'number' }, - disks: { - type: 'array', - items: { - type: 'object', - properties: { - drive: { type: 'string' }, - total: { type: 'integer' }, - free: { type: 'integer' }, - used_pct: { type: 'number' }, - }, - }, - }, - net: { - type: 'array', - items: { - type: 'object', - properties: { - adapter: { type: 'string' }, - ip: { type: 'string' }, - up: { type: 'boolean' }, - }, - }, - }, - }, - }, - }, - { - name: CapabilityNames.ProcessList, - version: '1.0', - description: '列出运行中的进程,可按 CPU/内存/名称排序', - risk: 'low', - params_schema: { - type: 'object', - properties: { - sort_by: { type: 'string', enum: ['cpu', 'memory', 'pid', 'name'], default: 'cpu' }, - limit: { type: 'integer', default: 50, minimum: 1, maximum: 500 }, - filter: filterSchema, - }, - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - processes: { - type: 'array', - items: { - type: 'object', - properties: { - pid: { type: 'integer' }, - name: { type: 'string' }, - cpu_pct: { type: 'number' }, - memory_bytes: { type: 'integer' }, - started_at: { type: 'integer' }, - }, - }, - }, - }, - }, - }, - { - name: CapabilityNames.ServiceList, - version: '1.0', - description: '列出 Windows 服务及运行状态', - risk: 'low', - params_schema: { - type: 'object', - properties: { - filter: { - type: 'object', - properties: { - name_pattern: { type: 'string' }, - state: { type: 'string', enum: ['running', 'stopped', 'paused'] }, - }, - additionalProperties: false, - }, - limit: { type: 'integer', default: 100, minimum: 1, maximum: 1000 }, - }, - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - services: { - type: 'array', - items: { - type: 'object', - properties: { - name: { type: 'string' }, - display_name: { type: 'string' }, - state: { type: 'string' }, - start_type: { type: 'string' }, - }, - }, - }, - }, - }, - }, - { - name: CapabilityNames.ShellExec, - version: '1.1', - description: '执行 PowerShell / cmd 命令并返回退出码与输出;async:true 时转后台任务立即返回 task_id', - risk: 'high', - params_schema: { - type: 'object', - properties: { - command: { type: 'string', minLength: 1, description: '要执行的命令' }, - shell: { type: 'string', enum: ['powershell', 'cmd'], default: 'powershell' }, - cwd: { type: 'string', description: '工作目录(可选)' }, - timeout_ms: { type: 'integer', default: 30000, minimum: 1000, maximum: 300000 }, - async: { type: 'boolean', default: false, description: '后台执行,立即返回 task_id(用 system.task.* 管理)' }, - wait_forever: { type: 'boolean', default: false, description: '仅 async 模式:不设超时(timeout_ms 失效)' }, - }, - required: ['command'], - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - exit_code: { type: 'integer' }, - stdout: { type: 'string' }, - stderr: { type: 'string' }, - duration_ms: { type: 'integer' }, - truncated: { type: 'boolean' }, - killed: { type: 'boolean' }, - }, - }, - }, - { - name: CapabilityNames.AppList, - version: '1.0', - description: '列出已安装软件(读取注册表卸载项 + winget list)', - risk: 'low', - params_schema: { - type: 'object', - properties: { filter: filterSchema }, - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - apps: { - type: 'array', - items: { - type: 'object', - properties: { - name: { type: 'string' }, - version: { type: 'string' }, - publisher: { type: 'string' }, - source: { type: 'string' }, - }, - }, - }, - }, - }, - }, - { - name: CapabilityNames.AppInstall, - version: '1.0', - description: '安装软件(优先 winget 静默安装)', - risk: 'high', - params_schema: { - type: 'object', - properties: { - package: { type: 'string', minLength: 1, description: 'winget 包 ID 或软件名' }, - id: { type: 'string', description: '精确 winget ID(优先使用)' }, - silent: { type: 'boolean', default: true }, - timeout_ms: { type: 'integer', default: 600000, minimum: 10000, maximum: 1800000 }, - }, - required: ['package'], - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - installed: { type: 'boolean' }, - name: { type: 'string' }, - version: { type: 'string' }, - source: { type: 'string' }, - detail: { type: 'string' }, - }, - }, - }, - - // ---------- v2 图形接管:屏幕感知 ---------- - { - name: CapabilityNames.ScreenInfo, - version: '1.0', - description: '获取显示器信息(分辨率、缩放、主屏标识)', - risk: 'low', - params_schema: { type: 'object', properties: {}, additionalProperties: false }, - returns_schema: { - type: 'object', - properties: { - displays: { - type: 'array', - items: { - type: 'object', - properties: { - id: { type: 'integer' }, - name: { type: 'string' }, - width: { type: 'integer' }, - height: { type: 'integer' }, - is_primary: { type: 'boolean' }, - scale: { type: 'number' }, - }, - }, - }, - }, - }, - }, - { - name: CapabilityNames.ScreenCapture, - version: '1.0', - description: '截取屏幕,返回 base64 图片(支持区域与缩放)', - risk: 'medium', - params_schema: { - type: 'object', - properties: { - display_id: { type: 'integer', default: 0, minimum: 0, description: '0 = 主屏' }, - format: { type: 'string', enum: ['png', 'jpeg'], default: 'jpeg' }, - quality: { type: 'integer', default: 85, minimum: 1, maximum: 100, description: '仅 jpeg 生效' }, - scale: { type: 'number', default: 1, minimum: 0.1, maximum: 1 }, - region: { - type: 'object', - properties: { - x: { type: 'integer' }, - y: { type: 'integer' }, - width: { type: 'integer', minimum: 1 }, - height: { type: 'integer', minimum: 1 }, - }, - required: ['x', 'y', 'width', 'height'], - additionalProperties: false, - }, - }, - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - image: { type: 'string', description: 'base64 编码的图片' }, - format: { type: 'string' }, - width: { type: 'integer' }, - height: { type: 'integer' }, - bytes: { type: 'integer' }, - captured_at: { type: 'integer' }, - }, - }, - }, - - // ---------- v2 图形接管:输入控制(默认关闭,需被控端显式开启) ---------- - { - name: CapabilityNames.MouseMove, - version: '1.0', - description: '移动鼠标到指定屏幕坐标', - risk: 'high', - params_schema: { - type: 'object', - properties: { - x: { type: 'integer', minimum: 0 }, - y: { type: 'integer', minimum: 0 }, - duration_ms: { type: 'integer', default: 0, minimum: 0, maximum: 5000, description: '平滑移动耗时' }, - }, - required: ['x', 'y'], - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { moved: { type: 'boolean' }, x: { type: 'integer' }, y: { type: 'integer' } }, - }, - }, - { - name: CapabilityNames.MouseClick, - version: '1.0', - description: '鼠标点击(可选先移动到指定坐标)', - risk: 'high', - params_schema: { - type: 'object', - properties: { - x: { type: 'integer', minimum: 0 }, - y: { type: 'integer', minimum: 0 }, - button: { type: 'string', enum: ['left', 'right', 'middle'], default: 'left' }, - count: { type: 'integer', default: 1, minimum: 1, maximum: 3 }, - }, - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - clicked: { type: 'boolean' }, - x: { type: 'integer' }, - y: { type: 'integer' }, - button: { type: 'string' }, - }, - }, - }, - { - name: CapabilityNames.MouseScroll, - version: '1.0', - description: '滚动鼠标滚轮(正数向上、负数向下)', - risk: 'high', - params_schema: { - type: 'object', - properties: { - delta: { type: 'integer', description: '滚动格数' }, - x: { type: 'integer', minimum: 0 }, - y: { type: 'integer', minimum: 0 }, - }, - required: ['delta'], - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { scrolled: { type: 'boolean' }, delta: { type: 'integer' } }, - }, - }, - { - name: CapabilityNames.MouseDrag, - version: '1.0', - description: - '鼠标拖拽:从 (from_x,from_y) 按下并拖到 (to_x,to_y) 释放。' + - '用于拖动文件/窗口、框选文本、滑动条。整段动作在单次调用内完成,步数按距离自适应。', - risk: 'high', - params_schema: { - type: 'object', - properties: { - from_x: { type: 'integer' }, - from_y: { type: 'integer' }, - to_x: { type: 'integer' }, - to_y: { type: 'integer' }, - button: { type: 'string', enum: ['left', 'right', 'middle'], default: 'left' }, - steps: { type: 'integer', minimum: 1, maximum: 200, description: '插值步数(默认按距离自适应)' }, - step_delay_ms: { type: 'integer', minimum: 0, maximum: 200, default: 12 }, - }, - required: ['from_x', 'from_y', 'to_x', 'to_y'], - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - dragged: { type: 'boolean' }, - from: { type: 'object', properties: { x: { type: 'integer' }, y: { type: 'integer' } } }, - to: { type: 'object', properties: { x: { type: 'integer' }, y: { type: 'integer' } } }, - button: { type: 'string' }, - steps: { type: 'integer' }, - }, - }, - }, - { - name: CapabilityNames.KeyType, - version: '1.0', - description: '输入一段文本(逐字符模拟键盘)', - risk: 'high', - params_schema: { - type: 'object', - properties: { - text: { type: 'string', minLength: 1, maxLength: 4096 }, - interval_ms: { type: 'integer', default: 10, minimum: 0, maximum: 1000 }, - }, - required: ['text'], - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { typed: { type: 'boolean' }, length: { type: 'integer' } }, - }, - }, - { - name: CapabilityNames.KeyPress, - version: '1.1', - description: - '按下按键。keys=["ctrl","c"] 表示和弦;配 repeat=3 可连按 3 次;' + - 'sequence=[["ctrl","c"],["ctrl","v"]] 表示按键序列', - risk: 'high', - params_schema: { - type: 'object', - properties: { - keys: { - type: 'array', - items: { type: 'string' }, - minItems: 1, - maxItems: 4, - description: '和弦按键列表,如 ["ctrl","shift","esc"]', - }, - repeat: { type: 'integer', minimum: 1, maximum: 50, default: 1, description: '和弦重复次数' }, - sequence: { - type: 'array', - items: { type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 4 }, - description: '和弦序列,如 [["up"],["up"],["enter"]];提供时忽略 keys/repeat', - }, - }, - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { pressed: { type: 'boolean' }, keys: { type: 'array', items: { type: 'string' } } }, - }, - }, - - // ---------- v3+ 审计 ---------- - { - name: CapabilityNames.AuditList, - version: '1.0', - description: '查询被控端审计日志:谁在何时调用了什么能力、结果如何', - risk: 'low', - params_schema: { - type: 'object', - properties: { - limit: { type: 'integer', default: 50, minimum: 1, maximum: 1000 }, - since: { type: 'integer', description: '仅返回该时间戳(Unix 毫秒)之后的记录' }, - client_id: { type: 'string', description: '按调用方筛选' }, - type: { type: 'string', enum: ['invoke', 'auth', 'acl', 'agent'], description: '按事件类别筛选' }, - }, - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - entries: { type: 'array', items: { type: 'object' } }, - total: { type: 'integer', description: '本次读取的总条数(未截断前)' }, - file: { type: 'string', description: '审计文件路径' }, - }, - }, - }, - - // ---------- v5 文件传输 ---------- - { - name: CapabilityNames.FsList, - version: '1.0', - description: '列出目录内容(支持 glob 过滤与递归)', - risk: 'low', - params_schema: { - type: 'object', - properties: { - path: { type: 'string', minLength: 1 }, - pattern: { type: 'string', description: 'glob 过滤,如 "*.log"' }, - recursive: { type: 'boolean', default: false }, - max_entries: { type: 'integer', default: 500, minimum: 1, maximum: 5000 }, - }, - required: ['path'], - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - entries: { - type: 'array', - items: { - type: 'object', - properties: { - name: { type: 'string' }, - path: { type: 'string' }, - type: { type: 'string', enum: ['file', 'dir', 'other'] }, - size: { type: 'integer' }, - mtime: { type: 'integer' }, - }, - }, - }, - total: { type: 'integer' }, - truncated: { type: 'boolean' }, - }, - }, - }, - { - name: CapabilityNames.FsStat, - version: '1.0', - description: '获取文件或目录的元信息', - risk: 'low', - params_schema: { - type: 'object', - properties: { path: { type: 'string', minLength: 1 } }, - required: ['path'], - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - path: { type: 'string' }, - type: { type: 'string', enum: ['file', 'dir', 'other'] }, - size: { type: 'integer' }, - mtime: { type: 'integer' }, - exists: { type: 'boolean' }, - }, - }, - }, - { - name: CapabilityNames.FsRead, - version: '1.0', - description: '读取文件内容(分块:用 offset/max_bytes 续读,eof 标识结束)', - risk: 'medium', - params_schema: { - type: 'object', - properties: { - path: { type: 'string', minLength: 1 }, - encoding: { type: 'string', enum: ['utf8', 'base64'], default: 'utf8' }, - offset: { type: 'integer', default: 0, minimum: 0, description: '起始字节偏移' }, - max_bytes: { type: 'integer', default: 1048576, minimum: 1, maximum: 8388608, description: '单次读取上限' }, - }, - required: ['path'], - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - data: { type: 'string' }, - encoding: { type: 'string' }, - offset: { type: 'integer' }, - bytes: { type: 'integer', description: '本次实际读取字节数' }, - total_bytes: { type: 'integer' }, - eof: { type: 'boolean' }, - sha256: { type: 'string', description: '整文件摘要(仅首次读取时返回)' }, - }, - }, - }, - { - name: CapabilityNames.FsWrite, - version: '1.0', - description: '写入文件(分块:append=true 追加;写入走临时文件 + 原子重命名)', - risk: 'high', - params_schema: { - type: 'object', - properties: { - path: { type: 'string', minLength: 1 }, - data: { type: 'string' }, - encoding: { type: 'string', enum: ['utf8', 'base64'], default: 'utf8' }, - append: { type: 'boolean', default: false }, - create_dirs: { type: 'boolean', default: false }, - }, - required: ['path', 'data'], - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - written: { type: 'integer', description: '本次写入字节数' }, - total_bytes: { type: 'integer', description: '写入后的文件总大小' }, - path: { type: 'string' }, - }, - }, - }, - { - name: CapabilityNames.AgentRestart, - version: '1.0', - description: - '受控重启被控端自身(延时后由独立进程/计划任务拉起,重启后自动沿用原配置)。' + - '解决「配置变更后无法让自身生效」的问题——不会因进程树清理而中断。', - risk: 'high', - params_schema: { - type: 'object', - properties: { - delay_ms: { - type: 'integer', - minimum: 1, - maximum: 3600000, - description: '延时多少毫秒后重启(默认 2000,给调用方留出返回结果的时间)', - default: 2000, - }, - reason: { type: 'string', description: '可选:重启原因(写入审计日志)' }, - }, - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - scheduled: { type: 'boolean', description: '重启任务是否已排入' }, - delay_ms: { type: 'integer' }, - mechanism: { type: 'string', description: '使用的机制:scheduled-task / detached-spawn' }, - message: { type: 'string' }, - }, - }, - }, - { - name: CapabilityNames.WindowList, - version: '1.0', - description: - '列出当前可见的顶层窗口(标题、所属进程、精确矩形、是否前台窗口)。Windows 用 Win32,macOS 用 AppleScript(需辅助功能权限)。' + - '配合 screen.capture 可在**已知窗口范围内**相对定位,避免全屏猜坐标。', - risk: 'low', - params_schema: { - type: 'object', - properties: { - title_pattern: { type: 'string', description: '可选:按标题正则过滤' }, - limit: { type: 'integer', default: 50 }, - }, - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - windows: { - type: 'array', - items: { - type: 'object', - properties: { - hwnd: { type: 'string', description: '窗口句柄(十六进制)' }, - title: { type: 'string' }, - process: { type: 'string' }, - pid: { type: 'integer' }, - x: { type: 'integer' }, - y: { type: 'integer' }, - width: { type: 'integer' }, - height: { type: 'integer' }, - is_foreground: { type: 'boolean' }, - is_minimized: { type: 'boolean' }, - }, - }, - }, - }, - }, - }, - { - name: CapabilityNames.WindowFocus, - version: '1.0', - description: '将指定窗口(按标题正则或句柄)置前并聚焦,返回其精确矩形。', - risk: 'medium', - params_schema: { - type: 'object', - properties: { - title: { type: 'string', description: '窗口标题(正则,匹配第一个)' }, - hwnd: { type: 'string', description: '或直接给窗口句柄' }, - wait_ms: { - type: 'integer', - minimum: 0, - maximum: 30000, - default: 0, - description: '等待窗口出现(应用启动有延迟时用;仅 title 模式有效)', - }, - }, - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - hwnd: { type: 'string' }, - title: { type: 'string' }, - x: { type: 'integer' }, - y: { type: 'integer' }, - width: { type: 'integer' }, - height: { type: 'integer' }, - focused: { type: 'boolean' }, - activated_by: { type: 'string', description: '激活方式:api(SetForegroundWindow)| click(标题栏点击兜底)' }, - }, - }, - }, - { - name: CapabilityNames.ScreenFind, - version: '1.1', - description: - '在界面中查找 UI 元素并返回其**屏幕坐标**。Windows:UIA 优先,找不到自动降级 OCR(截图识别,支持自绘 UI)。' + - 'macOS:统一走 Vision OCR(无 UIA 等价物)。返回中心点坐标,直接喂给 input.mouse.click。', - risk: 'low', - params_schema: { - type: 'object', - properties: { - text: { type: 'string', description: '要查找的文本(匹配元素 Name,支持子串)' }, - window: { type: 'string', description: '可选:限定在该窗口标题(正则)内查找' }, - control_type: { - type: 'string', - description: '可选:限定控件类型,如 Button / MenuItem / Edit / Text / ListItem(仅 UIA)', - }, - method: { - type: 'string', - enum: ['auto', 'uia', 'ocr', 'image'], - default: 'auto', - description: - 'auto=UIA 优先+OCR 兜底;uia=仅控件树;ocr=仅文字识别;image=仅图像模板匹配(需 template)', - }, - template: { - type: 'string', - description: - 'method=image 时必填:被控端上的模板图片路径(png/jpg/bmp)。' + - '用于纯图标/无文字控件 —— UIA 无控件树、OCR 无文字可读的场景', - }, - threshold: { - type: 'number', - minimum: 0.3, - maximum: 0.999, - default: 0.85, - description: 'method=image 的匹配阈值(零均值归一化互相关,越高越严格)', - }, - limit: { type: 'integer', default: 20 }, - wait_ms: { - type: 'integer', - minimum: 0, - maximum: 30000, - default: 0, - description: '等待元素出现的上限毫秒(0=只查一次)。界面有动画/加载时用', - }, - interval_ms: { type: 'integer', minimum: 100, maximum: 2000, default: 400 }, - region: { - type: 'object', - properties: { - x: { type: 'integer' }, - y: { type: 'integer' }, - width: { type: 'integer' }, - height: { type: 'integer' }, - }, - description: '可选:只在该屏幕区域查找(OCR/图像匹配更快更准;UIA 忽略此参数)', - }, - }, - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - engine: { type: 'string', description: '实际使用的引擎:uia | ocr' }, - waited_ms: { type: 'integer', description: '实际等待时长(便于诊断是否命中等待窗口)' }, - matches: { - type: 'array', - items: { - type: 'object', - properties: { - name: { type: 'string' }, - control_type: { type: 'string' }, - automation_id: { type: 'string' }, - x: { type: 'integer', description: '中心点 X(屏幕坐标)' }, - y: { type: 'integer', description: '中心点 Y(屏幕坐标)' }, - left: { type: 'integer' }, - top: { type: 'integer' }, - width: { type: 'integer' }, - height: { type: 'integer' }, - window_title: { type: 'string' }, - }, - }, - }, - }, - }, - }, - { - name: CapabilityNames.ScreenRecord, - version: '1.0', - description: - '录制屏幕为帧序列(JPEG),检测到 ffmpeg 时额外封装 mp4。产物留在被控端目录,' + - '用 fs.read / pull 取回。用于复现「间歇性」问题、记录 GUI 操作过程。', - risk: 'low', - params_schema: { - type: 'object', - properties: { - duration_ms: { type: 'integer', minimum: 1000, maximum: 60000, default: 5000 }, - fps: { type: 'integer', minimum: 1, maximum: 10, default: 2 }, - scale: { type: 'number', minimum: 0.1, maximum: 1, default: 0.5, description: '全屏录制时的缩放' }, - region: { - type: 'object', - properties: { - x: { type: 'integer' }, y: { type: 'integer' }, - width: { type: 'integer' }, height: { type: 'integer' }, - }, - description: '可选:只录制该区域(多屏时用负坐标)', - }, - }, - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - dir: { type: 'string', description: '帧序列目录(被控端路径)' }, - frames: { type: 'integer' }, - requested_frames: { type: 'integer' }, - elapsed_ms: { type: 'integer' }, - fps: { type: 'integer' }, - video_path: { type: 'string', description: '有 ffmpeg 时的 mp4 路径' }, - frames_only: { type: 'boolean' }, - message: { type: 'string' }, - }, - }, - }, - { - name: CapabilityNames.TaskList, - version: '1.0', - description: '列出当前后台异步任务(system.shell.exec 以 async:true 启动的命令)。', - risk: 'low', - params_schema: { type: 'object', properties: {}, additionalProperties: false }, - returns_schema: { - type: 'object', - properties: { - tasks: { - type: 'array', - items: { - type: 'object', - properties: { - task_id: { type: 'string' }, - command: { type: 'string', description: '命令(sha256 摘要后前 8 位)' }, - state: { type: 'string', description: 'running | done | killed | failed' }, - started_at: { type: 'integer' }, - duration_ms: { type: 'integer' }, - exit_code: { type: 'integer' }, - }, - }, - }, - }, - }, - }, - { - name: CapabilityNames.TaskGet, - version: '1.0', - description: - '查询异步任务状态与输出。任务结束后仍可查询(保留最近 N 条),输出支持增量读取(offset)。', - risk: 'low', - params_schema: { - type: 'object', - properties: { - task_id: { type: 'string' }, - stream: { type: 'string', enum: ['stdout', 'stderr'], default: 'stdout' }, - offset: { - type: 'integer', - description: '从输出的第 offset 字节开始读(增量拉取)', - default: 0, - }, - max_bytes: { type: 'integer', default: 262144 }, - }, - required: ['task_id'], - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - task_id: { type: 'string' }, - state: { type: 'string' }, - exit_code: { type: 'integer' }, - started_at: { type: 'integer' }, - duration_ms: { type: 'integer' }, - data: { type: 'string', description: '本次读取到的输出片段' }, - offset: { type: 'integer', description: '已读到的字节位置(下次续读用)' }, - total_bytes: { type: 'integer' }, - }, - }, - }, - { - name: CapabilityNames.TaskKill, - version: '1.0', - description: '强制终止一个后台异步任务(杀整棵进程树)。', - risk: 'medium', - params_schema: { - type: 'object', - properties: { task_id: { type: 'string' } }, - required: ['task_id'], - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { killed: { type: 'boolean' }, task_id: { type: 'string' } }, - }, - }, - { - name: CapabilityNames.ClipGet, - version: '1.1', - description: - '读取被控端剪贴板。format=auto(默认)优先取图片、无图则取文本;text 仅取文本;image 仅取图片。', - risk: 'medium', - params_schema: { - type: 'object', - properties: { - format: { type: 'string', enum: ['auto', 'text', 'image'], default: 'auto' }, - }, - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - type: { type: 'string', description: 'text | image' }, - text: { type: 'string' }, - format: { type: 'string', description: '图片格式(png)' }, - image_base64: { type: 'string' }, - bytes: { type: 'integer' }, - }, - }, - }, - { - name: CapabilityNames.ClipSet, - version: '1.1', - description: - '向被控端剪贴板写入文本或图片(Windows: Set-Clipboard / SetImage;macOS: pbcopy)。' + - '图片以 Base64 PNG 传入(image_base64),内部经临时文件传递以规避命令行长度限制。', - risk: 'medium', - params_schema: { - type: 'object', - properties: { - text: { type: 'string', description: '文本(与 image_base64 二选一)' }, - image_base64: { type: 'string', description: 'PNG 图片的 Base64(与 text 二选一)' }, - }, - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - type: { type: 'string' }, - written: { type: 'integer', description: '写入的字符数(文本)' }, - written_bytes: { type: 'integer', description: '写入的字节数(图片)' }, - }, - }, - }, - { - name: CapabilityNames.AuditVerify, - version: '1.0', - description: - '校验审计日志链完整性(v11 链式哈希):逐条重算哈希并比对 prev 链接,' + - '可发现条目被篡改、删除或替换。轮转跨文件连续校验。', - risk: 'low', - params_schema: { type: 'object', properties: {}, additionalProperties: false }, - returns_schema: { - type: 'object', - properties: { - ok: { type: 'boolean' }, - checked: { type: 'integer', description: '参与校验的条目数' }, - legacy: { type: 'integer', description: '无链字段的历史条目数(跳过校验)' }, - broken_at: { - type: 'object', - description: '首个异常位置(ok=false 时存在)', - properties: { - file: { type: 'string' }, - line: { type: 'integer' }, - reason: { type: 'string' }, - }, - }, - }, - }, - }, - { - name: CapabilityNames.Metrics, - version: '1.0', - description: - '按 PRD 2.2 汇总成功指标(数据源为审计日志):闭环成功率、装软件成功率、P95 时延、安全拦截率,' + - '并给出与目标阈值的达标判定;同时返回按能力细分统计。', - risk: 'low', - params_schema: { - type: 'object', - properties: { - since: { type: 'integer', description: '可选:只统计该时间戳(Unix 毫秒)之后的审计条目' }, - }, - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - close_loop: { - type: 'object', - properties: { - attempts: { type: 'integer' }, - ok: { type: 'integer' }, - failed: { type: 'integer' }, - success_rate: { type: 'number' }, - }, - }, - app_install: { type: 'object' }, - latency: { type: 'object' }, - security: { type: 'object' }, - verdict: { type: 'object', description: '四项指标的达标判定' }, - targets: { type: 'object' }, - by_capability: { type: 'object' }, - window: { type: 'object' }, - }, - }, - }, - { - name: CapabilityNames.NetApply, - version: '1.0', - description: - '**网络变更两阶段提交**(commit-confirm):备份当前网络配置 → 应用变更 → 注册一个 OS 级' + - '一次性计划任务到点自动回滚(不依赖 agent 进程存活)→ 控制端调用 system.net.confirm 确认即提交。' + - '用于远程改 IP / 切 DHCP 这类「改错就失联」的操作 —— 未确认则自动回滚,不会把自己关在门外。', - risk: 'high', - params_schema: { - type: 'object', - properties: { - mode: { type: 'string', enum: ['static', 'dhcp', 'command'], description: '变更类型;command=自定义命令(POSIX 仅支持此项)' }, - interface: { type: 'string', description: '网卡别名(如「Ethernet」);不填则自动取带默认网关的那块' }, - ip: { type: 'string', description: 'mode=static:新 IPv4' }, - mask: { type: 'string', description: 'mode=static:子网掩码(如 255.255.255.0)' }, - gateway: { type: 'string', description: 'mode=static:网关(可选)' }, - dns: { type: 'array', items: { type: 'string' }, description: 'mode=static:DNS 列表(可选)' }, - command: { type: 'string', description: 'mode=command:要执行的变更命令' }, - confirm_within_ms: { - type: 'integer', - minimum: 15000, - maximum: 600000, - default: 60000, - description: '确认窗口;逾期自动回滚(默认 60 秒)', - }, - }, - required: ['mode'], - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - applied: { type: 'boolean' }, - backup_path: { type: 'string', description: '变更前配置备份(netsh dump,可用 netsh -f 恢复)' }, - rollback_scheduled: { type: 'boolean' }, - task_name: { type: 'string' }, - rollback_at: { type: 'integer', description: '自动回滚时间戳(Unix 毫秒)' }, - confirm_within_ms: { type: 'integer' }, - before: { type: 'object', description: '变更前地址信息' }, - after: { type: 'object', description: '变更后地址信息' }, - hint: { type: 'string' }, - }, - }, - }, - { - name: CapabilityNames.NetConfirm, - version: '1.0', - description: - '确认(提交)网络变更:取消自动回滚任务。控制端通常在新地址上重连成功后调用。' + - '不传 task_name 则取消全部待确认项。', - risk: 'low', - params_schema: { - type: 'object', - properties: { task_name: { type: 'string', description: 'net.apply 返回的任务名;省略则取消全部' } }, - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - confirmed: { type: 'integer' }, - cancelled: { type: 'array', items: { type: 'string' } }, - remaining: { type: 'array', items: { type: 'string' } }, - }, - }, - }, - { - name: CapabilityNames.NetStatus, - version: '1.0', - description: '查看网络现状:各网卡地址、待确认的变更(含剩余秒数)、历史备份文件。', - risk: 'low', - params_schema: { type: 'object', properties: {}, additionalProperties: false }, - returns_schema: { - type: 'object', - properties: { - interfaces: { type: 'array', items: { type: 'object' } }, - pending: { type: 'array', items: { type: 'object' } }, - pending_count: { type: 'integer' }, - backups: { type: 'array', items: { type: 'object' } }, - note: { type: 'string' }, - }, - }, - }, - { - name: CapabilityNames.EventWatch, - version: '1.0', - description: - '订阅被控端事件:file=文件变动(增删改)、process=进程启停、net=监听端口开闭。' + - '被控端通过 event 通知主动推送(无需轮询);事件同时进入环形缓冲,可用 event.poll 拉取。', - risk: 'low', - params_schema: { - type: 'object', - properties: { - kind: { type: 'string', enum: ['file', 'process', 'net'] }, - path: { type: 'string', description: 'file 类型:监控的目录/文件路径' }, - pattern: { type: 'string', description: '可选:文件名 glob(如 *.log)或进程名子串' }, - recursive: { type: 'boolean', default: false, description: 'file 类型:是否递归子目录' }, - interval_ms: { - type: 'integer', - minimum: 1000, - maximum: 60000, - default: 5000, - description: 'process/net 类型的采样间隔', - }, - }, - required: ['kind'], - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - watch_id: { type: 'string' }, - kind: { type: 'string' }, - description: { type: 'string' }, - }, - }, - }, - { - name: CapabilityNames.EventUnwatch, - version: '1.0', - description: '取消一个事件订阅(连接断开时也会自动清理)。', - risk: 'low', - params_schema: { - type: 'object', - properties: { watch_id: { type: 'string' } }, - required: ['watch_id'], - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { removed: { type: 'boolean' } }, - }, - }, - { - name: CapabilityNames.EventList, - version: '1.0', - description: '列出当前生效的事件订阅及其已产生事件数。', - risk: 'low', - params_schema: { type: 'object', properties: {}, additionalProperties: false }, - returns_schema: { - type: 'object', - properties: { - watches: { - type: 'array', - items: { - type: 'object', - properties: { - watch_id: { type: 'string' }, - kind: { type: 'string' }, - description: { type: 'string' }, - events: { type: 'integer' }, - created_at: { type: 'integer' }, - }, - }, - }, - buffered: { type: 'integer', description: '环形缓冲中的事件总数' }, - }, - }, - }, - { - name: CapabilityNames.EventPoll, - version: '1.0', - description: - '拉取已缓冲的事件(适合无法接收推送的调用方,如 MCP:先 watch 再 poll)。' + - 'since 传入上次返回的 next_cursor 可增量拉取。', - risk: 'low', - params_schema: { - type: 'object', - properties: { - limit: { type: 'integer', minimum: 1, maximum: 500, default: 50 }, - since: { type: 'integer', description: '游标(上次返回的 next_cursor)' }, - watch_id: { type: 'string', description: '可选:只看某订阅' }, - }, - additionalProperties: false, - }, - returns_schema: { - type: 'object', - properties: { - events: { - type: 'array', - items: { - type: 'object', - properties: { - seq: { type: 'integer' }, - watch_id: { type: 'string' }, - kind: { type: 'string' }, - ts: { type: 'integer' }, - action: { type: 'string' }, - target: { type: 'string' }, - detail: { type: 'string' }, - }, - }, - }, - next_cursor: { type: 'integer' }, - dropped: { type: 'integer', description: '因缓冲上限被丢弃的事件数' }, - }, - }, - }, -]; - -/** 按名称查找能力描述。 */ export function findCapability(name: string): CapabilityDescriptor | undefined { - return CAPABILITY_MANIFEST.find((c) => c.name === name); + return MANIFEST.find((c) => c.name === name); } diff --git a/packages/protocol/src/capabilities/manifest/automation.ts b/packages/protocol/src/capabilities/manifest/automation.ts new file mode 100644 index 0000000..1114f92 --- /dev/null +++ b/packages/protocol/src/capabilities/manifest/automation.ts @@ -0,0 +1,329 @@ +import type { CapabilityDescriptor } from '../../messages.js'; +import { CapabilityNames } from '../names.js'; +import { filterSchema } from '../schemas.js'; + +/** automation 组能力清单 */ +export const AUTOMATION_CAPABILITIES: CapabilityDescriptor[] = [ + { + name: CapabilityNames.TaskList, + version: '1.0', + description: '列出当前后台异步任务(system.shell.exec 以 async:true 启动的命令)。', + risk: 'low', + params_schema: { type: 'object', properties: {}, additionalProperties: false }, + returns_schema: { + type: 'object', + properties: { + tasks: { + type: 'array', + items: { + type: 'object', + properties: { + task_id: { type: 'string' }, + command: { type: 'string', description: '命令(sha256 摘要后前 8 位)' }, + state: { type: 'string', description: 'running | done | killed | failed' }, + started_at: { type: 'integer' }, + duration_ms: { type: 'integer' }, + exit_code: { type: 'integer' }, + }, + }, + }, + }, + }, + }, + { + name: CapabilityNames.TaskGet, + version: '1.0', + description: + '查询异步任务状态与输出。任务结束后仍可查询(保留最近 N 条),输出支持增量读取(offset)。', + risk: 'low', + params_schema: { + type: 'object', + properties: { + task_id: { type: 'string' }, + stream: { type: 'string', enum: ['stdout', 'stderr'], default: 'stdout' }, + offset: { + type: 'integer', + description: '从输出的第 offset 字节开始读(增量拉取)', + default: 0, + }, + max_bytes: { type: 'integer', default: 262144 }, + }, + required: ['task_id'], + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + task_id: { type: 'string' }, + state: { type: 'string' }, + exit_code: { type: 'integer' }, + started_at: { type: 'integer' }, + duration_ms: { type: 'integer' }, + data: { type: 'string', description: '本次读取到的输出片段' }, + offset: { type: 'integer', description: '已读到的字节位置(下次续读用)' }, + total_bytes: { type: 'integer' }, + }, + }, + }, + { + name: CapabilityNames.TaskKill, + version: '1.0', + description: '强制终止一个后台异步任务(杀整棵进程树)。', + risk: 'medium', + params_schema: { + type: 'object', + properties: { task_id: { type: 'string' } }, + required: ['task_id'], + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { killed: { type: 'boolean' }, task_id: { type: 'string' } }, + }, + }, + { + name: CapabilityNames.ClipGet, + version: '1.1', + description: + '读取被控端剪贴板。format=auto(默认)优先取图片、无图则取文本;text 仅取文本;image 仅取图片。', + risk: 'medium', + params_schema: { + type: 'object', + properties: { + format: { type: 'string', enum: ['auto', 'text', 'image'], default: 'auto' }, + }, + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + type: { type: 'string', description: 'text | image' }, + text: { type: 'string' }, + format: { type: 'string', description: '图片格式(png)' }, + image_base64: { type: 'string' }, + bytes: { type: 'integer' }, + }, + }, + }, + { + name: CapabilityNames.ClipSet, + version: '1.1', + description: + '向被控端剪贴板写入文本或图片(Windows: Set-Clipboard / SetImage;macOS: pbcopy)。' + + '图片以 Base64 PNG 传入(image_base64),内部经临时文件传递以规避命令行长度限制。', + risk: 'medium', + params_schema: { + type: 'object', + properties: { + text: { type: 'string', description: '文本(与 image_base64 二选一)' }, + image_base64: { type: 'string', description: 'PNG 图片的 Base64(与 text 二选一)' }, + }, + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + type: { type: 'string' }, + written: { type: 'integer', description: '写入的字符数(文本)' }, + written_bytes: { type: 'integer', description: '写入的字节数(图片)' }, + }, + }, + }, + { + name: CapabilityNames.NetApply, + version: '1.0', + description: + '**网络变更两阶段提交**(commit-confirm):备份当前网络配置 → 应用变更 → 注册一个 OS 级' + + '一次性计划任务到点自动回滚(不依赖 agent 进程存活)→ 控制端调用 system.net.confirm 确认即提交。' + + '用于远程改 IP / 切 DHCP 这类「改错就失联」的操作 —— 未确认则自动回滚,不会把自己关在门外。', + risk: 'high', + params_schema: { + type: 'object', + properties: { + mode: { type: 'string', enum: ['static', 'dhcp', 'command'], description: '变更类型;command=自定义命令(POSIX 仅支持此项)' }, + interface: { type: 'string', description: '网卡别名(如「Ethernet」);不填则自动取带默认网关的那块' }, + ip: { type: 'string', description: 'mode=static:新 IPv4' }, + mask: { type: 'string', description: 'mode=static:子网掩码(如 255.255.255.0)' }, + gateway: { type: 'string', description: 'mode=static:网关(可选)' }, + dns: { type: 'array', items: { type: 'string' }, description: 'mode=static:DNS 列表(可选)' }, + command: { type: 'string', description: 'mode=command:要执行的变更命令' }, + confirm_within_ms: { + type: 'integer', + minimum: 15000, + maximum: 600000, + default: 60000, + description: '确认窗口;逾期自动回滚(默认 60 秒)', + }, + }, + required: ['mode'], + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + applied: { type: 'boolean' }, + backup_path: { type: 'string', description: '变更前配置备份(netsh dump,可用 netsh -f 恢复)' }, + rollback_scheduled: { type: 'boolean' }, + task_name: { type: 'string' }, + rollback_at: { type: 'integer', description: '自动回滚时间戳(Unix 毫秒)' }, + confirm_within_ms: { type: 'integer' }, + before: { type: 'object', description: '变更前地址信息' }, + after: { type: 'object', description: '变更后地址信息' }, + hint: { type: 'string' }, + }, + }, + }, + { + name: CapabilityNames.NetConfirm, + version: '1.0', + description: + '确认(提交)网络变更:取消自动回滚任务。控制端通常在新地址上重连成功后调用。' + + '不传 task_name 则取消全部待确认项。', + risk: 'low', + params_schema: { + type: 'object', + properties: { task_name: { type: 'string', description: 'net.apply 返回的任务名;省略则取消全部' } }, + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + confirmed: { type: 'integer' }, + cancelled: { type: 'array', items: { type: 'string' } }, + remaining: { type: 'array', items: { type: 'string' } }, + }, + }, + }, + { + name: CapabilityNames.NetStatus, + version: '1.0', + description: '查看网络现状:各网卡地址、待确认的变更(含剩余秒数)、历史备份文件。', + risk: 'low', + params_schema: { type: 'object', properties: {}, additionalProperties: false }, + returns_schema: { + type: 'object', + properties: { + interfaces: { type: 'array', items: { type: 'object' } }, + pending: { type: 'array', items: { type: 'object' } }, + pending_count: { type: 'integer' }, + backups: { type: 'array', items: { type: 'object' } }, + note: { type: 'string' }, + }, + }, + }, + { + name: CapabilityNames.EventWatch, + version: '1.0', + description: + '订阅被控端事件:file=文件变动(增删改)、process=进程启停、net=监听端口开闭。' + + '被控端通过 event 通知主动推送(无需轮询);事件同时进入环形缓冲,可用 event.poll 拉取。', + risk: 'low', + params_schema: { + type: 'object', + properties: { + kind: { type: 'string', enum: ['file', 'process', 'net'] }, + path: { type: 'string', description: 'file 类型:监控的目录/文件路径' }, + pattern: { type: 'string', description: '可选:文件名 glob(如 *.log)或进程名子串' }, + recursive: { type: 'boolean', default: false, description: 'file 类型:是否递归子目录' }, + interval_ms: { + type: 'integer', + minimum: 1000, + maximum: 60000, + default: 5000, + description: 'process/net 类型的采样间隔', + }, + }, + required: ['kind'], + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + watch_id: { type: 'string' }, + kind: { type: 'string' }, + description: { type: 'string' }, + }, + }, + }, + { + name: CapabilityNames.EventUnwatch, + version: '1.0', + description: '取消一个事件订阅(连接断开时也会自动清理)。', + risk: 'low', + params_schema: { + type: 'object', + properties: { watch_id: { type: 'string' } }, + required: ['watch_id'], + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { removed: { type: 'boolean' } }, + }, + }, + { + name: CapabilityNames.EventList, + version: '1.0', + description: '列出当前生效的事件订阅及其已产生事件数。', + risk: 'low', + params_schema: { type: 'object', properties: {}, additionalProperties: false }, + returns_schema: { + type: 'object', + properties: { + watches: { + type: 'array', + items: { + type: 'object', + properties: { + watch_id: { type: 'string' }, + kind: { type: 'string' }, + description: { type: 'string' }, + events: { type: 'integer' }, + created_at: { type: 'integer' }, + }, + }, + }, + buffered: { type: 'integer', description: '环形缓冲中的事件总数' }, + }, + }, + }, + { + name: CapabilityNames.EventPoll, + version: '1.0', + description: + '拉取已缓冲的事件(适合无法接收推送的调用方,如 MCP:先 watch 再 poll)。' + + 'since 传入上次返回的 next_cursor 可增量拉取。', + risk: 'low', + params_schema: { + type: 'object', + properties: { + limit: { type: 'integer', minimum: 1, maximum: 500, default: 50 }, + since: { type: 'integer', description: '游标(上次返回的 next_cursor)' }, + watch_id: { type: 'string', description: '可选:只看某订阅' }, + }, + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + events: { + type: 'array', + items: { + type: 'object', + properties: { + seq: { type: 'integer' }, + watch_id: { type: 'string' }, + kind: { type: 'string' }, + ts: { type: 'integer' }, + action: { type: 'string' }, + target: { type: 'string' }, + detail: { type: 'string' }, + }, + }, + }, + next_cursor: { type: 'integer' }, + dropped: { type: 'integer', description: '因缓冲上限被丢弃的事件数' }, + }, + }, + }, +]; diff --git a/packages/protocol/src/capabilities/manifest/files.ts b/packages/protocol/src/capabilities/manifest/files.ts new file mode 100644 index 0000000..b635ece --- /dev/null +++ b/packages/protocol/src/capabilities/manifest/files.ts @@ -0,0 +1,121 @@ +import type { CapabilityDescriptor } from '../../messages.js'; +import { CapabilityNames } from '../names.js'; +import { filterSchema } from '../schemas.js'; + +/** files 组能力清单 */ +export const FILES_CAPABILITIES: CapabilityDescriptor[] = [ + { + name: CapabilityNames.FsList, + version: '1.0', + description: '列出目录内容(支持 glob 过滤与递归)', + risk: 'low', + params_schema: { + type: 'object', + properties: { + path: { type: 'string', minLength: 1 }, + pattern: { type: 'string', description: 'glob 过滤,如 "*.log"' }, + recursive: { type: 'boolean', default: false }, + max_entries: { type: 'integer', default: 500, minimum: 1, maximum: 5000 }, + }, + required: ['path'], + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + entries: { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + path: { type: 'string' }, + type: { type: 'string', enum: ['file', 'dir', 'other'] }, + size: { type: 'integer' }, + mtime: { type: 'integer' }, + }, + }, + }, + total: { type: 'integer' }, + truncated: { type: 'boolean' }, + }, + }, + }, + { + name: CapabilityNames.FsStat, + version: '1.0', + description: '获取文件或目录的元信息', + risk: 'low', + params_schema: { + type: 'object', + properties: { path: { type: 'string', minLength: 1 } }, + required: ['path'], + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + path: { type: 'string' }, + type: { type: 'string', enum: ['file', 'dir', 'other'] }, + size: { type: 'integer' }, + mtime: { type: 'integer' }, + exists: { type: 'boolean' }, + }, + }, + }, + { + name: CapabilityNames.FsRead, + version: '1.0', + description: '读取文件内容(分块:用 offset/max_bytes 续读,eof 标识结束)', + risk: 'medium', + params_schema: { + type: 'object', + properties: { + path: { type: 'string', minLength: 1 }, + encoding: { type: 'string', enum: ['utf8', 'base64'], default: 'utf8' }, + offset: { type: 'integer', default: 0, minimum: 0, description: '起始字节偏移' }, + max_bytes: { type: 'integer', default: 1048576, minimum: 1, maximum: 8388608, description: '单次读取上限' }, + }, + required: ['path'], + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + data: { type: 'string' }, + encoding: { type: 'string' }, + offset: { type: 'integer' }, + bytes: { type: 'integer', description: '本次实际读取字节数' }, + total_bytes: { type: 'integer' }, + eof: { type: 'boolean' }, + sha256: { type: 'string', description: '整文件摘要(仅首次读取时返回)' }, + }, + }, + }, + { + name: CapabilityNames.FsWrite, + version: '1.0', + description: '写入文件(分块:append=true 追加;写入走临时文件 + 原子重命名)', + risk: 'high', + params_schema: { + type: 'object', + properties: { + path: { type: 'string', minLength: 1 }, + data: { type: 'string' }, + encoding: { type: 'string', enum: ['utf8', 'base64'], default: 'utf8' }, + append: { type: 'boolean', default: false }, + create_dirs: { type: 'boolean', default: false }, + }, + required: ['path', 'data'], + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + written: { type: 'integer', description: '本次写入字节数' }, + total_bytes: { type: 'integer', description: '写入后的文件总大小' }, + path: { type: 'string' }, + }, + }, + }, +]; diff --git a/packages/protocol/src/capabilities/manifest/graphics.ts b/packages/protocol/src/capabilities/manifest/graphics.ts new file mode 100644 index 0000000..1cd1f3c --- /dev/null +++ b/packages/protocol/src/capabilities/manifest/graphics.ts @@ -0,0 +1,413 @@ +import type { CapabilityDescriptor } from '../../messages.js'; +import { CapabilityNames } from '../names.js'; +import { filterSchema } from '../schemas.js'; + +/** graphics 组能力清单 */ +export const GRAPHICS_CAPABILITIES: CapabilityDescriptor[] = [ + { + name: CapabilityNames.ScreenInfo, + version: '1.0', + description: '获取显示器信息(分辨率、缩放、主屏标识)', + risk: 'low', + params_schema: { type: 'object', properties: {}, additionalProperties: false }, + returns_schema: { + type: 'object', + properties: { + displays: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'integer' }, + name: { type: 'string' }, + width: { type: 'integer' }, + height: { type: 'integer' }, + is_primary: { type: 'boolean' }, + scale: { type: 'number' }, + }, + }, + }, + }, + }, + }, + { + name: CapabilityNames.ScreenCapture, + version: '1.0', + description: '截取屏幕,返回 base64 图片(支持区域与缩放)', + risk: 'medium', + params_schema: { + type: 'object', + properties: { + display_id: { type: 'integer', default: 0, minimum: 0, description: '0 = 主屏' }, + format: { type: 'string', enum: ['png', 'jpeg'], default: 'jpeg' }, + quality: { type: 'integer', default: 85, minimum: 1, maximum: 100, description: '仅 jpeg 生效' }, + scale: { type: 'number', default: 1, minimum: 0.1, maximum: 1 }, + region: { + type: 'object', + properties: { + x: { type: 'integer' }, + y: { type: 'integer' }, + width: { type: 'integer', minimum: 1 }, + height: { type: 'integer', minimum: 1 }, + }, + required: ['x', 'y', 'width', 'height'], + additionalProperties: false, + }, + }, + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + image: { type: 'string', description: 'base64 编码的图片' }, + format: { type: 'string' }, + width: { type: 'integer' }, + height: { type: 'integer' }, + bytes: { type: 'integer' }, + captured_at: { type: 'integer' }, + }, + }, + }, + { + name: CapabilityNames.MouseMove, + version: '1.0', + description: '移动鼠标到指定屏幕坐标', + risk: 'high', + params_schema: { + type: 'object', + properties: { + x: { type: 'integer', minimum: 0 }, + y: { type: 'integer', minimum: 0 }, + duration_ms: { type: 'integer', default: 0, minimum: 0, maximum: 5000, description: '平滑移动耗时' }, + }, + required: ['x', 'y'], + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { moved: { type: 'boolean' }, x: { type: 'integer' }, y: { type: 'integer' } }, + }, + }, + { + name: CapabilityNames.MouseClick, + version: '1.0', + description: '鼠标点击(可选先移动到指定坐标)', + risk: 'high', + params_schema: { + type: 'object', + properties: { + x: { type: 'integer', minimum: 0 }, + y: { type: 'integer', minimum: 0 }, + button: { type: 'string', enum: ['left', 'right', 'middle'], default: 'left' }, + count: { type: 'integer', default: 1, minimum: 1, maximum: 3 }, + }, + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + clicked: { type: 'boolean' }, + x: { type: 'integer' }, + y: { type: 'integer' }, + button: { type: 'string' }, + }, + }, + }, + { + name: CapabilityNames.MouseScroll, + version: '1.0', + description: '滚动鼠标滚轮(正数向上、负数向下)', + risk: 'high', + params_schema: { + type: 'object', + properties: { + delta: { type: 'integer', description: '滚动格数' }, + x: { type: 'integer', minimum: 0 }, + y: { type: 'integer', minimum: 0 }, + }, + required: ['delta'], + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { scrolled: { type: 'boolean' }, delta: { type: 'integer' } }, + }, + }, + { + name: CapabilityNames.MouseDrag, + version: '1.0', + description: + '鼠标拖拽:从 (from_x,from_y) 按下并拖到 (to_x,to_y) 释放。' + + '用于拖动文件/窗口、框选文本、滑动条。整段动作在单次调用内完成,步数按距离自适应。', + risk: 'high', + params_schema: { + type: 'object', + properties: { + from_x: { type: 'integer' }, + from_y: { type: 'integer' }, + to_x: { type: 'integer' }, + to_y: { type: 'integer' }, + button: { type: 'string', enum: ['left', 'right', 'middle'], default: 'left' }, + steps: { type: 'integer', minimum: 1, maximum: 200, description: '插值步数(默认按距离自适应)' }, + step_delay_ms: { type: 'integer', minimum: 0, maximum: 200, default: 12 }, + }, + required: ['from_x', 'from_y', 'to_x', 'to_y'], + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + dragged: { type: 'boolean' }, + from: { type: 'object', properties: { x: { type: 'integer' }, y: { type: 'integer' } } }, + to: { type: 'object', properties: { x: { type: 'integer' }, y: { type: 'integer' } } }, + button: { type: 'string' }, + steps: { type: 'integer' }, + }, + }, + }, + { + name: CapabilityNames.KeyType, + version: '1.0', + description: '输入一段文本(逐字符模拟键盘)', + risk: 'high', + params_schema: { + type: 'object', + properties: { + text: { type: 'string', minLength: 1, maxLength: 4096 }, + interval_ms: { type: 'integer', default: 10, minimum: 0, maximum: 1000 }, + }, + required: ['text'], + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { typed: { type: 'boolean' }, length: { type: 'integer' } }, + }, + }, + { + name: CapabilityNames.KeyPress, + version: '1.1', + description: + '按下按键。keys=["ctrl","c"] 表示和弦;配 repeat=3 可连按 3 次;' + + 'sequence=[["ctrl","c"],["ctrl","v"]] 表示按键序列', + risk: 'high', + params_schema: { + type: 'object', + properties: { + keys: { + type: 'array', + items: { type: 'string' }, + minItems: 1, + maxItems: 4, + description: '和弦按键列表,如 ["ctrl","shift","esc"]', + }, + repeat: { type: 'integer', minimum: 1, maximum: 50, default: 1, description: '和弦重复次数' }, + sequence: { + type: 'array', + items: { type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 4 }, + description: '和弦序列,如 [["up"],["up"],["enter"]];提供时忽略 keys/repeat', + }, + }, + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { pressed: { type: 'boolean' }, keys: { type: 'array', items: { type: 'string' } } }, + }, + }, + { + name: CapabilityNames.WindowList, + version: '1.0', + description: + '列出当前可见的顶层窗口(标题、所属进程、精确矩形、是否前台窗口)。Windows 用 Win32,macOS 用 AppleScript(需辅助功能权限)。' + + '配合 screen.capture 可在**已知窗口范围内**相对定位,避免全屏猜坐标。', + risk: 'low', + params_schema: { + type: 'object', + properties: { + title_pattern: { type: 'string', description: '可选:按标题正则过滤' }, + limit: { type: 'integer', default: 50 }, + }, + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + windows: { + type: 'array', + items: { + type: 'object', + properties: { + hwnd: { type: 'string', description: '窗口句柄(十六进制)' }, + title: { type: 'string' }, + process: { type: 'string' }, + pid: { type: 'integer' }, + x: { type: 'integer' }, + y: { type: 'integer' }, + width: { type: 'integer' }, + height: { type: 'integer' }, + is_foreground: { type: 'boolean' }, + is_minimized: { type: 'boolean' }, + }, + }, + }, + }, + }, + }, + { + name: CapabilityNames.WindowFocus, + version: '1.0', + description: '将指定窗口(按标题正则或句柄)置前并聚焦,返回其精确矩形。', + risk: 'medium', + params_schema: { + type: 'object', + properties: { + title: { type: 'string', description: '窗口标题(正则,匹配第一个)' }, + hwnd: { type: 'string', description: '或直接给窗口句柄' }, + wait_ms: { + type: 'integer', + minimum: 0, + maximum: 30000, + default: 0, + description: '等待窗口出现(应用启动有延迟时用;仅 title 模式有效)', + }, + }, + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + hwnd: { type: 'string' }, + title: { type: 'string' }, + x: { type: 'integer' }, + y: { type: 'integer' }, + width: { type: 'integer' }, + height: { type: 'integer' }, + focused: { type: 'boolean' }, + activated_by: { type: 'string', description: '激活方式:api(SetForegroundWindow)| click(标题栏点击兜底)' }, + }, + }, + }, + { + name: CapabilityNames.ScreenFind, + version: '1.1', + description: + '在界面中查找 UI 元素并返回其**屏幕坐标**。Windows:UIA 优先,找不到自动降级 OCR(截图识别,支持自绘 UI)。' + + 'macOS:统一走 Vision OCR(无 UIA 等价物)。返回中心点坐标,直接喂给 input.mouse.click。', + risk: 'low', + params_schema: { + type: 'object', + properties: { + text: { type: 'string', description: '要查找的文本(匹配元素 Name,支持子串)' }, + window: { type: 'string', description: '可选:限定在该窗口标题(正则)内查找' }, + control_type: { + type: 'string', + description: '可选:限定控件类型,如 Button / MenuItem / Edit / Text / ListItem(仅 UIA)', + }, + method: { + type: 'string', + enum: ['auto', 'uia', 'ocr', 'image'], + default: 'auto', + description: + 'auto=UIA 优先+OCR 兜底;uia=仅控件树;ocr=仅文字识别;image=仅图像模板匹配(需 template)', + }, + template: { + type: 'string', + description: + 'method=image 时必填:被控端上的模板图片路径(png/jpg/bmp)。' + + '用于纯图标/无文字控件 —— UIA 无控件树、OCR 无文字可读的场景', + }, + threshold: { + type: 'number', + minimum: 0.3, + maximum: 0.999, + default: 0.85, + description: 'method=image 的匹配阈值(零均值归一化互相关,越高越严格)', + }, + limit: { type: 'integer', default: 20 }, + wait_ms: { + type: 'integer', + minimum: 0, + maximum: 30000, + default: 0, + description: '等待元素出现的上限毫秒(0=只查一次)。界面有动画/加载时用', + }, + interval_ms: { type: 'integer', minimum: 100, maximum: 2000, default: 400 }, + region: { + type: 'object', + properties: { + x: { type: 'integer' }, + y: { type: 'integer' }, + width: { type: 'integer' }, + height: { type: 'integer' }, + }, + description: '可选:只在该屏幕区域查找(OCR/图像匹配更快更准;UIA 忽略此参数)', + }, + }, + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + engine: { type: 'string', description: '实际使用的引擎:uia | ocr' }, + waited_ms: { type: 'integer', description: '实际等待时长(便于诊断是否命中等待窗口)' }, + matches: { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + control_type: { type: 'string' }, + automation_id: { type: 'string' }, + x: { type: 'integer', description: '中心点 X(屏幕坐标)' }, + y: { type: 'integer', description: '中心点 Y(屏幕坐标)' }, + left: { type: 'integer' }, + top: { type: 'integer' }, + width: { type: 'integer' }, + height: { type: 'integer' }, + window_title: { type: 'string' }, + }, + }, + }, + }, + }, + }, + { + name: CapabilityNames.ScreenRecord, + version: '1.0', + description: + '录制屏幕为帧序列(JPEG),检测到 ffmpeg 时额外封装 mp4。产物留在被控端目录,' + + '用 fs.read / pull 取回。用于复现「间歇性」问题、记录 GUI 操作过程。', + risk: 'low', + params_schema: { + type: 'object', + properties: { + duration_ms: { type: 'integer', minimum: 1000, maximum: 60000, default: 5000 }, + fps: { type: 'integer', minimum: 1, maximum: 10, default: 2 }, + scale: { type: 'number', minimum: 0.1, maximum: 1, default: 0.5, description: '全屏录制时的缩放' }, + region: { + type: 'object', + properties: { + x: { type: 'integer' }, y: { type: 'integer' }, + width: { type: 'integer' }, height: { type: 'integer' }, + }, + description: '可选:只录制该区域(多屏时用负坐标)', + }, + }, + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + dir: { type: 'string', description: '帧序列目录(被控端路径)' }, + frames: { type: 'integer' }, + requested_frames: { type: 'integer' }, + elapsed_ms: { type: 'integer' }, + fps: { type: 'integer' }, + video_path: { type: 'string', description: '有 ffmpeg 时的 mp4 路径' }, + frames_only: { type: 'boolean' }, + message: { type: 'string' }, + }, + }, + }, +]; diff --git a/packages/protocol/src/capabilities/manifest/index.ts b/packages/protocol/src/capabilities/manifest/index.ts new file mode 100644 index 0000000..90b1d37 --- /dev/null +++ b/packages/protocol/src/capabilities/manifest/index.ts @@ -0,0 +1,20 @@ +// 能力清单聚合(按组拆分后在此汇总,对外仍是单一 CAPABILITY_MANIFEST) +import type { CapabilityDescriptor } from '../../messages.js'; +import { AUTOMATION_CAPABILITIES } from './automation.js'; +import { FILES_CAPABILITIES } from './files.js'; +import { GRAPHICS_CAPABILITIES } from './graphics.js'; +import { SOFTWARE_CAPABILITIES } from './software.js'; +import { SYSTEM_CAPABILITIES } from './system.js'; + +/** + * v1 标准能力清单(39 项)。 + * 被控端按此声明并在 auth_ok 中返回;控制端据此展示与校验。 + * 顺序即 CLI/MCP 的展示顺序(系统面 → 软件 → 图形 → 文件 → 自动化)。 + */ +export const CAPABILITY_MANIFEST: CapabilityDescriptor[] = [ + ...SYSTEM_CAPABILITIES, + ...SOFTWARE_CAPABILITIES, + ...GRAPHICS_CAPABILITIES, + ...FILES_CAPABILITIES, + ...AUTOMATION_CAPABILITIES, +]; diff --git a/packages/protocol/src/capabilities/manifest/software.ts b/packages/protocol/src/capabilities/manifest/software.ts new file mode 100644 index 0000000..bfd1dba --- /dev/null +++ b/packages/protocol/src/capabilities/manifest/software.ts @@ -0,0 +1,62 @@ +import type { CapabilityDescriptor } from '../../messages.js'; +import { CapabilityNames } from '../names.js'; +import { filterSchema } from '../schemas.js'; + +/** software 组能力清单 */ +export const SOFTWARE_CAPABILITIES: CapabilityDescriptor[] = [ + { + name: CapabilityNames.AppList, + version: '1.0', + description: '列出已安装软件(读取注册表卸载项 + winget list)', + risk: 'low', + params_schema: { + type: 'object', + properties: { filter: filterSchema }, + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + apps: { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + version: { type: 'string' }, + publisher: { type: 'string' }, + source: { type: 'string' }, + }, + }, + }, + }, + }, + }, + { + name: CapabilityNames.AppInstall, + version: '1.0', + description: '安装软件(优先 winget 静默安装)', + risk: 'high', + params_schema: { + type: 'object', + properties: { + package: { type: 'string', minLength: 1, description: 'winget 包 ID 或软件名' }, + id: { type: 'string', description: '精确 winget ID(优先使用)' }, + silent: { type: 'boolean', default: true }, + timeout_ms: { type: 'integer', default: 600000, minimum: 10000, maximum: 1800000 }, + }, + required: ['package'], + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + installed: { type: 'boolean' }, + name: { type: 'string' }, + version: { type: 'string' }, + source: { type: 'string' }, + detail: { type: 'string' }, + }, + }, + }, +]; diff --git a/packages/protocol/src/capabilities/manifest/system.ts b/packages/protocol/src/capabilities/manifest/system.ts new file mode 100644 index 0000000..6e8d0b6 --- /dev/null +++ b/packages/protocol/src/capabilities/manifest/system.ts @@ -0,0 +1,303 @@ +import type { CapabilityDescriptor } from '../../messages.js'; +import { CapabilityNames } from '../names.js'; +import { filterSchema } from '../schemas.js'; + +/** system 组能力清单 */ +export const SYSTEM_CAPABILITIES: CapabilityDescriptor[] = [ + { + name: CapabilityNames.SystemInfo, + version: '1.0', + description: '获取 Windows 系统基本信息(主机名、OS、CPU、内存、开机时长)', + risk: 'low', + params_schema: { + type: 'object', + properties: { + fields: { + type: 'array', + description: '可选:只返回指定字段', + items: { type: 'string' }, + }, + }, + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + hostname: { type: 'string' }, + os: { type: 'string' }, + os_version: { type: 'string' }, + arch: { type: 'string' }, + cpu_model: { type: 'string' }, + cpu_cores: { type: 'integer' }, + memory_total: { type: 'integer', description: '字节' }, + uptime_sec: { type: 'integer' }, + is_admin: { type: 'boolean' }, + pid: { type: 'integer', description: '被控端进程 PID(v11)' }, + agent_script: { type: 'string', description: '被控端入口脚本路径,供一键升级定位(v11)' }, + agent_home: { type: 'string', description: '被控端数据目录 NODEAGENT_HOME(v11)' }, + node_path: { type: 'string', description: '被控端 Node 可执行文件路径(v11)' }, + ps_helper: { + type: 'object', + description: 'PowerShell 常驻助手状态(v15):spawned/hits/failures/consecutiveFailures/avg_ms 等', + }, + }, + }, + }, + { + name: CapabilityNames.SystemStatus, + version: '1.0', + description: '获取资源状态:CPU 占用、内存、磁盘、网络适配器', + risk: 'low', + params_schema: { type: 'object', properties: {}, additionalProperties: false }, + returns_schema: { + type: 'object', + properties: { + cpu_pct: { type: 'number' }, + memory_used: { type: 'integer' }, + memory_total: { type: 'integer' }, + memory_pct: { type: 'number' }, + disks: { + type: 'array', + items: { + type: 'object', + properties: { + drive: { type: 'string' }, + total: { type: 'integer' }, + free: { type: 'integer' }, + used_pct: { type: 'number' }, + }, + }, + }, + net: { + type: 'array', + items: { + type: 'object', + properties: { + adapter: { type: 'string' }, + ip: { type: 'string' }, + up: { type: 'boolean' }, + }, + }, + }, + }, + }, + }, + { + name: CapabilityNames.ProcessList, + version: '1.0', + description: '列出运行中的进程,可按 CPU/内存/名称排序', + risk: 'low', + params_schema: { + type: 'object', + properties: { + sort_by: { type: 'string', enum: ['cpu', 'memory', 'pid', 'name'], default: 'cpu' }, + limit: { type: 'integer', default: 50, minimum: 1, maximum: 500 }, + filter: filterSchema, + }, + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + processes: { + type: 'array', + items: { + type: 'object', + properties: { + pid: { type: 'integer' }, + name: { type: 'string' }, + cpu_pct: { type: 'number' }, + memory_bytes: { type: 'integer' }, + started_at: { type: 'integer' }, + }, + }, + }, + }, + }, + }, + { + name: CapabilityNames.ServiceList, + version: '1.0', + description: '列出 Windows 服务及运行状态', + risk: 'low', + params_schema: { + type: 'object', + properties: { + filter: { + type: 'object', + properties: { + name_pattern: { type: 'string' }, + state: { type: 'string', enum: ['running', 'stopped', 'paused'] }, + }, + additionalProperties: false, + }, + limit: { type: 'integer', default: 100, minimum: 1, maximum: 1000 }, + }, + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + services: { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + display_name: { type: 'string' }, + state: { type: 'string' }, + start_type: { type: 'string' }, + }, + }, + }, + }, + }, + }, + { + name: CapabilityNames.ShellExec, + version: '1.1', + description: '执行 PowerShell / cmd 命令并返回退出码与输出;async:true 时转后台任务立即返回 task_id', + risk: 'high', + params_schema: { + type: 'object', + properties: { + command: { type: 'string', minLength: 1, description: '要执行的命令' }, + shell: { type: 'string', enum: ['powershell', 'cmd'], default: 'powershell' }, + cwd: { type: 'string', description: '工作目录(可选)' }, + timeout_ms: { type: 'integer', default: 30000, minimum: 1000, maximum: 300000 }, + async: { type: 'boolean', default: false, description: '后台执行,立即返回 task_id(用 system.task.* 管理)' }, + wait_forever: { type: 'boolean', default: false, description: '仅 async 模式:不设超时(timeout_ms 失效)' }, + }, + required: ['command'], + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + exit_code: { type: 'integer' }, + stdout: { type: 'string' }, + stderr: { type: 'string' }, + duration_ms: { type: 'integer' }, + truncated: { type: 'boolean' }, + killed: { type: 'boolean' }, + }, + }, + }, + { + name: CapabilityNames.AuditList, + version: '1.0', + description: '查询被控端审计日志:谁在何时调用了什么能力、结果如何', + risk: 'low', + params_schema: { + type: 'object', + properties: { + limit: { type: 'integer', default: 50, minimum: 1, maximum: 1000 }, + since: { type: 'integer', description: '仅返回该时间戳(Unix 毫秒)之后的记录' }, + client_id: { type: 'string', description: '按调用方筛选' }, + type: { type: 'string', enum: ['invoke', 'auth', 'acl', 'agent'], description: '按事件类别筛选' }, + }, + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + entries: { type: 'array', items: { type: 'object' } }, + total: { type: 'integer', description: '本次读取的总条数(未截断前)' }, + file: { type: 'string', description: '审计文件路径' }, + }, + }, + }, + { + name: CapabilityNames.AgentRestart, + version: '1.0', + description: + '受控重启被控端自身(延时后由独立进程/计划任务拉起,重启后自动沿用原配置)。' + + '解决「配置变更后无法让自身生效」的问题——不会因进程树清理而中断。', + risk: 'high', + params_schema: { + type: 'object', + properties: { + delay_ms: { + type: 'integer', + minimum: 1, + maximum: 3600000, + description: '延时多少毫秒后重启(默认 2000,给调用方留出返回结果的时间)', + default: 2000, + }, + reason: { type: 'string', description: '可选:重启原因(写入审计日志)' }, + }, + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + scheduled: { type: 'boolean', description: '重启任务是否已排入' }, + delay_ms: { type: 'integer' }, + mechanism: { type: 'string', description: '使用的机制:scheduled-task / detached-spawn' }, + message: { type: 'string' }, + }, + }, + }, + { + name: CapabilityNames.AuditVerify, + version: '1.0', + description: + '校验审计日志链完整性(v11 链式哈希):逐条重算哈希并比对 prev 链接,' + + '可发现条目被篡改、删除或替换。轮转跨文件连续校验。', + risk: 'low', + params_schema: { type: 'object', properties: {}, additionalProperties: false }, + returns_schema: { + type: 'object', + properties: { + ok: { type: 'boolean' }, + checked: { type: 'integer', description: '参与校验的条目数' }, + legacy: { type: 'integer', description: '无链字段的历史条目数(跳过校验)' }, + broken_at: { + type: 'object', + description: '首个异常位置(ok=false 时存在)', + properties: { + file: { type: 'string' }, + line: { type: 'integer' }, + reason: { type: 'string' }, + }, + }, + }, + }, + }, + { + name: CapabilityNames.Metrics, + version: '1.0', + description: + '按 PRD 2.2 汇总成功指标(数据源为审计日志):闭环成功率、装软件成功率、P95 时延、安全拦截率,' + + '并给出与目标阈值的达标判定;同时返回按能力细分统计。', + risk: 'low', + params_schema: { + type: 'object', + properties: { + since: { type: 'integer', description: '可选:只统计该时间戳(Unix 毫秒)之后的审计条目' }, + }, + additionalProperties: false, + }, + returns_schema: { + type: 'object', + properties: { + close_loop: { + type: 'object', + properties: { + attempts: { type: 'integer' }, + ok: { type: 'integer' }, + failed: { type: 'integer' }, + success_rate: { type: 'number' }, + }, + }, + app_install: { type: 'object' }, + latency: { type: 'object' }, + security: { type: 'object' }, + verdict: { type: 'object', description: '四项指标的达标判定' }, + targets: { type: 'object' }, + by_capability: { type: 'object' }, + window: { type: 'object' }, + }, + }, + }, +]; diff --git a/packages/protocol/src/capabilities/names.ts b/packages/protocol/src/capabilities/names.ts new file mode 100644 index 0000000..c0c3183 --- /dev/null +++ b/packages/protocol/src/capabilities/names.ts @@ -0,0 +1,55 @@ +// 标准能力名(从 capabilities.ts 拆出) + +/** nodeagent v1 标准能力名。 */ +export const CapabilityNames = { + SystemInfo: 'system.info', + SystemStatus: 'system.status', + ProcessList: 'system.process.list', + ServiceList: 'system.service.list', + ShellExec: 'system.shell.exec', + AppList: 'app.list', + AppInstall: 'app.install', + // v2 图形接管 + ScreenInfo: 'screen.info', + ScreenCapture: 'screen.capture', + MouseMove: 'input.mouse.move', + MouseClick: 'input.mouse.click', + MouseScroll: 'input.mouse.scroll', + MouseDrag: 'input.mouse.drag', + KeyType: 'input.key.type', + KeyPress: 'input.key.press', + // v3+ 审计 + AuditList: 'system.audit.list', + // v5 文件传输 + FsList: 'fs.list', + FsStat: 'fs.stat', + FsRead: 'fs.read', + FsWrite: 'fs.write', + // v7 自持能力(受控重启) + AgentRestart: 'system.agent.restart', + // v8 GUI 语义(窗口与元素定位) + WindowList: 'window.list', + WindowFocus: 'window.focus', + ScreenFind: 'screen.find', + ScreenRecord: 'screen.record', + // v10 异步任务与剪贴板 + TaskList: 'system.task.list', + TaskGet: 'system.task.get', + TaskKill: 'system.task.kill', + ClipGet: 'clip.get', + ClipSet: 'clip.set', + // v11 安全加固 + AuditVerify: 'system.audit.verify', + Metrics: 'system.metrics', + // v16 网络变更两阶段提交 + NetApply: 'system.net.apply', + NetConfirm: 'system.net.confirm', + NetStatus: 'system.net.status', + // v12 事件订阅与监控 + EventWatch: 'event.watch', + EventUnwatch: 'event.unwatch', + EventList: 'event.list', + EventPoll: 'event.poll', +} as const; + +export type CapabilityName = (typeof CapabilityNames)[keyof typeof CapabilityNames]; diff --git a/packages/protocol/src/capabilities/schemas.ts b/packages/protocol/src/capabilities/schemas.ts new file mode 100644 index 0000000..b4885c4 --- /dev/null +++ b/packages/protocol/src/capabilities/schemas.ts @@ -0,0 +1,11 @@ +// 通用 schema 片段(从 capabilities.ts 拆出) +import type { JsonSchema } from '../messages.js'; + +/** 名称过滤片段(app.list 等复用) */ +export const filterSchema: JsonSchema = { + type: 'object', + properties: { + name_pattern: { type: 'string', description: '名称匹配(子串,不区分大小写)' }, + }, + additionalProperties: false, +};