robot_lab-a2a 0.2.7 → 0.2.8
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/.envrc +3 -0
- data/CHANGELOG.md +4 -0
- data/CLAUDE.md +2 -2
- data/README.md +2 -2
- data/Rakefile +0 -1
- data/lib/robot_lab/a2a/ask_user_tool.rb +6 -0
- data/lib/robot_lab/a2a/io_bridge.rb +13 -10
- data/lib/robot_lab/a2a/network_adapter.rb +4 -0
- data/lib/robot_lab/a2a/registry.rb +3 -0
- data/lib/robot_lab/a2a/robot_adapter.rb +16 -6
- data/lib/robot_lab/a2a/server.rb +10 -0
- data/lib/robot_lab/a2a/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6c741a3c521ca8442b326ef4bff336e8bcc5daaab9075055fda53dba9f57a50c
|
|
4
|
+
data.tar.gz: 06e2356682717b7e281fb738be8923e0cf8b0189b341b0aa9d7f9baacc76b8b6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 34c7333a443240f595631ca7299f0ab3f57f87d0542b2a67c20f92909d12459ab98d5eee0e1aa434eee6041793230817e08a099c0db926fa47c956bff412aae9
|
|
7
|
+
data.tar.gz: a982730ee7ab5c12b2b01f6c5ba90fcf550ee2753a01bd9026a73a2658e9f630af6f6217a74c4aa1fa2c10827d37eae521bb3730ece0d002b56ebef154738c29
|
data/.envrc
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.2.8] - 2026-09-09
|
|
4
|
+
|
|
5
|
+
Released in lockstep with `robot_lab` core v0.2.8: this gem now resolves the released core gem from RubyGems instead of the local sibling checkout (local-path development remains available via `BUNDLE_GEMFILE=Gemfile.local`). Also in this release: reek warnings triaged to zero, and the gem lifecycle tasks (build/install/release) moved from the Rakefile to the asgard task runner.
|
|
6
|
+
|
|
3
7
|
### Added
|
|
4
8
|
- `.loki` Asgard task file: `test`, `rubocop`, `rubocop_fix`, `flog`, `flay`, `quality`, `build`, `install`, `release`, and `console` tasks via the Asgard task runner
|
|
5
9
|
- `flay_check` Rake task: structural code duplication gate (mass threshold 50); integrated into the `quality` Rake task
|
data/CLAUDE.md
CHANGED
|
@@ -10,8 +10,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
rake test # Run full test suite (default rake task)
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
asgard build # Build the gem package
|
|
14
|
+
asgard install # Install gem locally
|
|
15
15
|
|
|
16
16
|
bin/setup # Install dependencies
|
|
17
17
|
bin/console # IRB session with gem loaded
|
data/README.md
CHANGED
|
@@ -85,8 +85,8 @@ Full documentation lives in [`docs/`](docs/):
|
|
|
85
85
|
```bash
|
|
86
86
|
bin/setup # install dependencies
|
|
87
87
|
rake test # run the full test suite
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
asgard quality # static analysis quality gates
|
|
89
|
+
asgard build # build the gem package
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
Run a single test file:
|
data/Rakefile
CHANGED
|
@@ -18,6 +18,12 @@ module RobotLab
|
|
|
18
18
|
|
|
19
19
|
attr_writer :event_queue, :answer_queue
|
|
20
20
|
|
|
21
|
+
def initialize(robot: nil)
|
|
22
|
+
super
|
|
23
|
+
@event_queue = nil
|
|
24
|
+
@answer_queue = nil
|
|
25
|
+
end
|
|
26
|
+
|
|
21
27
|
def execute(question:, choices: nil, default: nil)
|
|
22
28
|
raise 'A2A queues not injected — use RobotLab::A2A::RobotAdapter' unless @event_queue
|
|
23
29
|
|
|
@@ -56,20 +56,23 @@ module RobotLab
|
|
|
56
56
|
# --- input side (robot.input) ---
|
|
57
57
|
|
|
58
58
|
def gets
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
@output_buffer.clear
|
|
62
|
-
text
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
prompt = ::A2A::Models::Message.agent(
|
|
66
|
-
prompt_text.empty? ? 'Input required:' : prompt_text
|
|
67
|
-
)
|
|
68
|
-
@event_queue.push({ type: :ask, prompt: prompt })
|
|
59
|
+
prompt = ::A2A::Models::Message.agent(drain_prompt_text)
|
|
60
|
+
@event_queue.push({ type: :ask, prompt: })
|
|
69
61
|
|
|
70
62
|
answer = @answer_queue.pop
|
|
71
63
|
"#{answer}\n"
|
|
72
64
|
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
# Atomically empty the output buffer and return its text as the prompt.
|
|
69
|
+
def drain_prompt_text
|
|
70
|
+
@buffer_mutex.synchronize do
|
|
71
|
+
text = @output_buffer.dup.strip
|
|
72
|
+
@output_buffer.clear
|
|
73
|
+
text.empty? ? 'Input required:' : text
|
|
74
|
+
end
|
|
75
|
+
end
|
|
73
76
|
end
|
|
74
77
|
end
|
|
75
78
|
end
|
|
@@ -8,6 +8,8 @@ module RobotLab
|
|
|
8
8
|
# per-robot queue injection across all network robots, planned for a future
|
|
9
9
|
# release. Wrap individual robots with RobotAdapter for interactive flows.
|
|
10
10
|
class NetworkAdapter < ::A2A::Server::AgentExecutor
|
|
11
|
+
# :reek:ControlParameter -- interactive is only validated here; anything
|
|
12
|
+
# but :none is rejected with a pointer to RobotAdapter.
|
|
11
13
|
def initialize(network, interactive: :none)
|
|
12
14
|
super()
|
|
13
15
|
if interactive != :none
|
|
@@ -19,6 +21,8 @@ module RobotLab
|
|
|
19
21
|
@network = network
|
|
20
22
|
end
|
|
21
23
|
|
|
24
|
+
# :reek:FeatureEnvy -- AgentExecutor protocol method: it drives the task
|
|
25
|
+
# lifecycle (start/complete/emit) on the request context handed to it.
|
|
22
26
|
def call(context)
|
|
23
27
|
context.task.start!
|
|
24
28
|
context.emit_status
|
|
@@ -4,6 +4,9 @@ module RobotLab
|
|
|
4
4
|
module A2A
|
|
5
5
|
# Tracks live robot threads for in-progress interactive runs.
|
|
6
6
|
# Keyed by A2A task_id. Entries are removed when the robot thread finishes.
|
|
7
|
+
#
|
|
8
|
+
# :reek:InstanceVariableAssumption -- @mutex and @entries are class-level
|
|
9
|
+
# singleton state, assigned once in the class body; there is no #initialize.
|
|
7
10
|
class Registry
|
|
8
11
|
Entry = Data.define(:thread, :event_queue, :answer_queue)
|
|
9
12
|
|
|
@@ -16,6 +16,11 @@ module RobotLab
|
|
|
16
16
|
#
|
|
17
17
|
# Interactive modes keep the robot thread alive between A2A INPUT_REQUIRED
|
|
18
18
|
# and resume. Only works with in-process (Memory) storage.
|
|
19
|
+
#
|
|
20
|
+
# :reek:DataClump -- robot plus its event/answer queue pair thread through
|
|
21
|
+
# the per-mode injection helpers; a wrapper object would outlive its one use.
|
|
22
|
+
# :reek:RepeatedConditional -- @interactive is the adapter's mode switch;
|
|
23
|
+
# run, setup and teardown each dispatch on it once.
|
|
19
24
|
class RobotAdapter < ::A2A::Server::AgentExecutor
|
|
20
25
|
VALID_MODES = %i[none a2a_tool io_bridge].freeze
|
|
21
26
|
|
|
@@ -55,6 +60,8 @@ module RobotLab
|
|
|
55
60
|
|
|
56
61
|
# ── :none mode ────────────────────────────────────────────
|
|
57
62
|
|
|
63
|
+
# :reek:FeatureEnvy -- executor lifecycle: drives start/complete/emit on
|
|
64
|
+
# the request context handed to it by the A2A server.
|
|
58
65
|
def run_simple(context, input_text)
|
|
59
66
|
context.task.start!
|
|
60
67
|
context.emit_status
|
|
@@ -70,6 +77,8 @@ module RobotLab
|
|
|
70
77
|
|
|
71
78
|
# ── interactive modes ─────────────────────────────────────
|
|
72
79
|
|
|
80
|
+
# :reek:FeatureEnvy -- resume-vs-fresh dispatch on the request context;
|
|
81
|
+
# the fail/emit calls belong to the A2A task lifecycle, not another home.
|
|
73
82
|
def run_interactive(context, input_text, task_id)
|
|
74
83
|
if context.is_a?(::A2A::Server::ResumeContext)
|
|
75
84
|
entry = Registry.fetch(task_id)
|
|
@@ -85,6 +94,8 @@ module RobotLab
|
|
|
85
94
|
end
|
|
86
95
|
end
|
|
87
96
|
|
|
97
|
+
# :reek:TooManyStatements -- linear bootstrap of one interactive run:
|
|
98
|
+
# build queues, start task, spawn robot thread, register, then monitor.
|
|
88
99
|
def start_interactive_run(context, input_text, task_id)
|
|
89
100
|
event_queue = Queue.new
|
|
90
101
|
answer_queue = Queue.new
|
|
@@ -116,21 +127,20 @@ module RobotLab
|
|
|
116
127
|
|
|
117
128
|
def monitor_task(context, entry, _task_id)
|
|
118
129
|
event = entry.event_queue.pop
|
|
130
|
+
task = context.task
|
|
119
131
|
case event[:type]
|
|
120
132
|
when :ask
|
|
121
|
-
|
|
122
|
-
context.emit_status(final: true)
|
|
133
|
+
task.require_input!(message: event[:prompt])
|
|
123
134
|
when :done
|
|
124
135
|
artifact = ::A2A::Models::Artifact.new(
|
|
125
136
|
parts: [::A2A::Models::Part.text(event[:reply].to_s)],
|
|
126
137
|
name: 'reply'
|
|
127
138
|
)
|
|
128
|
-
|
|
129
|
-
context.emit_status(final: true)
|
|
139
|
+
task.complete!(artifacts: [artifact])
|
|
130
140
|
when :error
|
|
131
|
-
|
|
132
|
-
context.emit_status(final: true)
|
|
141
|
+
task.fail!(message: event[:error].message)
|
|
133
142
|
end
|
|
143
|
+
context.emit_status(final: true)
|
|
134
144
|
end
|
|
135
145
|
|
|
136
146
|
# ── interactive setup / teardown ──────────────────────────
|
data/lib/robot_lab/a2a/server.rb
CHANGED
|
@@ -29,9 +29,13 @@ module RobotLab
|
|
|
29
29
|
# map "/agents" do
|
|
30
30
|
# run RobotLab::A2A::Server.new.add_robot(my_robot).to_app
|
|
31
31
|
# end
|
|
32
|
+
#
|
|
33
|
+
# :reek:DataClump -- name/description/path is the agent-card identity trio;
|
|
34
|
+
# add_robot and add_network resolve caller overrides, build_agent_card consumes.
|
|
32
35
|
class Server
|
|
33
36
|
DEFAULT_VERSION = '1.0'
|
|
34
37
|
|
|
38
|
+
# :reek:ControlParameter -- nil storage means "default to in-process Memory".
|
|
35
39
|
def initialize(host: 'localhost', port: 9292, storage: nil, interactive: :none)
|
|
36
40
|
@host = host
|
|
37
41
|
@port = port
|
|
@@ -47,6 +51,8 @@ module RobotLab
|
|
|
47
51
|
# @param description [String, nil] overrides robot.description
|
|
48
52
|
# @param path [String, nil] URL path prefix (defaults to "/dns-label")
|
|
49
53
|
# @return [self] for chaining
|
|
54
|
+
# :reek:ControlParameter -- nil name/description/path mean "derive from
|
|
55
|
+
# the robot"; each `||` is a default, not a behavior switch.
|
|
50
56
|
def add_robot(robot, name: nil, description: nil, path: nil)
|
|
51
57
|
agent_name = (name || robot.name).to_s
|
|
52
58
|
agent_desc = description || robot.description || agent_name
|
|
@@ -65,6 +71,8 @@ module RobotLab
|
|
|
65
71
|
# @param description [String, nil]
|
|
66
72
|
# @param path [String, nil] URL path prefix (defaults to "/dns-label")
|
|
67
73
|
# @return [self] for chaining
|
|
74
|
+
# :reek:ControlParameter -- nil description/path mean "derive from name";
|
|
75
|
+
# each `||` is a default, not a behavior switch.
|
|
68
76
|
def add_network(network, name:, description: nil, path: nil)
|
|
69
77
|
if @interactive != :none
|
|
70
78
|
raise ArgumentError,
|
|
@@ -88,6 +96,8 @@ module RobotLab
|
|
|
88
96
|
end
|
|
89
97
|
|
|
90
98
|
# Return a Rack app for embedding in other servers (e.g. Rails, Puma).
|
|
99
|
+
# :reek:FeatureEnvy -- maps each registered agent config into its
|
|
100
|
+
# simple_a2a rack app; the config hashes are this class's own @agents values.
|
|
91
101
|
def to_app
|
|
92
102
|
url_map = @agents.transform_values do |cfg|
|
|
93
103
|
::A2A.server(
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: robot_lab-a2a
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dewayne VanHoozer
|
|
@@ -125,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
125
125
|
- !ruby/object:Gem::Version
|
|
126
126
|
version: '0'
|
|
127
127
|
requirements: []
|
|
128
|
-
rubygems_version: 4.0.
|
|
128
|
+
rubygems_version: 4.0.20
|
|
129
129
|
specification_version: 4
|
|
130
130
|
summary: Agent2Agent (A2A) protocol adapter for RobotLab
|
|
131
131
|
test_files: []
|