@ai-setting/roy-agent-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -0
- package/dist/bin/roy.js +127297 -0
- package/dist/roy-agent-darwin-arm64/bin/roy.js +127297 -0
- package/dist/roy-agent-darwin-x64/bin/roy.js +127297 -0
- package/dist/roy-agent-linux-arm64/bin/roy.js +127297 -0
- package/dist/roy-agent-linux-x64/bin/roy.js +127297 -0
- package/dist/roy-agent-windows-x64/bin/roy.js +127297 -0
- package/package.json +91 -0
- package/src/bin/roy.ts +12 -0
- package/src/cli.ts +101 -0
- package/src/commands/act.ts +480 -0
- package/src/commands/commands-add.ts +110 -0
- package/src/commands/commands-dirs.ts +70 -0
- package/src/commands/commands-info.ts +90 -0
- package/src/commands/commands-list.ts +161 -0
- package/src/commands/commands-remove.ts +147 -0
- package/src/commands/commands.ts +55 -0
- package/src/commands/config/config-service.test.ts +449 -0
- package/src/commands/config/config-service.ts +312 -0
- package/src/commands/config/deep-merge.test.ts +168 -0
- package/src/commands/config/deep-merge.ts +63 -0
- package/src/commands/config/export.ts +97 -0
- package/src/commands/config/filter-history-e2e.test.ts +141 -0
- package/src/commands/config/import-preserve-refs.test.ts +212 -0
- package/src/commands/config/import.ts +119 -0
- package/src/commands/config/index.ts +35 -0
- package/src/commands/config/list.ts +281 -0
- package/src/commands/config/roy-config-e2e.test.ts +297 -0
- package/src/commands/config/types.ts +54 -0
- package/src/commands/debug/index.ts +38 -0
- package/src/commands/debug/log.test.ts +233 -0
- package/src/commands/debug/log.ts +123 -0
- package/src/commands/debug/span.test.ts +297 -0
- package/src/commands/debug/span.ts +211 -0
- package/src/commands/debug/trace.test.ts +254 -0
- package/src/commands/debug/trace.ts +140 -0
- package/src/commands/eventsource/add.ts +133 -0
- package/src/commands/eventsource/index.ts +48 -0
- package/src/commands/eventsource/list.ts +194 -0
- package/src/commands/eventsource/remove.ts +95 -0
- package/src/commands/eventsource/start.ts +103 -0
- package/src/commands/eventsource/status.ts +185 -0
- package/src/commands/eventsource/stop.ts +89 -0
- package/src/commands/index.ts +22 -0
- package/src/commands/input-handler.test.ts +76 -0
- package/src/commands/input-handler.ts +43 -0
- package/src/commands/interactive-esc.test.ts +254 -0
- package/src/commands/interactive.shutdown.test.ts +122 -0
- package/src/commands/interactive.test.ts +221 -0
- package/src/commands/interactive.ts +1015 -0
- package/src/commands/lsp/check.ts +92 -0
- package/src/commands/lsp/index.ts +32 -0
- package/src/commands/lsp/install.ts +126 -0
- package/src/commands/lsp/list.ts +64 -0
- package/src/commands/mcp/index.ts +27 -0
- package/src/commands/mcp/list.ts +116 -0
- package/src/commands/mcp/reload.ts +70 -0
- package/src/commands/mcp/tools.ts +121 -0
- package/src/commands/memory/extract-e2e.test.ts +388 -0
- package/src/commands/memory/index.ts +11 -0
- package/src/commands/memory/memory-simplified.test.ts +58 -0
- package/src/commands/memory/memory.ts +25 -0
- package/src/commands/memory/organize.ts +300 -0
- package/src/commands/memory/recall.test.ts +120 -0
- package/src/commands/memory/recall.ts +88 -0
- package/src/commands/memory/record-extract-handle-query.test.ts +385 -0
- package/src/commands/memory/record-prompt-component.test.ts +343 -0
- package/src/commands/memory/record.test.ts +92 -0
- package/src/commands/memory/record.ts +332 -0
- package/src/commands/plugin.test.ts +292 -0
- package/src/commands/plugin.ts +267 -0
- package/src/commands/sessions/active.ts +96 -0
- package/src/commands/sessions/add-message.ts +96 -0
- package/src/commands/sessions/checkpoints.ts +154 -0
- package/src/commands/sessions/compact.test.ts +215 -0
- package/src/commands/sessions/compact.ts +269 -0
- package/src/commands/sessions/delete.ts +236 -0
- package/src/commands/sessions/get.ts +165 -0
- package/src/commands/sessions/grep.ts +233 -0
- package/src/commands/sessions/index.ts +95 -0
- package/src/commands/sessions/list.ts +210 -0
- package/src/commands/sessions/messages.test.ts +333 -0
- package/src/commands/sessions/messages.ts +248 -0
- package/src/commands/sessions/mock.ts +194 -0
- package/src/commands/sessions/new.ts +82 -0
- package/src/commands/sessions/rename.ts +98 -0
- package/src/commands/shared/event-handler.ts +213 -0
- package/src/commands/shared/event-message-formatter.ts +295 -0
- package/src/commands/shared/index.ts +11 -0
- package/src/commands/shared/query-executor.test.ts +434 -0
- package/src/commands/shared/query-executor.ts +324 -0
- package/src/commands/shared/repl-engine.test.ts +354 -0
- package/src/commands/shared/session-manager.test.ts +212 -0
- package/src/commands/shared/session-manager.ts +114 -0
- package/src/commands/skills/get.ts +90 -0
- package/src/commands/skills/index.ts +39 -0
- package/src/commands/skills/list.ts +129 -0
- package/src/commands/skills/reload.ts +59 -0
- package/src/commands/skills/search.ts +132 -0
- package/src/commands/skills/show-config.ts +93 -0
- package/src/commands/tasks/complete.ts +92 -0
- package/src/commands/tasks/create.ts +118 -0
- package/src/commands/tasks/delete.ts +86 -0
- package/src/commands/tasks/get.ts +116 -0
- package/src/commands/tasks/index.ts +53 -0
- package/src/commands/tasks/list.ts +140 -0
- package/src/commands/tasks/operations.ts +120 -0
- package/src/commands/tasks/update.ts +122 -0
- package/src/commands/tools/exec-tool.ts +128 -0
- package/src/commands/tools/get.ts +114 -0
- package/src/commands/tools/index.ts +35 -0
- package/src/commands/tools/list.ts +107 -0
- package/src/commands/tools/shared/index.ts +7 -0
- package/src/commands/tools/shared/schema-helper.ts +111 -0
- package/src/commands/workflow/commands/add.ts +315 -0
- package/src/commands/workflow/commands/get.ts +193 -0
- package/src/commands/workflow/commands/list.ts +137 -0
- package/src/commands/workflow/commands/nodes.ts +528 -0
- package/src/commands/workflow/commands/remove.ts +94 -0
- package/src/commands/workflow/commands/run.ts +398 -0
- package/src/commands/workflow/commands/status.ts +147 -0
- package/src/commands/workflow/commands/stop.ts +91 -0
- package/src/commands/workflow/commands/update.ts +130 -0
- package/src/commands/workflow/commands/validate.ts +139 -0
- package/src/commands/workflow/commands/workflow-cli.test.ts +196 -0
- package/src/commands/workflow/index.ts +65 -0
- package/src/commands/workflow/renderers.ts +358 -0
- package/src/commands/workflow/validators/index.ts +8 -0
- package/src/commands/workflow/validators/node-validator-factory.ts +40 -0
- package/src/commands/workflow/validators/node-validator.ts +125 -0
- package/src/commands/workflow/validators/nodes/agent-node-validator.ts +58 -0
- package/src/commands/workflow/validators/nodes/condition-node-validator.ts +34 -0
- package/src/commands/workflow/validators/nodes/decorator-node-validator.ts +45 -0
- package/src/commands/workflow/validators/nodes/merge-node-validator.ts +46 -0
- package/src/commands/workflow/validators/nodes/skill-node-validator.ts +33 -0
- package/src/commands/workflow/validators/nodes/tool-node-validator.ts +54 -0
- package/src/commands/workflow/validators/nodes/workflow-node-validator.ts +33 -0
- package/src/commands/workflow/validators/types.ts +78 -0
- package/src/commands/workflow/validators/workflow-validator.test.ts +273 -0
- package/src/commands/workflow/validators/workflow-validator.ts +320 -0
- package/src/index.ts +19 -0
- package/src/plugin/apply.ts +103 -0
- package/src/plugin/discover.ts +219 -0
- package/src/plugin/index.ts +45 -0
- package/src/plugin/registry.ts +272 -0
- package/src/plugin/types.ts +165 -0
- package/src/services/context-handler.service.test.ts +501 -0
- package/src/services/context-handler.service.ts +372 -0
- package/src/services/environment.service.commands-prompt.test.ts +167 -0
- package/src/services/environment.service.ts +656 -0
- package/src/services/output.service.test.ts +92 -0
- package/src/services/output.service.ts +122 -0
- package/src/services/quiet-mode.service.test.ts +114 -0
- package/src/services/quiet-mode.service.ts +81 -0
- package/src/services/stream-output.service.test.ts +214 -0
- package/src/services/stream-output.service.ts +323 -0
- package/src/util/which.test.ts +101 -0
- package/src/util/which.ts +55 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Workflow CLI Renderers
|
|
3
|
+
*
|
|
4
|
+
* Output formatters for workflow CLI commands
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import type { Workflow, WorkflowRun } from '@ai-setting/roy-agent-core/env/workflow/types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 计算字符串的视觉宽度(中文字符占2格)
|
|
12
|
+
*/
|
|
13
|
+
function visualWidth(str: string): number {
|
|
14
|
+
let width = 0;
|
|
15
|
+
for (const char of str) {
|
|
16
|
+
if (char.charCodeAt(0) > 255) {
|
|
17
|
+
width += 2;
|
|
18
|
+
} else {
|
|
19
|
+
width += 1;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return width;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 按视觉宽度截断字符串
|
|
27
|
+
*/
|
|
28
|
+
function truncateVisual(str: string, maxWidth: number): string {
|
|
29
|
+
if (!str) return '-';
|
|
30
|
+
let result = '';
|
|
31
|
+
let width = 0;
|
|
32
|
+
for (const char of str) {
|
|
33
|
+
const charWidth = char.charCodeAt(0) > 255 ? 2 : 1;
|
|
34
|
+
if (width + charWidth > maxWidth) break;
|
|
35
|
+
result += char;
|
|
36
|
+
width += charWidth;
|
|
37
|
+
}
|
|
38
|
+
return result || '-';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 格式化时间
|
|
43
|
+
*/
|
|
44
|
+
function formatDate(date: Date | string | undefined): string {
|
|
45
|
+
if (!date) return '-';
|
|
46
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
47
|
+
return d.toLocaleString('zh-CN');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 格式化时长
|
|
52
|
+
*/
|
|
53
|
+
function formatDuration(ms: number | undefined): string {
|
|
54
|
+
if (ms === undefined || ms === null) return '-';
|
|
55
|
+
if (ms < 1000) return `${ms}ms`;
|
|
56
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
57
|
+
if (ms < 3600000) return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`;
|
|
58
|
+
return `${Math.floor(ms / 3600000)}h ${Math.floor((ms % 3600000) / 60000)}m`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 获取状态颜色
|
|
63
|
+
*/
|
|
64
|
+
function statusColor(status: string): typeof chalk.green {
|
|
65
|
+
switch (status) {
|
|
66
|
+
case 'completed':
|
|
67
|
+
return chalk.green;
|
|
68
|
+
case 'running':
|
|
69
|
+
case 'idle':
|
|
70
|
+
return chalk.blue;
|
|
71
|
+
case 'paused':
|
|
72
|
+
return chalk.yellow;
|
|
73
|
+
case 'failed':
|
|
74
|
+
return chalk.red;
|
|
75
|
+
case 'stopped':
|
|
76
|
+
return chalk.gray;
|
|
77
|
+
default:
|
|
78
|
+
return chalk.white;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 渲染 Workflow 列表
|
|
84
|
+
*/
|
|
85
|
+
export function renderWorkflowList(workflows: Workflow[], options?: { json?: boolean }): string {
|
|
86
|
+
if (options?.json) {
|
|
87
|
+
return JSON.stringify({
|
|
88
|
+
workflows: workflows.map(w => ({
|
|
89
|
+
id: w.id,
|
|
90
|
+
name: w.name,
|
|
91
|
+
version: w.version,
|
|
92
|
+
description: w.description,
|
|
93
|
+
tags: w.tags,
|
|
94
|
+
createdAt: w.createdAt.toISOString(),
|
|
95
|
+
updatedAt: w.updatedAt.toISOString(),
|
|
96
|
+
})),
|
|
97
|
+
total: workflows.length,
|
|
98
|
+
}, null, 2);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (workflows.length === 0) {
|
|
102
|
+
return chalk.yellow('No workflows found');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const NAME_WIDTH = 30;
|
|
106
|
+
const VERSION_WIDTH = 10;
|
|
107
|
+
const TAGS_WIDTH = 20;
|
|
108
|
+
const UPDATED_WIDTH = 20;
|
|
109
|
+
const GAP = ' ';
|
|
110
|
+
|
|
111
|
+
const headerLine = [
|
|
112
|
+
chalk.bold('NAME'.padEnd(NAME_WIDTH)),
|
|
113
|
+
chalk.bold('VER'.padEnd(VERSION_WIDTH)),
|
|
114
|
+
chalk.bold('TAGS'.padEnd(TAGS_WIDTH)),
|
|
115
|
+
chalk.bold('UPDATED'),
|
|
116
|
+
].join(GAP);
|
|
117
|
+
|
|
118
|
+
const sepLine = '─'.repeat(NAME_WIDTH + VERSION_WIDTH + TAGS_WIDTH + UPDATED_WIDTH + GAP.length * 3);
|
|
119
|
+
|
|
120
|
+
const rows = workflows.map(w => {
|
|
121
|
+
const name = truncateVisual(w.name, NAME_WIDTH).padEnd(NAME_WIDTH);
|
|
122
|
+
const version = w.version.padEnd(VERSION_WIDTH);
|
|
123
|
+
const tags = truncateVisual(w.tags.join(', '), TAGS_WIDTH).padEnd(TAGS_WIDTH);
|
|
124
|
+
const updated = formatDate(w.updatedAt);
|
|
125
|
+
return `${name}${GAP}${version}${GAP}${tags}${GAP}${updated}`;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return [headerLine, sepLine, ...rows].join('\n');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 渲染 Workflow 详情
|
|
133
|
+
*/
|
|
134
|
+
export function renderWorkflowDetail(workflow: Workflow, options?: { includeRuns?: WorkflowRun[] }): string {
|
|
135
|
+
const lines: string[] = [];
|
|
136
|
+
|
|
137
|
+
lines.push(chalk.bold('\n📋 Workflow Details\n'));
|
|
138
|
+
lines.push(` ${chalk.cyan('ID:')} ${workflow.id}`);
|
|
139
|
+
lines.push(` ${chalk.cyan('Name:')} ${workflow.name}`);
|
|
140
|
+
lines.push(` ${chalk.cyan('Version:')} ${workflow.version}`);
|
|
141
|
+
lines.push(` ${chalk.cyan('Description:')} ${workflow.description || '-'}`);
|
|
142
|
+
lines.push(` ${chalk.cyan('Tags:')} ${workflow.tags.join(', ') || '-'}`);
|
|
143
|
+
lines.push(` ${chalk.cyan('Created:')} ${formatDate(workflow.createdAt)}`);
|
|
144
|
+
lines.push(` ${chalk.cyan('Updated:')} ${formatDate(workflow.updatedAt)}`);
|
|
145
|
+
|
|
146
|
+
// Nodes summary
|
|
147
|
+
const nodeCount = workflow.definition.nodes.length;
|
|
148
|
+
lines.push(chalk.bold('\n📊 Nodes Summary\n'));
|
|
149
|
+
lines.push(` Total: ${nodeCount} nodes`);
|
|
150
|
+
|
|
151
|
+
// Group nodes by type
|
|
152
|
+
const nodesByType: Record<string, number> = {};
|
|
153
|
+
for (const node of workflow.definition.nodes) {
|
|
154
|
+
nodesByType[node.type] = (nodesByType[node.type] || 0) + 1;
|
|
155
|
+
}
|
|
156
|
+
for (const [type, count] of Object.entries(nodesByType)) {
|
|
157
|
+
lines.push(` - ${type}: ${count}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Config summary
|
|
161
|
+
if (workflow.definition.config) {
|
|
162
|
+
lines.push(chalk.bold('\n⚙️ Configuration\n'));
|
|
163
|
+
if (workflow.definition.config.parallel_limit !== undefined) {
|
|
164
|
+
lines.push(` parallel_limit: ${workflow.definition.config.parallel_limit}`);
|
|
165
|
+
}
|
|
166
|
+
if (workflow.definition.config.timeout !== undefined) {
|
|
167
|
+
lines.push(` timeout: ${workflow.definition.config.timeout}ms`);
|
|
168
|
+
}
|
|
169
|
+
if (workflow.definition.config.debug !== undefined) {
|
|
170
|
+
lines.push(` debug: ${workflow.definition.config.debug}`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Runs summary (if included)
|
|
175
|
+
if (options?.includeRuns && options.includeRuns.length > 0) {
|
|
176
|
+
lines.push(chalk.bold('\n📜 Recent Runs\n'));
|
|
177
|
+
lines.push(renderRunsList(options.includeRuns.slice(0, 5)));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return lines.join('\n');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 渲染 Runs 列表
|
|
185
|
+
*/
|
|
186
|
+
export function renderRunsList(runs: WorkflowRun[], options?: { json?: boolean }): string {
|
|
187
|
+
if (options?.json) {
|
|
188
|
+
return JSON.stringify({
|
|
189
|
+
runs: runs.map(r => ({
|
|
190
|
+
id: r.id,
|
|
191
|
+
workflowId: r.workflowId,
|
|
192
|
+
status: r.status,
|
|
193
|
+
startedAt: r.startedAt?.toISOString(),
|
|
194
|
+
completedAt: r.completedAt?.toISOString(),
|
|
195
|
+
durationMs: r.durationMs,
|
|
196
|
+
error: r.error,
|
|
197
|
+
})),
|
|
198
|
+
total: runs.length,
|
|
199
|
+
}, null, 2);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (runs.length === 0) {
|
|
203
|
+
return chalk.yellow('No runs found');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const ID_WIDTH = 20;
|
|
207
|
+
const STATUS_WIDTH = 12;
|
|
208
|
+
const DURATION_WIDTH = 12;
|
|
209
|
+
const UPDATED_WIDTH = 20;
|
|
210
|
+
const GAP = ' ';
|
|
211
|
+
|
|
212
|
+
const headerLine = [
|
|
213
|
+
chalk.bold('RUN ID'.padEnd(ID_WIDTH)),
|
|
214
|
+
chalk.bold('STATUS'.padEnd(STATUS_WIDTH)),
|
|
215
|
+
chalk.bold('DURATION'.padEnd(DURATION_WIDTH)),
|
|
216
|
+
chalk.bold('STARTED'),
|
|
217
|
+
].join(GAP);
|
|
218
|
+
|
|
219
|
+
const sepLine = '─'.repeat(ID_WIDTH + STATUS_WIDTH + DURATION_WIDTH + UPDATED_WIDTH + GAP.length * 3);
|
|
220
|
+
|
|
221
|
+
const rows = runs.map(r => {
|
|
222
|
+
const id = truncateVisual(r.id, ID_WIDTH).padEnd(ID_WIDTH);
|
|
223
|
+
const status = statusColor(r.status)(r.status.padEnd(STATUS_WIDTH));
|
|
224
|
+
const duration = formatDuration(r.durationMs).padEnd(DURATION_WIDTH);
|
|
225
|
+
const started = formatDate(r.startedAt);
|
|
226
|
+
return `${id}${GAP}${status}${GAP}${duration}${GAP}${started}`;
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
return [headerLine, sepLine, ...rows].join('\n');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 渲染 Run 详情
|
|
234
|
+
*/
|
|
235
|
+
export function renderRunDetail(run: WorkflowRun): string {
|
|
236
|
+
const lines: string[] = [];
|
|
237
|
+
|
|
238
|
+
lines.push(chalk.bold('\n🏃 Run Details\n'));
|
|
239
|
+
lines.push(` ${chalk.cyan('Run ID:')} ${run.id}`);
|
|
240
|
+
lines.push(` ${chalk.cyan('Workflow:')} ${run.workflowId}`);
|
|
241
|
+
lines.push(` ${chalk.cyan('Status:')} ${statusColor(run.status)(run.status)}`);
|
|
242
|
+
lines.push(` ${chalk.cyan('Started:')} ${formatDate(run.startedAt)}`);
|
|
243
|
+
|
|
244
|
+
if (run.pausedAt) {
|
|
245
|
+
lines.push(` ${chalk.cyan('Paused:')} ${formatDate(run.pausedAt)}`);
|
|
246
|
+
}
|
|
247
|
+
if (run.resumedAt) {
|
|
248
|
+
lines.push(` ${chalk.cyan('Resumed:')} ${formatDate(run.resumedAt)}`);
|
|
249
|
+
}
|
|
250
|
+
if (run.stoppedAt) {
|
|
251
|
+
lines.push(` ${chalk.cyan('Stopped:')} ${formatDate(run.stoppedAt)}`);
|
|
252
|
+
}
|
|
253
|
+
if (run.completedAt) {
|
|
254
|
+
lines.push(` ${chalk.cyan('Completed:')} ${formatDate(run.completedAt)}`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
lines.push(` ${chalk.cyan('Duration:')} ${formatDuration(run.durationMs)}`);
|
|
258
|
+
|
|
259
|
+
if (run.error) {
|
|
260
|
+
lines.push(chalk.bold('\n❌ Error\n'));
|
|
261
|
+
lines.push(` ${chalk.red(run.error)}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (run.output) {
|
|
265
|
+
lines.push(chalk.bold('\n📤 Output\n'));
|
|
266
|
+
lines.push(' ' + JSON.stringify(run.output, null, 2).split('\n').join('\n '));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return lines.join('\n');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* 渲染工作流已添加成功
|
|
274
|
+
*/
|
|
275
|
+
export function renderWorkflowAdded(workflow: Workflow): string {
|
|
276
|
+
return chalk.green(`\n✅ Workflow '${workflow.name}' added successfully\n`) +
|
|
277
|
+
` ID: ${workflow.id}\n` +
|
|
278
|
+
` Version: ${workflow.version}\n`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 渲染工作流已更新成功
|
|
283
|
+
*/
|
|
284
|
+
export function renderWorkflowUpdated(workflow: Workflow): string {
|
|
285
|
+
return chalk.green(`\n✅ Workflow '${workflow.name}' updated successfully\n`) +
|
|
286
|
+
` ID: ${workflow.id}\n`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* 渲染工作流已删除成功
|
|
291
|
+
*/
|
|
292
|
+
export function renderWorkflowDeleted(name: string): string {
|
|
293
|
+
return chalk.green(`\n✅ Workflow '${name}' deleted successfully\n`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* 渲染工作流运行结果
|
|
298
|
+
*/
|
|
299
|
+
export function renderRunResult(result: { runId: string; status: string; output?: any; error?: string; durationMs?: number }): string {
|
|
300
|
+
const lines: string[] = [];
|
|
301
|
+
|
|
302
|
+
if (result.status === 'completed') {
|
|
303
|
+
lines.push(chalk.green('\n✅ Workflow completed successfully'));
|
|
304
|
+
} else if (result.status === 'failed') {
|
|
305
|
+
lines.push(chalk.red('\n❌ Workflow failed'));
|
|
306
|
+
} else if (result.status === 'stopped') {
|
|
307
|
+
lines.push(chalk.yellow('\n⚠️ Workflow stopped'));
|
|
308
|
+
} else {
|
|
309
|
+
lines.push(chalk.blue(`\n🔄 Workflow ${result.status}`));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
lines.push(` Run ID: ${result.runId}`);
|
|
313
|
+
|
|
314
|
+
if (result.durationMs) {
|
|
315
|
+
lines.push(` Duration: ${formatDuration(result.durationMs)}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (result.error) {
|
|
319
|
+
lines.push(chalk.red(`\n Error: ${result.error}`));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (result.output) {
|
|
323
|
+
lines.push(chalk.bold('\n📤 Output:'));
|
|
324
|
+
lines.push(JSON.stringify(result.output, null, 2));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return lines.join('\n');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* 渲染节点列表
|
|
332
|
+
*/
|
|
333
|
+
export function renderNodesList(nodes: Array<{ id?: string; type?: string; name?: string; depends_on?: string[] }>): string {
|
|
334
|
+
const ID_WIDTH = 25;
|
|
335
|
+
const TYPE_WIDTH = 12;
|
|
336
|
+
const NAME_WIDTH = 20;
|
|
337
|
+
const DEPS_WIDTH = 20;
|
|
338
|
+
const GAP = ' ';
|
|
339
|
+
|
|
340
|
+
const headerLine = [
|
|
341
|
+
chalk.bold('NODE ID'.padEnd(ID_WIDTH)),
|
|
342
|
+
chalk.bold('TYPE'.padEnd(TYPE_WIDTH)),
|
|
343
|
+
chalk.bold('NAME'.padEnd(NAME_WIDTH)),
|
|
344
|
+
chalk.bold('DEPENDS ON'),
|
|
345
|
+
].join(GAP);
|
|
346
|
+
|
|
347
|
+
const sepLine = '─'.repeat(ID_WIDTH + TYPE_WIDTH + NAME_WIDTH + DEPS_WIDTH + GAP.length * 3);
|
|
348
|
+
|
|
349
|
+
const rows = nodes.map(n => {
|
|
350
|
+
const id = truncateVisual(n.id || '-', ID_WIDTH).padEnd(ID_WIDTH);
|
|
351
|
+
const type = (n.type || '-').padEnd(TYPE_WIDTH);
|
|
352
|
+
const name = truncateVisual(n.name || '-', NAME_WIDTH).padEnd(NAME_WIDTH);
|
|
353
|
+
const deps = n.depends_on?.join(', ') || '-';
|
|
354
|
+
return `${id}${GAP}${type}${GAP}${name}${GAP}${deps}`;
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
return [headerLine, sepLine, ...rows].join('\n');
|
|
358
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Node validator factory
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { NodeValidator } from './node-validator';
|
|
6
|
+
import { ToolNodeValidator } from './nodes/tool-node-validator';
|
|
7
|
+
import { SkillNodeValidator } from './nodes/skill-node-validator';
|
|
8
|
+
import { AgentNodeValidator } from './nodes/agent-node-validator';
|
|
9
|
+
import { WorkflowNodeValidator } from './nodes/workflow-node-validator';
|
|
10
|
+
import { ConditionNodeValidator } from './nodes/condition-node-validator';
|
|
11
|
+
import { MergeNodeValidator } from './nodes/merge-node-validator';
|
|
12
|
+
import { DecoratorNodeValidator } from './nodes/decorator-node-validator';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Registry of node validators
|
|
16
|
+
*/
|
|
17
|
+
const validators: Map<string, NodeValidator> = new Map();
|
|
18
|
+
|
|
19
|
+
// Register built-in validators
|
|
20
|
+
validators.set('tool', new ToolNodeValidator());
|
|
21
|
+
validators.set('skill', new SkillNodeValidator());
|
|
22
|
+
validators.set('agent', new AgentNodeValidator());
|
|
23
|
+
validators.set('workflow', new WorkflowNodeValidator());
|
|
24
|
+
validators.set('condition', new ConditionNodeValidator());
|
|
25
|
+
validators.set('merge', new MergeNodeValidator());
|
|
26
|
+
validators.set('decorator', new DecoratorNodeValidator());
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get validator for a node type
|
|
30
|
+
*/
|
|
31
|
+
export function getNodeValidator(type: string): NodeValidator | null {
|
|
32
|
+
return validators.get(type) || null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get all supported node types
|
|
37
|
+
*/
|
|
38
|
+
export function getSupportedNodeTypes(): string[] {
|
|
39
|
+
return Array.from(validators.keys());
|
|
40
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Node validator base class and interface
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ValidationErrorType, type ValidationError, type RawNodeDefinition } from './types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Base interface for node validators
|
|
9
|
+
*/
|
|
10
|
+
export interface NodeValidator {
|
|
11
|
+
/** Node type this validator handles */
|
|
12
|
+
readonly nodeType: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Validate a node of this type
|
|
16
|
+
* @param node Raw node definition from YAML
|
|
17
|
+
* @returns List of validation errors (empty if valid)
|
|
18
|
+
*/
|
|
19
|
+
validate(node: RawNodeDefinition): ValidationError[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Base class for node validators with common utilities
|
|
24
|
+
*/
|
|
25
|
+
export abstract class BaseNodeValidator implements NodeValidator {
|
|
26
|
+
abstract readonly nodeType: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get required field names for this node type
|
|
30
|
+
*/
|
|
31
|
+
protected abstract getRequiredFields(): string[];
|
|
32
|
+
|
|
33
|
+
validate(node: RawNodeDefinition): ValidationError[] {
|
|
34
|
+
const errors: ValidationError[] = [];
|
|
35
|
+
|
|
36
|
+
// Check node ID
|
|
37
|
+
if (!node.id || typeof node.id !== 'string' || !node.id.trim()) {
|
|
38
|
+
errors.push({
|
|
39
|
+
type: ValidationErrorType.MISSING_REQUIRED_FIELD,
|
|
40
|
+
description: 'Node ID is missing or empty',
|
|
41
|
+
expected: 'id must be a non-empty string',
|
|
42
|
+
actual: String(node.id),
|
|
43
|
+
fix: 'Add an "id" field to the node, e.g., id: "node-1"',
|
|
44
|
+
nodeId: undefined,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check required fields
|
|
49
|
+
for (const field of this.getRequiredFields()) {
|
|
50
|
+
const value = node.config?.[field];
|
|
51
|
+
if (value === undefined || value === null || value === '') {
|
|
52
|
+
errors.push({
|
|
53
|
+
type: ValidationErrorType.MISSING_REQUIRED_FIELD,
|
|
54
|
+
description: `Required field "${field}" is missing`,
|
|
55
|
+
expected: `${field} must be a non-empty value`,
|
|
56
|
+
actual: String(value),
|
|
57
|
+
fix: `Add "${field}: <value>" to node config`,
|
|
58
|
+
nodeId: node.id,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Add type-specific validation
|
|
64
|
+
errors.push(...this.validateTypeSpecific(node));
|
|
65
|
+
|
|
66
|
+
return errors;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Override in subclasses for type-specific validation
|
|
71
|
+
*/
|
|
72
|
+
protected validateTypeSpecific(_node: RawNodeDefinition): ValidationError[] {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Helper: Check field type
|
|
78
|
+
*/
|
|
79
|
+
protected checkFieldType(
|
|
80
|
+
node: RawNodeDefinition,
|
|
81
|
+
field: string,
|
|
82
|
+
expectedType: string,
|
|
83
|
+
expectedFormat?: string
|
|
84
|
+
): ValidationError | null {
|
|
85
|
+
const value = node.config?.[field];
|
|
86
|
+
if (value === undefined || value === null) return null;
|
|
87
|
+
|
|
88
|
+
let isValid = false;
|
|
89
|
+
switch (expectedType) {
|
|
90
|
+
case 'string':
|
|
91
|
+
isValid = typeof value === 'string';
|
|
92
|
+
break;
|
|
93
|
+
case 'number':
|
|
94
|
+
isValid = typeof value === 'number' && value > 0;
|
|
95
|
+
break;
|
|
96
|
+
case 'boolean':
|
|
97
|
+
isValid = typeof value === 'boolean';
|
|
98
|
+
break;
|
|
99
|
+
case 'object':
|
|
100
|
+
isValid = typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
101
|
+
break;
|
|
102
|
+
case 'array':
|
|
103
|
+
isValid = Array.isArray(value);
|
|
104
|
+
break;
|
|
105
|
+
case 'string|boolean':
|
|
106
|
+
isValid = typeof value === 'string' || typeof value === 'boolean';
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!isValid) {
|
|
111
|
+
return {
|
|
112
|
+
type: ValidationErrorType.INVALID_FIELD_TYPE,
|
|
113
|
+
description: `Field "${field}" has invalid type`,
|
|
114
|
+
expected: expectedFormat || `${expectedType}`,
|
|
115
|
+
actual: typeof value === 'object' ? JSON.stringify(value) : String(value),
|
|
116
|
+
fix: expectedFormat?.includes('non-empty') && typeof value === 'string' && !value.trim()
|
|
117
|
+
? `Remove quotes or whitespace from ${field} value`
|
|
118
|
+
: `Check ${field} type: expected ${expectedFormat || expectedType}`,
|
|
119
|
+
nodeId: node.id,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Agent node validator
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { BaseNodeValidator } from '../node-validator';
|
|
6
|
+
import { ValidationErrorType } from '../types';
|
|
7
|
+
import type { RawNodeDefinition, ValidationError } from '../types';
|
|
8
|
+
|
|
9
|
+
export class AgentNodeValidator extends BaseNodeValidator {
|
|
10
|
+
readonly nodeType = 'agent';
|
|
11
|
+
|
|
12
|
+
protected getRequiredFields(): string[] {
|
|
13
|
+
return ['agent_type', 'prompt'];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
protected validateTypeSpecific(node: RawNodeDefinition): ValidationError[] {
|
|
17
|
+
const errors: ValidationError[] = [];
|
|
18
|
+
|
|
19
|
+
const agentType = node.config?.agent_type;
|
|
20
|
+
if (agentType !== undefined && typeof agentType !== 'string') {
|
|
21
|
+
errors.push({
|
|
22
|
+
type: ValidationErrorType.INVALID_FIELD_TYPE,
|
|
23
|
+
description: 'Field "agent_type" has invalid type',
|
|
24
|
+
expected: 'agent_type must be a string (e.g., "general", "research")',
|
|
25
|
+
actual: `typeof agent_type is "${typeof agentType}"`,
|
|
26
|
+
fix: `Change agent_type value to a string: agent_type: "${String(agentType)}"`,
|
|
27
|
+
nodeId: node.id,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const prompt = node.config?.prompt;
|
|
32
|
+
if (prompt !== undefined && typeof prompt !== 'string') {
|
|
33
|
+
errors.push({
|
|
34
|
+
type: ValidationErrorType.INVALID_FIELD_TYPE,
|
|
35
|
+
description: 'Field "prompt" has invalid type',
|
|
36
|
+
expected: 'prompt must be a string',
|
|
37
|
+
actual: `typeof prompt is "${typeof prompt}"`,
|
|
38
|
+
fix: `Change prompt value to a string`,
|
|
39
|
+
nodeId: node.id,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Check options if present
|
|
44
|
+
const options = node.config?.options;
|
|
45
|
+
if (options !== undefined && (typeof options !== 'object' || options === null || Array.isArray(options))) {
|
|
46
|
+
errors.push({
|
|
47
|
+
type: ValidationErrorType.INVALID_FIELD_TYPE,
|
|
48
|
+
description: 'Field "options" has invalid type',
|
|
49
|
+
expected: 'options must be an object',
|
|
50
|
+
actual: Array.isArray(options) ? 'array' : typeof options,
|
|
51
|
+
fix: 'Change options to an object: options: { timeout: 60000 }',
|
|
52
|
+
nodeId: node.id,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return errors;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Condition node validator
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { BaseNodeValidator } from '../node-validator';
|
|
6
|
+
import { ValidationErrorType } from '../types';
|
|
7
|
+
import type { RawNodeDefinition, ValidationError } from '../types';
|
|
8
|
+
|
|
9
|
+
export class ConditionNodeValidator extends BaseNodeValidator {
|
|
10
|
+
readonly nodeType = 'condition';
|
|
11
|
+
|
|
12
|
+
protected getRequiredFields(): string[] {
|
|
13
|
+
// No required fields for condition node
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
protected validateTypeSpecific(node: RawNodeDefinition): ValidationError[] {
|
|
18
|
+
const errors: ValidationError[] = [];
|
|
19
|
+
|
|
20
|
+
const condition = node.config?.condition;
|
|
21
|
+
if (condition !== undefined && typeof condition !== 'string' && typeof condition !== 'boolean') {
|
|
22
|
+
errors.push({
|
|
23
|
+
type: ValidationErrorType.INVALID_FIELD_TYPE,
|
|
24
|
+
description: 'Field "condition" has invalid type',
|
|
25
|
+
expected: 'condition must be a string or boolean',
|
|
26
|
+
actual: typeof condition,
|
|
27
|
+
fix: 'Change condition to a string (template) or boolean value',
|
|
28
|
+
nodeId: node.id,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return errors;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Decorator node validator
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { BaseNodeValidator } from '../node-validator';
|
|
6
|
+
import { ValidationErrorType } from '../types';
|
|
7
|
+
import type { RawNodeDefinition, ValidationError } from '../types';
|
|
8
|
+
|
|
9
|
+
export class DecoratorNodeValidator extends BaseNodeValidator {
|
|
10
|
+
readonly nodeType = 'decorator';
|
|
11
|
+
|
|
12
|
+
protected getRequiredFields(): string[] {
|
|
13
|
+
return ['_methodName', '_instance'];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
protected validateTypeSpecific(node: RawNodeDefinition): ValidationError[] {
|
|
17
|
+
const errors: ValidationError[] = [];
|
|
18
|
+
|
|
19
|
+
const methodName = node.config?._methodName;
|
|
20
|
+
if (methodName !== undefined && typeof methodName !== 'string') {
|
|
21
|
+
errors.push({
|
|
22
|
+
type: ValidationErrorType.INVALID_FIELD_TYPE,
|
|
23
|
+
description: 'Field "_methodName" has invalid type',
|
|
24
|
+
expected: '_methodName must be a string',
|
|
25
|
+
actual: typeof methodName,
|
|
26
|
+
fix: `Change _methodName value to a string: _methodName: "${String(methodName)}"`,
|
|
27
|
+
nodeId: node.id,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const instance = node.config?._instance;
|
|
32
|
+
if (instance !== undefined && typeof instance !== 'string') {
|
|
33
|
+
errors.push({
|
|
34
|
+
type: ValidationErrorType.INVALID_FIELD_TYPE,
|
|
35
|
+
description: 'Field "_instance" has invalid type',
|
|
36
|
+
expected: '_instance must be a string (template reference)',
|
|
37
|
+
actual: typeof instance,
|
|
38
|
+
fix: 'Change _instance to a template string: _instance: "{{input.instance}}"',
|
|
39
|
+
nodeId: node.id,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return errors;
|
|
44
|
+
}
|
|
45
|
+
}
|