@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,130 @@
1
+ /**
2
+ * @fileoverview Workflow Update Command
3
+ *
4
+ * Update an existing workflow
5
+ */
6
+
7
+ import { CommandModule } from 'yargs';
8
+ import { EnvironmentService } from '../../../services/environment.service';
9
+ import { OutputService } from '../../../services/output.service';
10
+ import { renderWorkflowUpdated } from '../renderers';
11
+ import chalk from 'chalk';
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+ import type { WorkflowService } from '@ai-setting/roy-agent-core/env/workflow/service';
15
+
16
+ export interface WorkflowUpdateOptions {
17
+ name: string;
18
+ file?: string;
19
+ description?: string;
20
+ tags?: string[];
21
+ taskId?: number;
22
+ config?: string;
23
+ }
24
+
25
+ /**
26
+ * WorkflowUpdateCommand - Update an existing workflow
27
+ */
28
+ export const WorkflowUpdateCommand: CommandModule<object, object> = {
29
+ command: 'update <name>',
30
+ describe: '更新已存在的 Workflow',
31
+
32
+ builder: (yargs) =>
33
+ yargs
34
+ .positional('name', {
35
+ describe: 'Workflow 名称',
36
+ type: 'string',
37
+ demandOption: true,
38
+ })
39
+ .option('file', {
40
+ alias: 'f',
41
+ describe: 'Workflow 定义文件 (YAML/JSON)',
42
+ type: 'string',
43
+ })
44
+ .option('description', {
45
+ alias: 'd',
46
+ describe: '更新描述',
47
+ type: 'string',
48
+ })
49
+ .option('tags', {
50
+ alias: 't',
51
+ describe: '更新标签(逗号分隔)',
52
+ type: 'string',
53
+ })
54
+ .option('task-id', {
55
+ alias: 'i',
56
+ describe: '更新关联任务 ID',
57
+ type: 'number',
58
+ })
59
+ .option('config', {
60
+ describe: '配置文件路径',
61
+ type: 'string',
62
+ }),
63
+
64
+ async handler(args) {
65
+ const a = args as any;
66
+ const output = new OutputService();
67
+ const envService = new EnvironmentService(output);
68
+
69
+ try {
70
+ await envService.create({ configPath: a.config });
71
+ const env = envService.getEnvironment();
72
+ if (!env) {
73
+ output.error("Failed to create environment");
74
+ process.exit(1);
75
+ }
76
+ const workflowComponent = env.getComponent('workflow') as any;
77
+
78
+ if (!workflowComponent) {
79
+ output.error('WorkflowComponent not available');
80
+ process.exit(1);
81
+ }
82
+
83
+ const service = workflowComponent.getService() as WorkflowService;
84
+
85
+ // Check if workflow exists
86
+ const existing = service.getWorkflow(a.name);
87
+ if (!existing) {
88
+ output.error(`Workflow not found: ${a.name}`);
89
+ process.exit(1);
90
+ }
91
+
92
+ // Prepare updates
93
+ const updates: any = {};
94
+
95
+ if (a.description !== undefined) {
96
+ updates.description = a.description;
97
+ }
98
+
99
+ if (a.taskId !== undefined) {
100
+ updates.metadata = { ...existing.metadata, taskId: a.taskId };
101
+ }
102
+
103
+ if (a.file) {
104
+ const filePath = path.resolve(a.file);
105
+ if (!fs.existsSync(filePath)) {
106
+ output.error(`File not found: ${filePath}`);
107
+ process.exit(1);
108
+ }
109
+
110
+ const content = fs.readFileSync(filePath, 'utf-8');
111
+ const { definition } = await workflowComponent.parseWorkflowFile(content, filePath);
112
+ updates.definition = definition;
113
+ }
114
+
115
+ // Parse tags if provided
116
+ const tags = a.tags ? a.tags.split(',').map((t: string) => t.trim()) : undefined;
117
+
118
+ // Update workflow
119
+ const workflow = await service.updateWorkflow(a.name, updates, { tags });
120
+
121
+ output.log(renderWorkflowUpdated(workflow));
122
+ output.log(chalk.green(`\n✅ Workflow '${a.name}' updated successfully`));
123
+ } catch (error) {
124
+ output.error(`Failed to update workflow: ${error}`);
125
+ process.exit(1);
126
+ } finally {
127
+ await envService.dispose();
128
+ }
129
+ },
130
+ };
@@ -0,0 +1,139 @@
1
+ /**
2
+ * @fileoverview Workflow validate command
3
+ */
4
+
5
+ import { CommandModule } from 'yargs';
6
+ import chalk from 'chalk';
7
+ import { readFileSync } from 'fs';
8
+ import { WorkflowValidator } from '../validators/workflow-validator';
9
+ import type { RawWorkflowDefinition, ValidationError } from '../validators/types';
10
+
11
+ export interface WorkflowValidateOptions {
12
+ input?: string;
13
+ yaml?: string;
14
+ json?: boolean;
15
+ }
16
+
17
+ /**
18
+ * Parse workflow from various input formats
19
+ */
20
+ async function parseWorkflowInput(input?: string, yamlStr?: string): Promise<RawWorkflowDefinition> {
21
+ // Helper to parse YAML
22
+ const parseYaml = async (content: string): Promise<RawWorkflowDefinition> => {
23
+ const YAML = await import('yaml');
24
+ return YAML.parse(content) as RawWorkflowDefinition;
25
+ };
26
+
27
+ // From file path
28
+ if (input && !yamlStr) {
29
+ try {
30
+ const content = readFileSync(input, 'utf-8');
31
+ return await parseYaml(content);
32
+ } catch (error: any) {
33
+ throw new Error(`Failed to read file "${input}": ${error.message}`);
34
+ }
35
+ }
36
+
37
+ // From --yaml flag
38
+ if (yamlStr) {
39
+ try {
40
+ return await parseYaml(yamlStr);
41
+ } catch (error: any) {
42
+ throw new Error(`Failed to parse YAML: ${error.message}`);
43
+ }
44
+ }
45
+
46
+ // From workflow ID (requires database lookup)
47
+ if (input && !input.includes(':') && !input.includes('-')) {
48
+ // TODO: Load from database if needed
49
+ throw new Error('Workflow ID lookup not implemented yet. Please use file path or --yaml flag.');
50
+ }
51
+
52
+ throw new Error('Please provide workflow input: file path, --yaml string, or workflow ID');
53
+ }
54
+
55
+ /**
56
+ * Render validation result
57
+ */
58
+ function renderResult(result: ReturnType<WorkflowValidator['validate']>, options: WorkflowValidateOptions): void {
59
+ // JSON output
60
+ if (options.json) {
61
+ console.log(JSON.stringify(result, null, 2));
62
+ return;
63
+ }
64
+
65
+ // Text output
66
+ if (result.valid) {
67
+ console.log(chalk.green('\n✅ Workflow validation PASSED\n'));
68
+ console.log(chalk.bold(`Workflow: ${result.workflowName}`));
69
+ console.log(`Nodes: ${result.nodeCount}`);
70
+
71
+ // List valid nodes
72
+ console.log('\nValid nodes:');
73
+ // Since we don't track valid nodes separately, we just show the count
74
+ console.log(chalk.green(' ✓ All nodes passed validation'));
75
+ } else {
76
+ console.log(chalk.red(`\n❌ Workflow validation FAILED (${result.errors.length} errors found)\n`));
77
+
78
+ result.errors.forEach((error: ValidationError, index: number) => {
79
+ console.log(chalk.red(`[Error ${index + 1}/${result.errors.length}]${error.nodeId ? ` Node "${error.nodeId}"` : ''}`));
80
+ console.log(` ${chalk.bold('Type:')} ${error.type}`);
81
+ console.log(` ${chalk.bold('Description:')} ${error.description}`);
82
+ console.log(` ${chalk.bold('Expected:')} ${error.expected}`);
83
+ console.log(` ${chalk.bold('Actual:')} ${error.actual}`);
84
+ console.log(` ${chalk.green('Fix:')} ${error.fix}`);
85
+ console.log();
86
+ });
87
+ }
88
+ }
89
+
90
+ /**
91
+ * WorkflowValidateCommand - Validate workflow YAML
92
+ */
93
+ export const WorkflowValidateCommand: CommandModule<object, WorkflowValidateOptions> = {
94
+ command: 'validate [input]',
95
+ describe: 'Validate workflow YAML syntax and node configurations',
96
+
97
+ builder: (yargs) =>
98
+ yargs
99
+ .positional('input', {
100
+ describe: 'Workflow file path or workflow ID',
101
+ type: 'string',
102
+ })
103
+ .option('yaml', {
104
+ describe: 'YAML content string (alternative to positional input)',
105
+ type: 'string',
106
+ alias: 'y',
107
+ })
108
+ .option('json', {
109
+ describe: 'Output in JSON format',
110
+ type: 'boolean',
111
+ alias: 'j',
112
+ }),
113
+
114
+ async handler(args) {
115
+ const options = args as WorkflowValidateOptions;
116
+
117
+ try {
118
+ // Parse input
119
+ const workflow = await parseWorkflowInput(options.input, options.yaml);
120
+ if (!workflow) {
121
+ console.error(chalk.red('Failed to parse workflow input'));
122
+ process.exit(1);
123
+ }
124
+
125
+ // Validate
126
+ const validator = new WorkflowValidator();
127
+ const result = validator.validate(workflow);
128
+
129
+ // Render result
130
+ renderResult(result, options);
131
+
132
+ // Exit with appropriate code
133
+ process.exit(result.valid ? 0 : 1);
134
+ } catch (error: any) {
135
+ console.error(chalk.red(`\nError: ${error.message}`));
136
+ process.exit(1);
137
+ }
138
+ },
139
+ };
@@ -0,0 +1,196 @@
1
+ /**
2
+ * @fileoverview Workflow CLI Tests
3
+ *
4
+ * Tests workflow CLI commands with taskId and search functionality.
5
+ */
6
+
7
+ import { describe, it, expect } from 'bun:test';
8
+ import path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import { setTimeout as sleep } from 'timers/promises';
11
+
12
+ // Get the project root
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+ const projectRoot = path.resolve(__dirname, '../../../../../..');
15
+ const royBin = path.join(projectRoot, 'packages/cli/src/bin/roy.ts');
16
+
17
+ // Helper function to run CLI command with proper timeout using Bun subprocess
18
+ async function runCliCommand(args: string[], timeoutMs: number = 10000): Promise<{ stdout: string; stderr: string }> {
19
+ const cmd = `bun run ${royBin} ${args.join(' ')}`;
20
+
21
+ // Create subprocess promise
22
+ const runProcess = async () => {
23
+ const proc = Bun.spawn({
24
+ cmd: ['bun', 'run', royBin, ...args],
25
+ cwd: projectRoot,
26
+ stdout: 'pipe',
27
+ stderr: 'pipe',
28
+ });
29
+
30
+ const [stdout, stderr] = await Promise.all([
31
+ new Response(proc.stdout).text(),
32
+ new Response(proc.stderr).text(),
33
+ ]);
34
+
35
+ const code = await proc.exited;
36
+ if (code !== 0 && code !== null) {
37
+ throw new Error(`Command exited with code ${code}: ${cmd}\nStderr: ${stderr}`);
38
+ }
39
+
40
+ return { stdout, stderr };
41
+ };
42
+
43
+ // Use Promise.race for timeout
44
+ return Promise.race([
45
+ runProcess(),
46
+ sleep(timeoutMs).then(() => {
47
+ throw new Error(`Command timed out after ${timeoutMs}ms: ${cmd}`);
48
+ }),
49
+ ]);
50
+ }
51
+
52
+ describe('Workflow CLI Commands', () => {
53
+ describe('workflow list --help', () => {
54
+ it('should show --task-id option', async () => {
55
+ const result = await runCliCommand(['workflow', 'list', '--help']);
56
+
57
+ expect(result.stdout).toContain('--task-id');
58
+ expect(result.stdout).toContain('按关联任务 ID 筛选');
59
+ });
60
+
61
+ it('should show --search option', async () => {
62
+ const result = await runCliCommand(['workflow', 'list', '--help']);
63
+
64
+ expect(result.stdout).toContain('--search');
65
+ expect(result.stdout).toContain('搜索 description 和 tags');
66
+ });
67
+ });
68
+
69
+ describe('workflow add --help', () => {
70
+ it('should show --task-id option', async () => {
71
+ const result = await runCliCommand(['workflow', 'add', '--help']);
72
+
73
+ expect(result.stdout).toContain('--task-id');
74
+ expect(result.stdout).toContain('关联任务 ID');
75
+ });
76
+ });
77
+
78
+ describe('workflow update --help', () => {
79
+ it('should show --task-id option', async () => {
80
+ const result = await runCliCommand(['workflow', 'update', '--help']);
81
+
82
+ expect(result.stdout).toContain('--task-id');
83
+ expect(result.stdout).toContain('更新关联任务 ID');
84
+ });
85
+ });
86
+ });
87
+
88
+ describe("workflow run CLI", () => {
89
+ describe("run --help", () => {
90
+ it("should show --session-id option for resume", async () => {
91
+ const result = await runCliCommand(['workflow', 'run', '--help']);
92
+
93
+ expect(result.stdout).toContain('--session-id');
94
+ expect(result.stdout).toContain('-s');
95
+ expect(result.stdout).toContain('恢复运行');
96
+ });
97
+
98
+ it("should show --file option for file-based workflows", async () => {
99
+ const result = await runCliCommand(['workflow', 'run', '--help']);
100
+
101
+ expect(result.stdout).toContain('--file');
102
+ expect(result.stdout).toContain('-f');
103
+ expect(result.stdout).toContain('文件路径');
104
+ });
105
+
106
+ it("should show --name option for custom registration", async () => {
107
+ const result = await runCliCommand(['workflow', 'run', '--help']);
108
+
109
+ expect(result.stdout).toContain('--name');
110
+ expect(result.stdout).toContain('-n');
111
+ expect(result.stdout).toContain('注册');
112
+ });
113
+
114
+ it("should show --register-only option", async () => {
115
+ const result = await runCliCommand(['workflow', 'run', '--help']);
116
+
117
+ expect(result.stdout).toContain('--register-only');
118
+ expect(result.stdout).toContain('-r');
119
+ expect(result.stdout).toContain('只注册不运行');
120
+ });
121
+
122
+ it("should show positional argument identifier", async () => {
123
+ const result = await runCliCommand(['workflow', 'run', '--help']);
124
+
125
+ expect(result.stdout).toContain('<identifier>');
126
+ expect(result.stdout).toContain('Workflow 名称或定义文件路径');
127
+ });
128
+ });
129
+
130
+ // Note: Integration tests for actual run/resume functionality are skipped
131
+ // because they require:
132
+ // 1. A valid workflow to be registered in the system
133
+ // 2. Environment setup with proper configuration
134
+ // 3. Long execution time for workflow runs
135
+ //
136
+ // These should be covered by E2E tests or integration tests with proper mocks.
137
+
138
+ describe.skip("run with actual workflow", () => {
139
+ it("should create new session and run", async () => {
140
+ // TODO: This requires a registered workflow and proper environment
141
+ // Would test: roy workflow run <workflowName>
142
+ // Expected: creates session, runs workflow, returns result
143
+ });
144
+
145
+ it("should validate session exists on resume", async () => {
146
+ // TODO: This requires a paused workflow session
147
+ // Would test: roy workflow run -s <sessionId> --input "response"
148
+ // Expected: validates session type is 'workflow', resumes execution
149
+ });
150
+ });
151
+ });
152
+
153
+ describe("workflow stop CLI", () => {
154
+ describe("stop --help", () => {
155
+ it("should show positional argument runId", async () => {
156
+ const result = await runCliCommand(['workflow', 'stop', '--help']);
157
+
158
+ expect(result.stdout).toContain('<runId>');
159
+ expect(result.stdout).toContain('Run ID');
160
+ });
161
+
162
+ it("should show --config option", async () => {
163
+ const result = await runCliCommand(['workflow', 'stop', '--help']);
164
+
165
+ expect(result.stdout).toContain('--config');
166
+ expect(result.stdout).toContain('配置文件路径');
167
+ });
168
+ });
169
+
170
+ // Note: Testing actual stop functionality requires a running/paused workflow session.
171
+ // This should be covered by E2E tests with proper setup.
172
+
173
+ describe.skip("stop with actual run", () => {
174
+ it("should call engine.stop for running session", async () => {
175
+ // TODO: This requires a running workflow session
176
+ // Would test: roy workflow stop <runId>
177
+ // Expected: calls service.stopRun(runId), confirms stop
178
+ });
179
+
180
+ it("should call engine.stop for paused session", async () => {
181
+ // TODO: This requires a paused workflow session
182
+ // Would test: roy workflow stop <sessionId>
183
+ // Expected: calls service.stopRun(sessionId), confirms stop
184
+ });
185
+
186
+ it("should error when session not found", async () => {
187
+ // TODO: This requires mocking the service to return null for getSession
188
+ // Expected: exits with error "Run not found"
189
+ });
190
+
191
+ it("should error when session not in stoppable state", async () => {
192
+ // TODO: This requires a completed/failed session
193
+ // Expected: exits with error "Cannot stop run in status: completed"
194
+ });
195
+ });
196
+ });
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @fileoverview Workflow Command - Main entry point
3
+ *
4
+ * Workflow DAG management and execution CLI commands
5
+ *
6
+ * Note: Resume functionality is unified into `workflow run -s <sessionId>`.
7
+ * Use `workflow run -s <sessionId> --input "response"` to resume a paused workflow.
8
+ */
9
+
10
+ import { CommandModule } from 'yargs';
11
+ import { WorkflowListCommand } from './commands/list';
12
+ import { WorkflowAddCommand } from './commands/add';
13
+ import { WorkflowGetCommand } from './commands/get';
14
+ import { WorkflowUpdateCommand } from './commands/update';
15
+ import { WorkflowRemoveCommand } from './commands/remove';
16
+ import { WorkflowRunCommand } from './commands/run';
17
+ import { WorkflowStopCommand } from './commands/stop';
18
+ import { WorkflowStatusCommand } from './commands/status';
19
+ import { WorkflowNodesCommand } from './commands/nodes';
20
+ import { WorkflowValidateCommand } from './commands/validate';
21
+
22
+ export interface WorkflowOptions {
23
+ command?: string;
24
+ }
25
+
26
+ /**
27
+ * WorkflowCommand - Workflow DAG management
28
+ */
29
+ export const WorkflowCommand: CommandModule<object, WorkflowOptions> = {
30
+ command: 'workflow',
31
+ describe: 'Workflow DAG management and execution - create, run, and monitor workflows',
32
+
33
+ builder: (yargs) =>
34
+ yargs
35
+ .command(WorkflowListCommand)
36
+ .command(WorkflowAddCommand)
37
+ .command(WorkflowNodesCommand)
38
+ .command(WorkflowValidateCommand)
39
+ .command(WorkflowGetCommand)
40
+ .command(WorkflowUpdateCommand)
41
+ .command(WorkflowRemoveCommand)
42
+ .command(WorkflowRunCommand)
43
+ .command(WorkflowStopCommand)
44
+ .command(WorkflowStatusCommand)
45
+ .demandCommand(1, '请指定子命令:list、add、validate、nodes、get、update、remove、run、stop、status\n\nResume: 使用 `workflow run -s <sessionId>` 恢复暂停的工作流')
46
+ .help(),
47
+
48
+ handler() {
49
+ // Default to help (handled by yargs demandCommand)
50
+ },
51
+ };
52
+
53
+ // Export subcommands
54
+ export {
55
+ WorkflowListCommand,
56
+ WorkflowAddCommand,
57
+ WorkflowGetCommand,
58
+ WorkflowUpdateCommand,
59
+ WorkflowRemoveCommand,
60
+ WorkflowRunCommand,
61
+ WorkflowStopCommand,
62
+ WorkflowStatusCommand,
63
+ WorkflowNodesCommand,
64
+ WorkflowValidateCommand,
65
+ };