import type {WorkflowCatalog} from '../../types/bot/workflow/metadata.ts'; import {node_registry, query_registry, start_registry} from './registry.ts'; import {event_registry} from './node_action/event_node/registry.ts'; import {describe, edge_input_output, result_output} from './metadata.ts'; import {node_fields, policy_fields} from './common_template.ts'; import {assert_json} from './runtime/value.ts'; import {validate_fields, validate_i18n, validate_output} from './runtime/template.ts'; export function validate_workflow_catalog(catalog: WorkflowCatalog): void { assert_json(catalog); if (catalog.catalog_version !== 1) throw new Error('Invalid workflow catalog version'); for (const group of [catalog.nodes, catalog.start_nodes, catalog.events, catalog.queries]) { const names = new Set(); for (const item of group) { if (!/^[a-zA-Z0-9_-]{1,100}$/.test(item.name) || names.has(item.name) || !/^\d+\.\d+\.\d+$/.test(item.version)) { throw new Error('Invalid or duplicate workflow catalog entry'); } names.add(item.name); validate_i18n(item.i18n); validate_fields(item.config); validate_output(item.output); } } for (const node of catalog.nodes) { const ports = new Set(); for (const port of node.ports) { if (!/^[a-zA-Z0-9_-]+$/.test(port.name) || ports.has(port.name)) throw new Error('Invalid workflow port'); ports.add(port.name); validate_i18n(port.i18n); } if (!ports.has('failure')) throw new Error('Workflow node needs failure port'); } validate_fields(catalog.common.node_fields); validate_fields(catalog.common.policy_fields); validate_output(catalog.common.result); validate_output(catalog.common.edge_input); for (const reference of catalog.common.references) validate_i18n(reference.i18n); } /** Pure catalog inspection: no Bot, actor, event subscriptions or user code execution. */ export function get_workflow_catalog(options: {allow_code?: boolean} = {}): WorkflowCatalog { const catalog: WorkflowCatalog = structuredClone({ catalog_version: 1, nodes: Object.values(node_registry).map(module => module.config_template), start_nodes: Object.values(start_registry).map(module => module.config_template), events: Object.values(event_registry).map(module => module.config_template), queries: Object.values(query_registry).map(module => module.config_template), common: { node_fields, policy_fields, result: result_output, edge_input: edge_input_output, references: [ {root: 'trigger', i18n: describe('触发参数', '本次运行的事件、手动输入或定时触发数据。')}, {root: 'vars', i18n: describe('运行变量', '当前分支变量,合并后包含无冲突的分支更新。')}, {root: 'outputs', i18n: describe('上游节点结果', '沿已执行连线传入的节点结果;条件分支和 any 合并不保证所有祖先都存在。')}, {root: 'last_result', i18n: describe('最近结果', '当前路径最近一个节点的执行结果和节点 ID。')}, {root: 'inputs', i18n: describe('当前入线', '以连线 ID 为键的直接上游结果;all 合并包含全部匹配入线。')}, ], }, }); const code = catalog.nodes.find(node => node.name === 'code'); if (code) code.availability = options.allow_code ? {available: true} : {available: false, reason: 'CODE_EXECUTION_DISABLED'}; validate_workflow_catalog(catalog); return catalog; }