@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.
Files changed (158) hide show
  1. package/README.md +126 -0
  2. package/dist/bin/roy.js +127297 -0
  3. package/dist/roy-agent-darwin-arm64/bin/roy.js +127297 -0
  4. package/dist/roy-agent-darwin-x64/bin/roy.js +127297 -0
  5. package/dist/roy-agent-linux-arm64/bin/roy.js +127297 -0
  6. package/dist/roy-agent-linux-x64/bin/roy.js +127297 -0
  7. package/dist/roy-agent-windows-x64/bin/roy.js +127297 -0
  8. package/package.json +91 -0
  9. package/src/bin/roy.ts +12 -0
  10. package/src/cli.ts +101 -0
  11. package/src/commands/act.ts +480 -0
  12. package/src/commands/commands-add.ts +110 -0
  13. package/src/commands/commands-dirs.ts +70 -0
  14. package/src/commands/commands-info.ts +90 -0
  15. package/src/commands/commands-list.ts +161 -0
  16. package/src/commands/commands-remove.ts +147 -0
  17. package/src/commands/commands.ts +55 -0
  18. package/src/commands/config/config-service.test.ts +449 -0
  19. package/src/commands/config/config-service.ts +312 -0
  20. package/src/commands/config/deep-merge.test.ts +168 -0
  21. package/src/commands/config/deep-merge.ts +63 -0
  22. package/src/commands/config/export.ts +97 -0
  23. package/src/commands/config/filter-history-e2e.test.ts +141 -0
  24. package/src/commands/config/import-preserve-refs.test.ts +212 -0
  25. package/src/commands/config/import.ts +119 -0
  26. package/src/commands/config/index.ts +35 -0
  27. package/src/commands/config/list.ts +281 -0
  28. package/src/commands/config/roy-config-e2e.test.ts +297 -0
  29. package/src/commands/config/types.ts +54 -0
  30. package/src/commands/debug/index.ts +38 -0
  31. package/src/commands/debug/log.test.ts +233 -0
  32. package/src/commands/debug/log.ts +123 -0
  33. package/src/commands/debug/span.test.ts +297 -0
  34. package/src/commands/debug/span.ts +211 -0
  35. package/src/commands/debug/trace.test.ts +254 -0
  36. package/src/commands/debug/trace.ts +140 -0
  37. package/src/commands/eventsource/add.ts +133 -0
  38. package/src/commands/eventsource/index.ts +48 -0
  39. package/src/commands/eventsource/list.ts +194 -0
  40. package/src/commands/eventsource/remove.ts +95 -0
  41. package/src/commands/eventsource/start.ts +103 -0
  42. package/src/commands/eventsource/status.ts +185 -0
  43. package/src/commands/eventsource/stop.ts +89 -0
  44. package/src/commands/index.ts +22 -0
  45. package/src/commands/input-handler.test.ts +76 -0
  46. package/src/commands/input-handler.ts +43 -0
  47. package/src/commands/interactive-esc.test.ts +254 -0
  48. package/src/commands/interactive.shutdown.test.ts +122 -0
  49. package/src/commands/interactive.test.ts +221 -0
  50. package/src/commands/interactive.ts +1015 -0
  51. package/src/commands/lsp/check.ts +92 -0
  52. package/src/commands/lsp/index.ts +32 -0
  53. package/src/commands/lsp/install.ts +126 -0
  54. package/src/commands/lsp/list.ts +64 -0
  55. package/src/commands/mcp/index.ts +27 -0
  56. package/src/commands/mcp/list.ts +116 -0
  57. package/src/commands/mcp/reload.ts +70 -0
  58. package/src/commands/mcp/tools.ts +121 -0
  59. package/src/commands/memory/extract-e2e.test.ts +388 -0
  60. package/src/commands/memory/index.ts +11 -0
  61. package/src/commands/memory/memory-simplified.test.ts +58 -0
  62. package/src/commands/memory/memory.ts +25 -0
  63. package/src/commands/memory/organize.ts +300 -0
  64. package/src/commands/memory/recall.test.ts +120 -0
  65. package/src/commands/memory/recall.ts +88 -0
  66. package/src/commands/memory/record-extract-handle-query.test.ts +385 -0
  67. package/src/commands/memory/record-prompt-component.test.ts +343 -0
  68. package/src/commands/memory/record.test.ts +92 -0
  69. package/src/commands/memory/record.ts +332 -0
  70. package/src/commands/plugin.test.ts +292 -0
  71. package/src/commands/plugin.ts +267 -0
  72. package/src/commands/sessions/active.ts +96 -0
  73. package/src/commands/sessions/add-message.ts +96 -0
  74. package/src/commands/sessions/checkpoints.ts +154 -0
  75. package/src/commands/sessions/compact.test.ts +215 -0
  76. package/src/commands/sessions/compact.ts +269 -0
  77. package/src/commands/sessions/delete.ts +236 -0
  78. package/src/commands/sessions/get.ts +165 -0
  79. package/src/commands/sessions/grep.ts +233 -0
  80. package/src/commands/sessions/index.ts +95 -0
  81. package/src/commands/sessions/list.ts +210 -0
  82. package/src/commands/sessions/messages.test.ts +333 -0
  83. package/src/commands/sessions/messages.ts +248 -0
  84. package/src/commands/sessions/mock.ts +194 -0
  85. package/src/commands/sessions/new.ts +82 -0
  86. package/src/commands/sessions/rename.ts +98 -0
  87. package/src/commands/shared/event-handler.ts +213 -0
  88. package/src/commands/shared/event-message-formatter.ts +295 -0
  89. package/src/commands/shared/index.ts +11 -0
  90. package/src/commands/shared/query-executor.test.ts +434 -0
  91. package/src/commands/shared/query-executor.ts +324 -0
  92. package/src/commands/shared/repl-engine.test.ts +354 -0
  93. package/src/commands/shared/session-manager.test.ts +212 -0
  94. package/src/commands/shared/session-manager.ts +114 -0
  95. package/src/commands/skills/get.ts +90 -0
  96. package/src/commands/skills/index.ts +39 -0
  97. package/src/commands/skills/list.ts +129 -0
  98. package/src/commands/skills/reload.ts +59 -0
  99. package/src/commands/skills/search.ts +132 -0
  100. package/src/commands/skills/show-config.ts +93 -0
  101. package/src/commands/tasks/complete.ts +92 -0
  102. package/src/commands/tasks/create.ts +118 -0
  103. package/src/commands/tasks/delete.ts +86 -0
  104. package/src/commands/tasks/get.ts +116 -0
  105. package/src/commands/tasks/index.ts +53 -0
  106. package/src/commands/tasks/list.ts +140 -0
  107. package/src/commands/tasks/operations.ts +120 -0
  108. package/src/commands/tasks/update.ts +122 -0
  109. package/src/commands/tools/exec-tool.ts +128 -0
  110. package/src/commands/tools/get.ts +114 -0
  111. package/src/commands/tools/index.ts +35 -0
  112. package/src/commands/tools/list.ts +107 -0
  113. package/src/commands/tools/shared/index.ts +7 -0
  114. package/src/commands/tools/shared/schema-helper.ts +111 -0
  115. package/src/commands/workflow/commands/add.ts +315 -0
  116. package/src/commands/workflow/commands/get.ts +193 -0
  117. package/src/commands/workflow/commands/list.ts +137 -0
  118. package/src/commands/workflow/commands/nodes.ts +528 -0
  119. package/src/commands/workflow/commands/remove.ts +94 -0
  120. package/src/commands/workflow/commands/run.ts +398 -0
  121. package/src/commands/workflow/commands/status.ts +147 -0
  122. package/src/commands/workflow/commands/stop.ts +91 -0
  123. package/src/commands/workflow/commands/update.ts +130 -0
  124. package/src/commands/workflow/commands/validate.ts +139 -0
  125. package/src/commands/workflow/commands/workflow-cli.test.ts +196 -0
  126. package/src/commands/workflow/index.ts +65 -0
  127. package/src/commands/workflow/renderers.ts +358 -0
  128. package/src/commands/workflow/validators/index.ts +8 -0
  129. package/src/commands/workflow/validators/node-validator-factory.ts +40 -0
  130. package/src/commands/workflow/validators/node-validator.ts +125 -0
  131. package/src/commands/workflow/validators/nodes/agent-node-validator.ts +58 -0
  132. package/src/commands/workflow/validators/nodes/condition-node-validator.ts +34 -0
  133. package/src/commands/workflow/validators/nodes/decorator-node-validator.ts +45 -0
  134. package/src/commands/workflow/validators/nodes/merge-node-validator.ts +46 -0
  135. package/src/commands/workflow/validators/nodes/skill-node-validator.ts +33 -0
  136. package/src/commands/workflow/validators/nodes/tool-node-validator.ts +54 -0
  137. package/src/commands/workflow/validators/nodes/workflow-node-validator.ts +33 -0
  138. package/src/commands/workflow/validators/types.ts +78 -0
  139. package/src/commands/workflow/validators/workflow-validator.test.ts +273 -0
  140. package/src/commands/workflow/validators/workflow-validator.ts +320 -0
  141. package/src/index.ts +19 -0
  142. package/src/plugin/apply.ts +103 -0
  143. package/src/plugin/discover.ts +219 -0
  144. package/src/plugin/index.ts +45 -0
  145. package/src/plugin/registry.ts +272 -0
  146. package/src/plugin/types.ts +165 -0
  147. package/src/services/context-handler.service.test.ts +501 -0
  148. package/src/services/context-handler.service.ts +372 -0
  149. package/src/services/environment.service.commands-prompt.test.ts +167 -0
  150. package/src/services/environment.service.ts +656 -0
  151. package/src/services/output.service.test.ts +92 -0
  152. package/src/services/output.service.ts +122 -0
  153. package/src/services/quiet-mode.service.test.ts +114 -0
  154. package/src/services/quiet-mode.service.ts +81 -0
  155. package/src/services/stream-output.service.test.ts +214 -0
  156. package/src/services/stream-output.service.ts +323 -0
  157. package/src/util/which.test.ts +101 -0
  158. 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
+ };