Current section

Files

Jump to
agent_session_manager lib asm session supervisor.ex
Raw

lib/asm/session/supervisor.ex

defmodule ASM.Session.Supervisor do
@moduledoc """
Root dynamic supervisor for session subtrees.
"""
use DynamicSupervisor
alias ASM.Execution.Config
@registry :asm_sessions
@spec start_link(keyword()) :: Supervisor.on_start()
def start_link(opts) do
DynamicSupervisor.start_link(__MODULE__, opts, name: __MODULE__)
end
@spec start_session(keyword()) :: DynamicSupervisor.on_start_child()
def start_session(opts), do: start_session(__MODULE__, opts)
@spec start_session(GenServer.server(), keyword()) :: DynamicSupervisor.on_start_child()
def start_session(supervisor, opts) when is_list(opts) do
session_id = Keyword.get_lazy(opts, :session_id, &ASM.Event.generate_id/0)
provider = Keyword.get(opts, :provider, :claude)
session_options =
opts
|> Keyword.drop([:session_id, :provider, :name, :options])
|> Keyword.merge(Keyword.get(opts, :options, []))
with {:ok, session_options} <- normalize_session_options(provider, session_options) do
subtree_opts =
opts
|> Keyword.put(:session_id, session_id)
|> Keyword.put(:provider, provider)
|> Keyword.put(:options, session_options)
DynamicSupervisor.start_child(supervisor, {ASM.Session.Subtree, subtree_opts})
end
end
@spec stop_session(String.t() | pid()) :: :ok | {:error, :not_found}
def stop_session(session_or_pid), do: stop_session(__MODULE__, session_or_pid)
@spec stop_session(GenServer.server(), String.t() | pid()) :: :ok | {:error, :not_found}
def stop_session(supervisor, pid) when is_pid(pid) do
case DynamicSupervisor.terminate_child(supervisor, pid) do
:ok -> :ok
{:error, _reason} -> {:error, :not_found}
end
end
def stop_session(supervisor, session_id) when is_binary(session_id) do
case Registry.lookup(@registry, {session_id, :subtree}) do
[{pid, _}] -> stop_session(supervisor, pid)
[] -> {:error, :not_found}
end
end
@spec list_sessions() :: [String.t()]
def list_sessions do
Registry.select(@registry, [{{{:"$1", :subtree}, :_, :_}, [], [:"$1"]}])
end
@impl true
def init(_opts) do
DynamicSupervisor.init(strategy: :one_for_one)
end
defp normalize_session_options(provider, session_options) when is_list(session_options) do
case Config.resolve(session_options, [], provider: provider) do
{:ok, %Config{} = execution_config} ->
{:ok, merge_execution_config(session_options, execution_config)}
{:error, error} ->
{:error, error}
end
end
defp merge_execution_config(session_options, %Config{} = execution_config) do
execution_environment = Config.to_execution_environment(execution_config)
session_options
|> Keyword.put(:execution_mode, execution_config.execution_mode)
|> Keyword.put(:transport_call_timeout_ms, execution_config.transport_call_timeout_ms)
|> Keyword.put(:execution_surface, Config.to_execution_surface(execution_config))
|> Keyword.put(:execution_environment, execution_environment)
|> Keyword.put(:allowed_tools, execution_environment.allowed_tools)
|> maybe_put(:workspace_root, execution_environment.workspace_root)
|> maybe_put(:approval_posture, execution_environment.approval_posture)
|> maybe_put(:permission_mode, execution_environment.permission_mode)
|> maybe_put(:provider_permission_mode, execution_config.provider_permission_mode)
end
defp maybe_put(opts, _key, nil), do: opts
defp maybe_put(opts, key, value), do: Keyword.put(opts, key, value)
end