Skip to content

节点开发规范

本文说明如何新增工作流节点,涵盖类型定义、执行器、配置面板与 UI 组件的完整开发流程。

概述

Automa Agent 的每个节点由类型定义执行器配置面板画布 UI 四部分组成。开发新节点时须按规范在多处注册,确保设计器与运行时行为一致。


目录结构

text
src/
├── automa/types/
│   ├── command-types.ts    # 命令分类定义(节点面板显示)
│   ├── node-config.ts      # 节点枚举、UI配置、表单配置
│   └── node-types.ts       # 节点类型定义(TypeScript接口)
├── main/engine/
│   ├── BaseNodeExecutor.ts # 执行器基类
│   └── executors/          # 节点执行器目录
│       ├── MouseClickExecutor.ts
│       ├── MouseMoveExecutor.ts
│       ├── KeyboardInputExecutor.ts
│       ├── OpenAppExecutor.ts
│       ├── OpenBrowserWindowExecutor.ts
│       └── DelayExecutor.ts
└── renderer/pages/workflow/
    ├── nodes/              # 节点 UI 组件
    │   ├── MouseClickNode.vue
    │   └── OpenAppNode.vue
    └── components/ConfigPanel/
        ├── ConfigPanel.vue      # 配置面板
        ├── ResourceSelector.vue # 资源选择器
        ├── AppSelector.vue      # 应用选择器
        └── WorkflowSelector.vue # 工作流选择器

节点开发完整检查清单

开发新节点时,必须完成以下所有步骤,缺一不可:

步骤文件操作必需
1command-types.ts在 CommandCategories 中添加命令定义
2node-config.ts在 NodeKey 枚举中添加节点键
3node-config.ts在 NodeTypesConfig 中添加节点UI配置
4node-config.ts定义节点表单配置(xxxFormConfig)
5node-types.ts定义节点配置接口(xxxNodeDefinition)
6node-types.ts在 SpecificNodeDefinition 联合类型中添加
7node-types.ts在 NodeDefinitionMap 映射中添加
8executors/创建节点执行器类
9nodes/创建节点 UI 组件
10ConfigPanel.vue导入并注册表单配置

节点开发步骤详解

1. 注册命令类型(command-types.ts)

src/automa/types/command-types.tsCommandCategories 中添加命令定义:

typescript
// 找到对应的分类,添加命令
{
  key: 'desktop',
  label: '桌面自动化',
  icon: 'icon-desktop',
  commands: [
    // ... 其他命令
    { 
      id: 'custom-node',           // 唯一ID
      name: '自定义节点',           // 显示名称
      type: 'custom-node',         // 节点类型(与 NodeKey 对应)
      icon: 'icon-xxx',            // 图标
      desc: '节点功能描述'          // 描述文字
    },
  ],
},

2. 定义节点类型(node-types.ts)

src/automa/types/node-types.ts 中定义节点配置接口:

typescript
/**
 * 自定义节点配置接口
 * @interface CustomNodeDefinition
 * @extends BaseNodeDefinition
 */
export interface CustomNodeDefinition extends BaseNodeDefinition {
  type: NodeKey.CUSTOM_NODE;
  config: {
    /** 配置项1 */
    option1: string;
    /** 配置项2 */
    option2?: number;
  };
}

// 在 SpecificNodeDefinition 联合类型中添加
export type SpecificNodeDefinition =
  | DelayNodeDefinition
  // ... 其他节点
  | CustomNodeDefinition;

// 在 NodeDefinitionMap 映射中添加
export type NodeDefinitionMap = {
  // ... 其他节点
  'custom-node': CustomNodeDefinition;
};

3. 注册节点类型(node-config.ts)

src/automa/types/node-config.ts 中添加节点键、UI配置和表单配置:

typescript
// 1. 在 NodeKey 枚举中添加节点键
export enum NodeKey {
  // ... 其他节点
  CUSTOM_NODE = 'custom-node',  // 与 command-types.ts 中的 type 保持一致
}

// 2. 在 NodeTypesConfig 中添加节点UI配置
export const NodeTypesConfig: NodeTypeI[] = [
  // ... 其他节点
  {
    label: '自定义节点',
    key: NodeKey.CUSTOM_NODE,
    type: NodeTreeType.NODE,
    desc: '自定义节点描述',
    color: '#1890ff',
    icon: 'erpfont icon-xxx',
  },
];

// 3. 定义表单配置
export const customNodeFormConfig: DynamicFormI = {
  showSide: false,
  cols: 1,
  fields: [
    {
      label: '配置项1',
      key: 'option1',
      type: 'text',
      span: 1,
      required: true,
    },
    {
      label: '配置项2',
      key: 'option2',
      type: 'integer',
      span: 1,
    },
  ],
};

4. 创建执行器(executors/)

src/main/engine/executors/ 目录创建执行器类,文件名为 CustomNodeExecutor.ts

typescript
import { BaseNodeExecutor } from '../BaseNodeExecutor';
import type { ExecutionContext, NodeExecutionResult } from '@automa/types/engine-types';
import type { CustomNodeDefinition } from '@automa/types/node-types';
import { NodeKey } from '@automa/types/node-config';

/**
 * 自定义节点执行器
 * @description 实现自定义节点的执行逻辑
 */
export default class CustomNodeExecutor extends BaseNodeExecutor {
  type = NodeKey.CUSTOM_NODE;

  async execute(node: CustomNodeDefinition, context: ExecutionContext): Promise<NodeExecutionResult> {
    const option1 = this.getConfig<string>(node, 'option1', '');
    const option2 = this.getConfig<number>(node, 'option2', 0);

    try {
      // 实现执行逻辑
      // 可以使用 this.desktopNative 访问原生功能
      
      // 解析变量表达式
      const resolvedValue = this.resolveExpression(option1, context);
      
      // 使用延时
      await this.delay(100);
      
      // 返回成功结果
      return this.success({ option1: resolvedValue, option2 });
    } catch (error) {
      const errMsg = error instanceof Error ? error.message : String(error);
      return this.failure(`执行失败: ${errMsg}`);
    }
  }
}

注意:执行器文件名必须以 Executor.ts 结尾,类名必须使用 export default 导出。

5. 注册表单配置(ConfigPanel.vue)

src/renderer/pages/workflow/components/ConfigPanel/ConfigPanel.vue 中注册表单配置:

typescript
// 1. 导入表单配置
import {
  // ... 其他配置
  customNodeFormConfig,
  NodeKey,
} from '@automa/types/node-config';

// 2. 在 currentFormConfig 计算属性的 switch 中添加 case
const currentFormConfig = computed<DynamicFormI>(() => {
  const nodeType = parseNodeType(node.value?.type);

  switch (nodeType) {
    // ... 其他节点
    case NodeKey.CUSTOM_NODE:
      return customNodeFormConfig;
    default:
      return null;
  }
});

完整示例:OpenBrowserWindow 节点

以下是 open-browser-window 节点的完整实现示例,展示了所有步骤的实际代码:

command-types.ts

typescript
{
  key: 'desktop',
  label: '桌面自动化',
  commands: [
    { id: 'open-browser-window', name: '打开窗口', type: 'open-browser-window', icon: 'icon-desktop', desc: '打开一个 BrowserWindow 窗口' },
  ],
},

node-config.ts

typescript
// NodeKey 枚举
export enum NodeKey {
  OPEN_BROWSER_WINDOW = 'open-browser-window',
}

// NodeTypesConfig 配置
{
  label: '打开浏览器窗口',
  key: NodeKey.OPEN_BROWSER_WINDOW,
  type: NodeTreeType.NODE,
  desc: '打开一个 BrowserWindow 窗口',
  color: '#1890ff',
  icon: 'erpfont icon-desktop',
},

// 表单配置
export const openBrowserWindowFormConfig: DynamicFormI = {
  showSide: false,
  cols: 1,
  fields: [
    { label: '选择窗口配置', key: 'workflowId', type: 'custom', component: 'workflow-selector' },
    { label: '窗口名称', key: 'name', type: 'text' },
    { label: '窗口地址', key: 'host', type: 'text' },
    // ... 更多字段
  ],
};

node-types.ts

typescript
export interface OpenBrowserWindowNodeDefinition extends BaseNodeDefinition {
  type: NodeKey.OPEN_BROWSER_WINDOW;
  config: {
    workflowId?: string;
    name?: string;
    host?: string;
    preload?: string;
    preloadCode?: string;
    json?: string;
  };
}

// 添加到 SpecificNodeDefinition
export type SpecificNodeDefinition =
  | ...
  | OpenBrowserWindowNodeDefinition;

// 添加到 NodeDefinitionMap
export type NodeDefinitionMap = {
  'open-browser-window': OpenBrowserWindowNodeDefinition;
};

OpenBrowserWindowExecutor.ts

typescript
export default class OpenBrowserWindowExecutor extends BaseNodeExecutor {
  type = NodeKey.OPEN_BROWSER_WINDOW;

  async execute(node: OpenBrowserWindowNodeDefinition, _context: ExecutionContext): Promise<NodeExecutionResult> {
    // 实现执行逻辑
  }
}

ConfigPanel.vue

typescript
import { openBrowserWindowFormConfig } from '@automa/types/node-config';

// switch 中添加
case NodeKey.OPEN_BROWSER_WINDOW:
  return openBrowserWindowFormConfig;

执行器基类 API

属性

属性类型说明
typeNodeKey节点类型标识
desktopNativeDesktopNative原生功能访问
elementStorageServiceElementStorageService元素存储服务

方法

方法参数返回值说明
executenode, contextPromise<NodeExecutionResult>执行节点
successoutput?, nextNodeId?NodeExecutionResult创建成功结果
failureerrorNodeExecutionResult创建失败结果
getConfig<T>node, key, defaultValue?T获取节点配置值
getVariable<T>context, name, defaultValue?T获取上下文变量
setVariablecontext, name, valuevoid设置上下文变量
resolveExpressionexpression, contextstring解析变量表达式
delaymsPromise<void>延时
getPositionByElementelementPromise<{x, y}>获取元素/图像位置

表单字段类型

类型说明示例
text文本输入名称、路径
textarea多行文本描述
integer整数输入超时时间
number数字输入坐标、速度
select下拉选择按钮类型
switch开关启用/禁用
custom自定义组件资源选择器

自定义组件

typescript
{
  label: '选择资源',
  key: 'element',
  type: 'custom',
  component: 'resource-selector', // 组件名
  span: 1,
}

已注册的自定义组件:

  • color-picker - 颜色选择器
  • resource-selector - 资源选择器(元素/图像)
  • app-selector - 应用选择器
  • workflow-selector - 工作流选择器
  • monaco-editor - 代码编辑器

条件显示

使用 show 函数控制字段显示:

typescript
{
  label: '超时时间',
  key: 'timeout',
  type: 'integer',
  show(modelValue) {
    return modelValue.waitForStart === true;
  },
}

最佳实践

  1. 错误处理:始终使用 try-catch 包装执行逻辑
  2. 类型安全:为配置项定义明确的类型
  3. 变量解析:使用 resolveExpression 支持动态值
  4. 日志记录:使用 createLog 记录执行过程
  5. 资源定位:使用 getPositionByElement 统一元素/图像定位
  6. 命名一致性:确保 command-types.ts 中的 type、NodeKey 枚举值、NodeDefinitionMap 键保持一致

常见问题

节点在面板中不显示

检查 command-types.ts 中是否添加了命令定义。

节点配置面板为空

检查 ConfigPanel.vue 中是否:

  1. 导入了表单配置
  2. 在 switch 语句中添加了对应的 case

节点执行报类型错误

检查 node-types.ts 中是否:

  1. 定义了节点接口
  2. 添加到了 SpecificNodeDefinition 联合类型
  3. 添加到了 NodeDefinitionMap 映射

执行器未被调用

检查执行器文件:

  1. 文件名是否以 Executor.ts 结尾
  2. 类是否使用 export default 导出
  3. type 属性是否与 NodeKey 枚举值一致

企业级桌面 RPA 自动化平台