x_aeon_agents 1.0.28
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.
- checksums.yaml +7 -0
- data/AGENTS.md +136 -0
- data/CHANGELOG.md +398 -0
- data/README.md +579 -0
- data/TODO.md +48 -0
- data/bin/xaa +5 -0
- data/lib/x_aeon_agents/agent_defaults.rb +60 -0
- data/lib/x_aeon_agents/agent_options.rb +58 -0
- data/lib/x_aeon_agents/agents/coder_agent.rb +82 -0
- data/lib/x_aeon_agents/agents/committer_agent.rb +71 -0
- data/lib/x_aeon_agents/agents/developer_agent.rb +235 -0
- data/lib/x_aeon_agents/agents/diff_interpreter_agent.rb +80 -0
- data/lib/x_aeon_agents/agents/documenter_agent.rb +44 -0
- data/lib/x_aeon_agents/agents/executor_agent.rb +9 -0
- data/lib/x_aeon_agents/agents/feedback_analyst_agent.rb +62 -0
- data/lib/x_aeon_agents/agents/gh_comments.gql +64 -0
- data/lib/x_aeon_agents/agents/git_diff_interpreter_agent.rb +57 -0
- data/lib/x_aeon_agents/agents/issue_implementer_agent.rb +88 -0
- data/lib/x_aeon_agents/agents/one_line_code_diff_summarizer_agent.rb +57 -0
- data/lib/x_aeon_agents/agents/plan_generator_agent.rb +51 -0
- data/lib/x_aeon_agents/agents/planner_agent.rb +89 -0
- data/lib/x_aeon_agents/agents/pull_request_creator_agent.rb +144 -0
- data/lib/x_aeon_agents/agents/readme/about_analyzer_agent.rb +55 -0
- data/lib/x_aeon_agents/agents/readme/contributing_agent.rb +47 -0
- data/lib/x_aeon_agents/agents/readme/development_agent.rb +54 -0
- data/lib/x_aeon_agents/agents/readme/documentation_agent.rb +43 -0
- data/lib/x_aeon_agents/agents/readme/features_agent.rb +45 -0
- data/lib/x_aeon_agents/agents/readme/how_it_works_agent.rb +47 -0
- data/lib/x_aeon_agents/agents/readme/license_agent.rb +43 -0
- data/lib/x_aeon_agents/agents/readme/public_api_agent.rb +44 -0
- data/lib/x_aeon_agents/agents/readme/quick_start_agent.rb +50 -0
- data/lib/x_aeon_agents/agents/readme/requirements_agent.rb +43 -0
- data/lib/x_aeon_agents/agents/readme_generator_agent.rb +438 -0
- data/lib/x_aeon_agents/agents/review_resolver_agent.rb +236 -0
- data/lib/x_aeon_agents/agents/review_responder_agent.rb +64 -0
- data/lib/x_aeon_agents/agents/skill_generator_agent.rb +95 -0
- data/lib/x_aeon_agents/agents/skill_installer_agent.rb +103 -0
- data/lib/x_aeon_agents/agents/task_starter_agent.rb +66 -0
- data/lib/x_aeon_agents/agents/tester_agent.rb +44 -0
- data/lib/x_aeon_agents/cli.rb +384 -0
- data/lib/x_aeon_agents/config.rb +110 -0
- data/lib/x_aeon_agents/gen_helpers.rb +270 -0
- data/lib/x_aeon_agents/helpers.rb +211 -0
- data/lib/x_aeon_agents/logger.rb +21 -0
- data/lib/x_aeon_agents/providers/cline.rb +78 -0
- data/lib/x_aeon_agents/version.rb +6 -0
- data/lib/x_aeon_agents.rb +38 -0
- metadata +296 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module XAeonAgents
|
|
2
|
+
module Agents
|
|
3
|
+
# Agent responsible for generating a reply to a review comment
|
|
4
|
+
class ReviewResponderAgent < ComposableAgents::Cline::Agent
|
|
5
|
+
prepend AgentDefaults
|
|
6
|
+
|
|
7
|
+
# Define input artifacts contracts
|
|
8
|
+
#
|
|
9
|
+
# @return [Hash{Symbol => Object}] Set of input artifacts description, per artifact name
|
|
10
|
+
def input_artifacts_contracts
|
|
11
|
+
super.merge(
|
|
12
|
+
{
|
|
13
|
+
conversations: {
|
|
14
|
+
description: 'All PR conversations and comments to be considered (context)',
|
|
15
|
+
type: :json
|
|
16
|
+
},
|
|
17
|
+
open_comment_for_reply: {
|
|
18
|
+
description: 'The exact comment to be replied to',
|
|
19
|
+
type: :json
|
|
20
|
+
},
|
|
21
|
+
requirements: 'The requirements that have been implemented (or "No requirements")',
|
|
22
|
+
plan: 'The implementation plan that was used to implement those requirements (or "No implementation plan")',
|
|
23
|
+
files_diffs: 'The code changes from implement_requirements workflow (or "No changes")'
|
|
24
|
+
}
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Define output artifacts contracts
|
|
29
|
+
#
|
|
30
|
+
# @return [Hash{Symbol => Object}] Set of output artifacts description, per artifact name
|
|
31
|
+
def output_artifacts_contracts
|
|
32
|
+
super.merge(
|
|
33
|
+
reply: {
|
|
34
|
+
description: 'The exact reply text to post',
|
|
35
|
+
type: :text
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Constructor
|
|
41
|
+
#
|
|
42
|
+
# @param agent_params [Hash{Symbol => Object}] Extra agent parameters
|
|
43
|
+
def initialize(**agent_params)
|
|
44
|
+
super(
|
|
45
|
+
name: 'ReviewResponder',
|
|
46
|
+
role: 'You are a review responder agent. Your role is to reply to feedback, ' \
|
|
47
|
+
'taking into account the feedback itself, and the work that has been done because of this feedback.',
|
|
48
|
+
objective: 'Generate a reply to a review comment',
|
|
49
|
+
**agent_params
|
|
50
|
+
)
|
|
51
|
+
self.constraints = <<~EO_CONSTRAINTS
|
|
52
|
+
- You are in read-only mode.
|
|
53
|
+
- Do NOT modify or write any file.
|
|
54
|
+
- The implementation work is already complete (captured in the artifacts).
|
|
55
|
+
- ONLY focus on addressing the specific comment of the `#{artifact_ref(:open_comment_for_reply)}` artifact appropriately.
|
|
56
|
+
- Do NOT answer or reply to any other comment.
|
|
57
|
+
- You already have ALL the information required.
|
|
58
|
+
- You MUST NOT ask follow-up questions.
|
|
59
|
+
- You MUST NOT ask for user confirmation.
|
|
60
|
+
EO_CONSTRAINTS
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
require 'pathname'
|
|
3
|
+
|
|
4
|
+
module XAeonAgents
|
|
5
|
+
module Agents
|
|
6
|
+
# Agent responsible for generating skill files from ERB templates.
|
|
7
|
+
class SkillGeneratorAgent < ComposableAgents::Agent
|
|
8
|
+
prepend AgentDefaults
|
|
9
|
+
|
|
10
|
+
# Define input artifacts contracts
|
|
11
|
+
#
|
|
12
|
+
# @return [Hash{Symbol => Object}] Set of input artifacts description, per artifact name
|
|
13
|
+
def input_artifacts_contracts
|
|
14
|
+
{
|
|
15
|
+
output_dir: 'Output directory for generated skills',
|
|
16
|
+
skill_names: {
|
|
17
|
+
description: 'Optional list of skill names to generate. If nil or empty, all skills are generated.',
|
|
18
|
+
optional: true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Define output artifacts contracts
|
|
24
|
+
#
|
|
25
|
+
# @return [Hash{Symbol => Object}] Set of output artifacts description, per artifact name
|
|
26
|
+
def output_artifacts_contracts
|
|
27
|
+
{ success: 'Whether the skill generation was successful' }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Execute the agent to generate skill files from ERB templates.
|
|
31
|
+
#
|
|
32
|
+
# @param output_dir [String] Output directory for generated skills
|
|
33
|
+
# @param skill_names [Array<String>, nil] Optional list of skill names to generate.
|
|
34
|
+
# Supports comma-separated values within each element. If nil or empty, all skills are generated.
|
|
35
|
+
# @return [Hash{Symbol => Object}] Output artifacts content
|
|
36
|
+
def run(output_dir: 'skills', skill_names: nil)
|
|
37
|
+
transformations = {
|
|
38
|
+
'.erb' => proc { |src_file| GenHelpers::ErbEvaluator.new(src_file).result }
|
|
39
|
+
}.freeze
|
|
40
|
+
|
|
41
|
+
src_dir = File.expand_path('skills.src')
|
|
42
|
+
src_pathname = Pathname.new(src_dir)
|
|
43
|
+
dest_dir = File.expand_path(output_dir)
|
|
44
|
+
|
|
45
|
+
FileUtils.mkdir_p(dest_dir)
|
|
46
|
+
|
|
47
|
+
# Normalize skill_names: flatten, split by comma, strip, compact, uniq
|
|
48
|
+
normalized_skill_names = (skill_names || [])
|
|
49
|
+
.flat_map { |name| name.split(',') }
|
|
50
|
+
.map(&:strip)
|
|
51
|
+
.reject(&:empty?)
|
|
52
|
+
.uniq
|
|
53
|
+
|
|
54
|
+
failed = false
|
|
55
|
+
Dir.glob(File.join(src_dir, '**', '*'), File::FNM_DOTMATCH)
|
|
56
|
+
.select { |f| File.file?(f) && File.basename(f) != '.skill_config.yml' }
|
|
57
|
+
.each do |src_file|
|
|
58
|
+
relative_path = Pathname.new(src_file).relative_path_from(src_pathname).to_s
|
|
59
|
+
# Determine the top-level skill directory for this file.
|
|
60
|
+
# Skip files whose top-level skill directory is not in the requested list
|
|
61
|
+
next if !normalized_skill_names.empty? && !normalized_skill_names.include?(relative_path.split('/').first)
|
|
62
|
+
|
|
63
|
+
file_ext = File.extname(relative_path)
|
|
64
|
+
dst_file = File.join(
|
|
65
|
+
dest_dir,
|
|
66
|
+
transformations.key?(file_ext) ? relative_path.sub(/#{Regexp.escape(file_ext)}$/, '') : relative_path
|
|
67
|
+
)
|
|
68
|
+
puts "Processing: #{relative_path}"
|
|
69
|
+
puts " Output: #{dst_file}"
|
|
70
|
+
begin
|
|
71
|
+
FileUtils.mkdir_p(File.dirname(dst_file))
|
|
72
|
+
if transformations.key?(file_ext)
|
|
73
|
+
File.write(dst_file, transformations[file_ext].call(src_file))
|
|
74
|
+
else
|
|
75
|
+
FileUtils.cp(src_file, dst_file)
|
|
76
|
+
end
|
|
77
|
+
puts ' Status: ✓ Processed successfully'
|
|
78
|
+
rescue StandardError => e
|
|
79
|
+
puts " Status: ✗ Error - #{e.message}\n #{e.backtrace.first}"
|
|
80
|
+
failed = true
|
|
81
|
+
end
|
|
82
|
+
puts
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
puts
|
|
86
|
+
if failed
|
|
87
|
+
puts 'Skills generated with some errors (see above).'
|
|
88
|
+
else
|
|
89
|
+
puts 'Skills generated successfully.'
|
|
90
|
+
end
|
|
91
|
+
{ success: !failed }
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
require 'front_matter_parser'
|
|
2
|
+
|
|
3
|
+
module XAeonAgents
|
|
4
|
+
module Agents
|
|
5
|
+
# Agent responsible for installing skills from the .skills manifest.
|
|
6
|
+
class SkillInstallerAgent < ComposableAgents::Agent
|
|
7
|
+
prepend AgentDefaults
|
|
8
|
+
|
|
9
|
+
# Define input artifacts contracts
|
|
10
|
+
#
|
|
11
|
+
# @return [Hash{Symbol => Object}] Set of input artifacts description, per artifact name
|
|
12
|
+
def input_artifacts_contracts
|
|
13
|
+
{ agent: 'Agent name to be used to install skills' }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Execute the agent to install skills from the .skills manifest.
|
|
17
|
+
#
|
|
18
|
+
# @param agent [String] Agent name to be used to install skills
|
|
19
|
+
# @return [Hash{Symbol => Object}] Output artifacts content
|
|
20
|
+
def run(agent: 'cline')
|
|
21
|
+
agent_name = agent.to_sym
|
|
22
|
+
original_no_color = ENV.fetch('NO_COLOR', nil)
|
|
23
|
+
ENV['NO_COLOR'] = '1'
|
|
24
|
+
begin
|
|
25
|
+
list_lines = `skillkit manifest`.split("\n")
|
|
26
|
+
ensure
|
|
27
|
+
ENV['NO_COLOR'] = original_no_color
|
|
28
|
+
end
|
|
29
|
+
list_lines = list_lines[(list_lines.index('Skills:') + 1)..]
|
|
30
|
+
list_lines[0..(list_lines.index('') - 1)].each_slice(2) do |repo_desc, skills_desc|
|
|
31
|
+
install_skills_recursive(
|
|
32
|
+
repo_desc[4..].strip,
|
|
33
|
+
skills_desc[12..].split(',').map(&:strip),
|
|
34
|
+
agent_name
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
puts
|
|
39
|
+
puts 'Skills identified in the skillkit manifest and their dependencies have been installed successfully'
|
|
40
|
+
{ installed: true }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# Install skills using skillkit, including dependencies recursively.
|
|
46
|
+
#
|
|
47
|
+
# @param repo [String] Repository
|
|
48
|
+
# @param skills [Array<String>] Skills to install
|
|
49
|
+
# @param agent [Symbol] Agent to use
|
|
50
|
+
def install_skills_recursive(repo, skills, agent)
|
|
51
|
+
return if skills.empty?
|
|
52
|
+
|
|
53
|
+
agents_config = {
|
|
54
|
+
cline: { skills_dir: '.cline/skills' }
|
|
55
|
+
}
|
|
56
|
+
skills_dir = agents_config[agent][:skills_dir]
|
|
57
|
+
|
|
58
|
+
puts "Install skills #{repo} / #{skills.join(',')}..."
|
|
59
|
+
system "skillkit install #{repo} --yes --skills=#{skills.join(',')} --agent=#{agent}", exception: true
|
|
60
|
+
fix_skills_metadata(skills, agent)
|
|
61
|
+
|
|
62
|
+
# Resolve dependencies
|
|
63
|
+
deps_per_repo = {}
|
|
64
|
+
skills.each do |skill|
|
|
65
|
+
deps = FrontMatterParser::Parser.parse_file("#{skills_dir}/#{skill}/SKILL.md").front_matter.dig('metadata', 'dependencies')
|
|
66
|
+
next if deps.nil?
|
|
67
|
+
|
|
68
|
+
deps.each do |skill_dep|
|
|
69
|
+
skill_dep = "#{repo}:#{skill_dep}" unless skill_dep.include?(':')
|
|
70
|
+
skill_dep_repo, skill_dep_name = skill_dep.split(':')
|
|
71
|
+
unless File.exist?("#{skills_dir}/#{skill_dep_name}/SKILL.md")
|
|
72
|
+
deps_per_repo[skill_dep_repo] ||= []
|
|
73
|
+
deps_per_repo[skill_dep_repo] << skill_dep_name unless deps_per_repo[skill_dep_repo].include?(skill_dep_name)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
deps_per_repo.each { |dep_repo, dep_skills| install_skills_recursive(dep_repo, dep_skills, agent) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Fix the .skillkit.json subpath property after skillkit install.
|
|
82
|
+
#
|
|
83
|
+
# @param skills [Array<String>] Installed skills
|
|
84
|
+
# @param agent [Symbol] Agent used
|
|
85
|
+
def fix_skills_metadata(skills, agent)
|
|
86
|
+
require 'json'
|
|
87
|
+
|
|
88
|
+
agents_config = { cline: { skills_dir: '.cline/skills' } }
|
|
89
|
+
skills_dir = agents_config[agent][:skills_dir]
|
|
90
|
+
|
|
91
|
+
skills.each do |skill_name|
|
|
92
|
+
json_file = "#{skills_dir}/#{skill_name}/.skillkit.json"
|
|
93
|
+
json = JSON.parse(File.read(json_file))
|
|
94
|
+
next if json['subpath'].start_with?('skills/')
|
|
95
|
+
|
|
96
|
+
json['subpath'] = "skills/#{json['subpath']}"
|
|
97
|
+
puts "Fix subpath of #{json_file}"
|
|
98
|
+
File.write(json_file, JSON.pretty_generate(json))
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module XAeonAgents
|
|
2
|
+
module Agents
|
|
3
|
+
# Agent responsible for opening a new git worktree for a task.
|
|
4
|
+
class TaskStarterAgent < ComposableAgents::Agent
|
|
5
|
+
# Exception raised when the target worktree directory is invalid
|
|
6
|
+
# (already exists but is not a git worktree, or is a worktree on a
|
|
7
|
+
# different branch than the requested one).
|
|
8
|
+
class TaskStarterError < StandardError
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
prepend AgentDefaults
|
|
12
|
+
|
|
13
|
+
# Define input artifacts contracts
|
|
14
|
+
#
|
|
15
|
+
# @return [Hash{Symbol => Object}] Set of input artifacts description, per artifact name
|
|
16
|
+
def input_artifacts_contracts
|
|
17
|
+
{ branch_name: 'The name of the git branch to create worktree for' }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Define output artifacts contracts
|
|
21
|
+
#
|
|
22
|
+
# @return [Hash{Symbol => Object}] Set of output artifacts description, per artifact name
|
|
23
|
+
def output_artifacts_contracts
|
|
24
|
+
{ worktree_dir: 'The directory where the worktree was created' }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Execute the agent to open a new git worktree for a feature branch.
|
|
28
|
+
#
|
|
29
|
+
# @param branch_name [String] Name of the git branch to create worktree for
|
|
30
|
+
# @return [Hash{Symbol => Object}] Output artifacts content
|
|
31
|
+
def run(branch_name:)
|
|
32
|
+
dir = ".worktrees/#{branch_name.tr('/', '_')}"
|
|
33
|
+
puts "Setting worktree #{dir} to work on branch #{branch_name}..."
|
|
34
|
+
# Create the branch if it does not exist (without checking it out)
|
|
35
|
+
Helpers.git.branch(branch_name).create unless Helpers.git.branches.any? { |branch| branch.name == branch_name }
|
|
36
|
+
# Create the git worktree only if it does not exist yet (idempotent)
|
|
37
|
+
if File.directory?(dir)
|
|
38
|
+
# The directory already exists: it must be a git worktree for the requested branch.
|
|
39
|
+
# A git worktree has a `.git` file (a gitdir pointer), not a `.git` directory.
|
|
40
|
+
unless File.file?(File.join(dir, '.git'))
|
|
41
|
+
raise TaskStarterError, <<~EO_MSG.strip
|
|
42
|
+
Directory '#{dir}' already exists but is not a git worktree (no '.git' pointer file found).
|
|
43
|
+
Please remove it or choose a different branch name.
|
|
44
|
+
EO_MSG
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# The directory is a worktree: ensure it tracks the requested branch.
|
|
48
|
+
worktree_branch = Git.open(dir).current_branch
|
|
49
|
+
if worktree_branch != branch_name
|
|
50
|
+
raise TaskStarterError, <<~EO_MSG.strip
|
|
51
|
+
Directory '#{dir}' is already a git worktree on branch '#{worktree_branch}', which differs from the requested branch '#{branch_name}'.
|
|
52
|
+
Please choose a different branch name or remove the existing worktree.
|
|
53
|
+
EO_MSG
|
|
54
|
+
end
|
|
55
|
+
else
|
|
56
|
+
# Call git worktree add on existing branches only
|
|
57
|
+
Helpers.git.lib.worktree_add(dir, branch_name)
|
|
58
|
+
end
|
|
59
|
+
# Push to remote if branch doesn't exist there yet
|
|
60
|
+
Helpers.git.push(Helpers.github_remote, branch_name, set_upstream: true)
|
|
61
|
+
Helpers.run_cmd("VSCodium.exe \"#{dir}\"")
|
|
62
|
+
{ worktree_dir: dir }
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module XAeonAgents
|
|
2
|
+
module Agents
|
|
3
|
+
# Agent responsible for fixing regressions induced by new features or fixes, while keeping initial requirements and implementation plan in mind.
|
|
4
|
+
# If decisions in the implementation plan prevent fixing regressions, modify the implementation plan and report those modifications.
|
|
5
|
+
class TesterAgent < ComposableAgents::Cline::Agent
|
|
6
|
+
prepend AgentDefaults
|
|
7
|
+
|
|
8
|
+
# Define input artifacts contracts
|
|
9
|
+
#
|
|
10
|
+
# @return [Hash{Symbol => Object}] Set of input artifacts description, per artifact name
|
|
11
|
+
def input_artifacts_contracts
|
|
12
|
+
super.merge(
|
|
13
|
+
requirements: 'The initial requirements',
|
|
14
|
+
plan: 'The implementation plan devised from the requirements',
|
|
15
|
+
files_diffs: 'The full list of files changes and differences that have been done to implement the initial ' \
|
|
16
|
+
'requirements following the implementation plan',
|
|
17
|
+
tests_output: 'The output of running the whole tests suite',
|
|
18
|
+
tests_cmd: 'The command line to be used to run the whole tests suite'
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Define output artifacts contracts
|
|
23
|
+
#
|
|
24
|
+
# @return [Hash{Symbol => Object}] Set of output artifacts description, per artifact name
|
|
25
|
+
def output_artifacts_contracts
|
|
26
|
+
super.merge(plan_modifications: 'The modification or divergence you considered from the implementation plan')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Constructor
|
|
30
|
+
#
|
|
31
|
+
# @param agent_params [Hash{Symbol => Object}] Extra agent parameters
|
|
32
|
+
def initialize(**agent_params)
|
|
33
|
+
super(
|
|
34
|
+
name: 'Tester',
|
|
35
|
+
objective: <<~EO_OBJECTIVE,
|
|
36
|
+
Fix any regression that has been induced by new features or fixes, while keeping the initial requirements and implementation plan in mind.
|
|
37
|
+
If the decisions taken in the implementation plan prevent you from fixing regressions, modify the implementation plan and report those modifications to the user.
|
|
38
|
+
EO_OBJECTIVE
|
|
39
|
+
**agent_params
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|