refactor(protocol): 拆分 1281 行的 capabilities.ts(纯搬移,公共 API 不变)

## 结果
    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 能力矩阵正常打印(含风险图标)✓
This commit is contained in:
jkooe committed 2026-10-01 21:11:59 +08:00
1 parent caa5b8a2c1
commit 4c6190285c
9 files changed
+1322 -1276

No files matched your search

File diff suppressed because it is too large. Load diff
@@ -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: '因缓冲上限被丢弃的事件数' },
},
},
},
];
@@ -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' },
},
},
},
];
@@ -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' },
},
},
},
];
@@ -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,
];
@@ -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' },
},
},
},
];
@@ -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' },
},
},
},
];
@@ -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];
@@ -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,
};