@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,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Merge 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 MergeNodeValidator extends BaseNodeValidator {
|
|
10
|
+
readonly nodeType = 'merge';
|
|
11
|
+
|
|
12
|
+
protected getRequiredFields(): string[] {
|
|
13
|
+
// No required fields, but depends_on should be validated externally
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
protected validateTypeSpecific(node: RawNodeDefinition): ValidationError[] {
|
|
18
|
+
const errors: ValidationError[] = [];
|
|
19
|
+
|
|
20
|
+
const strategy = node.config?.strategy;
|
|
21
|
+
if (strategy !== undefined) {
|
|
22
|
+
const validStrategies = ['collect', 'first', 'last', 'merge'];
|
|
23
|
+
if (typeof strategy === 'string' && !validStrategies.includes(strategy)) {
|
|
24
|
+
errors.push({
|
|
25
|
+
type: ValidationErrorType.INVALID_FIELD_TYPE,
|
|
26
|
+
description: 'Field "strategy" has invalid value',
|
|
27
|
+
expected: `strategy must be one of: ${validStrategies.join(', ')}`,
|
|
28
|
+
actual: String(strategy),
|
|
29
|
+
fix: `Change strategy to one of: ${validStrategies.join(', ')}`,
|
|
30
|
+
nodeId: node.id,
|
|
31
|
+
});
|
|
32
|
+
} else if (typeof strategy !== 'string') {
|
|
33
|
+
errors.push({
|
|
34
|
+
type: ValidationErrorType.INVALID_FIELD_TYPE,
|
|
35
|
+
description: 'Field "strategy" has invalid type',
|
|
36
|
+
expected: 'strategy must be a string',
|
|
37
|
+
actual: typeof strategy,
|
|
38
|
+
fix: 'Change strategy to a string value',
|
|
39
|
+
nodeId: node.id,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return errors;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Skill 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 SkillNodeValidator extends BaseNodeValidator {
|
|
10
|
+
readonly nodeType = 'skill';
|
|
11
|
+
|
|
12
|
+
protected getRequiredFields(): string[] {
|
|
13
|
+
return ['skill'];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
protected validateTypeSpecific(node: RawNodeDefinition): ValidationError[] {
|
|
17
|
+
const errors: ValidationError[] = [];
|
|
18
|
+
|
|
19
|
+
const skill = node.config?.skill;
|
|
20
|
+
if (skill !== undefined && typeof skill !== 'string') {
|
|
21
|
+
errors.push({
|
|
22
|
+
type: ValidationErrorType.INVALID_FIELD_TYPE,
|
|
23
|
+
description: 'Field "skill" has invalid type',
|
|
24
|
+
expected: 'skill must be a string (e.g., "text-summarizer")',
|
|
25
|
+
actual: `typeof skill is "${typeof skill}"`,
|
|
26
|
+
fix: `Change skill value to a string: skill: "${String(skill)}"`,
|
|
27
|
+
nodeId: node.id,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return errors;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tool 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 ToolNodeValidator extends BaseNodeValidator {
|
|
10
|
+
readonly nodeType = 'tool';
|
|
11
|
+
|
|
12
|
+
protected getRequiredFields(): string[] {
|
|
13
|
+
return ['tool'];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
protected validateTypeSpecific(node: RawNodeDefinition): import('../types').ValidationError[] {
|
|
17
|
+
const errors: import('../types').ValidationError[] = [];
|
|
18
|
+
|
|
19
|
+
const tool = node.config?.tool;
|
|
20
|
+
|
|
21
|
+
// Check tool is string
|
|
22
|
+
if (tool !== undefined && typeof tool !== 'string') {
|
|
23
|
+
errors.push({
|
|
24
|
+
type: ValidationErrorType.INVALID_FIELD_TYPE,
|
|
25
|
+
description: 'Field "tool" has invalid type',
|
|
26
|
+
expected: 'tool must be a string (e.g., "bash", "read-file")',
|
|
27
|
+
actual: `typeof tool is "${typeof tool}"`,
|
|
28
|
+
fix: `Change tool value to a string: tool: "${String(tool)}"`,
|
|
29
|
+
nodeId: node.id,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Check timeout if present
|
|
34
|
+
const timeoutError = this.checkFieldType(node, 'timeout', 'number', 'positive number (milliseconds)');
|
|
35
|
+
if (timeoutError) {
|
|
36
|
+
errors.push(timeoutError);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Check input if present
|
|
40
|
+
const input = node.config?.input;
|
|
41
|
+
if (input !== undefined && (typeof input !== 'object' || input === null || Array.isArray(input))) {
|
|
42
|
+
errors.push({
|
|
43
|
+
type: ValidationErrorType.INVALID_FIELD_TYPE,
|
|
44
|
+
description: 'Field "input" has invalid type',
|
|
45
|
+
expected: 'input must be an object (key-value pairs)',
|
|
46
|
+
actual: Array.isArray(input) ? 'array' : typeof input,
|
|
47
|
+
fix: 'Change input to an object: input: { key: "value" }',
|
|
48
|
+
nodeId: node.id,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return errors;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Workflow 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 WorkflowNodeValidator extends BaseNodeValidator {
|
|
10
|
+
readonly nodeType = 'workflow';
|
|
11
|
+
|
|
12
|
+
protected getRequiredFields(): string[] {
|
|
13
|
+
return ['workflow_name'];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
protected validateTypeSpecific(node: RawNodeDefinition): ValidationError[] {
|
|
17
|
+
const errors: ValidationError[] = [];
|
|
18
|
+
|
|
19
|
+
const workflowName = node.config?.workflow_name;
|
|
20
|
+
if (workflowName !== undefined && typeof workflowName !== 'string') {
|
|
21
|
+
errors.push({
|
|
22
|
+
type: ValidationErrorType.INVALID_FIELD_TYPE,
|
|
23
|
+
description: 'Field "workflow_name" has invalid type',
|
|
24
|
+
expected: 'workflow_name must be a string',
|
|
25
|
+
actual: `typeof workflow_name is "${typeof workflowName}"`,
|
|
26
|
+
fix: `Change workflow_name value to a string: workflow_name: "${String(workflowName)}"`,
|
|
27
|
+
nodeId: node.id,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return errors;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Workflow validation types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Validation error types
|
|
7
|
+
*/
|
|
8
|
+
export enum ValidationErrorType {
|
|
9
|
+
MISSING_REQUIRED_FIELD = 'MISSING_REQUIRED_FIELD',
|
|
10
|
+
INVALID_FIELD_TYPE = 'INVALID_FIELD_TYPE',
|
|
11
|
+
INVALID_NODE_TYPE = 'INVALID_NODE_TYPE',
|
|
12
|
+
INVALID_REFERENCE = 'INVALID_REFERENCE',
|
|
13
|
+
CIRCULAR_DEPENDENCY = 'CIRCULAR_DEPENDENCY',
|
|
14
|
+
EMPTY_NODES = 'EMPTY_NODES',
|
|
15
|
+
DUPLICATE_NODE_ID = 'DUPLICATE_NODE_ID',
|
|
16
|
+
INVALID_TEMPLATE_REFERENCE = 'INVALID_TEMPLATE_REFERENCE',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validation error detail
|
|
21
|
+
*/
|
|
22
|
+
export interface ValidationError {
|
|
23
|
+
/** Error identifier for programmatic handling */
|
|
24
|
+
type: ValidationErrorType;
|
|
25
|
+
/** Human-readable error description */
|
|
26
|
+
description: string;
|
|
27
|
+
/** Expected value or format */
|
|
28
|
+
expected: string;
|
|
29
|
+
/** Actual value encountered */
|
|
30
|
+
actual: string;
|
|
31
|
+
/** Suggested fix */
|
|
32
|
+
fix: string;
|
|
33
|
+
/** Node ID if error is node-specific */
|
|
34
|
+
nodeId?: string;
|
|
35
|
+
/** Template reference if error is template-related (e.g., "{{nodes.step-one.output}}") */
|
|
36
|
+
template?: string;
|
|
37
|
+
/** Referenced node ID if the template references a non-existent node */
|
|
38
|
+
referencedNodeId?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Validation result
|
|
43
|
+
*/
|
|
44
|
+
export interface ValidationResult {
|
|
45
|
+
/** Whether validation passed */
|
|
46
|
+
valid: boolean;
|
|
47
|
+
/** List of validation errors */
|
|
48
|
+
errors: ValidationError[];
|
|
49
|
+
/** Validated workflow name */
|
|
50
|
+
workflowName?: string;
|
|
51
|
+
/** Number of nodes validated */
|
|
52
|
+
nodeCount?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Raw node definition from YAML (before strict typing)
|
|
57
|
+
*/
|
|
58
|
+
export interface RawNodeDefinition {
|
|
59
|
+
id?: string;
|
|
60
|
+
type?: string;
|
|
61
|
+
name?: string;
|
|
62
|
+
config?: Record<string, unknown>;
|
|
63
|
+
depends_on?: string[];
|
|
64
|
+
condition?: string;
|
|
65
|
+
timeout?: unknown;
|
|
66
|
+
retry?: unknown;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Raw workflow definition from YAML
|
|
71
|
+
*/
|
|
72
|
+
export interface RawWorkflowDefinition {
|
|
73
|
+
name?: unknown;
|
|
74
|
+
version?: unknown;
|
|
75
|
+
description?: unknown;
|
|
76
|
+
nodes?: RawNodeDefinition[];
|
|
77
|
+
config?: unknown;
|
|
78
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview WorkflowValidator tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { WorkflowValidator } from './workflow-validator';
|
|
7
|
+
import { ValidationErrorType } from './types';
|
|
8
|
+
|
|
9
|
+
describe('WorkflowValidator', () => {
|
|
10
|
+
const validator = new WorkflowValidator();
|
|
11
|
+
|
|
12
|
+
describe('structure validation', () => {
|
|
13
|
+
it('should fail when name is missing', () => {
|
|
14
|
+
const result = validator.validate({
|
|
15
|
+
nodes: [{ id: 'node-1', type: 'tool', config: { tool: 'echo' } }],
|
|
16
|
+
});
|
|
17
|
+
expect(result.valid).toBe(false);
|
|
18
|
+
expect(result.errors[0].type).toBe(ValidationErrorType.MISSING_REQUIRED_FIELD);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should fail when nodes is empty', () => {
|
|
22
|
+
const result = validator.validate({ name: 'test' });
|
|
23
|
+
expect(result.valid).toBe(false);
|
|
24
|
+
expect(result.errors[0].type).toBe(ValidationErrorType.EMPTY_NODES);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should pass valid workflow', () => {
|
|
28
|
+
const result = validator.validate({
|
|
29
|
+
name: 'test-workflow',
|
|
30
|
+
version: '1.0',
|
|
31
|
+
nodes: [
|
|
32
|
+
{ id: 'node-1', type: 'tool', config: { tool: 'echo', message: 'hello' } },
|
|
33
|
+
],
|
|
34
|
+
});
|
|
35
|
+
expect(result.valid).toBe(true);
|
|
36
|
+
expect(result.errors).toHaveLength(0);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('node validation', () => {
|
|
41
|
+
it('should validate tool node', () => {
|
|
42
|
+
const result = validator.validate({
|
|
43
|
+
name: 'test',
|
|
44
|
+
nodes: [{ id: 'node-1', type: 'tool', config: {} }],
|
|
45
|
+
});
|
|
46
|
+
expect(result.valid).toBe(false);
|
|
47
|
+
expect(result.errors.some(e => e.nodeId === 'node-1')).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should detect agent node using agentName instead of agent_type', () => {
|
|
51
|
+
// 常见错误:使用 agentName 而不是 agent_type
|
|
52
|
+
const result = validator.validate({
|
|
53
|
+
name: 'test',
|
|
54
|
+
nodes: [{
|
|
55
|
+
id: 'agent-1',
|
|
56
|
+
type: 'agent',
|
|
57
|
+
config: {
|
|
58
|
+
agentName: 'my-agent', // 错误:应该是 agent_type
|
|
59
|
+
prompt: 'hello world'
|
|
60
|
+
}
|
|
61
|
+
}],
|
|
62
|
+
});
|
|
63
|
+
expect(result.valid).toBe(false);
|
|
64
|
+
// 应该有 MISSING_REQUIRED_FIELD 错误
|
|
65
|
+
expect(result.errors.some(e =>
|
|
66
|
+
e.type === ValidationErrorType.MISSING_REQUIRED_FIELD &&
|
|
67
|
+
e.description.includes('agent_type')
|
|
68
|
+
)).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should pass agent node with correct agent_type field', () => {
|
|
72
|
+
const result = validator.validate({
|
|
73
|
+
name: 'test',
|
|
74
|
+
nodes: [{
|
|
75
|
+
id: 'agent-1',
|
|
76
|
+
type: 'agent',
|
|
77
|
+
config: {
|
|
78
|
+
agent_type: 'general', // 正确:使用 agent_type
|
|
79
|
+
prompt: 'hello world'
|
|
80
|
+
}
|
|
81
|
+
}],
|
|
82
|
+
});
|
|
83
|
+
expect(result.valid).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('dependency validation', () => {
|
|
88
|
+
it('should fail on invalid reference', () => {
|
|
89
|
+
const result = validator.validate({
|
|
90
|
+
name: 'test',
|
|
91
|
+
nodes: [
|
|
92
|
+
{ id: 'node-1', type: 'tool', config: { tool: 'echo' }, depends_on: ['non-existent'] },
|
|
93
|
+
],
|
|
94
|
+
});
|
|
95
|
+
expect(result.valid).toBe(false);
|
|
96
|
+
expect(result.errors.some(e => e.type === ValidationErrorType.INVALID_REFERENCE)).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should fail on circular dependency', () => {
|
|
100
|
+
const result = validator.validate({
|
|
101
|
+
name: 'test',
|
|
102
|
+
nodes: [
|
|
103
|
+
{ id: 'node-1', type: 'tool', config: { tool: 'echo' }, depends_on: ['node-2'] },
|
|
104
|
+
{ id: 'node-2', type: 'tool', config: { tool: 'echo' }, depends_on: ['node-1'] },
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
expect(result.valid).toBe(false);
|
|
108
|
+
expect(result.errors.some(e => e.type === ValidationErrorType.CIRCULAR_DEPENDENCY)).toBe(true);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('template reference validation', () => {
|
|
113
|
+
it('should pass when template references existing node', () => {
|
|
114
|
+
const result = validator.validate({
|
|
115
|
+
name: 'test',
|
|
116
|
+
nodes: [
|
|
117
|
+
{
|
|
118
|
+
id: 'step-one',
|
|
119
|
+
type: 'tool',
|
|
120
|
+
config: { tool: 'echo', message: 'hello' }
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: 'step-two',
|
|
124
|
+
type: 'tool',
|
|
125
|
+
config: { tool: 'echo', message: '{{step-one.output}}' },
|
|
126
|
+
depends_on: ['step-one']
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
});
|
|
130
|
+
expect(result.valid).toBe(true);
|
|
131
|
+
expect(result.errors.some(e => e.type === ValidationErrorType.INVALID_TEMPLATE_REFERENCE)).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should fail when template references non-existent node', () => {
|
|
135
|
+
const result = validator.validate({
|
|
136
|
+
name: 'test',
|
|
137
|
+
nodes: [
|
|
138
|
+
{
|
|
139
|
+
id: 'step-two',
|
|
140
|
+
type: 'tool',
|
|
141
|
+
config: { tool: 'echo', message: '{{non-existent-node.output}}' },
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
expect(result.valid).toBe(false);
|
|
146
|
+
const templateError = result.errors.find(e => e.type === ValidationErrorType.INVALID_TEMPLATE_REFERENCE);
|
|
147
|
+
expect(templateError).toBeDefined();
|
|
148
|
+
expect(templateError?.referencedNodeId).toBe('non-existent-node');
|
|
149
|
+
expect(templateError?.fix).toContain('non-existent-node');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should fail when template uses nodes.xxx syntax with non-existent node', () => {
|
|
153
|
+
const result = validator.validate({
|
|
154
|
+
name: 'test',
|
|
155
|
+
nodes: [
|
|
156
|
+
{
|
|
157
|
+
id: 'step-two',
|
|
158
|
+
type: 'tool',
|
|
159
|
+
config: { tool: 'echo', message: '{{nodes.missing-step.output}}' },
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
});
|
|
163
|
+
expect(result.valid).toBe(false);
|
|
164
|
+
const templateError = result.errors.find(e => e.type === ValidationErrorType.INVALID_TEMPLATE_REFERENCE);
|
|
165
|
+
expect(templateError).toBeDefined();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should detect similar node ID with hyphen/underscore mismatch', () => {
|
|
169
|
+
const result = validator.validate({
|
|
170
|
+
name: 'test',
|
|
171
|
+
nodes: [
|
|
172
|
+
{
|
|
173
|
+
id: 'step_one', // underscore
|
|
174
|
+
type: 'tool',
|
|
175
|
+
config: { tool: 'echo', message: 'hello' }
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: 'step-two',
|
|
179
|
+
type: 'tool',
|
|
180
|
+
config: { tool: 'echo', message: '{{step-one.output}}' }, // hyphen
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
});
|
|
184
|
+
expect(result.valid).toBe(false);
|
|
185
|
+
const templateError = result.errors.find(e => e.type === ValidationErrorType.INVALID_TEMPLATE_REFERENCE);
|
|
186
|
+
expect(templateError).toBeDefined();
|
|
187
|
+
expect(templateError?.fix).toContain('step_one');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should pass when template references workflow input', () => {
|
|
191
|
+
const result = validator.validate({
|
|
192
|
+
name: 'test',
|
|
193
|
+
nodes: [
|
|
194
|
+
{
|
|
195
|
+
id: 'step-one',
|
|
196
|
+
type: 'tool',
|
|
197
|
+
config: { tool: 'echo', message: '{{input.userName}}' },
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
});
|
|
201
|
+
expect(result.valid).toBe(true);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should pass when using $-style template syntax', () => {
|
|
205
|
+
const result = validator.validate({
|
|
206
|
+
name: 'test',
|
|
207
|
+
nodes: [
|
|
208
|
+
{
|
|
209
|
+
id: 'step-one',
|
|
210
|
+
type: 'tool',
|
|
211
|
+
config: { tool: 'echo', message: 'hello' }
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: 'step-two',
|
|
215
|
+
type: 'tool',
|
|
216
|
+
config: { tool: 'echo', message: '${step-one.output}' },
|
|
217
|
+
depends_on: ['step-one']
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
});
|
|
221
|
+
expect(result.valid).toBe(true);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should fail when $-style template references non-existent node', () => {
|
|
225
|
+
const result = validator.validate({
|
|
226
|
+
name: 'test',
|
|
227
|
+
nodes: [
|
|
228
|
+
{
|
|
229
|
+
id: 'step-two',
|
|
230
|
+
type: 'tool',
|
|
231
|
+
config: { tool: 'echo', message: '${missing-step.output}' },
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
});
|
|
235
|
+
expect(result.valid).toBe(false);
|
|
236
|
+
const templateError = result.errors.find(e => e.type === ValidationErrorType.INVALID_TEMPLATE_REFERENCE);
|
|
237
|
+
expect(templateError).toBeDefined();
|
|
238
|
+
expect(templateError?.referencedNodeId).toBe('missing-step');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should handle nested template references in complex configs', () => {
|
|
242
|
+
const result = validator.validate({
|
|
243
|
+
name: 'test',
|
|
244
|
+
nodes: [
|
|
245
|
+
{
|
|
246
|
+
id: 'fetch-data',
|
|
247
|
+
type: 'tool',
|
|
248
|
+
config: { tool: 'http-request', url: 'https://api.example.com' }
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
id: 'process',
|
|
252
|
+
type: 'tool',
|
|
253
|
+
config: {
|
|
254
|
+
tool: 'transform',
|
|
255
|
+
data: {
|
|
256
|
+
source: '{{fetch-data.output.body}}',
|
|
257
|
+
format: '{{input.format}}',
|
|
258
|
+
missing: '{{non-existent.output}}'
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
depends_on: ['fetch-data']
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
});
|
|
265
|
+
expect(result.valid).toBe(false);
|
|
266
|
+
const templateError = result.errors.find(e =>
|
|
267
|
+
e.type === ValidationErrorType.INVALID_TEMPLATE_REFERENCE &&
|
|
268
|
+
e.referencedNodeId === 'non-existent'
|
|
269
|
+
);
|
|
270
|
+
expect(templateError).toBeDefined();
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
});
|