claude_agent 0.7.10 → 0.7.11

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.
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeAgent
4
+ # Enumerable collection of {LiveToolActivity} entries with auto-wiring.
5
+ #
6
+ # Attaches to an {EventHandler} or {Client} and automatically tracks
7
+ # tool lifecycle events (:tool_use, :tool_result, :tool_progress),
8
+ # maintaining a live view of active and completed tools.
9
+ #
10
+ # @example Specific callbacks
11
+ # tracker = ToolActivityTracker.attach(client)
12
+ # tracker.on_start { |entry| puts "▸ #{entry.display_label}" }
13
+ # tracker.on_progress { |entry| puts " #{entry.elapsed&.round(1)}s..." }
14
+ # tracker.on_complete { |entry| puts "✓ #{entry.display_label}" }
15
+ #
16
+ # @example Catch-all callback
17
+ # tracker.on_change { |event, entry| log(event, entry.id) }
18
+ #
19
+ # @example Poll running tools
20
+ # tracker.running.each { |t| render_spinner(t) }
21
+ # tracker.done.each { |t| render_checkmark(t) }
22
+ #
23
+ class ToolActivityTracker
24
+ include Enumerable
25
+
26
+ # Create a tracker and wire it to an EventHandler or Client.
27
+ #
28
+ # @param target [EventHandler, Client] The event source to wire into
29
+ # @return [ToolActivityTracker]
30
+ def self.attach(target)
31
+ tracker = new
32
+ handler = target.is_a?(EventHandler) ? target : target.event_handler
33
+ tracker.send(:wire, handler)
34
+ tracker
35
+ end
36
+
37
+ def initialize
38
+ @entries = []
39
+ @index = {}
40
+ @callbacks = {}
41
+ end
42
+
43
+ # Register a callback for when a tool starts running.
44
+ #
45
+ # @yield [entry] Called when a tool use is detected
46
+ # @yieldparam entry [LiveToolActivity] The new entry
47
+ # @return [self]
48
+ def on_start(&block)
49
+ @callbacks[:started] = block
50
+ self
51
+ end
52
+
53
+ # Register a callback for when a tool completes (success or error).
54
+ #
55
+ # @yield [entry] Called when a tool result is received
56
+ # @yieldparam entry [LiveToolActivity] The completed entry
57
+ # @return [self]
58
+ def on_complete(&block)
59
+ @callbacks[:completed] = block
60
+ self
61
+ end
62
+
63
+ # Register a callback for tool progress updates.
64
+ #
65
+ # @yield [entry] Called when elapsed time is updated
66
+ # @yieldparam entry [LiveToolActivity] The updated entry
67
+ # @return [self]
68
+ def on_progress(&block)
69
+ @callbacks[:progress] = block
70
+ self
71
+ end
72
+
73
+ # Register a catch-all callback for any state change.
74
+ #
75
+ # Fires with :started, :completed, or :progress and the affected entry.
76
+ # Fires in addition to specific callbacks (on_start, on_complete, on_progress).
77
+ #
78
+ # @yield [event, entry] Called on each state change
79
+ # @yieldparam event [Symbol] :started, :completed, or :progress
80
+ # @yieldparam entry [LiveToolActivity] The affected entry
81
+ # @return [self]
82
+ def on_change(&block)
83
+ @callbacks[:change] = block
84
+ self
85
+ end
86
+
87
+ # @yield [LiveToolActivity]
88
+ # @return [Enumerator<LiveToolActivity>]
89
+ def each(&block)
90
+ @entries.each(&block)
91
+ end
92
+
93
+ # Number of tracked activities.
94
+ # @return [Integer]
95
+ def size
96
+ @entries.size
97
+ end
98
+
99
+ # Whether there are no tracked activities.
100
+ # @return [Boolean]
101
+ def empty?
102
+ @entries.empty?
103
+ end
104
+
105
+ # Look up a LiveToolActivity by tool use ID.
106
+ #
107
+ # @param id [String] Tool use ID
108
+ # @return [LiveToolActivity, nil]
109
+ def find_by_id(id)
110
+ @index[id]
111
+ end
112
+
113
+ alias_method :[], :find_by_id
114
+
115
+ # All currently running activities.
116
+ # @return [Array<LiveToolActivity>]
117
+ def running
118
+ @entries.select(&:running?)
119
+ end
120
+
121
+ # All completed (success) activities.
122
+ # @return [Array<LiveToolActivity>]
123
+ def done
124
+ @entries.select(&:done?)
125
+ end
126
+
127
+ # All errored activities.
128
+ # @return [Array<LiveToolActivity>]
129
+ def errored
130
+ @entries.select(&:error?)
131
+ end
132
+
133
+ # Clear all tracked state. Call between turns.
134
+ # @return [void]
135
+ def reset!
136
+ @entries.clear
137
+ @index.clear
138
+ end
139
+
140
+ private
141
+
142
+ def wire(handler)
143
+ handler.on(:tool_use) { |tool_use| handle_tool_use(tool_use) }
144
+ handler.on(:tool_result) { |tool_result, _tool_use| handle_tool_result(tool_result) }
145
+ handler.on(:tool_progress) { |message| handle_tool_progress(message) }
146
+ end
147
+
148
+ def emit(event, entry)
149
+ @callbacks[event]&.call(entry)
150
+ @callbacks[:change]&.call(event, entry)
151
+ end
152
+
153
+ def handle_tool_use(tool_use)
154
+ entry = LiveToolActivity.new(tool_use)
155
+ @entries << entry
156
+ @index[tool_use.id] = entry
157
+ emit(:started, entry)
158
+ end
159
+
160
+ def handle_tool_result(tool_result)
161
+ entry = @index[tool_result.tool_use_id]
162
+ return unless entry
163
+
164
+ entry.complete!(tool_result)
165
+ emit(:completed, entry)
166
+ end
167
+
168
+ def handle_tool_progress(message)
169
+ entry = @index[message.tool_use_id]
170
+ return unless entry
171
+
172
+ entry.update_elapsed(message.elapsed_time_seconds)
173
+ emit(:progress, entry)
174
+ end
175
+ end
176
+ end
@@ -15,7 +15,7 @@ module ClaudeAgent
15
15
 
16
16
  # API key source types (TypeScript SDK parity)
17
17
  # Indicates where the API key was sourced from
18
- API_KEY_SOURCES = %w[user project org temporary].freeze
18
+ API_KEY_SOURCES = %w[user project org temporary oauth].freeze
19
19
 
20
20
  # Tools preset configuration (TypeScript SDK parity)
21
21
  #
@@ -52,8 +52,8 @@ module ClaudeAgent
52
52
  # model.value # => "claude-3-opus"
53
53
  # model.display_name # => "Claude 3 Opus"
54
54
  #
55
- ModelInfo = Data.define(:value, :display_name, :description) do
56
- def initialize(value:, display_name: nil, description: nil)
55
+ ModelInfo = Data.define(:value, :display_name, :description, :supports_effort, :supported_effort_levels, :supports_adaptive_thinking) do
56
+ def initialize(value:, display_name: nil, description: nil, supports_effort: nil, supported_effort_levels: nil, supports_adaptive_thinking: nil)
57
57
  super
58
58
  end
59
59
  end
@@ -64,8 +64,19 @@ module ClaudeAgent
64
64
  # @example
65
65
  # status = McpServerStatus.new(name: "filesystem", status: "connected", server_info: {name: "fs", version: "1.0"})
66
66
  #
67
- McpServerStatus = Data.define(:name, :status, :server_info) do
68
- def initialize(name:, status:, server_info: nil)
67
+ McpServerStatus = Data.define(:name, :status, :server_info, :error, :config, :scope, :tools) do
68
+ def initialize(name:, status:, server_info: nil, error: nil, config: nil, scope: nil, tools: nil)
69
+ super
70
+ end
71
+ end
72
+
73
+ # Task usage statistics for TaskNotificationMessage (TypeScript SDK parity)
74
+ #
75
+ # @example
76
+ # usage = TaskUsage.new(total_tokens: 5000, tool_uses: 3, duration_ms: 2500)
77
+ #
78
+ TaskUsage = Data.define(:total_tokens, :tool_uses, :duration_ms) do
79
+ def initialize(total_tokens: 0, tool_uses: 0, duration_ms: 0)
69
80
  super
70
81
  end
71
82
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeAgent
4
- VERSION = "0.7.10"
4
+ VERSION = "0.7.11"
5
5
  end
data/lib/claude_agent.rb CHANGED
@@ -27,6 +27,8 @@ require_relative "claude_agent/cumulative_usage"
27
27
  require_relative "claude_agent/event_handler"
28
28
  require_relative "claude_agent/turn_result"
29
29
  require_relative "claude_agent/tool_activity"
30
+ require_relative "claude_agent/live_tool_activity"
31
+ require_relative "claude_agent/tool_activity_tracker"
30
32
  require_relative "claude_agent/query"
31
33
  require_relative "claude_agent/client"
32
34
  require_relative "claude_agent/conversation"
data/sig/claude_agent.rbs CHANGED
@@ -128,16 +128,31 @@ module ClaudeAgent
128
128
  attr_reader value: String
129
129
  attr_reader display_name: String?
130
130
  attr_reader description: String?
131
+ attr_reader supports_effort: bool?
132
+ attr_reader supported_effort_levels: Array[String]?
133
+ attr_reader supports_adaptive_thinking: bool?
131
134
 
132
- def initialize: (value: String, ?display_name: String?, ?description: String?) -> void
135
+ def initialize: (value: String, ?display_name: String?, ?description: String?, ?supports_effort: bool?, ?supported_effort_levels: Array[String]?, ?supports_adaptive_thinking: bool?) -> void
133
136
  end
134
137
 
135
138
  class McpServerStatus
136
139
  attr_reader name: String
137
140
  attr_reader status: String
138
141
  attr_reader server_info: Hash[String, untyped]?
142
+ attr_reader error: String?
143
+ attr_reader config: Hash[String, untyped]?
144
+ attr_reader scope: String?
145
+ attr_reader tools: Array[Hash[String, untyped]]?
146
+
147
+ def initialize: (name: String, status: String, ?server_info: Hash[String, untyped]?, ?error: String?, ?config: Hash[String, untyped]?, ?scope: String?, ?tools: Array[Hash[String, untyped]]?) -> void
148
+ end
139
149
 
140
- def initialize: (name: String, status: String, ?server_info: Hash[String, untyped]?) -> void
150
+ class TaskUsage
151
+ attr_reader total_tokens: Integer
152
+ attr_reader tool_uses: Integer
153
+ attr_reader duration_ms: Integer
154
+
155
+ def initialize: (?total_tokens: Integer, ?tool_uses: Integer, ?duration_ms: Integer) -> void
141
156
  end
142
157
 
143
158
  class AccountInfo
@@ -357,15 +372,13 @@ module ClaudeAgent
357
372
  # Hooks
358
373
  attr_accessor hooks: Hash[String, Array[HookMatcher]]?
359
374
 
360
- # Settings & sandbox
361
- attr_accessor settings: String?
375
+ # Sandbox
362
376
  attr_accessor sandbox: SandboxSettings?
363
377
 
364
378
  # Environment
365
379
  attr_accessor cwd: String?
366
380
  attr_accessor add_dirs: Array[String]
367
381
  attr_accessor env: Hash[String, String]
368
- attr_accessor user: String?
369
382
  attr_accessor agent: String?
370
383
 
371
384
  # CLI configuration
@@ -636,8 +649,9 @@ module ClaudeAgent
636
649
  attr_reader uuid: String
637
650
  attr_reader session_id: String
638
651
  attr_reader status: String
652
+ attr_reader permission_mode: String?
639
653
 
640
- def initialize: (uuid: String, session_id: String, status: String) -> void
654
+ def initialize: (uuid: String, session_id: String, status: String, ?permission_mode: String?) -> void
641
655
  def type: () -> :status
642
656
  end
643
657
 
@@ -649,8 +663,9 @@ module ClaudeAgent
649
663
  attr_reader tool_name: String
650
664
  attr_reader parent_tool_use_id: String?
651
665
  attr_reader elapsed_time_seconds: Float
666
+ attr_reader task_id: String?
652
667
 
653
- def initialize: (uuid: String, session_id: String, tool_use_id: String, tool_name: String, elapsed_time_seconds: Float, ?parent_tool_use_id: String?) -> void
668
+ def initialize: (uuid: String, session_id: String, tool_use_id: String, tool_name: String, elapsed_time_seconds: Float, ?parent_tool_use_id: String?, ?task_id: String?) -> void
654
669
  def type: () -> :tool_progress
655
670
  end
656
671
 
@@ -694,8 +709,10 @@ module ClaudeAgent
694
709
  attr_reader status: String
695
710
  attr_reader output_file: String
696
711
  attr_reader summary: String
712
+ attr_reader tool_use_id: String?
713
+ attr_reader usage: TaskUsage?
697
714
 
698
- def initialize: (uuid: String, session_id: String, task_id: String, status: String, output_file: String, summary: String) -> void
715
+ def initialize: (uuid: String, session_id: String, task_id: String, status: String, output_file: String, summary: String, ?tool_use_id: String?, ?usage: TaskUsage?) -> void
699
716
  def type: () -> :task_notification
700
717
  def completed?: () -> bool
701
718
  def failed?: () -> bool
@@ -897,8 +914,9 @@ module ClaudeAgent
897
914
 
898
915
  class StopInput < BaseHookInput
899
916
  attr_reader stop_hook_active: bool
917
+ attr_reader last_assistant_message: String?
900
918
 
901
- def initialize: (?stop_hook_active: bool, **untyped) -> void
919
+ def initialize: (?stop_hook_active: bool, ?last_assistant_message: String?, **untyped) -> void
902
920
  end
903
921
 
904
922
  class SubagentStartInput < BaseHookInput
@@ -912,8 +930,10 @@ module ClaudeAgent
912
930
  attr_reader stop_hook_active: bool
913
931
  attr_reader agent_id: String?
914
932
  attr_reader agent_transcript_path: String?
933
+ attr_reader agent_type: String?
934
+ attr_reader last_assistant_message: String?
915
935
 
916
- def initialize: (?stop_hook_active: bool, ?agent_id: String?, ?agent_transcript_path: String?, **untyped) -> void
936
+ def initialize: (?stop_hook_active: bool, ?agent_id: String?, ?agent_transcript_path: String?, ?agent_type: String?, ?last_assistant_message: String?, **untyped) -> void
917
937
  end
918
938
 
919
939
  class PreCompactInput < BaseHookInput
@@ -1132,14 +1152,44 @@ module ClaudeAgent
1132
1152
 
1133
1153
  # Event handler for dispatching typed events from message streams
1134
1154
  class EventHandler
1155
+ TYPE_EVENTS: Array[Symbol]
1156
+ DECOMPOSED_EVENTS: Array[Symbol]
1157
+ META_EVENTS: Array[Symbol]
1158
+ EVENTS: Array[Symbol]
1159
+
1135
1160
  def initialize: () -> void
1136
1161
  def on: (Symbol event) { (*untyped) -> void } -> self
1162
+
1163
+ # Meta events
1137
1164
  def on_message: () { (message) -> void } -> self
1165
+
1166
+ # Type-based events
1167
+ def on_user: () { (UserMessage | UserMessageReplay) -> void } -> self
1168
+ def on_assistant: () { (AssistantMessage) -> void } -> self
1169
+ def on_system: () { (SystemMessage) -> void } -> self
1170
+ def on_result: () { (ResultMessage) -> void } -> self
1171
+ def on_stream_event: () { (StreamEvent) -> void } -> self
1172
+ def on_compact_boundary: () { (CompactBoundaryMessage) -> void } -> self
1173
+ def on_status: () { (StatusMessage) -> void } -> self
1174
+ def on_tool_progress: () { (ToolProgressMessage) -> void } -> self
1175
+ def on_hook_response: () { (HookResponseMessage) -> void } -> self
1176
+ def on_auth_status: () { (AuthStatusMessage) -> void } -> self
1177
+ def on_task_notification: () { (TaskNotificationMessage) -> void } -> self
1178
+ def on_hook_started: () { (HookStartedMessage) -> void } -> self
1179
+ def on_hook_progress: () { (HookProgressMessage) -> void } -> self
1180
+ def on_tool_use_summary: () { (ToolUseSummaryMessage) -> void } -> self
1181
+ def on_task_started: () { (TaskStartedMessage) -> void } -> self
1182
+ def on_task_progress: () { (TaskProgressMessage) -> void } -> self
1183
+ def on_rate_limit_event: () { (RateLimitEvent) -> void } -> self
1184
+ def on_prompt_suggestion: () { (PromptSuggestionMessage) -> void } -> self
1185
+ def on_files_persisted: () { (FilesPersistedEvent) -> void } -> self
1186
+
1187
+ # Decomposed events
1138
1188
  def on_text: () { (String) -> void } -> self
1139
1189
  def on_thinking: () { (String) -> void } -> self
1140
1190
  def on_tool_use: () { (ToolUseBlock | ServerToolUseBlock) -> void } -> self
1141
1191
  def on_tool_result: () { (ToolResultBlock | ServerToolResultBlock, (ToolUseBlock | ServerToolUseBlock)?) -> void } -> self
1142
- def on_result: () { (ResultMessage) -> void } -> self
1192
+
1143
1193
  def handle: (message) -> void
1144
1194
  def reset!: () -> void
1145
1195
  def has_handlers?: () -> bool
@@ -1164,14 +1214,64 @@ module ClaudeAgent
1164
1214
  def duration: () -> Float?
1165
1215
  end
1166
1216
 
1217
+ # Mutable wrapper around a ToolUseBlock for real-time status tracking
1218
+ class LiveToolActivity
1219
+ attr_reader tool_use: ToolUseBlock | ServerToolUseBlock
1220
+ attr_reader tool_result: ToolResultBlock | ServerToolResultBlock | nil
1221
+ attr_reader status: Symbol
1222
+ attr_reader started_at: Time
1223
+ attr_reader elapsed: Float?
1224
+
1225
+ def initialize: (ToolUseBlock | ServerToolUseBlock tool_use) -> void
1226
+ def id: () -> String
1227
+ def name: () -> String
1228
+ def input: () -> Hash[Symbol | String, untyped]
1229
+ def display_label: () -> String
1230
+ def summary: (?max: Integer) -> String
1231
+ def file_path: () -> String?
1232
+ def complete!: (ToolResultBlock | ServerToolResultBlock result) -> void
1233
+ def update_elapsed: (Float seconds) -> void
1234
+ def running?: () -> bool
1235
+ def done?: () -> bool
1236
+ def error?: () -> bool
1237
+ def complete?: () -> bool
1238
+ def to_h: () -> Hash[Symbol, untyped]
1239
+ def inspect: () -> String
1240
+ end
1241
+
1242
+ # Enumerable collection of LiveToolActivity entries with auto-wiring
1243
+ class ToolActivityTracker
1244
+ include Enumerable[LiveToolActivity]
1245
+
1246
+ def self.attach: (EventHandler | Client target) -> ToolActivityTracker
1247
+
1248
+ def initialize: () -> void
1249
+ def on_start: () { (LiveToolActivity) -> void } -> self
1250
+ def on_complete: () { (LiveToolActivity) -> void } -> self
1251
+ def on_progress: () { (LiveToolActivity) -> void } -> self
1252
+ def on_change: () { (Symbol, LiveToolActivity) -> void } -> self
1253
+ def each: () { (LiveToolActivity) -> void } -> void
1254
+ | () -> Enumerator[LiveToolActivity, void]
1255
+ def size: () -> Integer
1256
+ def empty?: () -> bool
1257
+ def find_by_id: (String id) -> LiveToolActivity?
1258
+ def []: (String id) -> LiveToolActivity?
1259
+ def running: () -> Array[LiveToolActivity]
1260
+ def done: () -> Array[LiveToolActivity]
1261
+ def errored: () -> Array[LiveToolActivity]
1262
+ def reset!: () -> void
1263
+ end
1264
+
1167
1265
  # High-level conversation interface managing the full lifecycle
1168
1266
  class Conversation
1169
1267
  CONVERSATION_KEYS: Array[Symbol]
1268
+ CALLBACK_ALIASES: Hash[Symbol, Symbol]
1170
1269
 
1171
1270
  attr_reader turns: Array[TurnResult]
1172
1271
  attr_reader messages: Array[message]
1173
1272
  attr_reader tool_activity: Array[ToolActivity]
1174
1273
  attr_reader client: Client
1274
+ attr_reader tool_tracker: ToolActivityTracker?
1175
1275
 
1176
1276
  def self.open: (**untyped) { (Conversation) -> void } -> void
1177
1277
  def self.resume: (String session_id, **untyped) -> Conversation
@@ -1245,12 +1345,37 @@ module ClaudeAgent
1245
1345
  def send_message: (String | Array[content_block] content, ?session_id: String, ?uuid: String?) -> void
1246
1346
  alias query send_message
1247
1347
  def on: (Symbol event) { (*untyped) -> void } -> self
1348
+
1349
+ # Meta events
1248
1350
  def on_message: () { (message) -> void } -> self
1351
+
1352
+ # Type-based events
1353
+ def on_user: () { (UserMessage | UserMessageReplay) -> void } -> self
1354
+ def on_assistant: () { (AssistantMessage) -> void } -> self
1355
+ def on_system: () { (SystemMessage) -> void } -> self
1356
+ def on_result: () { (ResultMessage) -> void } -> self
1357
+ def on_stream_event: () { (StreamEvent) -> void } -> self
1358
+ def on_compact_boundary: () { (CompactBoundaryMessage) -> void } -> self
1359
+ def on_status: () { (StatusMessage) -> void } -> self
1360
+ def on_tool_progress: () { (ToolProgressMessage) -> void } -> self
1361
+ def on_hook_response: () { (HookResponseMessage) -> void } -> self
1362
+ def on_auth_status: () { (AuthStatusMessage) -> void } -> self
1363
+ def on_task_notification: () { (TaskNotificationMessage) -> void } -> self
1364
+ def on_hook_started: () { (HookStartedMessage) -> void } -> self
1365
+ def on_hook_progress: () { (HookProgressMessage) -> void } -> self
1366
+ def on_tool_use_summary: () { (ToolUseSummaryMessage) -> void } -> self
1367
+ def on_task_started: () { (TaskStartedMessage) -> void } -> self
1368
+ def on_task_progress: () { (TaskProgressMessage) -> void } -> self
1369
+ def on_rate_limit_event: () { (RateLimitEvent) -> void } -> self
1370
+ def on_prompt_suggestion: () { (PromptSuggestionMessage) -> void } -> self
1371
+ def on_files_persisted: () { (FilesPersistedEvent) -> void } -> self
1372
+
1373
+ # Decomposed events
1249
1374
  def on_text: () { (String) -> void } -> self
1250
1375
  def on_thinking: () { (String) -> void } -> self
1251
1376
  def on_tool_use: () { (ToolUseBlock | ServerToolUseBlock) -> void } -> self
1252
1377
  def on_tool_result: () { (ToolResultBlock | ServerToolResultBlock, (ToolUseBlock | ServerToolUseBlock)?) -> void } -> self
1253
- def on_result: () { (ResultMessage) -> void } -> self
1378
+
1254
1379
  def receive_messages: () { (message) -> void } -> void
1255
1380
  | () -> Enumerator[message, void]
1256
1381
  def receive_response: () { (message) -> void } -> void
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: claude_agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.10
4
+ version: 0.7.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Carr
@@ -62,6 +62,7 @@ files:
62
62
  - lib/claude_agent/get_session_messages.rb
63
63
  - lib/claude_agent/hooks.rb
64
64
  - lib/claude_agent/list_sessions.rb
65
+ - lib/claude_agent/live_tool_activity.rb
65
66
  - lib/claude_agent/logging.rb
66
67
  - lib/claude_agent/mcp/server.rb
67
68
  - lib/claude_agent/mcp/tool.rb
@@ -78,6 +79,7 @@ files:
78
79
  - lib/claude_agent/session_paths.rb
79
80
  - lib/claude_agent/spawn.rb
80
81
  - lib/claude_agent/tool_activity.rb
82
+ - lib/claude_agent/tool_activity_tracker.rb
81
83
  - lib/claude_agent/transport/base.rb
82
84
  - lib/claude_agent/transport/subprocess.rb
83
85
  - lib/claude_agent/turn_result.rb