tinker-agent 1.0.45 โ†’ 1.0.47

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/agents.rb CHANGED
@@ -205,7 +205,7 @@ If blocked or workflow is broken:
205
205
  },
206
206
  'researcher' => {
207
207
  name: 'tinker-autonomous-researcher',
208
- skills: ['researcher-workflow', 'researcher-digest', 'memory', 'proposal-execution', 'memory-consolidation', 'retrospective'],
208
+ skills: ['researcher-tactical', 'researcher-strategic', 'researcher-digest', 'memory', 'proposal-execution', 'memory-consolidation', 'retrospective'],
209
209
  banner: <<~BANNER
210
210
  You are the TINKER RESEARCHER agent operating in FULLY AUTONOMOUS MODE.
211
211
  Your role is AUTONOMOUS ANALYSIS and PROPOSAL GENERATION.
@@ -0,0 +1,239 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TinkerAgent
4
+ module Agent
5
+ def self.run(agent_type, config, agent_configs)
6
+ unless agent_configs.keys.include?(agent_type)
7
+ puts "โŒ Unknown agent type: #{agent_type}"
8
+ puts " Available: #{agent_configs.keys.join(', ')}"
9
+ exit 1
10
+ end
11
+
12
+ agent_def = agent_configs[agent_type]
13
+ agent_config = config.dig("agents", agent_type) || {}
14
+ container_name = agent_config["container_name"] || agent_def[:name]
15
+
16
+ puts "๐Ÿš€ Starting #{agent_type} agent..."
17
+
18
+ # Stop existing container if running
19
+ system("docker", "rm", "-f", container_name, err: File::NULL, out: File::NULL)
20
+
21
+ # Write banner to a persistent temp file (not auto-deleted)
22
+ banner_path = "/tmp/tinker-agent-banner-#{agent_type}.txt"
23
+ File.write(banner_path, agent_def[:banner])
24
+
25
+ docker_cmd = build_docker_command(container_name, agent_type, config, agent_config, agent_def, banner_path)
26
+
27
+ success = system(*docker_cmd)
28
+
29
+ if success
30
+ puts "โœ… Agent started in background"
31
+ puts ""
32
+ puts " Attach: npx tinker-agent attach #{agent_type}"
33
+ puts " Logs: docker logs -f #{container_name}"
34
+ puts " Stop: docker stop #{container_name}"
35
+ else
36
+ puts "โŒ Failed to start agent"
37
+ exit 1
38
+ end
39
+ end
40
+
41
+ def self.attach(agent_type, config, agent_configs)
42
+ unless agent_configs.keys.include?(agent_type)
43
+ puts "โŒ Unknown agent type: #{agent_type}"
44
+ exit 1
45
+ end
46
+
47
+ agent_def = agent_configs[agent_type]
48
+ agent_config = config.dig("agents", agent_type) || {}
49
+ container_name = agent_config["container_name"] || agent_def[:name]
50
+
51
+ running = `docker ps --filter name=^#{container_name}$ --format '{{.Names}}'`.strip
52
+
53
+ if running.empty?
54
+ puts "โš ๏ธ #{agent_type} agent is not running. Auto-starting..."
55
+ Docker.build_image(config)
56
+ run(agent_type, config, agent_configs)
57
+ sleep 3
58
+ end
59
+
60
+ puts "๐Ÿ“Ž Attaching to #{agent_type} agent..."
61
+
62
+ user = detect_agent_user(container_name)
63
+ puts " User: #{user}"
64
+
65
+ # Wait for tmux session to be ready
66
+ 10.times do
67
+ if system("docker", "exec", "-u", user, container_name, "tmux", "has-session", "-t", "agent", err: File::NULL, out: File::NULL)
68
+ break
69
+ end
70
+ sleep 1
71
+ end
72
+
73
+ # Attach to agent session which has the status bar
74
+ # Must run as agent user since tmux server runs under that user
75
+ exec("docker", "exec", "-it", "-u", user, container_name, "tmux", "attach", "-t", "agent")
76
+ end
77
+
78
+ private
79
+
80
+ def self.build_docker_command(container_name, agent_type, config, agent_config, agent_def, banner_path)
81
+ docker_cmd = [
82
+ "docker", "run", "-d",
83
+ "--name", container_name,
84
+ "--network", "host",
85
+ "--restart", "unless-stopped",
86
+ "--tmpfs", "/rails/tmp",
87
+ "--tmpfs", "/rails/log"
88
+ ]
89
+
90
+ # Inject custom environment variables from config
91
+ # Merge global env with agent-specific env (agent-specific takes precedence)
92
+ merged_env = {}
93
+ merged_env.merge!(config["env"]) if config["env"]
94
+ merged_env.merge!(agent_config["env"]) if agent_config["env"]
95
+
96
+ if merged_env.any?
97
+ merged_env.each do |k, v|
98
+ docker_cmd += ["-e", "#{k}=#{v}"]
99
+ end
100
+ global_count = config["env"]&.size || 0
101
+ agent_count = agent_config["env"]&.size || 0
102
+ if agent_count > 0
103
+ puts "๐ŸŒฟ Injected #{global_count} global + #{agent_count} agent-specific env vars"
104
+ else
105
+ puts "๐ŸŒฟ Injected #{global_count} custom env vars from config"
106
+ end
107
+ end
108
+
109
+ docker_cmd += [
110
+ # Mount Claude config
111
+ "-v", "#{ENV['HOME']}/.claude.json:/tmp/cfg/claude.json:ro",
112
+ "-v", "#{ENV['HOME']}/.claude:/tmp/cfg/claude_dir:ro",
113
+ "-v", "#{banner_path}:/etc/tinker/system-prompt.txt:ro",
114
+ "-e", "TINKER_VERSION=main",
115
+ "-e", "SKILLS=#{agent_def[:skills]&.join(',')}",
116
+ "-e", "AGENT_TYPE=#{agent_type}",
117
+ "-e", "PROJECT_ID=#{config['project_id']}",
118
+ "-e", "RAILS_WS_URL=#{config['rails_ws_url']}",
119
+ "-e", "RAILS_API_URL=#{config['rails_api_url']}",
120
+ "-e", "RAILS_API_KEY=#{agent_config['mcp_api_key']}"
121
+ ]
122
+
123
+ add_github_auth!(docker_cmd, config)
124
+ add_git_config!(docker_cmd, config)
125
+ add_development_mounts!(docker_cmd)
126
+
127
+ local_setup_script = File.join(File.dirname(__FILE__), "..", "..", "setup-agent.rb")
128
+
129
+ if File.exist?(local_setup_script)
130
+ docker_cmd += [Config.image_name(config), "ruby", "/tmp/setup-agent.rb"]
131
+ else
132
+ docker_cmd += [Config.image_name(config)]
133
+ end
134
+
135
+ docker_cmd
136
+ end
137
+
138
+ def self.add_github_auth!(docker_cmd, config)
139
+ github = config["github"] || {}
140
+
141
+ if github["method"] == "app"
142
+ key_path = File.expand_path(github["app_private_key_path"].to_s)
143
+
144
+ unless File.exist?(key_path) && !File.directory?(key_path)
145
+ puts "โŒ Error: GitHub App private key not found at: #{key_path}"
146
+ puts " Please check 'app_private_key_path' in tinker.env.rb"
147
+ exit 1
148
+ end
149
+
150
+ docker_cmd.concat([
151
+ "-e", "GITHUB_APP_CLIENT_ID=#{github['app_client_id']}",
152
+ "-e", "GITHUB_APP_INSTALLATION_ID=#{github['app_installation_id']}",
153
+ "-v", "#{key_path}:/tmp/github-app-privkey.pem:ro"
154
+ ])
155
+ puts "๐Ÿ” Using GitHub App authentication"
156
+ elsif github["token"]
157
+ docker_cmd.concat(["-e", "GH_TOKEN=#{github['token']}"])
158
+ puts "๐Ÿ”‘ Using GitHub token authentication"
159
+ else
160
+ puts "โŒ Error: No GitHub authentication configured"
161
+ puts " Please configure 'github' in tinker.env.rb"
162
+ exit 1
163
+ end
164
+ end
165
+
166
+ def self.add_git_config!(docker_cmd, config)
167
+ return unless (git_config = config["git"])
168
+
169
+ docker_cmd.concat(["-e", "GIT_USER_NAME=#{git_config['user_name']}"]) if git_config["user_name"]
170
+ docker_cmd.concat(["-e", "GIT_USER_EMAIL=#{git_config['user_email']}"]) if git_config["user_email"]
171
+ end
172
+
173
+ def self.add_development_mounts!(docker_cmd)
174
+ # Check for local setup-agent.rb (for development)
175
+ local_setup_script = File.join(File.dirname(__FILE__), "..", "..", "setup-agent.rb")
176
+
177
+ # Check for local agent-bridge binaries (for development)
178
+ arch = `uname -m`.strip
179
+ linux_arch = (arch == "x86_64") ? "amd64" : "arm64"
180
+ linux_bridge = File.join(Dir.pwd, "tinker-public", "bin", "agent-bridge-linux-#{linux_arch}")
181
+
182
+ local_bridge_default = File.join(Dir.pwd, "bin", "agent-bridge")
183
+ local_tmux = File.join(File.dirname(__FILE__), "..", "..", "bin", "agent-bridge-tmux")
184
+
185
+ if File.exist?(local_setup_script)
186
+ puts "๐Ÿ”ง Using local setup-agent.rb for development"
187
+ docker_cmd.concat(["-v", "#{File.expand_path(local_setup_script)}:/tmp/setup-agent.rb:ro"])
188
+ end
189
+
190
+ if File.exist?(linux_bridge)
191
+ puts "๐Ÿ”ง Using local linux binary: #{linux_bridge}"
192
+ docker_cmd.concat(["-v", "#{linux_bridge}:/tmp/agent-bridge:ro"])
193
+ elsif File.exist?(local_bridge_default)
194
+ # Check if it's a binary or script
195
+ is_script = File.read(local_bridge_default, 4) == "#!/b"
196
+ if is_script
197
+ puts "โš ๏ธ bin/agent-bridge is a host wrapper script. Please run 'bin/build-bridge' to generate linux binaries."
198
+ else
199
+ puts "๐Ÿ”ง Using local agent-bridge binary"
200
+ docker_cmd.concat(["-v", "#{local_bridge_default}:/tmp/agent-bridge:ro"])
201
+ end
202
+ end
203
+
204
+ if File.exist?(local_tmux)
205
+ docker_cmd.concat(["-v", "#{File.expand_path(local_tmux)}:/tmp/agent-bridge-tmux:ro"])
206
+ end
207
+ end
208
+
209
+ def self.detect_agent_user(container_name)
210
+ # Determine the user to attach as
211
+ # Robust method: find the user running the agent process (tmux or bridge)
212
+ user = `docker exec #{container_name} ps aux | grep "[a]gent-bridge-tmux" | awk '{print $1}' | head -n 1`.strip
213
+
214
+ if user.empty?
215
+ user = `docker exec #{container_name} ps aux | grep "[t]mux new-session" | awk '{print $1}' | head -n 1`.strip
216
+ end
217
+
218
+ if user.empty?
219
+ # Fallback to previous heuristic
220
+ detected_user = `docker exec #{container_name} whoami 2>/dev/null`.strip
221
+ if detected_user == "root" || detected_user.empty?
222
+ uid = Process.uid
223
+ mapped_user = `docker exec #{container_name} getent passwd #{uid} | cut -d: -f1`.strip
224
+ user = mapped_user unless mapped_user.empty?
225
+ else
226
+ user = detected_user
227
+ end
228
+ end
229
+
230
+ if user.empty?
231
+ # Final Fallback
232
+ user = "rails"
233
+ puts "โš ๏ธ Could not detect agent user, defaulting to '#{user}'"
234
+ end
235
+
236
+ user
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module TinkerAgent
6
+ module Config
7
+ def self.load
8
+ rb_config_file = File.join(Dir.pwd, "tinker.env.rb")
9
+
10
+ unless File.exist?(rb_config_file)
11
+ puts "โŒ Error: tinker.env.rb not found in current directory"
12
+ puts ""
13
+ puts "Create tinker.env.rb:"
14
+ puts " {"
15
+ puts " project_id: 1,"
16
+ puts " rails_ws_url: '...',"
17
+ puts " # ..."
18
+ puts " # Paste your stripped .env content here:"
19
+ puts " dot_env: <<~ENV"
20
+ puts " STRIPE_KEY=sk_test_..."
21
+ puts " OPENAI_KEY=sk-..."
22
+ puts " ENV"
23
+ puts " }"
24
+ puts " echo 'tinker.env.rb' >> .gitignore"
25
+ exit 1
26
+ end
27
+
28
+ puts "โš™๏ธ Loading configuration from tinker.env.rb"
29
+ config = eval(File.read(rb_config_file), binding, rb_config_file)
30
+
31
+ # Convert symbols to strings for easier handling before JSON normalization
32
+ config = config.transform_keys(&:to_s)
33
+
34
+ # Parse dot_env heredoc if present
35
+ if (dotenv = config["dot_env"])
36
+ config["env"] ||= {}
37
+ # Ensure env is string-keyed
38
+ config["env"] = config["env"].transform_keys(&:to_s)
39
+
40
+ dotenv.each_line do |line|
41
+ line = line.strip
42
+ next if line.empty? || line.start_with?('#')
43
+ k, v = line.split('=', 2)
44
+ next unless k && v
45
+ # Remove surrounding quotes and trailing comments (simple)
46
+ v = v.strip.gsub(/^['"]|['"]$/, '')
47
+ config["env"][k.strip] = v
48
+ end
49
+
50
+ config.delete("dot_env")
51
+ puts "๐ŸŒฟ Parsed dot_env into #{config['env'].size} environment variables"
52
+ end
53
+
54
+ # Normalize per-agent env hashes
55
+ if config["agents"].is_a?(Hash)
56
+ config["agents"].each do |agent_key, agent_config|
57
+ agent_key = agent_key.to_s
58
+ if agent_config.is_a?(Hash) && agent_config["env"].is_a?(Hash)
59
+ agent_config["env"] = agent_config["env"].transform_keys(&:to_s)
60
+ elsif agent_config.is_a?(Hash) && agent_config[:env].is_a?(Hash)
61
+ agent_config["env"] = agent_config[:env].transform_keys(&:to_s)
62
+ end
63
+ end
64
+ end
65
+
66
+ # Normalize symbols to strings for consistency via JSON round-trip
67
+ JSON.parse(JSON.generate(config))
68
+ end
69
+
70
+ def self.image_name(config)
71
+ if config["project_id"]
72
+ "tinker-sandbox-#{config['project_id']}"
73
+ else
74
+ "tinker-sandbox"
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module TinkerAgent
6
+ module Docker
7
+ def self.check_dockerfile!
8
+ unless File.exist?("Dockerfile.sandbox")
9
+ puts "โŒ Error: Dockerfile.sandbox not found"
10
+ puts ""
11
+ puts "Please create Dockerfile.sandbox by copying your existing Dockerfile"
12
+ puts "and adding the required agent dependencies."
13
+ puts ""
14
+ puts "See https://github.com/RoM4iK/tinker-public/blob/main/README.md for instructions."
15
+ exit 1
16
+ end
17
+ end
18
+
19
+ def self.build_image(config)
20
+ check_dockerfile!
21
+
22
+ user_id = `id -u`.strip
23
+ group_id = `id -g`.strip
24
+
25
+ puts "๐Ÿ—๏ธ Building Docker image..."
26
+
27
+ # Handle .dockerignore.sandbox
28
+ dockerignore_sandbox = ".dockerignore.sandbox"
29
+ dockerignore_original = ".dockerignore"
30
+ dockerignore_backup = ".dockerignore.bak"
31
+
32
+ has_sandbox_ignore = File.exist?(dockerignore_sandbox)
33
+ has_original_ignore = File.exist?(dockerignore_original)
34
+
35
+ if has_sandbox_ignore
36
+ puts "๐Ÿ“ฆ Swapping .dockerignore with .dockerignore.sandbox..."
37
+ if has_original_ignore
38
+ FileUtils.mv(dockerignore_original, dockerignore_backup)
39
+ end
40
+ FileUtils.cp(dockerignore_sandbox, dockerignore_original)
41
+ end
42
+
43
+ success = false
44
+ begin
45
+ success = system(
46
+ "docker", "build",
47
+ "--build-arg", "USER_ID=#{user_id}",
48
+ "--build-arg", "GROUP_ID=#{group_id}",
49
+ "-t", Config.image_name(config),
50
+ "-f", "Dockerfile.sandbox",
51
+ "."
52
+ )
53
+ ensure
54
+ if has_sandbox_ignore
55
+ # Restore original state
56
+ FileUtils.rm(dockerignore_original) if File.exist?(dockerignore_original)
57
+ if has_original_ignore
58
+ FileUtils.mv(dockerignore_backup, dockerignore_original)
59
+ end
60
+ puts "๐Ÿงน Restored original .dockerignore"
61
+ end
62
+ end
63
+
64
+ unless success
65
+ puts "โŒ Failed to build Docker image"
66
+ exit 1
67
+ end
68
+
69
+ puts "โœ… Docker image built"
70
+ end
71
+ end
72
+ end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tinker-agent",
3
- "version": "1.0.45",
3
+ "version": "1.0.47",
4
4
  "description": "Tinker Agent Runner",
5
5
  "bin": {
6
6
  "tinker-agent": "./run-tinker-agent.rb"
@@ -9,6 +9,7 @@
9
9
  "run-tinker-agent.rb",
10
10
  "agents.rb",
11
11
  "setup-agent.rb",
12
+ "lib/",
12
13
  "bin/agent-bridge-tmux",
13
14
  "bin/install-agent.sh"
14
15
  ],