agent_c 2.9979 → 2.71828
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/CLAUDE.md +1 -1
- data/README.md +1 -1
- data/TODO.md +5 -98
- data/docs/chat-methods.md +6 -5
- data/docs/pipeline-tips-and-tricks.md +0 -382
- data/docs/testing.md +19 -19
- data/lib/agent_c/agent/chat_response.rb +4 -10
- data/lib/agent_c/batch.rb +4 -40
- data/lib/agent_c/configs/repo.rb +1 -1
- data/lib/agent_c/pipeline.rb +83 -47
- data/lib/agent_c/processor.rb +1 -1
- data/lib/agent_c/schema.rb +22 -8
- data/lib/agent_c/session.rb +2 -1
- data/lib/agent_c/tools/edit_file.rb +1 -3
- data/lib/agent_c/tools.rb +0 -1
- data/lib/agent_c/utils/git.rb +14 -26
- data/lib/agent_c/version.rb +1 -1
- metadata +2 -5
- data/docs/batch.md +0 -503
- data/lib/agent_c/pipelines/agent.rb +0 -219
- data/lib/agent_c/tools/git_status.rb +0 -30
data/docs/testing.md
CHANGED
|
@@ -98,9 +98,9 @@ def test_agent_step_inline
|
|
|
98
98
|
# Match the LITERAL prompt strings with %{placeholders}, not interpolated values
|
|
99
99
|
dummy_chat = DummyChat.new(responses: {
|
|
100
100
|
"Summarize the document titled %{title}" =>
|
|
101
|
-
'{" "summary": "A comprehensive report"}',
|
|
101
|
+
'{"status": "success", "summary": "A comprehensive report"}',
|
|
102
102
|
"Categorize this document: %{summary}" =>
|
|
103
|
-
'{" "category": "Research"}'
|
|
103
|
+
'{"status": "success", "category": "Research"}'
|
|
104
104
|
})
|
|
105
105
|
|
|
106
106
|
session = test_session(
|
|
@@ -133,8 +133,8 @@ def test_i18n_agent_step
|
|
|
133
133
|
# I18n interpolates BEFORE sending to DummyChat
|
|
134
134
|
# DummyChat receives: "Process file report.pdf" (interpolated!)
|
|
135
135
|
dummy_chat = DummyChat.new(responses: {
|
|
136
|
-
"Process file report.pdf" => '{}', # ✓ Correct
|
|
137
|
-
"Process file %{file_name}" => '{}' # ✗ Wrong - won't match
|
|
136
|
+
"Process file report.pdf" => '{"status": "success"}', # ✓ Correct
|
|
137
|
+
"Process file %{file_name}" => '{"status": "success"}' # ✗ Wrong - won't match
|
|
138
138
|
})
|
|
139
139
|
|
|
140
140
|
session = test_session(
|
|
@@ -185,7 +185,7 @@ def test_agent_step_with_i18n
|
|
|
185
185
|
# Configure DummyChat with matching response
|
|
186
186
|
dummy_chat = DummyChat.new(responses: {
|
|
187
187
|
"Summarize the document titled 'My Document' in category 'Technical'" =>
|
|
188
|
-
'{" "summary": "This is a technical document about programming."}'
|
|
188
|
+
'{"status": "success", "summary": "This is a technical document about programming."}'
|
|
189
189
|
})
|
|
190
190
|
|
|
191
191
|
# Create session with DummyChat
|
|
@@ -231,7 +231,7 @@ def test_agent_step_with_regex_matching
|
|
|
231
231
|
|
|
232
232
|
# Use regex to match prompts flexibly
|
|
233
233
|
dummy_chat = DummyChat.new(responses: {
|
|
234
|
-
/Process document:/ => '{" "category": "Processed"}'
|
|
234
|
+
/Process document:/ => '{"status": "success", "category": "Processed"}'
|
|
235
235
|
})
|
|
236
236
|
|
|
237
237
|
session = test_session(
|
|
@@ -270,7 +270,7 @@ def test_agent_step_failure
|
|
|
270
270
|
})
|
|
271
271
|
|
|
272
272
|
dummy_chat = DummyChat.new(responses: {
|
|
273
|
-
"This will fail" => '{"
|
|
273
|
+
"This will fail" => '{"status": "error", "message": "Processing failed"}'
|
|
274
274
|
})
|
|
275
275
|
|
|
276
276
|
session = test_session(
|
|
@@ -332,11 +332,11 @@ def test_multi_step_pipeline
|
|
|
332
332
|
# Configure responses for each step
|
|
333
333
|
dummy_chat = DummyChat.new(responses: {
|
|
334
334
|
"Extract title" =>
|
|
335
|
-
'{" "title": "Research Paper"}',
|
|
335
|
+
'{"status": "success", "title": "Research Paper"}',
|
|
336
336
|
"Summarize document: Research Paper" =>
|
|
337
|
-
'{" "summary": "A study on testing"}',
|
|
337
|
+
'{"status": "success", "summary": "A study on testing"}',
|
|
338
338
|
/Categorize: Research Paper - A study on testing/ =>
|
|
339
|
-
'{" "category": "Research"}'
|
|
339
|
+
'{"status": "success", "category": "Research"}'
|
|
340
340
|
})
|
|
341
341
|
|
|
342
342
|
session = test_session(
|
|
@@ -444,7 +444,7 @@ session = Session.new(
|
|
|
444
444
|
chat_provider: ->(**params) {
|
|
445
445
|
DummyChat.new(
|
|
446
446
|
responses: {
|
|
447
|
-
"What is 2+2?" => '{" "answer": "4"}'
|
|
447
|
+
"What is 2+2?" => '{"status": "success", "answer": "4"}'
|
|
448
448
|
},
|
|
449
449
|
**params
|
|
450
450
|
)
|
|
@@ -493,7 +493,7 @@ session = Session.new(
|
|
|
493
493
|
chat_provider: ->(**params) {
|
|
494
494
|
DummyChat.new(
|
|
495
495
|
responses: {
|
|
496
|
-
"What is Ruby?" => '{" "answer": "A programming language"}'
|
|
496
|
+
"What is Ruby?" => '{"status": "success", "answer": "A programming language"}'
|
|
497
497
|
},
|
|
498
498
|
**params
|
|
499
499
|
)
|
|
@@ -508,7 +508,7 @@ session = Session.new(
|
|
|
508
508
|
chat_provider: ->(**params) {
|
|
509
509
|
DummyChat.new(
|
|
510
510
|
responses: {
|
|
511
|
-
/extract.*email/ => '{" "email": "user@example.com"}'
|
|
511
|
+
/extract.*email/ => '{"status": "success", "email": "user@example.com"}'
|
|
512
512
|
},
|
|
513
513
|
**params
|
|
514
514
|
)
|
|
@@ -529,7 +529,7 @@ session = Session.new(
|
|
|
529
529
|
chat_provider: ->(**params) {
|
|
530
530
|
DummyChat.new(
|
|
531
531
|
responses: {
|
|
532
|
-
->(text) { text.include?("hello") } => '{" "greeting": "Hi!"}'
|
|
532
|
+
->(text) { text.include?("hello") } => '{"status": "success", "greeting": "Hi!"}'
|
|
533
533
|
},
|
|
534
534
|
**params
|
|
535
535
|
)
|
|
@@ -559,7 +559,7 @@ session = Session.new(
|
|
|
559
559
|
responses: {
|
|
560
560
|
"Write file" => -> {
|
|
561
561
|
File.write("/tmp/test.txt", "content")
|
|
562
|
-
'{" "path": "/tmp/test.txt"}'
|
|
562
|
+
'{"status": "success", "path": "/tmp/test.txt"}'
|
|
563
563
|
}
|
|
564
564
|
},
|
|
565
565
|
**params
|
|
@@ -607,7 +607,7 @@ session = Session.new(
|
|
|
607
607
|
chat_provider: ->(**params) {
|
|
608
608
|
DummyChat.new(
|
|
609
609
|
responses: {
|
|
610
|
-
"Process data" => '{" "result": "processed"}'
|
|
610
|
+
"Process data" => '{"status": "success", "result": "processed"}'
|
|
611
611
|
},
|
|
612
612
|
**params
|
|
613
613
|
)
|
|
@@ -630,7 +630,7 @@ session = Session.new(
|
|
|
630
630
|
chat_provider: ->(**params) {
|
|
631
631
|
DummyChat.new(
|
|
632
632
|
responses: {
|
|
633
|
-
"Impossible task" => '{"
|
|
633
|
+
"Impossible task" => '{"status": "error", "message": "Cannot complete"}'
|
|
634
634
|
},
|
|
635
635
|
**params
|
|
636
636
|
)
|
|
@@ -659,7 +659,7 @@ class MyFeatureTest < Minitest::Test
|
|
|
659
659
|
chat_provider: ->(**params) {
|
|
660
660
|
DummyChat.new(
|
|
661
661
|
responses: {
|
|
662
|
-
/extract.*email/ => '{" "email": "john@example.com"}'
|
|
662
|
+
/extract.*email/ => '{"status": "success", "email": "john@example.com"}'
|
|
663
663
|
},
|
|
664
664
|
**params
|
|
665
665
|
)
|
|
@@ -680,7 +680,7 @@ class MyFeatureTest < Minitest::Test
|
|
|
680
680
|
chat_provider: ->(**params) {
|
|
681
681
|
DummyChat.new(
|
|
682
682
|
responses: {
|
|
683
|
-
"Invalid input" => '{"
|
|
683
|
+
"Invalid input" => '{"status": "error", "message": "Input validation failed"}'
|
|
684
684
|
},
|
|
685
685
|
**params
|
|
686
686
|
)
|
|
@@ -11,27 +11,21 @@ module AgentC
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def success?
|
|
14
|
-
|
|
14
|
+
status == "success"
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def status
|
|
18
|
-
|
|
19
|
-
"success"
|
|
20
|
-
else
|
|
21
|
-
"error"
|
|
22
|
-
end
|
|
18
|
+
@raw_response.fetch("status")
|
|
23
19
|
end
|
|
24
20
|
|
|
25
21
|
def data
|
|
26
22
|
raise "Cannot call data on failed response. Use error_message instead." unless success?
|
|
27
|
-
|
|
28
|
-
raw_response
|
|
23
|
+
raw_response.reject { |k, _| k == "status" }
|
|
29
24
|
end
|
|
30
25
|
|
|
31
26
|
def error_message
|
|
32
27
|
raise "Cannot call error_message on successful response. Use data instead." if success?
|
|
33
|
-
|
|
34
|
-
raw_response.fetch("unable_to_fulfill_request_error")
|
|
28
|
+
raw_response.fetch("message")
|
|
35
29
|
end
|
|
36
30
|
end
|
|
37
31
|
end
|
data/lib/agent_c/batch.rb
CHANGED
|
@@ -23,8 +23,8 @@ module AgentC
|
|
|
23
23
|
@git = git
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
def call
|
|
27
|
-
processor.call
|
|
26
|
+
def call
|
|
27
|
+
processor.call
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def add_task(record)
|
|
@@ -43,50 +43,14 @@ module AgentC
|
|
|
43
43
|
pending_count = tasks.count { |task| task.pending? }
|
|
44
44
|
failed_count = tasks.count { |task| task.failed? }
|
|
45
45
|
|
|
46
|
-
out.puts "Total: #{tasks.count}"
|
|
47
46
|
out.puts "Succeeded: #{succeeded_count}"
|
|
48
47
|
out.puts "Pending: #{pending_count}"
|
|
49
48
|
out.puts "Failed: #{failed_count}"
|
|
50
49
|
|
|
51
|
-
# Calculate time span
|
|
52
|
-
if tasks.any?
|
|
53
|
-
created_ats = tasks.map(&:created_at).compact
|
|
54
|
-
updated_ats = tasks.map(&:updated_at).compact
|
|
55
|
-
|
|
56
|
-
if created_ats.any? && updated_ats.any?
|
|
57
|
-
earliest = created_ats.min
|
|
58
|
-
latest = updated_ats.max
|
|
59
|
-
time_span_seconds = (latest - earliest).to_i
|
|
60
|
-
|
|
61
|
-
hours = time_span_seconds / 3600
|
|
62
|
-
minutes = (time_span_seconds % 3600) / 60
|
|
63
|
-
seconds = time_span_seconds % 60
|
|
64
|
-
|
|
65
|
-
out.puts "Time: #{hours} hrs, #{minutes} mins, #{seconds} secs"
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# Count worktrees
|
|
70
|
-
worktree_count = store.workspace.count
|
|
71
|
-
out.puts "Worktrees: #{worktree_count}"
|
|
72
|
-
|
|
73
50
|
cost_data = session.cost
|
|
74
51
|
out.puts "Run cost: $#{'%.2f' % cost_data.run}"
|
|
75
52
|
out.puts "Project total cost: $#{'%.2f' % cost_data.project}"
|
|
76
53
|
|
|
77
|
-
# Cost and time per task
|
|
78
|
-
if tasks.count > 0
|
|
79
|
-
cost_per_task = cost_data.run / tasks.count.to_f
|
|
80
|
-
out.puts "Cost per task: $#{'%.2f' % cost_per_task}"
|
|
81
|
-
|
|
82
|
-
if tasks.any? && created_ats&.any? && updated_ats&.any?
|
|
83
|
-
total_minutes = time_span_seconds / 60.0
|
|
84
|
-
|
|
85
|
-
minutes_per_task = total_minutes * worktree_count / tasks.count
|
|
86
|
-
out.puts "Minutes per task: #{'%.2f' % minutes_per_task}"
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
54
|
if failed_count > 0
|
|
91
55
|
out.puts "\nFirst #{[failed_count, 3].min} failed task(s):"
|
|
92
56
|
tasks.select { |task| task.failed? }.first(3).each do |task|
|
|
@@ -101,8 +65,8 @@ module AgentC
|
|
|
101
65
|
context.store
|
|
102
66
|
end
|
|
103
67
|
|
|
104
|
-
def
|
|
105
|
-
context.
|
|
68
|
+
def workspace
|
|
69
|
+
context.workspace
|
|
106
70
|
end
|
|
107
71
|
|
|
108
72
|
def session
|
data/lib/agent_c/configs/repo.rb
CHANGED
data/lib/agent_c/pipeline.rb
CHANGED
|
@@ -36,19 +36,84 @@ module AgentC
|
|
|
36
36
|
self.steps << Step.new(name:, block:)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def
|
|
39
|
+
def agent_step(name, **params, &block)
|
|
40
40
|
step(name) do
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
resolved_params = (
|
|
42
|
+
if block
|
|
43
|
+
instance_exec(&block)
|
|
44
|
+
elsif params.empty?
|
|
45
|
+
i18n_attributes = (
|
|
46
|
+
if record.respond_to?(:i18n_attributes)
|
|
47
|
+
record.i18n_attributes
|
|
48
|
+
else
|
|
49
|
+
record.attributes
|
|
50
|
+
end
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
{
|
|
54
|
+
tool_args: {
|
|
55
|
+
workspace_dir: workspace.dir,
|
|
56
|
+
env: workspace.env,
|
|
57
|
+
},
|
|
58
|
+
cached_prompt: I18n.t("#{name}.cached_prompts"),
|
|
59
|
+
prompt: I18n.t("#{name}.prompt", **i18n_attributes.symbolize_keys),
|
|
60
|
+
tools: I18n.t("#{name}.tools"),
|
|
61
|
+
schema: -> {
|
|
62
|
+
next unless I18n.exists?("#{name}.response_schema")
|
|
63
|
+
|
|
64
|
+
I18n.t("#{name}.response_schema").each do |name, spec|
|
|
65
|
+
extra = spec.except(:required, :description, :type)
|
|
66
|
+
|
|
67
|
+
if extra.key?(:of)
|
|
68
|
+
extra[:of] = extra[:of]&.to_sym
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
send(
|
|
72
|
+
spec.fetch(:type, "string"),
|
|
73
|
+
name,
|
|
74
|
+
required: spec.fetch(:required, true),
|
|
75
|
+
description: spec.fetch(:description),
|
|
76
|
+
**extra
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else
|
|
82
|
+
i18n_attributes = (
|
|
83
|
+
if record.respond_to?(:i18n_attributes)
|
|
84
|
+
record.i18n_attributes
|
|
85
|
+
else
|
|
86
|
+
record.attributes
|
|
87
|
+
end
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
{
|
|
91
|
+
tool_args: {
|
|
92
|
+
workspace_dir: workspace.dir,
|
|
93
|
+
env: workspace.env,
|
|
94
|
+
}
|
|
95
|
+
}.tap { |hash|
|
|
96
|
+
if params.key?(:prompt_key)
|
|
97
|
+
hash[:prompt] = I18n.t(params[:prompt_key], **i18n_attributes.symbolize_keys)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
if params.key?(:cached_prompt_keys)
|
|
101
|
+
hash[:cached_prompt] = params[:cached_prompt_keys].map { I18n.t(_1) }
|
|
102
|
+
end
|
|
103
|
+
}.merge(params.except(:cached_prompt_keys, :prompt_key))
|
|
104
|
+
end
|
|
105
|
+
)
|
|
45
106
|
|
|
46
|
-
|
|
47
|
-
|
|
107
|
+
result = session.prompt(
|
|
108
|
+
on_chat_created: -> (id) { task.chat_ids << id},
|
|
109
|
+
**resolved_params
|
|
110
|
+
)
|
|
48
111
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
112
|
+
if result.success?
|
|
113
|
+
task.record.update!(result.data)
|
|
114
|
+
else
|
|
115
|
+
task.fail!(result.error_message)
|
|
116
|
+
end
|
|
52
117
|
end
|
|
53
118
|
end
|
|
54
119
|
end
|
|
@@ -58,17 +123,18 @@ module AgentC
|
|
|
58
123
|
|
|
59
124
|
log("start")
|
|
60
125
|
|
|
61
|
-
|
|
126
|
+
self.class.steps.each do |step|
|
|
62
127
|
break if task.failed?
|
|
63
128
|
|
|
64
|
-
step = self.class.steps.find { !task.completed_steps.include?(_1.name.to_s) }
|
|
65
|
-
break if step.nil?
|
|
66
|
-
|
|
67
|
-
@rewind_to = nil
|
|
68
129
|
|
|
69
130
|
store.transaction do
|
|
70
131
|
log_prefix = "step: '#{step.name}'"
|
|
71
132
|
|
|
133
|
+
if task.completed_steps.include?(step.name.to_s)
|
|
134
|
+
log("#{log_prefix} already completed, skipping")
|
|
135
|
+
next
|
|
136
|
+
end
|
|
137
|
+
|
|
72
138
|
log("#{log_prefix} start")
|
|
73
139
|
|
|
74
140
|
instance_exec(&step.block)
|
|
@@ -76,31 +142,6 @@ module AgentC
|
|
|
76
142
|
if task.failed?
|
|
77
143
|
log("#{log_prefix} failed, executing on_failures")
|
|
78
144
|
self.class.on_failures.each { instance_exec(&_1)}
|
|
79
|
-
elsif @rewind_to
|
|
80
|
-
matching_steps = task.completed_steps.select { _1 == @rewind_to }
|
|
81
|
-
|
|
82
|
-
if matching_steps.count == 0
|
|
83
|
-
raise ArgumentError, <<~TXT
|
|
84
|
-
Cannot rewind to a step that's not been completed yet:
|
|
85
|
-
|
|
86
|
-
rewind_to!(#{@rewind_to.inspect})
|
|
87
|
-
completed_steps: #{task.completed_steps.inspect}
|
|
88
|
-
TXT
|
|
89
|
-
elsif matching_steps.count > 1
|
|
90
|
-
raise ArgumentError, <<~TXT
|
|
91
|
-
Cannot rewind to a step with a non-distinct name. The step
|
|
92
|
-
name appears multiple times:
|
|
93
|
-
|
|
94
|
-
rewind_to!(#{@rewind_to.inspect})
|
|
95
|
-
completed_steps: #{task.completed_steps.inspect}
|
|
96
|
-
TXT
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
log("#{log_prefix} rewind_to! #{@rewind_to.inspect}")
|
|
100
|
-
task
|
|
101
|
-
.completed_steps
|
|
102
|
-
.index(@rewind_to)
|
|
103
|
-
.then { task.update!(completed_steps: task.completed_steps[0..._1]) }
|
|
104
145
|
else
|
|
105
146
|
log("#{log_prefix} done")
|
|
106
147
|
task.completed_steps << step.name.to_s
|
|
@@ -132,12 +173,8 @@ module AgentC
|
|
|
132
173
|
task.store
|
|
133
174
|
end
|
|
134
175
|
|
|
135
|
-
def
|
|
136
|
-
@
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def git
|
|
140
|
-
@_git ||= @git.call(workspace.dir)
|
|
176
|
+
def repo
|
|
177
|
+
@repo ||= @git.call(workspace.dir)
|
|
141
178
|
end
|
|
142
179
|
|
|
143
180
|
def log(msg)
|
|
@@ -147,6 +184,5 @@ module AgentC
|
|
|
147
184
|
def logger
|
|
148
185
|
session.logger
|
|
149
186
|
end
|
|
150
|
-
|
|
151
187
|
end
|
|
152
188
|
end
|
data/lib/agent_c/processor.rb
CHANGED
|
@@ -66,7 +66,7 @@ module AgentC
|
|
|
66
66
|
def call_synchronous(workspace)
|
|
67
67
|
while(handler = next_handler(workspace))
|
|
68
68
|
handler.call
|
|
69
|
-
yield
|
|
69
|
+
yield if block_given? # allow the invoker to do work inbetween handler calls
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
72
|
|
data/lib/agent_c/schema.rb
CHANGED
|
@@ -41,13 +41,14 @@ module AgentC
|
|
|
41
41
|
|
|
42
42
|
class ErrorSchema < RubyLLM::Schema
|
|
43
43
|
string(
|
|
44
|
-
:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
task and/or unable to fulfill the other schema provided.
|
|
44
|
+
:status,
|
|
45
|
+
enum: ["error"],
|
|
46
|
+
)
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
string(
|
|
49
|
+
:message,
|
|
50
|
+
description: <<~TXT
|
|
51
|
+
A brief description of the reason you could not fulfill the request.
|
|
51
52
|
TXT
|
|
52
53
|
)
|
|
53
54
|
end
|
|
@@ -55,10 +56,23 @@ module AgentC
|
|
|
55
56
|
def self.result(schema: nil, &)
|
|
56
57
|
# Create the success schema
|
|
57
58
|
success_schema = (
|
|
58
|
-
if
|
|
59
|
+
if schema.nil?
|
|
59
60
|
Class.new(RubyLLM::Schema) do
|
|
61
|
+
string(
|
|
62
|
+
:status,
|
|
63
|
+
enum: ["success"],
|
|
64
|
+
)
|
|
65
|
+
|
|
60
66
|
instance_exec(&) if block_given?
|
|
61
|
-
|
|
67
|
+
end
|
|
68
|
+
elsif schema.respond_to?(:call)
|
|
69
|
+
Class.new(RubyLLM::Schema) do
|
|
70
|
+
string(
|
|
71
|
+
:status,
|
|
72
|
+
enum: ["success"],
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
instance_exec(&schema)
|
|
62
76
|
end
|
|
63
77
|
else
|
|
64
78
|
schema
|
data/lib/agent_c/session.rb
CHANGED
|
@@ -115,7 +115,8 @@ module AgentC
|
|
|
115
115
|
Agent::ChatResponse.new(
|
|
116
116
|
chat_id: chat_instance.id,
|
|
117
117
|
raw_response: {
|
|
118
|
-
"
|
|
118
|
+
"status" => "error",
|
|
119
|
+
"message" => ["#{e.class.name}:#{e.message}", e.backtrace].join("\n")
|
|
119
120
|
},
|
|
120
121
|
)
|
|
121
122
|
end
|
|
@@ -14,7 +14,7 @@ module AgentC
|
|
|
14
14
|
string(
|
|
15
15
|
:mode,
|
|
16
16
|
description: "Operation mode: 'overwrite' (replace entire file), 'replace_lines' (replace line range), 'insert_at_line' (insert before specified line), 'append' (add to end)",
|
|
17
|
-
enum: ["overwrite", "replace_lines", "insert_at_line", "append"
|
|
17
|
+
enum: ["overwrite", "replace_lines", "insert_at_line", "append"],
|
|
18
18
|
required: true
|
|
19
19
|
)
|
|
20
20
|
string(
|
|
@@ -55,8 +55,6 @@ module AgentC
|
|
|
55
55
|
workspace_path = Paths.relative_to_dir(workspace_dir, path)
|
|
56
56
|
|
|
57
57
|
case mode
|
|
58
|
-
when "delete"
|
|
59
|
-
FileUtils.rm_f(path)
|
|
60
58
|
when "overwrite"
|
|
61
59
|
FileUtils.mkdir_p(File.dirname(workspace_path))
|
|
62
60
|
File.write(workspace_path, content)
|
data/lib/agent_c/tools.rb
CHANGED
data/lib/agent_c/utils/git.rb
CHANGED
|
@@ -6,62 +6,50 @@ module AgentC
|
|
|
6
6
|
module Utils
|
|
7
7
|
# Git utility class for managing Git operations in worktrees
|
|
8
8
|
class Git
|
|
9
|
-
attr_reader :
|
|
9
|
+
attr_reader :repo_path
|
|
10
10
|
|
|
11
|
-
def initialize(
|
|
12
|
-
@
|
|
11
|
+
def initialize(repo_path)
|
|
12
|
+
@repo_path = repo_path
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def create_worktree(
|
|
15
|
+
def create_worktree(dir:, branch:, revision:)
|
|
16
16
|
# Prune any stale worktrees first
|
|
17
|
-
shell.run!("cd #{
|
|
17
|
+
shell.run!("cd #{repo_path} && git worktree prune")
|
|
18
18
|
|
|
19
19
|
# Remove worktree at dir if it exists (don't fail if it doesn't exist)
|
|
20
|
-
shell.run!("cd #{
|
|
20
|
+
shell.run!("cd #{repo_path} && (git worktree remove #{Shellwords.escape(dir)} --force 2>/dev/null || true)")
|
|
21
21
|
|
|
22
22
|
shell.run!(
|
|
23
23
|
<<~TXT
|
|
24
|
-
cd #{
|
|
24
|
+
cd #{repo_path} && \
|
|
25
25
|
git worktree add \
|
|
26
26
|
-B #{Shellwords.escape(branch)} \
|
|
27
|
-
#{Shellwords.escape(
|
|
27
|
+
#{Shellwords.escape(dir)} \
|
|
28
28
|
#{Shellwords.escape(revision)}
|
|
29
29
|
TXT
|
|
30
30
|
)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def diff
|
|
34
|
-
#
|
|
35
|
-
# in the diff.
|
|
36
|
-
shell.run!(
|
|
37
|
-
<<~TXT
|
|
38
|
-
cd #{dir} && \
|
|
39
|
-
git add --all --intent-to-add && \
|
|
40
|
-
git diff --relative
|
|
41
|
-
TXT
|
|
42
|
-
)
|
|
34
|
+
shell.run!("cd #{repo_path} && git diff")
|
|
43
35
|
end
|
|
44
36
|
|
|
45
37
|
def last_revision
|
|
46
|
-
shell.run!("cd #{
|
|
38
|
+
shell.run!("cd #{repo_path} && git rev-parse @").strip
|
|
47
39
|
end
|
|
48
40
|
|
|
49
41
|
def commit_all(message)
|
|
50
|
-
shell.run!("cd #{
|
|
42
|
+
shell.run!("cd #{repo_path} && git add --all && git commit --no-gpg-sign -m #{Shellwords.escape(message)}")
|
|
51
43
|
last_revision
|
|
52
44
|
end
|
|
53
45
|
|
|
54
46
|
def fixup_commit(revision)
|
|
55
|
-
shell.run!("cd #{
|
|
47
|
+
shell.run!("cd #{repo_path} && git add --all && git commit --no-gpg-sign --fixup #{revision}")
|
|
56
48
|
last_revision
|
|
57
49
|
end
|
|
58
50
|
|
|
59
51
|
def reset_hard_all
|
|
60
|
-
shell.run!("cd #{
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def status
|
|
64
|
-
shell.run!("cd #{dir} && git status")
|
|
52
|
+
shell.run!("cd #{repo_path} && git add --all && git reset --hard")
|
|
65
53
|
end
|
|
66
54
|
|
|
67
55
|
def clean?
|
|
@@ -71,7 +59,7 @@ module AgentC
|
|
|
71
59
|
def uncommitted_changes?
|
|
72
60
|
# Check for any changes including untracked files
|
|
73
61
|
# Returns true if there are uncommitted changes (staged, unstaged, or untracked)
|
|
74
|
-
status = shell.run!("cd #{
|
|
62
|
+
status = shell.run!("cd #{repo_path} && git status --porcelain")
|
|
75
63
|
!status.strip.empty?
|
|
76
64
|
end
|
|
77
65
|
|
data/lib/agent_c/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: agent_c
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: '2.
|
|
4
|
+
version: '2.71828'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Pete Kinnecom
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-02-
|
|
11
|
+
date: 2026-02-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: zeitwerk
|
|
@@ -108,7 +108,6 @@ files:
|
|
|
108
108
|
- Rakefile
|
|
109
109
|
- TODO.md
|
|
110
110
|
- agent_c.gemspec
|
|
111
|
-
- docs/batch.md
|
|
112
111
|
- docs/chat-methods.md
|
|
113
112
|
- docs/cost-reporting.md
|
|
114
113
|
- docs/pipeline-tips-and-tricks.md
|
|
@@ -128,7 +127,6 @@ files:
|
|
|
128
127
|
- lib/agent_c/db/store.rb
|
|
129
128
|
- lib/agent_c/errors.rb
|
|
130
129
|
- lib/agent_c/pipeline.rb
|
|
131
|
-
- lib/agent_c/pipelines/agent.rb
|
|
132
130
|
- lib/agent_c/processor.rb
|
|
133
131
|
- lib/agent_c/prompts.yml
|
|
134
132
|
- lib/agent_c/schema.rb
|
|
@@ -139,7 +137,6 @@ files:
|
|
|
139
137
|
- lib/agent_c/tools/dir_glob.rb
|
|
140
138
|
- lib/agent_c/tools/edit_file.rb
|
|
141
139
|
- lib/agent_c/tools/file_metadata.rb
|
|
142
|
-
- lib/agent_c/tools/git_status.rb
|
|
143
140
|
- lib/agent_c/tools/grep.rb
|
|
144
141
|
- lib/agent_c/tools/paths.rb
|
|
145
142
|
- lib/agent_c/tools/read_file.rb
|