yatfa 1.0.66 → 1.0.67
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 +20 -5
- package/bin/run-yatfa-agent.rb +11 -5
- package/lib/yatfa_agent/agent.rb +120 -43
- package/lib/yatfa_agent/config.rb +5 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,21 +104,34 @@ Launches interactive setup wizard to configure `yatfa.env.rb`.
|
|
|
104
104
|
|
|
105
105
|
### Run Agents
|
|
106
106
|
|
|
107
|
+
When you run an agent, the CLI automatically:
|
|
108
|
+
1. **Checks if the agent is already running** - prompts to Attach, Restart, or Cancel
|
|
109
|
+
2. **Starts the agent** - creates the Docker container
|
|
110
|
+
3. **Prompts to attach** - after starting, asks if you want to attach to the session
|
|
111
|
+
|
|
107
112
|
```bash
|
|
108
|
-
# Start an agent
|
|
109
113
|
npx yatfa worker
|
|
110
114
|
npx yatfa planner
|
|
111
115
|
npx yatfa reviewer
|
|
112
116
|
npx yatfa researcher
|
|
113
117
|
```
|
|
114
118
|
|
|
115
|
-
|
|
119
|
+
#### Non-interactive Mode (for CI/scripts)
|
|
116
120
|
|
|
117
121
|
```bash
|
|
118
|
-
|
|
122
|
+
# Run without prompts
|
|
123
|
+
npx yatfa worker --no-prompt
|
|
124
|
+
# or
|
|
125
|
+
YATFA_NO_PROMPT=1 npx yatfa worker
|
|
119
126
|
```
|
|
120
127
|
|
|
121
|
-
|
|
128
|
+
### Attach to Running Agents (deprecated)
|
|
129
|
+
|
|
130
|
+
The `attach` command still works but is deprecated. Running `npx yatfa <agent-type>` now prompts to attach automatically.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npx yatfa attach worker # Deprecated - use 'npx yatfa worker' instead
|
|
134
|
+
```
|
|
122
135
|
|
|
123
136
|
### List Running Agents
|
|
124
137
|
|
|
@@ -241,8 +254,10 @@ See hook files for implementation details and customization options.
|
|
|
241
254
|
|
|
242
255
|
## Attaching to Running Agent
|
|
243
256
|
|
|
257
|
+
After starting an agent, the CLI will prompt you to attach. You can also attach directly:
|
|
258
|
+
|
|
244
259
|
```bash
|
|
245
|
-
docker exec -it <container> tmux attach -t agent
|
|
260
|
+
docker exec -it <container> tmux attach -t agent
|
|
246
261
|
```
|
|
247
262
|
|
|
248
263
|
Press `Ctrl+B` then `D` to detach.
|
package/bin/run-yatfa-agent.rb
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
# YATFA Agent Runner (Yet Another Tool For Agents)
|
|
5
5
|
# Usage: npx yatfa [worker|planner|reviewer|researcher]
|
|
6
|
-
# npx yatfa attach [agent-type]
|
|
7
6
|
# npx yatfa setup
|
|
8
7
|
#
|
|
9
8
|
# Requirements:
|
|
@@ -22,9 +21,12 @@ def show_usage
|
|
|
22
21
|
puts "YATFA Agent Runner (Yet Another Tool For Agents)"
|
|
23
22
|
puts ""
|
|
24
23
|
puts "Usage:"
|
|
25
|
-
puts " npx yatfa setup
|
|
26
|
-
puts " npx yatfa [worker|planner|reviewer|researcher]
|
|
27
|
-
puts "
|
|
24
|
+
puts " npx yatfa setup # Interactive setup wizard"
|
|
25
|
+
puts " npx yatfa [worker|planner|reviewer|researcher] # Run agent (prompts to attach)"
|
|
26
|
+
puts ""
|
|
27
|
+
puts "Options:"
|
|
28
|
+
puts " --no-prompt, -d Run without prompts (for CI/scripts)"
|
|
29
|
+
puts " YATFA_NO_PROMPT=1 Environment variable alternative"
|
|
28
30
|
puts ""
|
|
29
31
|
puts "Setup:"
|
|
30
32
|
puts " Run 'npx yatfa setup' for interactive configuration"
|
|
@@ -49,8 +51,12 @@ if command == "setup"
|
|
|
49
51
|
exit 0
|
|
50
52
|
end
|
|
51
53
|
|
|
52
|
-
# Handle attach command
|
|
54
|
+
# Handle attach command (deprecated - will still work)
|
|
53
55
|
if command == "attach"
|
|
56
|
+
puts ""
|
|
57
|
+
puts "⚠️ Deprecation: The 'attach' command is deprecated"
|
|
58
|
+
puts " Running `npx yatfa <agent-type>` now prompts to attach automatically."
|
|
59
|
+
puts ""
|
|
54
60
|
agent_type = ARGV[1]&.downcase
|
|
55
61
|
abort "Usage: npx yatfa attach [agent-type]" unless agent_type
|
|
56
62
|
config = YatfaAgent::Config.load
|
package/lib/yatfa_agent/agent.rb
CHANGED
|
@@ -3,6 +3,72 @@ require 'fileutils'
|
|
|
3
3
|
|
|
4
4
|
module YatfaAgent
|
|
5
5
|
module Agent
|
|
6
|
+
# Check if container is currently running
|
|
7
|
+
def self.container_running?(container_name)
|
|
8
|
+
# Use IO.popen with array arguments to avoid shell injection
|
|
9
|
+
result = IO.popen(["docker", "ps", "--filter", "name=^#{container_name}$", "--format", "{{.Names}}"], err: File::NULL, &:read).strip
|
|
10
|
+
!result.empty?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Prompt user for input with a default value
|
|
14
|
+
def self.prompt(message, default: nil, options: nil)
|
|
15
|
+
loop do
|
|
16
|
+
print message
|
|
17
|
+
input = $stdin.gets&.strip&.downcase || ""
|
|
18
|
+
|
|
19
|
+
# Handle default on empty input
|
|
20
|
+
if input.empty? && default
|
|
21
|
+
return default
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# If options specified, validate input
|
|
25
|
+
if options
|
|
26
|
+
valid = options.any? { |opt| opt.downcase == input || opt.downcase.start_with?(input) }
|
|
27
|
+
if valid
|
|
28
|
+
# Return the full option that matches
|
|
29
|
+
return options.find { |opt| opt.downcase.start_with?(input) }
|
|
30
|
+
end
|
|
31
|
+
puts " Invalid option. Please choose: #{options.join('/')}"
|
|
32
|
+
else
|
|
33
|
+
return input
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Check if running in non-interactive mode
|
|
39
|
+
def self.non_interactive?
|
|
40
|
+
!$stdin.tty? || ENV['YATFA_NO_PROMPT'] == '1' || ARGV.include?('--no-prompt') || ARGV.include?('-d')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Handle running container: prompt user for action
|
|
44
|
+
def self.handle_running_container(agent_type, container_name, config, agent_configs)
|
|
45
|
+
puts "⚠️ #{agent_type} agent is already running (#{container_name})"
|
|
46
|
+
puts ""
|
|
47
|
+
|
|
48
|
+
if non_interactive?
|
|
49
|
+
puts " Running in non-interactive mode. Run 'npx yatfa #{agent_type}' to attach, or stop the container first."
|
|
50
|
+
puts " docker stop #{container_name}"
|
|
51
|
+
exit 0
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
choice = prompt(" [A]ttach / [R]estart / [C]ancel? [A/r/c]: ", default: "A", options: %w[A R C])
|
|
55
|
+
|
|
56
|
+
case choice
|
|
57
|
+
when "A"
|
|
58
|
+
puts ""
|
|
59
|
+
attach(agent_type, config, agent_configs)
|
|
60
|
+
when "R"
|
|
61
|
+
puts ""
|
|
62
|
+
puts "🔄 Stopping existing container..."
|
|
63
|
+
system("docker", "stop", container_name, err: File::NULL, out: File::NULL)
|
|
64
|
+
system("docker", "rm", "-f", container_name, err: File::NULL, out: File::NULL)
|
|
65
|
+
return true # Continue with start
|
|
66
|
+
when "C"
|
|
67
|
+
puts " Cancelled."
|
|
68
|
+
exit 0
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
6
72
|
def self.run(agent_type, config, agent_configs)
|
|
7
73
|
unless agent_configs.keys.include?(agent_type)
|
|
8
74
|
puts "❌ Unknown agent type: #{agent_type}"
|
|
@@ -14,10 +80,15 @@ module YatfaAgent
|
|
|
14
80
|
agent_config = config.dig("agents", agent_type) || {}
|
|
15
81
|
container_name = agent_config["container_name"] || agent_def[:name]
|
|
16
82
|
|
|
17
|
-
|
|
83
|
+
# Check if container is already running and handle interactively
|
|
84
|
+
if container_running?(container_name)
|
|
85
|
+
should_continue = handle_running_container(agent_type, container_name, config, agent_configs)
|
|
86
|
+
# If user chose Attach or Cancel, the method calls exec/exit, so we never reach here
|
|
87
|
+
# If user chose Restart, should_continue is true and we proceed to start a new container
|
|
88
|
+
return unless should_continue
|
|
89
|
+
end
|
|
18
90
|
|
|
19
|
-
|
|
20
|
-
system("docker", "rm", "-f", container_name, err: File::NULL, out: File::NULL)
|
|
91
|
+
puts "🚀 Starting #{agent_type} agent..."
|
|
21
92
|
|
|
22
93
|
# Write banner to a persistent temp file (not auto-deleted)
|
|
23
94
|
banner_path = "/tmp/yatfa-banner-#{agent_type}.txt"
|
|
@@ -47,15 +118,46 @@ module YatfaAgent
|
|
|
47
118
|
if success
|
|
48
119
|
puts "✅ Agent started in background"
|
|
49
120
|
puts ""
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
121
|
+
|
|
122
|
+
# Prompt to attach (interactive mode only)
|
|
123
|
+
prompt_to_attach(agent_type, container_name, config, agent_configs)
|
|
53
124
|
else
|
|
54
125
|
puts "❌ Failed to start agent"
|
|
55
126
|
exit 1
|
|
56
127
|
end
|
|
57
128
|
end
|
|
58
129
|
|
|
130
|
+
# Prompt user to attach after starting a new agent
|
|
131
|
+
def self.prompt_to_attach(agent_type, container_name, config, agent_configs)
|
|
132
|
+
if non_interactive?
|
|
133
|
+
# Non-interactive: just print helpful info
|
|
134
|
+
puts " Logs: docker logs -f #{container_name}"
|
|
135
|
+
puts " Stop: docker stop #{container_name}"
|
|
136
|
+
puts " Attach: docker exec -it #{container_name} tmux attach -t agent"
|
|
137
|
+
return
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
choice = prompt(" Agent started. Attach to session? [Y/n]: ", default: "Y", options: %w[Y N])
|
|
141
|
+
|
|
142
|
+
if choice == "Y"
|
|
143
|
+
puts ""
|
|
144
|
+
# Wait briefly for container to initialize
|
|
145
|
+
print " Waiting for agent to initialize"
|
|
146
|
+
5.times do
|
|
147
|
+
print "."
|
|
148
|
+
sleep 1
|
|
149
|
+
end
|
|
150
|
+
puts ""
|
|
151
|
+
attach(agent_type, config, agent_configs)
|
|
152
|
+
else
|
|
153
|
+
puts ""
|
|
154
|
+
puts " Agent is running in background."
|
|
155
|
+
puts " Logs: docker logs -f #{container_name}"
|
|
156
|
+
puts " Stop: docker stop #{container_name}"
|
|
157
|
+
puts " Attach: docker exec -it #{container_name} tmux attach -t agent"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
59
161
|
def self.attach(agent_type, config, agent_configs)
|
|
60
162
|
unless agent_configs.keys.include?(agent_type)
|
|
61
163
|
puts "❌ Unknown agent type: #{agent_type}"
|
|
@@ -66,7 +168,7 @@ module YatfaAgent
|
|
|
66
168
|
agent_config = config.dig("agents", agent_type) || {}
|
|
67
169
|
container_name = agent_config["container_name"] || agent_def[:name]
|
|
68
170
|
|
|
69
|
-
running =
|
|
171
|
+
running = IO.popen(["docker", "ps", "--filter", "name=^#{container_name}$", "--format", "{{.Names}}"], err: File::NULL, &:read).strip
|
|
70
172
|
|
|
71
173
|
if running.empty?
|
|
72
174
|
puts "⚠️ #{agent_type} agent is not running. Auto-starting..."
|
|
@@ -208,52 +310,27 @@ module YatfaAgent
|
|
|
208
310
|
|
|
209
311
|
def self.add_development_mounts!(docker_cmd)
|
|
210
312
|
# Check for local setup-agent.rb (for development)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
# Try multiple possible paths for the binary (built Go binaries in dist/)
|
|
218
|
-
possible_paths = [
|
|
219
|
-
File.join(Dir.pwd, "overrides", "agent-bridge-linux-#{linux_arch}"), # YATFA_SANDBOX/overrides
|
|
220
|
-
File.join(Dir.pwd, "yatfa-public", "dist", "agent-bridge-linux-#{linux_arch}"), # Running from metafolder
|
|
221
|
-
File.join(Dir.pwd, "dist", "agent-bridge-linux-#{linux_arch}"), # Running from yatfa-public
|
|
313
|
+
# Try multiple paths: relative to gem, relative to cwd
|
|
314
|
+
setup_script_paths = [
|
|
315
|
+
File.join(File.dirname(__FILE__), "..", "..", "setup-agent.rb"), # Relative to gem
|
|
316
|
+
File.join(Dir.pwd, "tinker-public", "setup-agent.rb"), # Running from metafolder
|
|
317
|
+
File.join(Dir.pwd, "setup-agent.rb"), # Running from yatfa-public
|
|
222
318
|
]
|
|
223
319
|
|
|
224
|
-
|
|
225
|
-
|
|
320
|
+
local_setup_script = nil
|
|
321
|
+
setup_script_paths.each do |path|
|
|
226
322
|
if File.exist?(path)
|
|
227
|
-
|
|
323
|
+
local_setup_script = path
|
|
228
324
|
break
|
|
229
325
|
end
|
|
230
326
|
end
|
|
231
327
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if File.exist?(local_setup_script)
|
|
328
|
+
# Mount local setup-agent.rb for development if present
|
|
329
|
+
# Binaries are always fetched from bucket by setup-agent.rb
|
|
330
|
+
if local_setup_script
|
|
236
331
|
puts "🔧 Using local setup-agent.rb for development"
|
|
237
332
|
docker_cmd.concat(["-v", "#{File.expand_path(local_setup_script)}:/tmp/setup-agent.rb:ro"])
|
|
238
333
|
end
|
|
239
|
-
|
|
240
|
-
if linux_bridge && File.exist?(linux_bridge)
|
|
241
|
-
puts "🔧 Using local linux binary: #{linux_bridge}"
|
|
242
|
-
docker_cmd.concat(["-v", "#{File.expand_path(linux_bridge)}:/tmp/agent-bridge:ro"])
|
|
243
|
-
elsif File.exist?(local_bridge_default)
|
|
244
|
-
# Check if it's a binary or script
|
|
245
|
-
is_script = File.read(local_bridge_default, 4) == "#!/b"
|
|
246
|
-
if is_script
|
|
247
|
-
puts "⚠️ bin/agent-bridge is a host wrapper script. Please run 'bin/build-bridge' to generate linux binaries."
|
|
248
|
-
else
|
|
249
|
-
puts "🔧 Using local agent-bridge binary"
|
|
250
|
-
docker_cmd.concat(["-v", "#{local_bridge_default}:/tmp/agent-bridge:ro"])
|
|
251
|
-
end
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
if File.exist?(local_tmux)
|
|
255
|
-
docker_cmd.concat(["-v", "#{File.expand_path(local_tmux)}:/tmp/agent-bridge-tmux:ro"])
|
|
256
|
-
end
|
|
257
334
|
end
|
|
258
335
|
|
|
259
336
|
def self.detect_agent_user(container_name)
|
|
@@ -5,16 +5,14 @@ require "json"
|
|
|
5
5
|
module YatfaAgent
|
|
6
6
|
module Config
|
|
7
7
|
def self.load
|
|
8
|
-
|
|
8
|
+
config_file = File.join(Dir.pwd, "yatfa.env.rb")
|
|
9
9
|
|
|
10
|
-
unless File.exist?(
|
|
10
|
+
unless File.exist?(config_file)
|
|
11
11
|
puts "❌ Error: yatfa.env.rb not found in current directory"
|
|
12
12
|
puts ""
|
|
13
13
|
puts "Create yatfa.env.rb:"
|
|
14
14
|
puts " {"
|
|
15
15
|
puts " yatfa_url: 'https://yatfa.example.com', # Required"
|
|
16
|
-
puts " # OR legacy name:"
|
|
17
|
-
puts " rails_api_url: 'https://yatfa.example.com/api/v1',"
|
|
18
16
|
puts " # ..."
|
|
19
17
|
puts " # Paste your stripped .env content here:"
|
|
20
18
|
puts " dot_env: <<~ENV"
|
|
@@ -23,11 +21,11 @@ module YatfaAgent
|
|
|
23
21
|
puts " ENV"
|
|
24
22
|
puts " }"
|
|
25
23
|
puts " echo 'yatfa.env.rb' >> .gitignore"
|
|
26
|
-
|
|
24
|
+
exit 1
|
|
27
25
|
end
|
|
28
26
|
|
|
29
27
|
puts "⚙️ Loading configuration from yatfa.env.rb"
|
|
30
|
-
config = eval(File.read(
|
|
28
|
+
config = eval(File.read(config_file), binding, config_file)
|
|
31
29
|
|
|
32
30
|
# Convert symbols to strings for easier handling before JSON normalization
|
|
33
31
|
config = config.transform_keys(&:to_s)
|
|
@@ -69,8 +67,7 @@ module YatfaAgent
|
|
|
69
67
|
end
|
|
70
68
|
|
|
71
69
|
def self.yatfa_url(config)
|
|
72
|
-
|
|
73
|
-
config["yatfa_url"] || config["yafta_url"] || config["rails_api_url"]
|
|
70
|
+
config["yatfa_url"] || config["rails_api_url"]
|
|
74
71
|
end
|
|
75
72
|
|
|
76
73
|
def self.api_url(config)
|