phronomy 0.11.1 → 0.12.0
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/lib/phronomy/agent/base.rb +5 -5
- data/lib/phronomy/agent/invocation_session.rb +26 -17
- data/lib/phronomy/multi_agent/orchestrator.rb +2 -2
- data/lib/phronomy/tools/mcp.rb +16 -9
- data/lib/phronomy/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9b5c5fc2f42107629541d051e1330e90e48298ccf19192440e7be77a30b6ca68
|
|
4
|
+
data.tar.gz: 78e4440ec72afb39b7021810c8cdfc8de44f94f15d97442f91000eff50aa967c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 79c98ed427ae1c55d245f871ba9248279199496b27535dfaca2b94d89998bfa95583c8b87a7c192f00f5143c755f6234a4f513ec37c86172ea2c286579e9b72a
|
|
7
|
+
data.tar.gz: 30d8c27a6a0a0d003da85438ee903fc73af648079c83ab42618ce53d98570b9cfaa39144a71c49e8cce77d94b0f679bb82b87b484e06e654eb8298379e8dc093
|
data/lib/phronomy/agent/base.rb
CHANGED
|
@@ -1173,7 +1173,7 @@ module Phronomy
|
|
|
1173
1173
|
effective_name = resolved.new.name
|
|
1174
1174
|
rejected_class = Class.new(resolved) do
|
|
1175
1175
|
tool_name effective_name
|
|
1176
|
-
define_method(:call) do |_args|
|
|
1176
|
+
define_method(:call) do |_args, **_kwargs|
|
|
1177
1177
|
"Tool execution denied: scope :#{scope} is not permitted."
|
|
1178
1178
|
end
|
|
1179
1179
|
end
|
|
@@ -1199,9 +1199,9 @@ module Phronomy
|
|
|
1199
1199
|
effective_name = resolved.new.name
|
|
1200
1200
|
resolved = Class.new(resolved) do
|
|
1201
1201
|
tool_name effective_name
|
|
1202
|
-
define_method(:call) do |args|
|
|
1202
|
+
define_method(:call) do |args, **kwargs|
|
|
1203
1203
|
if handler.call(name, args)
|
|
1204
|
-
super(args)
|
|
1204
|
+
super(args, **kwargs)
|
|
1205
1205
|
else
|
|
1206
1206
|
"Tool execution denied."
|
|
1207
1207
|
end
|
|
@@ -1216,8 +1216,8 @@ module Phronomy
|
|
|
1216
1216
|
effective_name4 = resolved.new.name
|
|
1217
1217
|
Class.new(resolved) do
|
|
1218
1218
|
tool_name effective_name4
|
|
1219
|
-
define_method(:call) do |args|
|
|
1220
|
-
result = super(args)
|
|
1219
|
+
define_method(:call) do |args, **kwargs|
|
|
1220
|
+
result = super(args, **kwargs)
|
|
1221
1221
|
result_filters.inject(result) { |val, f| f.call(val, tool_name: name, args: args) }
|
|
1222
1222
|
end
|
|
1223
1223
|
end
|
|
@@ -298,27 +298,36 @@ module Phronomy
|
|
|
298
298
|
end
|
|
299
299
|
end
|
|
300
300
|
|
|
301
|
-
#
|
|
302
|
-
#
|
|
303
|
-
#
|
|
304
|
-
#
|
|
305
|
-
#
|
|
306
|
-
# and
|
|
301
|
+
# Dispatch the tool off the EventLoop thread via ToolExecutor, which
|
|
302
|
+
# routes based on the tool's execution_mode class attribute:
|
|
303
|
+
# :blocking_io (default) → BlockingAdapterPool (bounded thread pool)
|
|
304
|
+
# :cooperative → Runtime.instance.spawn (scheduler task)
|
|
305
|
+
# Wrap the awaitable in Task.deferred so FSMSession recognises it as
|
|
306
|
+
# an async action and sets async_pending = true.
|
|
307
307
|
tc_id = tc.id
|
|
308
308
|
tc_args = tc.arguments
|
|
309
309
|
tc_name = tc.name
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
310
|
+
ct = ctx.config[:cancellation_token]
|
|
311
|
+
awaitable = tool_instance.call_async(tc_args, cancellation_token: ct)
|
|
312
|
+
result_task = Phronomy::Task.deferred(name: "tool-exec:#{tc_name}")
|
|
313
|
+
awaitable.on_complete do |result, error|
|
|
314
|
+
if error
|
|
315
|
+
result_task.backend.unblock(nil, error)
|
|
316
|
+
result_task.transition!(:failed, error: error)
|
|
317
|
+
else
|
|
318
|
+
ctx.chat.add_message(
|
|
319
|
+
role: :tool,
|
|
320
|
+
content: result.to_s,
|
|
321
|
+
tool_call_id: tc_id
|
|
322
|
+
)
|
|
323
|
+
ctx.pending_tool_call = nil
|
|
324
|
+
ctx.tool_call_pending = false
|
|
325
|
+
ctx.approval_required = false
|
|
326
|
+
result_task.backend.unblock(ctx, nil)
|
|
327
|
+
result_task.transition!(:completed, value: ctx)
|
|
328
|
+
end
|
|
321
329
|
end
|
|
330
|
+
result_task
|
|
322
331
|
end
|
|
323
332
|
private_class_method :executing_tool_action
|
|
324
333
|
|
|
@@ -252,9 +252,9 @@ module Phronomy
|
|
|
252
252
|
effective_name = prepared.new.name
|
|
253
253
|
Class.new(prepared) do
|
|
254
254
|
tool_name effective_name
|
|
255
|
-
define_method(:call) do |args|
|
|
255
|
+
define_method(:call) do |args, **kwargs|
|
|
256
256
|
self._orchestrator_context = orch.instance_variable_get(:@_orchestrator_context)
|
|
257
|
-
super(args)
|
|
257
|
+
super(args, **kwargs)
|
|
258
258
|
end
|
|
259
259
|
end
|
|
260
260
|
end
|
data/lib/phronomy/tools/mcp.rb
CHANGED
|
@@ -35,39 +35,43 @@ module Phronomy
|
|
|
35
35
|
# - "stdio://<command>" — spawn a child process
|
|
36
36
|
# - "http://<url>" / "https://<url>" — connect to an HTTP/SSE server
|
|
37
37
|
# @param tool_name [String] the tool name as registered in the MCP server
|
|
38
|
+
# @param headers [Hash] additional HTTP request headers forwarded to every
|
|
39
|
+
# request (tool discovery and tool execution). Ignored for stdio transports.
|
|
40
|
+
# Typical use: <tt>headers: { "Authorization" => "Bearer #{ENV['API_KEY']}" }</tt>
|
|
38
41
|
# @return [Mcp] a configured subclass instance ready for use with an Agent
|
|
39
42
|
# @api public
|
|
40
|
-
def from_server(server_uri, tool_name:)
|
|
43
|
+
def from_server(server_uri, tool_name:, headers: {})
|
|
41
44
|
# Use a short-lived transport only to query the tool definition,
|
|
42
45
|
# then close it. Each Mcp instance creates its own transport
|
|
43
46
|
# so that concurrent callers never share IO streams.
|
|
44
|
-
transport = build_transport(server_uri)
|
|
47
|
+
transport = build_transport(server_uri, headers: headers)
|
|
45
48
|
begin
|
|
46
49
|
tool_def = transport.fetch_tool(tool_name)
|
|
47
50
|
ensure
|
|
48
51
|
transport.close
|
|
49
52
|
end
|
|
50
|
-
build_tool_class(tool_name, server_uri, tool_def).new
|
|
53
|
+
build_tool_class(tool_name, server_uri, tool_def, headers: headers).new
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
private
|
|
54
57
|
|
|
55
|
-
def build_transport(uri)
|
|
58
|
+
def build_transport(uri, headers: {})
|
|
56
59
|
scheme, path = uri.split("://", 2)
|
|
57
60
|
case scheme
|
|
58
61
|
when "stdio"
|
|
59
62
|
StdioTransport.new(path)
|
|
60
63
|
when "http", "https"
|
|
61
|
-
HttpTransport.new(uri)
|
|
64
|
+
HttpTransport.new(uri, headers: headers)
|
|
62
65
|
else
|
|
63
66
|
raise ArgumentError, "Unsupported MCP transport scheme: #{scheme.inspect}. Supported: 'stdio://', 'http://', 'https://'."
|
|
64
67
|
end
|
|
65
68
|
end
|
|
66
69
|
|
|
67
|
-
def build_tool_class(tool_name, server_uri, tool_def)
|
|
70
|
+
def build_tool_class(tool_name, server_uri, tool_def, headers: {})
|
|
68
71
|
klass = Class.new(Mcp)
|
|
69
72
|
klass.tool_name(tool_name)
|
|
70
73
|
klass.instance_variable_set(:@mcp_server_uri, server_uri)
|
|
74
|
+
klass.instance_variable_set(:@mcp_headers, headers)
|
|
71
75
|
|
|
72
76
|
# Register description and params from the MCP tool definition.
|
|
73
77
|
klass.description(tool_def[:description] || tool_name)
|
|
@@ -82,7 +86,8 @@ module Phronomy
|
|
|
82
86
|
# never share IO streams, eliminating the need for synchronisation.
|
|
83
87
|
klass.define_method(:initialize) do
|
|
84
88
|
uri = self.class.instance_variable_get(:@mcp_server_uri)
|
|
85
|
-
|
|
89
|
+
hdrs = self.class.instance_variable_get(:@mcp_headers) || {}
|
|
90
|
+
@mcp_transport = self.class.send(:build_transport, uri, headers: hdrs)
|
|
86
91
|
end
|
|
87
92
|
|
|
88
93
|
klass.define_method(:execute) do |**args|
|
|
@@ -249,8 +254,10 @@ module Phronomy
|
|
|
249
254
|
raise Phronomy::ToolError,
|
|
250
255
|
"MCP stdio server did not start within #{@startup_timeout} seconds"
|
|
251
256
|
end
|
|
252
|
-
|
|
253
|
-
|
|
257
|
+
# Do NOT call @stdout.gets here: gets() blocks until a newline arrives,
|
|
258
|
+
# which hangs indefinitely when the server emits no startup line or a
|
|
259
|
+
# partial line without '\n'. IO.select already confirmed the server is
|
|
260
|
+
# alive and responsive; the first rpc_call will consume actual output.
|
|
254
261
|
end
|
|
255
262
|
end
|
|
256
263
|
|
data/lib/phronomy/version.rb
CHANGED