diff --git a/lib/sagents_live_debugger/layouts.ex b/lib/sagents_live_debugger/layouts.ex
index 0a558d7..e95693d 100644
--- a/lib/sagents_live_debugger/layouts.ex
+++ b/lib/sagents_live_debugger/layouts.ex
@@ -714,6 +714,24 @@ defmodule SagentsLiveDebugger.Layouts do
color: #991b1b;
}
+ .result-status.status-interrupted {
+ background: #fef3c7;
+ color: #92400e;
+ }
+
+ .tool-result-interrupt-data {
+ margin-top: 0.5rem;
+ padding: 0.5rem;
+ background: #fffbeb;
+ border: 1px solid #fcd34d;
+ border-radius: 0.375rem;
+ }
+
+ .tool-result-interrupt-data pre {
+ margin: 0;
+ font-size: 0.75rem;
+ }
+
/* Message Metadata */
.message-metadata {
margin-top: 0.75rem;
diff --git a/lib/sagents_live_debugger/live/agent_list_live.ex b/lib/sagents_live_debugger/live/agent_list_live.ex
index 063f58b..495098e 100644
--- a/lib/sagents_live_debugger/live/agent_list_live.ex
+++ b/lib/sagents_live_debugger/live/agent_list_live.ex
@@ -2114,6 +2114,14 @@ defmodule SagentsLiveDebugger.AgentListLive do
defp format_event_data(event) do
case event do
+ {:status_changed, :interrupted, data} ->
+ %{
+ type: "status_changed",
+ status: "interrupted",
+ summary: "Status: interrupted — #{format_interrupt_summary(data)}",
+ interrupt_data: data
+ }
+
{:status_changed, status, _data} ->
%{
type: "status_changed",
@@ -2129,7 +2137,15 @@ defmodule SagentsLiveDebugger.AgentListLive do
Enum.map(tool_results, fn result ->
name = Map.get(result, :name, "unknown")
is_error = Map.get(result, :is_error, false)
- status = if is_error, do: "✗", else: "✓"
+ is_interrupt = Map.get(result, :is_interrupt, false)
+
+ status =
+ cond do
+ is_interrupt -> "✋"
+ is_error -> "✗"
+ true -> "✓"
+ end
+
"#{name} #{status}"
end)
@@ -2338,6 +2354,32 @@ defmodule SagentsLiveDebugger.AgentListLive do
end
end
+ defp format_interrupt_summary(%{
+ type: :subagent_hitl,
+ subagent_type: type,
+ interrupt_data: inner
+ }) do
+ tools =
+ inner
+ |> Map.get(:action_requests, [])
+ |> Enum.map(&(&1[:tool_name] || &1["tool_name"] || "unknown"))
+ |> Enum.join(", ")
+
+ "sub-agent (#{type}) awaiting approval for: #{tools}"
+ end
+
+ defp format_interrupt_summary(%{action_requests: action_requests})
+ when is_list(action_requests) do
+ tools =
+ action_requests
+ |> Enum.map(&(&1[:tool_name] || &1["tool_name"] || "unknown"))
+ |> Enum.join(", ")
+
+ "awaiting approval for: #{tools}"
+ end
+
+ defp format_interrupt_summary(_), do: "awaiting human input"
+
defp format_action_data(action_data) when is_tuple(action_data) do
case action_data do
{action_name, data} when is_atom(action_name) ->
diff --git a/lib/sagents_live_debugger/live/components/message_components.ex b/lib/sagents_live_debugger/live/components/message_components.ex
index 69a1650..fde9904 100644
--- a/lib/sagents_live_debugger/live/components/message_components.ex
+++ b/lib/sagents_live_debugger/live/components/message_components.ex
@@ -193,10 +193,24 @@ defmodule SagentsLiveDebugger.Live.Components.MessageComponents do
attr :tool_result, :map, required: true
def tool_result_item(assigns) do
+ is_interrupt = Map.get(assigns.tool_result, :is_interrupt, false)
+ interrupt_data = Map.get(assigns.tool_result, :interrupt_data)
+
+ assigns =
+ assigns
+ |> assign(:is_interrupt, is_interrupt)
+ |> assign(:interrupt_data, interrupt_data)
+ |> assign(:formatted_interrupt_data, format_interrupt_data(interrupt_data))
+
~H"""
+ <%= if @is_interrupt && @interrupt_data do %>
+
+ <.highlight_code code={@formatted_interrupt_data} language="elixir" />
+
+ <% end %>
<.highlight_code
code={format_tool_result(@tool_result.content)}
@@ -216,6 +235,12 @@ defmodule SagentsLiveDebugger.Live.Components.MessageComponents do
"""
end
+ defp format_interrupt_data(nil), do: ""
+
+ defp format_interrupt_data(data) do
+ inspect(data, pretty: true, limit: :infinity)
+ end
+
defp detect_result_language(content) when is_binary(content) do
if String.match?(content, ~r/^\s*[\{\[]/) do
"json"
diff --git a/mix.exs b/mix.exs
index 210ca86..0d3e8d2 100644
--- a/mix.exs
+++ b/mix.exs
@@ -41,7 +41,7 @@ defmodule SagentsLiveDebugger.MixProject do
{:phoenix_live_view, "~> 1.0"},
{:jason, "~> 1.4"},
# {:sagents, path: "../sagents"},
- {:sagents, "~> 0.2.1"},
+ {:sagents, "~> 0.3.0"},
{:horde, "~> 0.10.0", optional: true},
# markdown and code highlighting (autumn)
{:mdex, "~> 0.11.0"},
diff --git a/mix.lock b/mix.lock
index bff77bb..2617538 100644
--- a/mix.lock
+++ b/mix.lock
@@ -14,7 +14,7 @@
"hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
- "langchain": {:hex, :langchain, "0.6.0", "f1245cc3d329e50e3d8eea82efa5dd80ad476b84f6c48a76527155952d5cea75", [:mix], [{:abacus, "~> 2.1.0", [hex: :abacus, repo: "hexpm", optional: true]}, {:ecto, "~> 3.10", [hex: :ecto, repo: "hexpm", optional: false]}, {:gettext, "~> 0.26.2 or ~> 1.0.0", [hex: :gettext, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: true]}, {:nx, ">= 0.7.0", [hex: :nx, repo: "hexpm", optional: true]}, {:req, ">= 0.5.2", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "3faa3bef61cf94d35d62f187507260ad24fe19e9555b02c4c7827316bbf64bc3"},
+ "langchain": {:hex, :langchain, "0.6.1", "f9d949ff8ee1608151e3038442c17feea871e78679477919222772da7bce2924", [:mix], [{:abacus, "~> 2.1.0", [hex: :abacus, repo: "hexpm", optional: true]}, {:ecto, "~> 3.10", [hex: :ecto, repo: "hexpm", optional: false]}, {:gettext, "~> 0.26.2 or ~> 1.0.0", [hex: :gettext, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: true]}, {:nx, ">= 0.7.0", [hex: :nx, repo: "hexpm", optional: true]}, {:req, ">= 0.5.2", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "607167099c527822b5f455b0303ae1b0a5b812941b7348024893e12118de6e65"},
"libring": {:hex, :libring, "1.7.0", "4f245d2f1476cd7ed8f03740f6431acba815401e40299208c7f5c640e1883bda", [:mix], [], "hexpm", "070e3593cb572e04f2c8470dd0c119bc1817a7a0a7f88229f43cf0345268ec42"},
"lumis": {:hex, :lumis, "0.1.0", "6d3b495457d0608f8fe32fe6fa917eb17c921bd0087583a7f4c420c0009888fd", [:mix], [{:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:rustler, "~> 0.29", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.6", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "4de203bc5811ad21f0c6b0442612a3fc1ae9bea7fbfe7037452db9e9dfdaaab3"},
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
@@ -39,7 +39,7 @@
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
"req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"},
"rustler_precompiled": {:hex, :rustler_precompiled, "0.8.4", "700a878312acfac79fb6c572bb8b57f5aae05fe1cf70d34b5974850bbf2c05bf", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "3b33d99b540b15f142ba47944f7a163a25069f6d608783c321029bc1ffb09514"},
- "sagents": {:hex, :sagents, "0.2.1", "493282354029544ddc2a0c5d4fde8e3008222817e8dc6f034b87a520711a0fec", [:mix], [{:ecto, "~> 3.10", [hex: :ecto, repo: "hexpm", optional: false]}, {:horde, "~> 0.10", [hex: :horde, repo: "hexpm", optional: true]}, {:langchain, "~> 0.6.0", [hex: :langchain, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "7b8c42f4505f36806418ead6fd6c0a4c0ba3410672ff48a0e5ed4dac3fe8111c"},
+ "sagents": {:hex, :sagents, "0.3.0", "6b13f50636dd4aba801f2823cf2a2d56808fd97c540d0b7bf2b7a1768e676d24", [:mix], [{:ecto, "~> 3.10", [hex: :ecto, repo: "hexpm", optional: false]}, {:horde, "~> 0.10", [hex: :horde, repo: "hexpm", optional: true]}, {:langchain, "~> 0.6.1", [hex: :langchain, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "ac3d277add80100956bd3d071ae51234248f7807992e4a55f9614adfb41c29be"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"telemetry_poller": {:hex, :telemetry_poller, "1.3.0", "d5c46420126b5ac2d72bc6580fb4f537d35e851cc0f8dbd571acf6d6e10f5ec7", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "51f18bed7128544a50f75897db9974436ea9bfba560420b646af27a9a9b35211"},