@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,398 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Workflow Run Command
|
|
3
|
+
*
|
|
4
|
+
* Run a workflow from file or by name
|
|
5
|
+
*
|
|
6
|
+
* Workflow execution model:
|
|
7
|
+
* - run starts the workflow
|
|
8
|
+
* - if ask_user is triggered, workflow stops with status='paused'
|
|
9
|
+
* - resume with: workflow run -s <sessionId> --input "response"
|
|
10
|
+
*
|
|
11
|
+
* OpenTelemetry W3C TraceContext Support:
|
|
12
|
+
* - Supports TRACEPARENT environment variable for trace context propagation
|
|
13
|
+
* - When running via bash tool from interactive mode, TRACEPARENT is passed
|
|
14
|
+
* - The traceId and parentSpanId are extracted from TRACEPARENT
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { CommandModule } from 'yargs';
|
|
18
|
+
import { EnvironmentService } from '../../../services/environment.service';
|
|
19
|
+
import { OutputService } from '../../../services/output.service';
|
|
20
|
+
import { renderRunResult } from '../renderers';
|
|
21
|
+
import chalk from 'chalk';
|
|
22
|
+
import fs from 'fs';
|
|
23
|
+
import path from 'path';
|
|
24
|
+
import YAML from 'yaml';
|
|
25
|
+
import type { WorkflowService } from '@ai-setting/roy-agent-core/env/workflow/service';
|
|
26
|
+
import type { WorkflowDefinition } from '@ai-setting/roy-agent-core/env/workflow/types';
|
|
27
|
+
import { getTracerProvider, propagation, wrapFunction } from '@ai-setting/roy-agent-core';
|
|
28
|
+
|
|
29
|
+
export interface WorkflowRunOptions {
|
|
30
|
+
identifier: string;
|
|
31
|
+
debug?: boolean;
|
|
32
|
+
input?: string;
|
|
33
|
+
parallelLimit?: number;
|
|
34
|
+
timeout?: number;
|
|
35
|
+
watch?: boolean;
|
|
36
|
+
file?: boolean;
|
|
37
|
+
name?: string;
|
|
38
|
+
registerOnly?: boolean;
|
|
39
|
+
config?: string;
|
|
40
|
+
/** Resume from existing session */
|
|
41
|
+
sessionId?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* WorkflowRunCommand - Run a workflow
|
|
46
|
+
*
|
|
47
|
+
* Supports two modes:
|
|
48
|
+
* 1. Run by name: `roy workflow run my-workflow`
|
|
49
|
+
* 2. Run from file: `roy workflow run workflow.yaml --file`
|
|
50
|
+
* - Optionally register with custom name: `roy workflow run workflow.yaml --file --name my-workflow`
|
|
51
|
+
* - Register only without running: `roy workflow run workflow.yaml --file --register-only`
|
|
52
|
+
*
|
|
53
|
+
* Resume paused workflow:
|
|
54
|
+
* `roy workflow run -s <sessionId> --input "user response"`
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Run workflow implementation
|
|
59
|
+
*/
|
|
60
|
+
const runWorkflow = wrapFunction(
|
|
61
|
+
async function runWorkflowImpl(args: WorkflowRunOptions): Promise<void> {
|
|
62
|
+
const output = new OutputService();
|
|
63
|
+
const envService = new EnvironmentService(output);
|
|
64
|
+
|
|
65
|
+
// ========================================================================
|
|
66
|
+
// Initialize OpenTelemetry Trace Context from Environment Variable
|
|
67
|
+
//
|
|
68
|
+
// When running via bash tool from interactive mode, the TRACEPARENT is passed
|
|
69
|
+
// as environment variable. We initialize the OTel tracer here so that spans
|
|
70
|
+
// in workflow execution are associated with the parent trace.
|
|
71
|
+
// ========================================================================
|
|
72
|
+
|
|
73
|
+
// 优先使用 W3C TraceContext (TRACEPARENT)
|
|
74
|
+
const traceparent = process.env.TRACEPARENT;
|
|
75
|
+
let traceId: string | undefined;
|
|
76
|
+
let parentSpanId: string | undefined;
|
|
77
|
+
|
|
78
|
+
if (traceparent) {
|
|
79
|
+
const extracted = propagation.extract(process.env as Record<string, string | undefined>);
|
|
80
|
+
if (extracted) {
|
|
81
|
+
traceId = extracted.traceId;
|
|
82
|
+
parentSpanId = extracted.spanId; // 发送方 span ID 作为新 span 的 parentSpanId
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 回退到旧的 TRACE_ID 环境变量(向后兼容)
|
|
87
|
+
if (!traceId) {
|
|
88
|
+
traceId = process.env.TRACE_ID;
|
|
89
|
+
parentSpanId = process.env.PARENT_SPAN_ID;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
output.info(`[OTel Trace] TRACEPARENT=${traceparent || 'none'}, traceId=${traceId}, parentSpanId=${parentSpanId}`);
|
|
93
|
+
|
|
94
|
+
// 初始化 OTel tracer 并创建 span
|
|
95
|
+
let workflowSpan: any = undefined;
|
|
96
|
+
try {
|
|
97
|
+
const provider = getTracerProvider();
|
|
98
|
+
await provider.initialize();
|
|
99
|
+
const tracer = provider.getTracer('roy-tracer');
|
|
100
|
+
|
|
101
|
+
// 获取恢复后的上下文(用于调试)
|
|
102
|
+
const restoredContext = tracer.getCurrentContext();
|
|
103
|
+
output.info(`[OTel Trace] Restored context: ${JSON.stringify(restoredContext)}`);
|
|
104
|
+
|
|
105
|
+
// 从环境变量恢复上下文后创建 span
|
|
106
|
+
// 注意:调用 getTracer 时会触发 restoreFromEnv,但不会自动创建 span
|
|
107
|
+
// 需要手动调用 startSpan 来创建新的 span
|
|
108
|
+
workflowSpan = tracer.startSpan("workflow.run", {
|
|
109
|
+
attributes: {
|
|
110
|
+
'workflow.name': args.identifier,
|
|
111
|
+
'workflow.file': args.file || false,
|
|
112
|
+
'source': 'workflow-cli',
|
|
113
|
+
'workflow.parentSpanId': parentSpanId || 'none',
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
output.info(`[OTel Trace] Started span: ${workflowSpan.spanContext.spanId}, traceId: ${workflowSpan.spanContext.traceId}, parentSpanId: ${workflowSpan.spanContext.parentSpanId || 'root'}`);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
output.warn(`[OTel Trace] Failed to initialize tracer: ${error}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
await envService.create({ configPath: args.config });
|
|
124
|
+
const env = envService.getEnvironment();
|
|
125
|
+
if (!env) {
|
|
126
|
+
output.error("Failed to create environment");
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
const workflowComponent = env.getComponent('workflow');
|
|
130
|
+
|
|
131
|
+
if (!workflowComponent) {
|
|
132
|
+
output.error('WorkflowComponent not available');
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 获取 WorkflowService
|
|
137
|
+
const service = (workflowComponent as any).workflowService as WorkflowService;
|
|
138
|
+
|
|
139
|
+
if (!service) {
|
|
140
|
+
output.error('WorkflowService not initialized. Please run "roy workflow init" first.');
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Parse input
|
|
145
|
+
let input: Record<string, any> = {};
|
|
146
|
+
if (args.input) {
|
|
147
|
+
try {
|
|
148
|
+
input = JSON.parse(args.input);
|
|
149
|
+
} catch {
|
|
150
|
+
output.error('Invalid JSON in --input option');
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Prepare run options
|
|
156
|
+
const runOptions: any = {
|
|
157
|
+
debug: args.debug,
|
|
158
|
+
parallelLimit: args.parallelLimit,
|
|
159
|
+
timeout: args.timeout,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Handle session resume (-s sessionId)
|
|
163
|
+
if (args.sessionId) {
|
|
164
|
+
// Resume from existing session using unified run()
|
|
165
|
+
// Session ID format is workflow_{runId}, but user provides runId
|
|
166
|
+
const sessionId = args.sessionId.startsWith('workflow_')
|
|
167
|
+
? args.sessionId
|
|
168
|
+
: `workflow_${args.sessionId}`;
|
|
169
|
+
|
|
170
|
+
output.log(chalk.blue(`🔄 Resuming workflow from session: ${sessionId}`));
|
|
171
|
+
|
|
172
|
+
// Get session to verify it's a workflow session
|
|
173
|
+
const sessionComponent = (workflowComponent as any).sessionComponent;
|
|
174
|
+
if (!sessionComponent) {
|
|
175
|
+
output.error('SessionComponent not available');
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const session = await sessionComponent.get(sessionId);
|
|
180
|
+
if (!session) {
|
|
181
|
+
output.error(`Session not found: ${sessionId}`);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const metadata = session.metadata;
|
|
186
|
+
if (metadata?.type !== 'workflow') {
|
|
187
|
+
output.error(`Session is not a workflow session: ${sessionId}`);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Get workflow from session metadata to create engine
|
|
192
|
+
const workflowName = metadata.workflowName;
|
|
193
|
+
if (!workflowName) {
|
|
194
|
+
output.error('Workflow name not found in session metadata');
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const workflow = service.getWorkflowByName(workflowName);
|
|
199
|
+
if (!workflow) {
|
|
200
|
+
output.error(`Workflow not found: ${workflowName}`);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Create engine and use unified run()
|
|
205
|
+
// The engine's run(sessionId) will detect session exists and infer resume point
|
|
206
|
+
// input is used as userResponse when resuming from ask_user interrupt
|
|
207
|
+
const engine = (service as any).engineFactory(workflow);
|
|
208
|
+
const result = await engine.run(sessionId, {
|
|
209
|
+
input: args.input || undefined,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
output.log(renderRunResult({
|
|
213
|
+
runId: sessionId,
|
|
214
|
+
status: result.status,
|
|
215
|
+
output: result.output,
|
|
216
|
+
error: result.error,
|
|
217
|
+
durationMs: result.durationMs,
|
|
218
|
+
}));
|
|
219
|
+
|
|
220
|
+
// 结束 OTel span
|
|
221
|
+
if (workflowSpan) {
|
|
222
|
+
workflowSpan.end();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
await envService.dispose();
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Determine if identifier is a file or workflow name
|
|
230
|
+
const isFile = args.file ||
|
|
231
|
+
args.identifier.endsWith('.yaml') ||
|
|
232
|
+
args.identifier.endsWith('.yml') ||
|
|
233
|
+
args.identifier.endsWith('.json');
|
|
234
|
+
|
|
235
|
+
if (isFile) {
|
|
236
|
+
// Run from file - first register, then run
|
|
237
|
+
const filePath = path.resolve(args.identifier);
|
|
238
|
+
if (!fs.existsSync(filePath)) {
|
|
239
|
+
output.error(`File not found: ${filePath}`);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
244
|
+
|
|
245
|
+
// 解析 YAML 或 JSON 文件
|
|
246
|
+
let definition: WorkflowDefinition;
|
|
247
|
+
if (filePath.endsWith('.json')) {
|
|
248
|
+
definition = JSON.parse(content);
|
|
249
|
+
} else {
|
|
250
|
+
definition = YAML.parse(content);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 如果指定了 --name,覆盖定义中的名称
|
|
254
|
+
const registrationName = args.name || definition.name;
|
|
255
|
+
if (args.name) {
|
|
256
|
+
definition = { ...definition, name: args.name };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// 注册 workflow
|
|
260
|
+
const workflow = await service.createWorkflow(definition, { force: true });
|
|
261
|
+
output.log(chalk.blue(`📝 Registering workflow: ${registrationName}`));
|
|
262
|
+
output.log(chalk.green(`✅ Workflow registered: ${workflow.name} (${workflow.id})`));
|
|
263
|
+
|
|
264
|
+
// 如果是 --register-only 模式,只注册不运行
|
|
265
|
+
if (args.registerOnly) {
|
|
266
|
+
output.log(chalk.gray('ℹ️ Use --register-only, skipping execution'));
|
|
267
|
+
// 结束 OTel span
|
|
268
|
+
if (workflowSpan) {
|
|
269
|
+
workflowSpan.end();
|
|
270
|
+
}
|
|
271
|
+
await envService.dispose();
|
|
272
|
+
process.exit(0);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// 运行 workflow
|
|
276
|
+
output.log(chalk.blue(`🚀 Running workflow: ${workflow.name}`));
|
|
277
|
+
const result = await service.runWorkflow(definition, input, runOptions);
|
|
278
|
+
|
|
279
|
+
// Output result
|
|
280
|
+
output.log(renderRunResult({
|
|
281
|
+
runId: result.runId,
|
|
282
|
+
status: result.status,
|
|
283
|
+
output: result.output,
|
|
284
|
+
error: result.error,
|
|
285
|
+
durationMs: result.durationMs,
|
|
286
|
+
}));
|
|
287
|
+
|
|
288
|
+
// If paused, show resume hint
|
|
289
|
+
if (result.status === 'paused') {
|
|
290
|
+
output.log(chalk.gray(`\n💡 Use "roy workflow run -s ${result.runId}" to resume`));
|
|
291
|
+
}
|
|
292
|
+
} else {
|
|
293
|
+
// Run by name
|
|
294
|
+
output.log(chalk.blue(`🚀 Running workflow: ${args.identifier}`));
|
|
295
|
+
const result = await service.runWorkflow(args.identifier, input, runOptions);
|
|
296
|
+
|
|
297
|
+
// Output result
|
|
298
|
+
output.log(renderRunResult({
|
|
299
|
+
runId: result.runId,
|
|
300
|
+
status: result.status,
|
|
301
|
+
output: result.output,
|
|
302
|
+
error: result.error,
|
|
303
|
+
durationMs: result.durationMs,
|
|
304
|
+
}));
|
|
305
|
+
|
|
306
|
+
// If paused, show resume hint
|
|
307
|
+
if (result.status === 'paused') {
|
|
308
|
+
output.log(chalk.gray(`\n💡 Use "roy workflow run -s ${result.runId}" to resume`));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// 结束 OTel span
|
|
313
|
+
if (workflowSpan) {
|
|
314
|
+
workflowSpan.end();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
await envService.dispose();
|
|
318
|
+
process.exit(0);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
// 结束 OTel span(带错误)
|
|
321
|
+
if (workflowSpan) {
|
|
322
|
+
workflowSpan.end(undefined, error instanceof Error ? error : new Error(String(error)));
|
|
323
|
+
}
|
|
324
|
+
output.error(`Failed to run workflow: ${error}`);
|
|
325
|
+
await envService.dispose();
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
"workflow.cli.run",
|
|
330
|
+
{ recordParams: true, recordResult: true, log: true }
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
export const WorkflowRunCommand: CommandModule<object, WorkflowRunOptions> = {
|
|
334
|
+
command: 'run <identifier>',
|
|
335
|
+
describe: '运行 Workflow (支持从文件或名称运行)',
|
|
336
|
+
|
|
337
|
+
builder: (yargs) =>
|
|
338
|
+
yargs
|
|
339
|
+
.positional('identifier', {
|
|
340
|
+
describe: 'Workflow 名称或定义文件路径',
|
|
341
|
+
type: 'string',
|
|
342
|
+
demandOption: true,
|
|
343
|
+
})
|
|
344
|
+
.option('debug', {
|
|
345
|
+
alias: 'd',
|
|
346
|
+
describe: 'Debug 模式',
|
|
347
|
+
type: 'boolean',
|
|
348
|
+
default: false,
|
|
349
|
+
})
|
|
350
|
+
.option('input', {
|
|
351
|
+
alias: 'i',
|
|
352
|
+
describe: '输入参数 (JSON 格式)',
|
|
353
|
+
type: 'string',
|
|
354
|
+
})
|
|
355
|
+
.option('parallel-limit', {
|
|
356
|
+
describe: '并行限制',
|
|
357
|
+
type: 'number',
|
|
358
|
+
})
|
|
359
|
+
.option('timeout', {
|
|
360
|
+
alias: 't',
|
|
361
|
+
describe: '超时时间 (毫秒)',
|
|
362
|
+
type: 'number',
|
|
363
|
+
})
|
|
364
|
+
.option('watch', {
|
|
365
|
+
alias: 'w',
|
|
366
|
+
describe: '实时显示执行进度',
|
|
367
|
+
type: 'boolean',
|
|
368
|
+
default: false,
|
|
369
|
+
})
|
|
370
|
+
.option('file', {
|
|
371
|
+
alias: 'f',
|
|
372
|
+
describe: 'identifier 是文件路径,自动注册后运行',
|
|
373
|
+
type: 'boolean',
|
|
374
|
+
default: false,
|
|
375
|
+
})
|
|
376
|
+
.option('name', {
|
|
377
|
+
alias: 'n',
|
|
378
|
+
describe: '注册时的名称(仅 --file 模式有效)',
|
|
379
|
+
type: 'string',
|
|
380
|
+
})
|
|
381
|
+
.option('register-only', {
|
|
382
|
+
alias: 'r',
|
|
383
|
+
describe: '只注册不运行(仅 --file 模式有效)',
|
|
384
|
+
type: 'boolean',
|
|
385
|
+
default: false,
|
|
386
|
+
})
|
|
387
|
+
.option('config', {
|
|
388
|
+
describe: '配置文件路径',
|
|
389
|
+
type: 'string',
|
|
390
|
+
})
|
|
391
|
+
.option('session-id', {
|
|
392
|
+
alias: 's',
|
|
393
|
+
describe: '从已有 session 恢复运行(支持 workflow pause/interrupt 恢复)',
|
|
394
|
+
type: 'string',
|
|
395
|
+
}),
|
|
396
|
+
|
|
397
|
+
handler: runWorkflow,
|
|
398
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Workflow Status Command
|
|
3
|
+
*
|
|
4
|
+
* Get workflow run status
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { CommandModule } from 'yargs';
|
|
8
|
+
import { EnvironmentService } from '../../../services/environment.service';
|
|
9
|
+
import { OutputService } from '../../../services/output.service';
|
|
10
|
+
import { renderRunDetail } from '../renderers';
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
import type { WorkflowService, SessionInfo } from '@ai-setting/roy-agent-core/env/workflow/service';
|
|
13
|
+
import type { WorkflowRun } from '@ai-setting/roy-agent-core/env/workflow/types';
|
|
14
|
+
|
|
15
|
+
export interface WorkflowStatusOptions {
|
|
16
|
+
runId: string;
|
|
17
|
+
watch?: boolean;
|
|
18
|
+
interval?: number;
|
|
19
|
+
config?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* WorkflowStatusCommand - Get workflow run status
|
|
24
|
+
*/
|
|
25
|
+
export const WorkflowStatusCommand: CommandModule<object, WorkflowStatusOptions> = {
|
|
26
|
+
command: 'status <runId>',
|
|
27
|
+
describe: '查看 Workflow 运行状态',
|
|
28
|
+
|
|
29
|
+
builder: (yargs) =>
|
|
30
|
+
yargs
|
|
31
|
+
.positional('runId', {
|
|
32
|
+
describe: 'Run ID',
|
|
33
|
+
type: 'string',
|
|
34
|
+
demandOption: true,
|
|
35
|
+
})
|
|
36
|
+
.option('watch', {
|
|
37
|
+
alias: 'w',
|
|
38
|
+
describe: '持续监控状态',
|
|
39
|
+
type: 'boolean',
|
|
40
|
+
default: false,
|
|
41
|
+
})
|
|
42
|
+
.option('interval', {
|
|
43
|
+
alias: 'i',
|
|
44
|
+
describe: '监控间隔(毫秒)',
|
|
45
|
+
type: 'number',
|
|
46
|
+
default: 1000,
|
|
47
|
+
})
|
|
48
|
+
.option('config', {
|
|
49
|
+
describe: '配置文件路径',
|
|
50
|
+
type: 'string',
|
|
51
|
+
}),
|
|
52
|
+
|
|
53
|
+
async handler(args) {
|
|
54
|
+
const output = new OutputService();
|
|
55
|
+
const envService = new EnvironmentService(output);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
await envService.create({ configPath: args.config });
|
|
59
|
+
const env = envService.getEnvironment();
|
|
60
|
+
if (!env) {
|
|
61
|
+
output.error("Failed to create environment");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
const workflowComponent = env.getComponent('workflow') as any;
|
|
65
|
+
|
|
66
|
+
if (!workflowComponent) {
|
|
67
|
+
output.error('WorkflowComponent not available');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const service = workflowComponent.getService() as WorkflowService;
|
|
72
|
+
|
|
73
|
+
if (args.watch) {
|
|
74
|
+
// Watch mode
|
|
75
|
+
let lastStatus = '';
|
|
76
|
+
while (true) {
|
|
77
|
+
const session: SessionInfo | null = await service.getSession(args.runId);
|
|
78
|
+
if (!session) {
|
|
79
|
+
output.error(`Run not found: ${args.runId}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const metadata = session.metadata;
|
|
84
|
+
const status = metadata.status;
|
|
85
|
+
|
|
86
|
+
if (status !== lastStatus) {
|
|
87
|
+
const run: WorkflowRun = {
|
|
88
|
+
id: session.id,
|
|
89
|
+
workflowId: (metadata as any).workflowId || '',
|
|
90
|
+
workflowName: metadata.workflowName,
|
|
91
|
+
status,
|
|
92
|
+
startedAt: session.createdAt,
|
|
93
|
+
debugMode: false,
|
|
94
|
+
};
|
|
95
|
+
output.log(renderRunDetail(run));
|
|
96
|
+
lastStatus = status;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check if terminal state
|
|
100
|
+
if (['completed', 'failed', 'stopped'].includes(status)) {
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Wait for next check
|
|
105
|
+
await new Promise(resolve => setTimeout(resolve, args.interval));
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
// Single status check
|
|
109
|
+
const session: SessionInfo | null = await service.getSession(args.runId);
|
|
110
|
+
if (!session) {
|
|
111
|
+
output.error(`Run not found: ${args.runId}`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const metadata = session.metadata;
|
|
116
|
+
const status = metadata.status;
|
|
117
|
+
|
|
118
|
+
const run: WorkflowRun = {
|
|
119
|
+
id: session.id,
|
|
120
|
+
workflowId: (metadata as any).workflowId || '',
|
|
121
|
+
workflowName: metadata.workflowName,
|
|
122
|
+
status,
|
|
123
|
+
startedAt: session.createdAt,
|
|
124
|
+
debugMode: false,
|
|
125
|
+
};
|
|
126
|
+
output.log(renderRunDetail(run));
|
|
127
|
+
|
|
128
|
+
// Show status emoji
|
|
129
|
+
const statusEmoji: Record<string, string> = {
|
|
130
|
+
idle: '⏸️',
|
|
131
|
+
running: '🔄',
|
|
132
|
+
paused: '⏸️',
|
|
133
|
+
completed: '✅',
|
|
134
|
+
failed: '❌',
|
|
135
|
+
stopped: '⏹️',
|
|
136
|
+
};
|
|
137
|
+
const emoji = statusEmoji[status] || '❓';
|
|
138
|
+
output.log(chalk.bold(`\n${emoji} Status: ${status.toUpperCase()}`));
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
output.error(`Failed to get workflow status: ${error}`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
} finally {
|
|
144
|
+
await envService.dispose();
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Workflow Stop Command
|
|
3
|
+
*
|
|
4
|
+
* Stop a running or paused workflow
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { CommandModule } from 'yargs';
|
|
8
|
+
import { EnvironmentService } from '../../../services/environment.service';
|
|
9
|
+
import { OutputService } from '../../../services/output.service';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import type { WorkflowService, SessionInfo } from '@ai-setting/roy-agent-core/env/workflow/service';
|
|
12
|
+
import { wrapFunction } from '@ai-setting/roy-agent-core';
|
|
13
|
+
|
|
14
|
+
export interface WorkflowStopOptions {
|
|
15
|
+
runId: string;
|
|
16
|
+
config?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Stop workflow implementation
|
|
21
|
+
*/
|
|
22
|
+
const stopWorkflow = wrapFunction(
|
|
23
|
+
async function stopWorkflowImpl(args: WorkflowStopOptions): Promise<void> {
|
|
24
|
+
const output = new OutputService();
|
|
25
|
+
const envService = new EnvironmentService(output);
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
await envService.create({ configPath: args.config });
|
|
29
|
+
const env = envService.getEnvironment();
|
|
30
|
+
if (!env) {
|
|
31
|
+
output.error("Failed to create environment");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const workflowComponent = env.getComponent('workflow') as any;
|
|
35
|
+
|
|
36
|
+
if (!workflowComponent) {
|
|
37
|
+
output.error('WorkflowComponent not available');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const service = workflowComponent.getService() as WorkflowService;
|
|
42
|
+
|
|
43
|
+
// Check if run exists
|
|
44
|
+
const session: SessionInfo | null = await service.getSession(args.runId);
|
|
45
|
+
if (!session) {
|
|
46
|
+
output.error(`Run not found: ${args.runId}`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const metadata = session.metadata;
|
|
51
|
+
if (!['running', 'paused'].includes(metadata.status)) {
|
|
52
|
+
output.error(`Cannot stop run in status: ${metadata.status}`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Stop the run
|
|
57
|
+
await service.stopRun(args.runId);
|
|
58
|
+
|
|
59
|
+
output.log(chalk.red(`\n⏹️ Workflow stopped: ${args.runId}`));
|
|
60
|
+
} catch (error) {
|
|
61
|
+
output.error(`Failed to stop workflow: ${error}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
} finally {
|
|
64
|
+
await envService.dispose();
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"workflow.cli.stop",
|
|
68
|
+
{ recordParams: true, recordResult: true, log: true }
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* WorkflowStopCommand - Stop a running or paused workflow
|
|
73
|
+
*/
|
|
74
|
+
export const WorkflowStopCommand: CommandModule<object, WorkflowStopOptions> = {
|
|
75
|
+
command: 'stop <runId>',
|
|
76
|
+
describe: '停止运行中的 Workflow',
|
|
77
|
+
|
|
78
|
+
builder: (yargs) =>
|
|
79
|
+
yargs
|
|
80
|
+
.positional('runId', {
|
|
81
|
+
describe: 'Run ID',
|
|
82
|
+
type: 'string',
|
|
83
|
+
demandOption: true,
|
|
84
|
+
})
|
|
85
|
+
.option('config', {
|
|
86
|
+
describe: '配置文件路径',
|
|
87
|
+
type: 'string',
|
|
88
|
+
}),
|
|
89
|
+
|
|
90
|
+
handler: stopWorkflow,
|
|
91
|
+
};
|