agentf 0.4.7 → 0.5.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.
- checksums.yaml +4 -4
- data/lib/agentf/agents/architect.rb +4 -0
- data/lib/agentf/agents/base.rb +29 -1
- data/lib/agentf/agents/debugger.rb +31 -8
- data/lib/agentf/agents/designer.rb +18 -7
- data/lib/agentf/agents/documenter.rb +6 -0
- data/lib/agentf/agents/explorer.rb +30 -11
- data/lib/agentf/agents/reviewer.rb +5 -0
- data/lib/agentf/agents/security.rb +24 -14
- data/lib/agentf/agents/specialist.rb +31 -17
- data/lib/agentf/agents/tester.rb +46 -7
- data/lib/agentf/cli/agent.rb +95 -0
- data/lib/agentf/cli/eval.rb +203 -0
- data/lib/agentf/cli/install.rb +7 -0
- data/lib/agentf/cli/memory.rb +82 -30
- data/lib/agentf/cli/router.rb +15 -3
- data/lib/agentf/cli/update.rb +9 -2
- data/lib/agentf/commands/memory_reviewer.rb +10 -2
- data/lib/agentf/commands/metrics.rb +16 -14
- data/lib/agentf/commands/registry.rb +28 -0
- data/lib/agentf/evals/report.rb +134 -0
- data/lib/agentf/evals/runner.rb +771 -0
- data/lib/agentf/evals/scenario.rb +211 -0
- data/lib/agentf/installer.rb +486 -348
- data/lib/agentf/mcp/server.rb +291 -49
- data/lib/agentf/memory.rb +46 -53
- data/lib/agentf/service/providers.rb +10 -62
- data/lib/agentf/version.rb +1 -1
- data/lib/agentf/workflow_engine.rb +204 -73
- data/lib/agentf.rb +9 -3
- metadata +8 -3
- data/lib/agentf/packs.rb +0 -74
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6072a9a176fba8e2376b10cc125d78d450057c90c4aa5d95158797429504d1f5
|
|
4
|
+
data.tar.gz: f66dc58d0030e1ba289d3f7046811190b9ae818aca61021729c9da12d19e710c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0397d2603cc060239303773b370f14cc9585b94b4ba5109207ac7f705b18aec244f83443478c15c1ed40be4650c20f9f13d669f224dfc916374e924da4ab6cb1'
|
|
7
|
+
data.tar.gz: a8f7562d3bc1f8bd43897b4692c18a4096509d2c4ce413eaa1c992c61c2a7fada7c0c52228c543d62387c9a56c37672819f1becfd39240bc83066491947678f8
|
data/lib/agentf/agents/base.rb
CHANGED
|
@@ -61,7 +61,15 @@ module Agentf
|
|
|
61
61
|
)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
# Unified execution entrypoint for all agents. Concrete agents must
|
|
65
|
+
# implement `execute(task:, context:, agents:, commands:, logger:)`.
|
|
66
|
+
def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
|
|
67
|
+
raise NotImplementedError, "#{self.class} must implement #execute"
|
|
68
|
+
end
|
|
69
|
+
|
|
64
70
|
def log(message)
|
|
71
|
+
return if ENV["AGENTF_SUPPRESS_AGENT_LOGS"] == "true"
|
|
72
|
+
|
|
65
73
|
puts "\n[#{@name}] #{message}"
|
|
66
74
|
end
|
|
67
75
|
|
|
@@ -83,8 +91,28 @@ module Agentf
|
|
|
83
91
|
result: result
|
|
84
92
|
)
|
|
85
93
|
|
|
86
|
-
|
|
94
|
+
result
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Helper to centralize memory write confirmation handling.
|
|
98
|
+
# Yields a block that performs the memory write. If the memory layer
|
|
99
|
+
# requires confirmation (ask_first policy) a structured hash is
|
|
100
|
+
# returned with confirmation details so agents can merge that into
|
|
101
|
+
# their own return payloads or let the orchestrator handle prompting.
|
|
102
|
+
def safe_memory_write(attempted: {})
|
|
103
|
+
begin
|
|
104
|
+
yield
|
|
105
|
+
rescue Agentf::Memory::RedisMemory::ConfirmationRequired => e
|
|
106
|
+
log "[MEMORY] Confirmation required: #{e.message} -- details=#{e.details.inspect}"
|
|
107
|
+
{
|
|
108
|
+
"confirmation_required" => true,
|
|
109
|
+
"confirmation_details" => e.details,
|
|
110
|
+
"attempted" => attempted,
|
|
111
|
+
"confirmed_write_token" => "confirmed",
|
|
112
|
+
"confirmation_prompt" => "Ask the user whether to save this memory. If they approve, rerun the same tool with confirmedWrite=confirmed. If they decline, do not retry."
|
|
113
|
+
}
|
|
87
114
|
end
|
|
88
115
|
end
|
|
116
|
+
end
|
|
89
117
|
end
|
|
90
118
|
end
|
|
@@ -66,14 +66,32 @@ module Agentf
|
|
|
66
66
|
|
|
67
67
|
analysis = @commands.parse_error(error)
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
res = safe_memory_write(attempted: { action: "store_lesson", title: "Debugged: #{error[0..50]}...", tags: ["debugging", "error", "fix"], agent: name }) do
|
|
70
|
+
memory.store_episode(
|
|
71
|
+
type: "lesson",
|
|
72
|
+
title: "Debugged: #{error[0..50]}...",
|
|
73
|
+
description: "Root cause: #{analysis.possible_causes.first}. Fix: #{analysis.suggested_fix}",
|
|
74
|
+
context: context.to_s,
|
|
75
|
+
tags: ["debugging", "error", "fix"],
|
|
76
|
+
agent: name
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if res.is_a?(Hash) && res["confirmation_required"]
|
|
81
|
+
log "Root cause: #{analysis.possible_causes.first}"
|
|
82
|
+
log "Suggested fix: #{analysis.suggested_fix}"
|
|
83
|
+
return {
|
|
84
|
+
"success" => false,
|
|
85
|
+
"confirmation_required" => true,
|
|
86
|
+
"confirmation_details" => res["confirmation_details"],
|
|
87
|
+
"analysis" => {
|
|
88
|
+
"error_type" => analysis.error_type,
|
|
89
|
+
"possible_causes" => analysis.possible_causes,
|
|
90
|
+
"suggested_fix" => analysis.suggested_fix,
|
|
91
|
+
"stack_trace" => analysis.stack_trace
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
end
|
|
77
95
|
|
|
78
96
|
log "Root cause: #{analysis.possible_causes.first}"
|
|
79
97
|
log "Suggested fix: #{analysis.suggested_fix}"
|
|
@@ -90,6 +108,11 @@ module Agentf
|
|
|
90
108
|
}
|
|
91
109
|
end
|
|
92
110
|
end
|
|
111
|
+
|
|
112
|
+
def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
|
|
113
|
+
error_text = task.is_a?(String) ? task : context["error"]
|
|
114
|
+
diagnose(error_text, context: context)
|
|
115
|
+
end
|
|
93
116
|
end
|
|
94
117
|
end
|
|
95
118
|
end
|
|
@@ -64,13 +64,19 @@ module Agentf
|
|
|
64
64
|
|
|
65
65
|
spec = @commands.generate_component("GeneratedComponent", design_spec)
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
res = safe_memory_write(attempted: { action: "store_success", title: "Implemented design: #{design_spec}", tags: ["design", "ui", framework], agent: name }) do
|
|
68
|
+
memory.store_success(
|
|
69
|
+
title: "Implemented design: #{design_spec}",
|
|
70
|
+
description: "Created #{spec.name} in #{spec.framework}",
|
|
71
|
+
context: "Framework: #{framework}",
|
|
72
|
+
tags: ["design", "ui", framework],
|
|
73
|
+
agent: name
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if res.is_a?(Hash) && res["confirmation_required"]
|
|
78
|
+
return { "design_spec" => design_spec, "component" => spec.name, "framework" => framework, "generated_code" => spec.code, "success" => true }.merge(res)
|
|
79
|
+
end
|
|
74
80
|
|
|
75
81
|
log "Created component: #{spec.name}"
|
|
76
82
|
|
|
@@ -83,6 +89,11 @@ module Agentf
|
|
|
83
89
|
}
|
|
84
90
|
end
|
|
85
91
|
end
|
|
92
|
+
|
|
93
|
+
def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
|
|
94
|
+
spec = task.is_a?(String) ? task : context["design_spec"]
|
|
95
|
+
implement_design(spec, framework: context["framework"] || "react")
|
|
96
|
+
end
|
|
86
97
|
end
|
|
87
98
|
end
|
|
88
99
|
end
|
|
@@ -69,6 +69,12 @@ module Agentf
|
|
|
69
69
|
"total_memories" => memories.size
|
|
70
70
|
}
|
|
71
71
|
end
|
|
72
|
+
|
|
73
|
+
def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
|
|
74
|
+
project = task.is_a?(String) ? task : (context["project_name"] || "project")
|
|
75
|
+
sync_docs(project)
|
|
76
|
+
end
|
|
77
|
+
|
|
72
78
|
end
|
|
73
79
|
end
|
|
74
80
|
end
|
|
@@ -11,8 +11,8 @@ module Agentf
|
|
|
11
11
|
COMMANDS = %w[glob grep read_file].freeze
|
|
12
12
|
MEMORY_CONCEPTS = {
|
|
13
13
|
"reads" => [],
|
|
14
|
-
"writes" => ["
|
|
15
|
-
"policy" => "Store
|
|
14
|
+
"writes" => ["store_lesson"],
|
|
15
|
+
"policy" => "Store research findings as lessons after user confirmation."
|
|
16
16
|
}.freeze
|
|
17
17
|
|
|
18
18
|
def self.description
|
|
@@ -46,7 +46,7 @@ module Agentf
|
|
|
46
46
|
def self.policy_boundaries
|
|
47
47
|
{
|
|
48
48
|
"always" => ["Return concrete file evidence"],
|
|
49
|
-
"ask_first" => ["Scanning outside configured base path", "Persisting
|
|
49
|
+
"ask_first" => ["Scanning outside configured base path", "Persisting research lessons to memory"],
|
|
50
50
|
"never" => ["Mutate project files during exploration"],
|
|
51
51
|
"required_inputs" => [],
|
|
52
52
|
"required_outputs" => ["files", "context_gathered"]
|
|
@@ -63,19 +63,38 @@ module Agentf
|
|
|
63
63
|
|
|
64
64
|
files = @commands.glob(query, file_types: nil)
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
66
|
+
res = safe_memory_write(attempted: { action: "store_lesson", title: "Research finding: #{query}", tags: ["research", "exploration"], agent: name }) do
|
|
67
|
+
memory.store_lesson(
|
|
68
|
+
title: "Research finding: #{query}",
|
|
69
|
+
description: "Found #{files.size} relevant files during exploration",
|
|
70
|
+
context: "Search pattern: #{file_pattern || 'all files'}",
|
|
71
|
+
tags: ["research", "exploration"],
|
|
72
|
+
agent: name
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if res.is_a?(Hash) && res["confirmation_required"]
|
|
77
|
+
log "Memory confirmation required during exploration: #{res['confirmation_details'].inspect}"
|
|
78
|
+
return {
|
|
79
|
+
"files" => files,
|
|
80
|
+
"context_gathered" => true,
|
|
81
|
+
"confirmation_required" => true,
|
|
82
|
+
"confirmation_details" => res["confirmation_details"],
|
|
83
|
+
"attempted" => res["attempted"],
|
|
84
|
+
"confirmed_write_token" => res["confirmed_write_token"],
|
|
85
|
+
"confirmation_prompt" => res["confirmation_prompt"]
|
|
86
|
+
}
|
|
87
|
+
end
|
|
74
88
|
|
|
75
89
|
log "Found #{files.size} files"
|
|
76
90
|
|
|
77
91
|
{ "query" => query, "files" => files, "context_gathered" => true }
|
|
78
92
|
end
|
|
93
|
+
|
|
94
|
+
def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
|
|
95
|
+
query = context["explore_query"] || task || "*.rb"
|
|
96
|
+
explore(query, file_pattern: context["file_pattern"])
|
|
97
|
+
end
|
|
79
98
|
end
|
|
80
99
|
end
|
|
81
100
|
end
|
|
@@ -77,6 +77,11 @@ module Agentf
|
|
|
77
77
|
{ "approved" => approved, "issues" => issues }
|
|
78
78
|
end
|
|
79
79
|
end
|
|
80
|
+
|
|
81
|
+
def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
|
|
82
|
+
subtask = task.is_a?(Hash) ? task : context["execution"] || {}
|
|
83
|
+
review(subtask)
|
|
84
|
+
end
|
|
80
85
|
end
|
|
81
86
|
end
|
|
82
87
|
end
|
|
@@ -66,27 +66,37 @@ module Agentf
|
|
|
66
66
|
summary = summarize_findings(findings)
|
|
67
67
|
|
|
68
68
|
if findings["issues"].empty?
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
69
|
+
res = safe_memory_write(attempted: { action: "store_success", title: "Security review passed", tags: ["security", "pass"], agent: name }) do
|
|
70
|
+
memory.store_success(
|
|
71
|
+
title: "Security review passed",
|
|
72
|
+
description: summary,
|
|
73
|
+
context: task,
|
|
74
|
+
tags: ["security", "pass"],
|
|
75
|
+
agent: name
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
return findings.merge(res) if res.is_a?(Hash) && res["confirmation_required"]
|
|
76
79
|
else
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
res = safe_memory_write(attempted: { action: "store_pitfall", title: "Security findings detected", tags: ["security", "warning"], agent: name }) do
|
|
81
|
+
memory.store_pitfall(
|
|
82
|
+
title: "Security findings detected",
|
|
83
|
+
description: summary,
|
|
84
|
+
context: task,
|
|
85
|
+
tags: ["security", "warning"],
|
|
86
|
+
agent: name
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
return findings.merge(res) if res.is_a?(Hash) && res["confirmation_required"]
|
|
84
90
|
end
|
|
85
91
|
|
|
86
92
|
findings.merge("best_practices" => @commands.best_practices)
|
|
87
93
|
end
|
|
88
94
|
end
|
|
89
95
|
|
|
96
|
+
def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
|
|
97
|
+
assess(task: task, context: context)
|
|
98
|
+
end
|
|
99
|
+
|
|
90
100
|
private
|
|
91
101
|
|
|
92
102
|
def summarize_findings(findings)
|
|
@@ -52,7 +52,9 @@ module Agentf
|
|
|
52
52
|
}
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
def execute(
|
|
55
|
+
def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
|
|
56
|
+
subtask = task.is_a?(Hash) ? task : (context["current_subtask"] || { "description" => task })
|
|
57
|
+
|
|
56
58
|
normalized_subtask = subtask.merge(
|
|
57
59
|
"id" => subtask["id"] || "ad-hoc",
|
|
58
60
|
"description" => subtask["description"] || "Execute implementation step"
|
|
@@ -64,23 +66,35 @@ module Agentf
|
|
|
64
66
|
success = normalized_subtask.fetch("success", true)
|
|
65
67
|
|
|
66
68
|
if success
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
69
|
+
res = safe_memory_write(attempted: { action: "store_success", title: "Completed: #{normalized_subtask['description']}", tags: ["implementation", normalized_subtask.fetch("language", "general")], agent: name }) do
|
|
70
|
+
memory.store_success(
|
|
71
|
+
title: "Completed: #{normalized_subtask['description']}",
|
|
72
|
+
description: "Successfully executed subtask #{normalized_subtask['id']}",
|
|
73
|
+
context: "Working on #{normalized_subtask.fetch('task', 'unknown task')}",
|
|
74
|
+
tags: ["implementation", normalized_subtask.fetch("language", "general")],
|
|
75
|
+
agent: name
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
if res.is_a?(Hash) && res["confirmation_required"]
|
|
80
|
+
log "Memory confirmation required when storing success: #{res['confirmation_details'].inspect}"
|
|
81
|
+
return { "subtask_id" => normalized_subtask["id"], "success" => success, "result" => "Code executed", "confirmation_required" => true, "confirmation_details" => res["confirmation_details"], "attempted" => res["attempted"] }
|
|
82
|
+
end
|
|
75
83
|
else
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
res = safe_memory_write(attempted: { action: "store_pitfall", title: "Failed: #{normalized_subtask['description']}", tags: ["failure", "implementation"], agent: name }) do
|
|
85
|
+
memory.store_pitfall(
|
|
86
|
+
title: "Failed: #{normalized_subtask['description']}",
|
|
87
|
+
description: "Subtask #{normalized_subtask['id']} failed",
|
|
88
|
+
context: "Working on #{normalized_subtask.fetch('task', 'unknown task')}",
|
|
89
|
+
tags: ["failure", "implementation"],
|
|
90
|
+
agent: name
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
if res.is_a?(Hash) && res["confirmation_required"]
|
|
95
|
+
log "Memory confirmation required when storing pitfall: #{res['confirmation_details'].inspect}"
|
|
96
|
+
return { "subtask_id" => normalized_subtask["id"], "success" => success, "result" => "Code executed", "confirmation_required" => true, "confirmation_details" => res["confirmation_details"], "attempted" => res["attempted"] }
|
|
97
|
+
end
|
|
84
98
|
end
|
|
85
99
|
|
|
86
100
|
{ "subtask_id" => normalized_subtask["id"], "success" => success, "result" => "Code executed" }
|
data/lib/agentf/agents/tester.rb
CHANGED
|
@@ -63,13 +63,20 @@ module Agentf
|
|
|
63
63
|
|
|
64
64
|
template = @commands.generate_unit_tests(code_file)
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
res = safe_memory_write(attempted: { action: "store_success", title: "Generated #{test_type} tests for #{code_file}", tags: ["testing", test_type, code_file.split(".").last], agent: name }) do
|
|
67
|
+
memory.store_success(
|
|
68
|
+
title: "Generated #{test_type} tests for #{code_file}",
|
|
69
|
+
description: "Created #{template.test_file} with #{test_type} tests",
|
|
70
|
+
context: "Test framework: #{template.framework}",
|
|
71
|
+
tags: ["testing", test_type, code_file.split(".").last],
|
|
72
|
+
agent: name
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
if res.is_a?(Hash) && res["confirmation_required"]
|
|
77
|
+
log "Memory confirmation required when storing generated tests: #{res['confirmation_details'].inspect}"
|
|
78
|
+
return { "test_file" => template.test_file, "test_type" => test_type, "generated_code" => template.test_code, "confirmation_required" => true, "confirmation_details" => res["confirmation_details"], "attempted" => res["attempted"] }
|
|
79
|
+
end
|
|
73
80
|
|
|
74
81
|
log "Created: #{template.test_file}"
|
|
75
82
|
|
|
@@ -90,6 +97,38 @@ module Agentf
|
|
|
90
97
|
|
|
91
98
|
{ "test_file" => test_file, "passed" => result["passed"] }
|
|
92
99
|
end
|
|
100
|
+
|
|
101
|
+
def execute(task:, context: {}, agents: {}, commands: {}, logger: nil)
|
|
102
|
+
# Support provider-driven TDD red-phase: when context signals a red phase,
|
|
103
|
+
# generate tests via the tester commands (if provided) and return a
|
|
104
|
+
# simulated failing test signature so orchestrator flows can short-circuit.
|
|
105
|
+
if context.to_h["tdd_phase"] == "red"
|
|
106
|
+
tester_commands = if commands.respond_to?(:fetch)
|
|
107
|
+
commands.fetch("tester", nil)
|
|
108
|
+
else
|
|
109
|
+
commands["tester"]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
begin
|
|
113
|
+
tester_commands&.generate_unit_tests(context.to_h["source_file"]) if tester_commands&.respond_to?(:generate_unit_tests)
|
|
114
|
+
rescue StandardError
|
|
115
|
+
# ignore command errors for the simulated red phase
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
return { "tdd_phase" => "red", "passed" => false, "failure_signature" => "expected-failure-#{context.to_h["source_file"] || 'unspecified'}" }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
action = context["action"] || (task.is_a?(String) ? "generate_tests" : context["action"])
|
|
122
|
+
case action
|
|
123
|
+
when "generate_tests"
|
|
124
|
+
code_file = task.is_a?(String) ? task : context["code_file"]
|
|
125
|
+
generate_tests(code_file, test_type: context["test_type"] || "unit")
|
|
126
|
+
when "run_tests"
|
|
127
|
+
run_tests(context["test_file"] || task)
|
|
128
|
+
else
|
|
129
|
+
{ "error" => "Unknown action for Tester: #{action}" }
|
|
130
|
+
end
|
|
131
|
+
end
|
|
93
132
|
end
|
|
94
133
|
end
|
|
95
134
|
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "arg_parser"
|
|
4
|
+
require_relative "../commands/registry"
|
|
5
|
+
require_relative "../commands"
|
|
6
|
+
require_relative "../agents"
|
|
7
|
+
require_relative "../memory"
|
|
8
|
+
|
|
9
|
+
module Agentf
|
|
10
|
+
module CLI
|
|
11
|
+
# CLI entry for running a single agent and returning JSON output.
|
|
12
|
+
class Agent
|
|
13
|
+
include ArgParser
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
@memory = Agentf::Memory::RedisMemory.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def run(args)
|
|
20
|
+
if args.empty? || args.include?("--help") || args.include?("help")
|
|
21
|
+
show_help
|
|
22
|
+
return
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Allow callers (like the TypeScript plugin) to append `--json` to
|
|
26
|
+
# request machine-readable output. Strip it here so it's not treated as
|
|
27
|
+
# part of the agent payload.
|
|
28
|
+
args = args.dup
|
|
29
|
+
json_output = !args.delete("--json").nil?
|
|
30
|
+
confirmed_write = parse_single_option(args, "--confirmed-write=")
|
|
31
|
+
|
|
32
|
+
agent_name = args.shift
|
|
33
|
+
payload = args.join(" ")
|
|
34
|
+
|
|
35
|
+
# Build command registry with default implementations
|
|
36
|
+
registry = Agentf::Commands::Registry.new
|
|
37
|
+
# Register known command providers
|
|
38
|
+
registry.register("explorer", Agentf::Commands::Explorer.new)
|
|
39
|
+
registry.register("tester", Agentf::Commands::Tester.new)
|
|
40
|
+
registry.register("debugger", Agentf::Commands::Debugger.new)
|
|
41
|
+
registry.register("designer", Agentf::Commands::Designer.new)
|
|
42
|
+
registry.register("security", Agentf::Commands::SecurityScanner.new)
|
|
43
|
+
registry.register("architecture", Agentf::Commands::Architecture.new)
|
|
44
|
+
|
|
45
|
+
# Load agents (classes already required via lib/agentf)
|
|
46
|
+
agents = {}
|
|
47
|
+
Agentf::Agents.constants.each do |const|
|
|
48
|
+
klass = Agentf::Agents.const_get(const)
|
|
49
|
+
next unless klass.is_a?(Class) && klass < Agentf::Agents::Base
|
|
50
|
+
agents[klass.typed_name] = klass.new(@memory)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
agent = agents[agent_name.upcase]
|
|
54
|
+
unless agent
|
|
55
|
+
$stderr.puts JSON.generate({ ok: false, error: "Agent not found: #{agent_name}" })
|
|
56
|
+
exit 1
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Parse possible JSON payload
|
|
60
|
+
parsed = nil
|
|
61
|
+
begin
|
|
62
|
+
parsed = JSON.parse(payload) unless payload.strip.empty?
|
|
63
|
+
rescue StandardError
|
|
64
|
+
parsed = payload
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
previous = ENV["AGENTF_SUPPRESS_AGENT_LOGS"]
|
|
68
|
+
previous_auto_confirm = ENV["AGENTF_AUTO_CONFIRM_MEMORIES"]
|
|
69
|
+
ENV["AGENTF_SUPPRESS_AGENT_LOGS"] = "true" if json_output
|
|
70
|
+
ENV["AGENTF_AUTO_CONFIRM_MEMORIES"] = "true" unless confirmed_write.to_s.empty?
|
|
71
|
+
|
|
72
|
+
result = agent.execute(
|
|
73
|
+
task: parsed || payload,
|
|
74
|
+
context: { "confirmed_write" => confirmed_write },
|
|
75
|
+
agents: agents,
|
|
76
|
+
commands: registry,
|
|
77
|
+
logger: json_output ? nil : method(:puts)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
puts JSON.generate(result)
|
|
81
|
+
ensure
|
|
82
|
+
ENV["AGENTF_SUPPRESS_AGENT_LOGS"] = previous if json_output
|
|
83
|
+
ENV["AGENTF_AUTO_CONFIRM_MEMORIES"] = previous_auto_confirm unless confirmed_write.to_s.empty?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def show_help
|
|
87
|
+
puts <<~HELP
|
|
88
|
+
Usage: agentf agent <AGENT_NAME> [payload] [--json] [--confirmed-write=<token>]
|
|
89
|
+
|
|
90
|
+
Runs a single agent and prints JSON result.
|
|
91
|
+
HELP
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|