diff --git a/README.md b/README.md index 10f0dfc..a89d6b6 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ AgentJido is a Phoenix 1.8 application for the Jido AI agent ecosystem. - Elixir 1.18+ - PostgreSQL 14+ +- Node.js 18+ (for asset compilation) ### Setup @@ -26,7 +27,10 @@ AgentJido is a Phoenix 1.8 application for the Jido AI agent ecosystem. git clone https://github.com/agentjido/agent_jido.git cd agent_jido -# Install dependencies and setup database +# Install JavaScript dependencies +cd assets && npm install && cd .. + +# Install Elixir dependencies and setup database mix setup # Start the Phoenix server diff --git a/assets/js/ash_rpc.ts b/assets/js/ash_rpc.ts index 5b55d24..317a581 100644 --- a/assets/js/ash_rpc.ts +++ b/assets/js/ash_rpc.ts @@ -16,20 +16,20 @@ // Utility Types // Resource schema constraint -type TypedSchema = { +export type TypedSchema = { __type: "Resource" | "TypedMap" | "Union"; __primitiveFields: string; }; // Utility type to convert union to intersection -type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( +export type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( k: infer I, ) => void ? I : never; // Helper type to infer union field values, avoiding duplication between array and non-array unions -type InferUnionFieldValue< +export type InferUnionFieldValue< UnionSchema extends { __type: "Union"; __primitiveFields: any }, FieldSelection extends any[], > = UnionToIntersection< @@ -41,34 +41,34 @@ type InferUnionFieldValue< : FieldSelection[FieldIndex] extends Record ? { [UnionKey in keyof FieldSelection[FieldIndex]]: UnionKey extends keyof UnionSchema - ? UnionSchema[UnionKey] extends { __array: true; __type: "TypedMap"; __primitiveFields: infer TypedMapFields } + ? NonNullable extends { __array: true; __type: "TypedMap"; __primitiveFields: infer TypedMapFields } ? FieldSelection[FieldIndex][UnionKey] extends any[] ? Array< UnionToIntersection< { [FieldIdx in keyof FieldSelection[FieldIndex][UnionKey]]: FieldSelection[FieldIndex][UnionKey][FieldIdx] extends TypedMapFields - ? FieldSelection[FieldIndex][UnionKey][FieldIdx] extends keyof UnionSchema[UnionKey] - ? { [P in FieldSelection[FieldIndex][UnionKey][FieldIdx]]: UnionSchema[UnionKey][P] } + ? FieldSelection[FieldIndex][UnionKey][FieldIdx] extends keyof NonNullable + ? { [P in FieldSelection[FieldIndex][UnionKey][FieldIdx]]: NonNullable[P] } : never : never; }[number] > > | null : never - : UnionSchema[UnionKey] extends { __type: "TypedMap"; __primitiveFields: infer TypedMapFields } + : NonNullable extends { __type: "TypedMap"; __primitiveFields: infer TypedMapFields } ? FieldSelection[FieldIndex][UnionKey] extends any[] ? UnionToIntersection< { [FieldIdx in keyof FieldSelection[FieldIndex][UnionKey]]: FieldSelection[FieldIndex][UnionKey][FieldIdx] extends TypedMapFields - ? FieldSelection[FieldIndex][UnionKey][FieldIdx] extends keyof UnionSchema[UnionKey] - ? { [P in FieldSelection[FieldIndex][UnionKey][FieldIdx]]: UnionSchema[UnionKey][P] } + ? FieldSelection[FieldIndex][UnionKey][FieldIdx] extends keyof NonNullable + ? { [P in FieldSelection[FieldIndex][UnionKey][FieldIdx]]: NonNullable[P] } : never : never; }[number] > | null : never - : UnionSchema[UnionKey] extends TypedSchema - ? InferResult + : NonNullable extends TypedSchema + ? InferResult, FieldSelection[FieldIndex][UnionKey]> : never : never; } @@ -76,21 +76,21 @@ type InferUnionFieldValue< }[number] >; -type HasComplexFields = keyof Omit< +export type HasComplexFields = keyof Omit< T, "__primitiveFields" | "__type" | T["__primitiveFields"] > extends never ? false : true; -type ComplexFieldKeys = keyof Omit< +export type ComplexFieldKeys = keyof Omit< T, "__primitiveFields" | "__type" | T["__primitiveFields"] >; -type LeafFieldSelection = T["__primitiveFields"]; +export type LeafFieldSelection = T["__primitiveFields"]; -type ComplexFieldSelection = { +export type ComplexFieldSelection = { [K in ComplexFieldKeys]?: T[K] extends { __type: "Relationship"; __resource: infer Resource; @@ -119,17 +119,17 @@ type ComplexFieldSelection = { : T[K] extends { __type: "Union"; __primitiveFields: infer PrimitiveFields } ? T[K] extends { __array: true } ? (PrimitiveFields | { - [UnionKey in keyof Omit]?: T[K][UnionKey] extends { __type: "TypedMap"; __primitiveFields: any } - ? T[K][UnionKey]["__primitiveFields"][] - : T[K][UnionKey] extends TypedSchema - ? UnifiedFieldSelection[] + [UnionKey in keyof Omit]?: NonNullable extends { __type: "TypedMap"; __primitiveFields: any } + ? NonNullable["__primitiveFields"][] + : NonNullable extends TypedSchema + ? UnifiedFieldSelection>[] : never; })[] : (PrimitiveFields | { - [UnionKey in keyof Omit]?: T[K][UnionKey] extends { __type: "TypedMap"; __primitiveFields: any } - ? T[K][UnionKey]["__primitiveFields"][] - : T[K][UnionKey] extends TypedSchema - ? UnifiedFieldSelection[] + [UnionKey in keyof Omit]?: NonNullable extends { __type: "TypedMap"; __primitiveFields: any } + ? NonNullable["__primitiveFields"][] + : NonNullable extends TypedSchema + ? UnifiedFieldSelection>[] : never; })[] : NonNullable extends TypedSchema @@ -138,12 +138,12 @@ type ComplexFieldSelection = { }; // Main type: Use explicit base case detection to prevent infinite recursion -type UnifiedFieldSelection = +export type UnifiedFieldSelection = HasComplexFields extends false ? LeafFieldSelection // Base case: only primitives, no recursion : LeafFieldSelection | ComplexFieldSelection; // Recursive case -type InferFieldValue< +export type InferFieldValue< T extends TypedSchema, Field, > = Field extends T["__primitiveFields"] @@ -279,20 +279,16 @@ type InferFieldValue< : never : T[K] extends { __type: "Union"; __primitiveFields: any } ? T[K] extends { __array: true } - ? { - [CurrentK in K]: T[CurrentK] extends { __type: "Union"; __primitiveFields: any } - ? Field[CurrentK] extends any[] - ? Array> | null - : never - : never - } - : { - [CurrentK in K]: T[CurrentK] extends { __type: "Union"; __primitiveFields: any } - ? Field[CurrentK] extends any[] - ? InferUnionFieldValue | null - : never - : never - } + ? Field[K] extends any[] + ? null extends T[K] + ? Array> | null + : Array> + : never + : Field[K] extends any[] + ? null extends T[K] + ? InferUnionFieldValue | null + : InferUnionFieldValue + : never : NonNullable extends TypedSchema ? null extends T[K] ? InferResult, Field[K]> | null @@ -302,7 +298,7 @@ type InferFieldValue< } : never; -type InferResult< +export type InferResult< T extends TypedSchema, SelectedFields extends UnifiedFieldSelection[] | undefined, > = SelectedFields extends undefined @@ -319,14 +315,14 @@ type InferResult< // Pagination conditional types // Checks if a page configuration object has any pagination parameters -type HasPaginationParams = +export type HasPaginationParams = Page extends { offset: any } ? true : Page extends { after: any } ? true : Page extends { before: any } ? true : false; // Infer which pagination type is being used from the page config -type InferPaginationType = +export type InferPaginationType = Page extends { offset: any } ? "offset" : Page extends { after: any } | { before: any } ? "keyset" : never; @@ -335,7 +331,7 @@ type InferPaginationType = // For single pagination type support (offset-only or keyset-only) // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-unused-vars -type ConditionalPaginatedResult< +export type ConditionalPaginatedResult< Page, RecordType, PaginatedType @@ -347,7 +343,7 @@ type ConditionalPaginatedResult< // For actions supporting both offset and keyset pagination // Infers the specific pagination type based on which params were passed -type ConditionalPaginatedResultMixed< +export type ConditionalPaginatedResultMixed< Page, RecordType, OffsetType, @@ -533,7 +529,7 @@ export function buildCSRFHeaders(headers: Record = {}): Record( +export async function executeActionRpcRequest( payload: Record, config: ActionConfig ): Promise { @@ -584,7 +580,7 @@ async function executeActionRpcRequest( * Handles hooks, request configuration, fetch execution, and error handling * @param config Configuration matching ValidationConfig */ -async function executeValidationRpcRequest( +export async function executeValidationRpcRequest( payload: Record, config: ValidationConfig ): Promise { diff --git a/config/config.exs b/config/config.exs index e2e83b9..720cdc9 100644 --- a/config/config.exs +++ b/config/config.exs @@ -87,7 +87,8 @@ config :agent_jido, generators: [timestamp_type: :utc_datetime], ash_domains: [AgentJido.Accounts, AgentJido.GitHub, AgentJido.Forge.Domain, AgentJido.Folio], ash_authentication: [return_error_on_invalid_magic_link_token?: true], - mailer: [from_name: "Agent Jido"] + mailer: [from_name: "Agent Jido"], + forge_fake_sprite_client?: config_env() in [:dev, :test] # Configure the endpoint config :agent_jido, AgentJidoWeb.Endpoint, diff --git a/lib/agent_jido/accounts.ex b/lib/agent_jido/accounts.ex index a219ecc..69b3b62 100644 --- a/lib/agent_jido/accounts.ex +++ b/lib/agent_jido/accounts.ex @@ -1,4 +1,5 @@ defmodule AgentJido.Accounts do + @moduledoc false use Ash.Domain, otp_app: :agent_jido, extensions: [AshAdmin.Domain] admin do diff --git a/lib/agent_jido/accounts/api_key.ex b/lib/agent_jido/accounts/api_key.ex index a0d25ef..0822664 100644 --- a/lib/agent_jido/accounts/api_key.ex +++ b/lib/agent_jido/accounts/api_key.ex @@ -1,4 +1,5 @@ defmodule AgentJido.Accounts.ApiKey do + @moduledoc false use Ash.Resource, otp_app: :agent_jido, domain: AgentJido.Accounts, diff --git a/lib/agent_jido/accounts/token.ex b/lib/agent_jido/accounts/token.ex index 3213ef7..7667174 100644 --- a/lib/agent_jido/accounts/token.ex +++ b/lib/agent_jido/accounts/token.ex @@ -1,4 +1,5 @@ defmodule AgentJido.Accounts.Token do + @moduledoc false use Ash.Resource, otp_app: :agent_jido, domain: AgentJido.Accounts, diff --git a/lib/agent_jido/accounts/user.ex b/lib/agent_jido/accounts/user.ex index 388084f..c23de5f 100644 --- a/lib/agent_jido/accounts/user.ex +++ b/lib/agent_jido/accounts/user.ex @@ -1,4 +1,5 @@ defmodule AgentJido.Accounts.User do + @moduledoc false use Ash.Resource, otp_app: :agent_jido, domain: AgentJido.Accounts, diff --git a/lib/agent_jido/application.ex b/lib/agent_jido/application.ex index 4f484d9..ddbe86a 100644 --- a/lib/agent_jido/application.ex +++ b/lib/agent_jido/application.ex @@ -38,7 +38,7 @@ defmodule AgentJido.Application do end defp forge_dev_children do - if Mix.env() in [:dev, :test] do + if Application.get_env(:agent_jido, :forge_fake_sprite_client?, false) do [{AgentJido.Forge.SpriteClient.Fake, []}] else [] diff --git a/lib/agent_jido/error.ex b/lib/agent_jido/error.ex index 0a550e3..017cfee 100644 --- a/lib/agent_jido/error.ex +++ b/lib/agent_jido/error.ex @@ -65,6 +65,8 @@ defmodule AgentJido.Error do @moduledoc "Error for invalid input parameters." use Splode.Error, class: :invalid + @type t :: %__MODULE__{} + def message(%{message: message}), do: message end @@ -72,6 +74,8 @@ defmodule AgentJido.Error do @moduledoc "Error for validation failures." use Splode.Error, class: :invalid + @type t :: %__MODULE__{} + def message(%{message: message, field: field}) when not is_nil(field) do "Validation failed for #{field}: #{message}" end @@ -83,6 +87,8 @@ defmodule AgentJido.Error do @moduledoc "Error for runtime execution failures." use Splode.Error, class: :execution + @type t :: %__MODULE__{} + def message(%{message: message}), do: message end @@ -90,32 +96,46 @@ defmodule AgentJido.Error do @moduledoc "Error for configuration issues." use Splode.Error, class: :config + @type t :: %__MODULE__{} + def message(%{message: message}), do: message end # Helper functions @doc "Creates a validation error with optional details." - @spec validation_error(String.t(), map()) :: ValidationError.t() + @spec validation_error(String.t(), map() | keyword()) :: ValidationError.t() def validation_error(message, details \\ %{}) do - ValidationError.exception(Map.merge(%{message: message}, details)) + ValidationError.exception(merge_error_details(message, details)) end @doc "Creates an invalid input error." - @spec invalid_input_error(String.t(), map()) :: InvalidInputError.t() + @spec invalid_input_error(String.t(), map() | keyword()) :: InvalidInputError.t() def invalid_input_error(message, details \\ %{}) do - InvalidInputError.exception(Map.merge(%{message: message}, details)) + InvalidInputError.exception(merge_error_details(message, details)) end @doc "Creates an execution failure error." - @spec execution_error(String.t(), map()) :: ExecutionFailureError.t() + @spec execution_error(String.t(), map() | keyword()) :: ExecutionFailureError.t() def execution_error(message, details \\ %{}) do - ExecutionFailureError.exception(Map.merge(%{message: message}, details)) + ExecutionFailureError.exception(merge_error_details(message, details)) end @doc "Creates a configuration error." - @spec config_error(String.t(), map()) :: ConfigurationError.t() + @spec config_error(String.t(), map() | keyword()) :: ConfigurationError.t() def config_error(message, details \\ %{}) do - ConfigurationError.exception(Map.merge(%{message: message}, details)) + ConfigurationError.exception(merge_error_details(message, details)) + end + + defp merge_error_details(message, details) when is_list(details) do + Keyword.merge([message: message], details) + end + + defp merge_error_details(message, details) when is_map(details) do + Keyword.merge([message: message], Map.to_list(details)) + end + + defp merge_error_details(message, nil) do + [message: message] end end diff --git a/lib/agent_jido/folio.ex b/lib/agent_jido/folio.ex index c91b8fb..608a3ee 100644 --- a/lib/agent_jido/folio.ex +++ b/lib/agent_jido/folio.ex @@ -1,4 +1,5 @@ defmodule AgentJido.Folio do + @moduledoc false use Ash.Domain resources do diff --git a/lib/agent_jido/forge.ex b/lib/agent_jido/forge.ex index c8dd6c9..5d504e4 100644 --- a/lib/agent_jido/forge.ex +++ b/lib/agent_jido/forge.ex @@ -157,26 +157,16 @@ defmodule AgentJido.Forge do defp do_run_loop(session_id, opts, max_iterations, iteration, return_error_result?) do case run_iteration(session_id, opts) do - {:ok, %{status: :done} = result} -> - {:ok, result} - - {:ok, %{status: :needs_input} = result} -> - {:ok, result} - - {:ok, %{status: :blocked} = result} -> + {:ok, %{status: status} = result} when status in [:done, :needs_input, :blocked] -> {:ok, result} {:ok, %{status: :error} = result} -> - if return_error_result? do - {:ok, result} - else - {:error, {:iteration_error, result}} - end + handle_error_result(result, return_error_result?) - {:ok, %{continue: true} = _result} -> + {:ok, %{continue: true}} -> do_run_loop(session_id, opts, max_iterations, iteration + 1, return_error_result?) - {:ok, %{status: :continue} = _result} -> + {:ok, %{status: :continue}} -> do_run_loop(session_id, opts, max_iterations, iteration + 1, return_error_result?) {:ok, result} -> @@ -187,6 +177,9 @@ defmodule AgentJido.Forge do end end + defp handle_error_result(result, true = _return_error_result?), do: {:ok, result} + defp handle_error_result(result, false = _return_error_result?), do: {:error, {:iteration_error, result}} + # Session lifecycle operations @doc """ diff --git a/lib/agent_jido/forge/domain.ex b/lib/agent_jido/forge/domain.ex index c7b6c5d..55acf80 100644 --- a/lib/agent_jido/forge/domain.ex +++ b/lib/agent_jido/forge/domain.ex @@ -1,4 +1,5 @@ defmodule AgentJido.Forge.Domain do + @moduledoc false use Ash.Domain, otp_app: :agent_jido, extensions: [AshAdmin.Domain] admin do diff --git a/lib/agent_jido/forge/manager.ex b/lib/agent_jido/forge/manager.ex index a4ca404..6c388fa 100644 --- a/lib/agent_jido/forge/manager.ex +++ b/lib/agent_jido/forge/manager.ex @@ -90,46 +90,11 @@ defmodule AgentJido.Forge.Manager do def handle_call({:start_session, session_id, spec}, _from, state) do runner_type = Map.get(spec, :runner) || Map.get(spec, :runner_type) || Map.get(spec, "runner_type") || :shell - cond do - MapSet.size(state.sessions) >= state.max_sessions -> - {:reply, {:error, :max_sessions_reached}, state} - - Map.get(state.runner_counts, runner_type, 0) >= - Map.get(state.max_per_runner, runner_type, 100) -> - {:reply, {:error, {:runner_limit_reached, runner_type}}, state} - - true -> - case Registry.lookup(@registry, session_id) do - [{pid, _}] -> - {:reply, {:error, {:already_started, pid}}, state} - - [] -> - Persistence.record_session_started(session_id, spec) - - child_spec = {SpriteSession, {session_id, spec, []}} - - case DynamicSupervisor.start_child(@supervisor, child_spec) do - {:ok, pid} -> - Process.monitor(pid) - new_sessions = MapSet.put(state.sessions, session_id) - new_session_runners = Map.put(state.session_runners, session_id, runner_type) - new_runner_counts = Map.update(state.runner_counts, runner_type, 1, &(&1 + 1)) - - Logger.debug("Started session #{session_id} with pid #{inspect(pid)}") - ForgePubSub.broadcast_sessions({:session_started, session_id}) - - {:reply, {:ok, pid}, - %{ - state - | sessions: new_sessions, - session_runners: new_session_runners, - runner_counts: new_runner_counts - }} - - {:error, reason} -> - {:reply, {:error, reason}, state} - end - end + with :ok <- check_session_limits(state, runner_type), + :ok <- check_session_not_running(session_id) do + do_start_session(session_id, spec, runner_type, state) + else + {:error, reason} -> {:reply, {:error, reason}, state} end end @@ -168,6 +133,52 @@ defmodule AgentJido.Forge.Manager do {:noreply, new_state} end + defp check_session_limits(state, runner_type) do + cond do + MapSet.size(state.sessions) >= state.max_sessions -> + {:error, :max_sessions_reached} + + Map.get(state.runner_counts, runner_type, 0) >= Map.get(state.max_per_runner, runner_type, 100) -> + {:error, {:runner_limit_reached, runner_type}} + + true -> + :ok + end + end + + defp check_session_not_running(session_id) do + case Registry.lookup(@registry, session_id) do + [{pid, _}] -> {:error, {:already_started, pid}} + [] -> :ok + end + end + + defp do_start_session(session_id, spec, runner_type, state) do + Persistence.record_session_started(session_id, spec) + child_spec = {SpriteSession, {session_id, spec, []}} + + case DynamicSupervisor.start_child(@supervisor, child_spec) do + {:ok, pid} -> + Process.monitor(pid) + new_state = update_state_for_started_session(state, session_id, runner_type) + Logger.debug("Started session #{session_id} with pid #{inspect(pid)}") + ForgePubSub.broadcast_sessions({:session_started, session_id}) + {:reply, {:ok, pid}, new_state} + + {:error, reason} -> + {:reply, {:error, reason}, state} + end + end + + defp update_state_for_started_session(state, session_id, runner_type) do + %{ + state + | sessions: MapSet.put(state.sessions, session_id), + session_runners: Map.put(state.session_runners, session_id, runner_type), + runner_counts: Map.update(state.runner_counts, runner_type, 1, &(&1 + 1)) + } + end + defp decrement_session(state, session_id) do runner_type = Map.get(state.session_runners, session_id) diff --git a/lib/agent_jido/forge/operations.ex b/lib/agent_jido/forge/operations.ex index ccefe55..dba6dc8 100644 --- a/lib/agent_jido/forge/operations.ex +++ b/lib/agent_jido/forge/operations.ex @@ -10,7 +10,7 @@ defmodule AgentJido.Forge.Operations do require Logger alias AgentJido.Forge.{Manager, PubSub} - alias AgentJido.Forge.Resources.{Session, Event, Checkpoint} + alias AgentJido.Forge.Resources.{Checkpoint, Event, Session} @doc """ Resume a session from its last checkpoint. diff --git a/lib/agent_jido/forge/persistence.ex b/lib/agent_jido/forge/persistence.ex index e3a7ed4..9856a59 100644 --- a/lib/agent_jido/forge/persistence.ex +++ b/lib/agent_jido/forge/persistence.ex @@ -21,7 +21,7 @@ defmodule AgentJido.Forge.Persistence do require Logger - alias AgentJido.Forge.Resources.{Session, Event, ExecSession} + alias AgentJido.Forge.Resources.{Event, ExecSession, Session} @doc """ Check if persistence is enabled. @@ -187,22 +187,24 @@ defmodule AgentJido.Forge.Persistence do @spec log_event(String.t(), String.t(), map()) :: :ok def log_event(session_id, event_type, data \\ %{}) do if enabled?() do - Task.start(fn -> - with {:ok, session} <- find_session(session_id) do - Event - |> Ash.Changeset.for_create(:log, %{ - session_id: session.id, - event_type: event_type, - data: data - }) - |> Ash.create() - end - end) + Task.start(fn -> do_log_event(session_id, event_type, data) end) end :ok end + defp do_log_event(session_id, event_type, data) do + with {:ok, session} <- find_session(session_id) do + Event + |> Ash.Changeset.for_create(:log, %{ + session_id: session.id, + event_type: event_type, + data: data + }) + |> Ash.create() + end + end + # Private helpers defp find_session(session_id) when is_binary(session_id) do diff --git a/lib/agent_jido/forge/resources/checkpoint.ex b/lib/agent_jido/forge/resources/checkpoint.ex index be6c33d..3761dc6 100644 --- a/lib/agent_jido/forge/resources/checkpoint.ex +++ b/lib/agent_jido/forge/resources/checkpoint.ex @@ -1,4 +1,5 @@ defmodule AgentJido.Forge.Resources.Checkpoint do + @moduledoc false use Ash.Resource, otp_app: :agent_jido, domain: AgentJido.Forge.Domain, diff --git a/lib/agent_jido/forge/resources/event.ex b/lib/agent_jido/forge/resources/event.ex index 2228586..158de94 100644 --- a/lib/agent_jido/forge/resources/event.ex +++ b/lib/agent_jido/forge/resources/event.ex @@ -1,4 +1,5 @@ defmodule AgentJido.Forge.Resources.Event do + @moduledoc false use Ash.Resource, otp_app: :agent_jido, domain: AgentJido.Forge.Domain, diff --git a/lib/agent_jido/forge/resources/exec_session.ex b/lib/agent_jido/forge/resources/exec_session.ex index f68cb49..815b2a9 100644 --- a/lib/agent_jido/forge/resources/exec_session.ex +++ b/lib/agent_jido/forge/resources/exec_session.ex @@ -1,4 +1,5 @@ defmodule AgentJido.Forge.Resources.ExecSession do + @moduledoc false use Ash.Resource, otp_app: :agent_jido, domain: AgentJido.Forge.Domain, diff --git a/lib/agent_jido/forge/resources/session.ex b/lib/agent_jido/forge/resources/session.ex index eeef6f0..2efedf1 100644 --- a/lib/agent_jido/forge/resources/session.ex +++ b/lib/agent_jido/forge/resources/session.ex @@ -1,4 +1,5 @@ defmodule AgentJido.Forge.Resources.Session do + @moduledoc false use Ash.Resource, otp_app: :agent_jido, domain: AgentJido.Forge.Domain, diff --git a/lib/agent_jido/forge/resources/sprite_spec.ex b/lib/agent_jido/forge/resources/sprite_spec.ex index cd65da9..40298f3 100644 --- a/lib/agent_jido/forge/resources/sprite_spec.ex +++ b/lib/agent_jido/forge/resources/sprite_spec.ex @@ -1,4 +1,5 @@ defmodule AgentJido.Forge.Resources.SpriteSpec do + @moduledoc false use Ash.Resource, otp_app: :agent_jido, domain: AgentJido.Forge.Domain, diff --git a/lib/agent_jido/forge/resources/workflow.ex b/lib/agent_jido/forge/resources/workflow.ex index d9bcbc5..8a448e2 100644 --- a/lib/agent_jido/forge/resources/workflow.ex +++ b/lib/agent_jido/forge/resources/workflow.ex @@ -1,4 +1,5 @@ defmodule AgentJido.Forge.Resources.Workflow do + @moduledoc false use Ash.Resource, otp_app: :agent_jido, domain: AgentJido.Forge.Domain, diff --git a/lib/agent_jido/forge/runner.ex b/lib/agent_jido/forge/runner.ex index ca37bf4..b9c7642 100644 --- a/lib/agent_jido/forge/runner.ex +++ b/lib/agent_jido/forge/runner.ex @@ -47,8 +47,10 @@ defmodule AgentJido.Forge.Runner do Apply external input to the runner. Called when the runner is in `:needs_input` status and input has been provided. + Return `{:ok, new_state}` to update the runner state after applying input. """ - @callback apply_input(sprite_client(), input(), state()) :: :ok | {:error, term()} + @callback apply_input(sprite_client(), input(), state()) :: + :ok | {:ok, state()} | {:error, term()} @doc """ Handle streaming output from the sprite. diff --git a/lib/agent_jido/forge/runners/claude_code.ex b/lib/agent_jido/forge/runners/claude_code.ex index cda86ce..bed0c5f 100644 --- a/lib/agent_jido/forge/runners/claude_code.ex +++ b/lib/agent_jido/forge/runners/claude_code.ex @@ -26,6 +26,7 @@ defmodule AgentJido.Forge.Runners.ClaudeCode do @behaviour AgentJido.Forge.Runner + alias AgentJido.Forge.Runner alias AgentJido.Forge.SpriteClient @forge_home "/var/local/forge" @@ -33,35 +34,37 @@ defmodule AgentJido.Forge.Runners.ClaudeCode do @impl true def init(client, config) do with :ok <- setup_directories(client), - :ok <- setup_claude_settings(client, config), - :ok <- setup_templates(client, config) do - :ok + :ok <- setup_claude_settings(client, config) do + setup_templates(client, config) end end @impl true def run_iteration(client, state, opts) do + {model, max_turns, max_budget, prompt} = resolve_run_options(state, opts) + cmd = build_claude_command(model, max_turns, max_budget, prompt) + + client + |> SpriteClient.exec(cmd, timeout: :infinity) + |> handle_exec_result() + end + + defp resolve_run_options(state, opts) do model = opts[:model] || state[:model] || "claude-sonnet-4-20250514" max_turns = opts[:max_turns] || state[:max_turns] || 200 max_budget = opts[:max_budget] || state[:max_budget] || 10.0 prompt = opts[:prompt] || state[:prompt] + {model, max_turns, max_budget, prompt} + end - cmd = build_claude_command(model, max_turns, max_budget, prompt) + defp handle_exec_result({output, 0}), do: {:ok, parse_claude_output(output)} - case SpriteClient.exec(client, cmd, timeout: :infinity) do - {output, 0} -> - result = parse_claude_output(output) - {:ok, result} - - {output, code} -> - {:ok, - %{ - status: :error, - output: output, - error: "Claude exited with code #{code}", - metadata: %{exit_code: code} - }} - end + defp handle_exec_result({output, code}) do + {:ok, + Runner.error("Claude exited with code #{code}", + output: output, + metadata: %{exit_code: code} + )} end @impl true @@ -115,9 +118,8 @@ defmodule AgentJido.Forge.Runners.ClaudeCode do end defp setup_templates(client, config) do - with :ok <- maybe_write_template(client, config, :prompt_template, "iterate.md"), - :ok <- maybe_write_template(client, config, :context_template, "context.md") do - :ok + with :ok <- maybe_write_template(client, config, :prompt_template, "iterate.md") do + maybe_write_template(client, config, :context_template, "context.md") end end @@ -178,11 +180,10 @@ defmodule AgentJido.Forge.Runners.ClaudeCode do cond do is_nil(last_event) -> - %{status: :done, output: output, metadata: %{}} + Runner.done(output: output, metadata: %{}) last_event["type"] == "result" && last_event["subtype"] == "success" -> - %{ - status: :done, + Runner.done( output: output, summary: last_event["result"], metadata: %{ @@ -190,18 +191,17 @@ defmodule AgentJido.Forge.Runners.ClaudeCode do duration_ms: last_event["duration_ms"], session_id: last_event["session_id"] } - } + ) last_event["type"] == "result" && last_event["subtype"] == "error_max_turns" -> - %{ - status: :continue, + Runner.continue( output: output, summary: "Max turns reached", metadata: %{cost_usd: last_event["cost_usd"]} - } + ) true -> - %{status: :done, output: output, metadata: %{}} + Runner.done(output: output, metadata: %{}) end end diff --git a/lib/agent_jido/forge/sprite_client/fake.ex b/lib/agent_jido/forge/sprite_client/fake.ex index ce1a579..d0434a7 100644 --- a/lib/agent_jido/forge/sprite_client/fake.ex +++ b/lib/agent_jido/forge/sprite_client/fake.ex @@ -173,18 +173,7 @@ defmodule AgentJido.Forge.SpriteClient.Fake do case Map.keys(sprites) do [sprite_id | _] -> - Agent.update(agent_pid, fn sprites -> - update_in(sprites, [sprite_id, :env], fn existing_env -> - # Normalize all env values to strings (binaries) - normalized_map = - env_map - |> Enum.map(fn {k, v} -> {to_binary_string(k), to_binary_string(v)} end) - |> Map.new() - - Map.merge(existing_env || %{}, normalized_map) - end) - end) - + Agent.update(agent_pid, &merge_env_into_sprite(&1, sprite_id, env_map)) :ok [] -> @@ -192,6 +181,17 @@ defmodule AgentJido.Forge.SpriteClient.Fake do end end + defp merge_env_into_sprite(sprites, sprite_id, env_map) do + normalized_map = normalize_env_map(env_map) + update_in(sprites, [sprite_id, :env], &Map.merge(&1 || %{}, normalized_map)) + end + + defp normalize_env_map(env_map) do + env_map + |> Enum.map(fn {k, v} -> {to_binary_string(k), to_binary_string(v)} end) + |> Map.new() + end + @impl true def destroy(%__MODULE__{agent_pid: agent_pid} = _client, sprite_id) do ensure_agent_started(agent_pid) diff --git a/lib/agent_jido/forge/sprite_client/live.ex b/lib/agent_jido/forge/sprite_client/live.ex index f3c0ab5..b5163cd 100644 --- a/lib/agent_jido/forge/sprite_client/live.ex +++ b/lib/agent_jido/forge/sprite_client/live.ex @@ -131,9 +131,7 @@ defmodule AgentJido.Forge.SpriteClient.Live do def inject_env(%__MODULE__{sprite: sprite} = _client, env_map) do env_lines = - env_map - |> Enum.map(fn {k, v} -> "export #{k}=\"#{escape_value(v)}\"" end) - |> Enum.join("\n") + Enum.map_join(env_map, "\n", fn {k, v} -> "export #{k}=\"#{escape_value(v)}\"" end) encoded = Base.encode64(env_lines <> "\n") diff --git a/lib/agent_jido/forge/sprite_session.ex b/lib/agent_jido/forge/sprite_session.ex index 6e32a32..2a0366b 100644 --- a/lib/agent_jido/forge/sprite_session.ex +++ b/lib/agent_jido/forge/sprite_session.ex @@ -279,6 +279,17 @@ defmodule AgentJido.Forge.SpriteSession do Persistence.record_input_applied(state.session_id, state.runner_state) {:reply, :ok, new_state} + {:ok, new_runner_state} -> + new_state = %{ + state + | state: :ready, + runner_state: new_runner_state, + last_activity: DateTime.utc_now() + } + + Persistence.record_input_applied(state.session_id, new_runner_state) + {:reply, :ok, new_state} + {:error, reason} -> {:reply, {:error, reason}, state} end diff --git a/lib/agent_jido/forge/workers/streaming_exec_session_worker.ex b/lib/agent_jido/forge/workers/streaming_exec_session_worker.ex index 46242da..6cbddb5 100644 --- a/lib/agent_jido/forge/workers/streaming_exec_session_worker.ex +++ b/lib/agent_jido/forge/workers/streaming_exec_session_worker.ex @@ -11,7 +11,7 @@ defmodule AgentJido.Forge.Workers.StreamingExecSessionWorker do require Logger alias AgentJido.Forge.PubSub, as: ForgePubSub - alias AgentJido.Forge.Resources.{ExecSession, Event} + alias AgentJido.Forge.Resources.{Event, ExecSession} @chunk_coalesce_ms 50 @max_buffer_size 64 * 1024 diff --git a/lib/agent_jido/github.ex b/lib/agent_jido/github.ex index d28ddb2..5d0b96b 100644 --- a/lib/agent_jido/github.ex +++ b/lib/agent_jido/github.ex @@ -1,4 +1,5 @@ defmodule AgentJido.GitHub do + @moduledoc false use Ash.Domain, otp_app: :agent_jido, extensions: [AshAdmin.Domain] admin do diff --git a/lib/agent_jido/github/issue_analysis.ex b/lib/agent_jido/github/issue_analysis.ex index 1732006..a9f8089 100644 --- a/lib/agent_jido/github/issue_analysis.ex +++ b/lib/agent_jido/github/issue_analysis.ex @@ -1,4 +1,5 @@ defmodule AgentJido.GitHub.IssueAnalysis do + @moduledoc false use Ash.Resource, otp_app: :agent_jido, domain: AgentJido.GitHub, diff --git a/lib/agent_jido/github/webhook_delivery.ex b/lib/agent_jido/github/webhook_delivery.ex index 4365c1d..94a22f3 100644 --- a/lib/agent_jido/github/webhook_delivery.ex +++ b/lib/agent_jido/github/webhook_delivery.ex @@ -1,4 +1,5 @@ defmodule AgentJido.GitHub.WebhookDelivery do + @moduledoc false use Ash.Resource, otp_app: :agent_jido, domain: AgentJido.GitHub, diff --git a/lib/agent_jido/github_issue_bot/cli/run.ex b/lib/agent_jido/github_issue_bot/cli/run.ex index 9f8a706..1f24156 100644 --- a/lib/agent_jido/github_issue_bot/cli/run.ex +++ b/lib/agent_jido/github_issue_bot/cli/run.ex @@ -10,9 +10,9 @@ defmodule AgentJido.GithubIssueBot.CLI.Run do mix run -e "AgentJido.GithubIssueBot.CLI.Run.run(run_id: \\"test-run\\")" """ + alias AgentJido.GithubIssueBot.IssueRun.CoordinatorAgent alias Jido.AgentServer alias Jido.Signal - alias AgentJido.GithubIssueBot.IssueRun.CoordinatorAgent require Logger @@ -119,7 +119,7 @@ defmodule AgentJido.GithubIssueBot.CLI.Run do Enum.each(artifacts, fn {name, artifact} -> IO.puts("\n--- #{name} ---") - IO.inspect(artifact, pretty: true, limit: :infinity) + IO.puts(inspect(artifact, pretty: true, limit: :infinity)) end) end @@ -132,9 +132,9 @@ defmodule AgentJido.GithubIssueBot.CLI.Run do print_artifacts(state.artifacts) end - if length(state.errors) > 0 do + if state.errors != [] do IO.puts("\n--- Errors ---") - Enum.each(state.errors, &IO.inspect/1) + Enum.each(state.errors, fn error -> IO.puts(inspect(error)) end) end end end diff --git a/lib/agent_jido/github_issue_bot/issue_run/actions/pull_request_result_action.ex b/lib/agent_jido/github_issue_bot/issue_run/actions/pull_request_result_action.ex index 45719f8..97934af 100644 --- a/lib/agent_jido/github_issue_bot/issue_run/actions/pull_request_result_action.ex +++ b/lib/agent_jido/github_issue_bot/issue_run/actions/pull_request_result_action.ex @@ -31,9 +31,7 @@ defmodule AgentJido.GithubIssueBot.IssueRun.Actions.PullRequestResultAction do cond do current_run_id != run_id -> - Logger.warning( - "Received pull_request result for wrong run: #{run_id} (expected #{current_run_id})" - ) + Logger.warning("Received pull_request result for wrong run: #{run_id} (expected #{current_run_id})") {:ok, %{}} diff --git a/lib/agent_jido/github_issue_bot/issue_run/actions/research_result_action.ex b/lib/agent_jido/github_issue_bot/issue_run/actions/research_result_action.ex index e566711..14ef900 100644 --- a/lib/agent_jido/github_issue_bot/issue_run/actions/research_result_action.ex +++ b/lib/agent_jido/github_issue_bot/issue_run/actions/research_result_action.ex @@ -18,8 +18,8 @@ defmodule AgentJido.GithubIssueBot.IssueRun.Actions.ResearchResultAction do summary: [type: :string, default: ""] ] - alias Jido.Agent.Directive alias AgentJido.GithubIssueBot.PullRequest.PullRequestCoordinator + alias Jido.Agent.Directive require Logger @@ -30,9 +30,7 @@ defmodule AgentJido.GithubIssueBot.IssueRun.Actions.ResearchResultAction do cond do current_run_id != run_id -> - Logger.warning( - "Received research result for wrong run: #{run_id} (expected #{current_run_id})" - ) + Logger.warning("Received research result for wrong run: #{run_id} (expected #{current_run_id})") {:ok, %{}} diff --git a/lib/agent_jido/github_issue_bot/issue_run/actions/start_run_action.ex b/lib/agent_jido/github_issue_bot/issue_run/actions/start_run_action.ex index 769f2c0..ef3b15b 100644 --- a/lib/agent_jido/github_issue_bot/issue_run/actions/start_run_action.ex +++ b/lib/agent_jido/github_issue_bot/issue_run/actions/start_run_action.ex @@ -11,8 +11,8 @@ defmodule AgentJido.GithubIssueBot.IssueRun.Actions.StartRunAction do issue: [type: :map, required: true] ] - alias Jido.Agent.Directive alias AgentJido.GithubIssueBot.Triage.TriageAgent + alias Jido.Agent.Directive require Logger diff --git a/lib/agent_jido/github_issue_bot/issue_run/actions/triage_result_action.ex b/lib/agent_jido/github_issue_bot/issue_run/actions/triage_result_action.ex index fc44c38..4ac2530 100644 --- a/lib/agent_jido/github_issue_bot/issue_run/actions/triage_result_action.ex +++ b/lib/agent_jido/github_issue_bot/issue_run/actions/triage_result_action.ex @@ -14,8 +14,8 @@ defmodule AgentJido.GithubIssueBot.IssueRun.Actions.TriageResultAction do summary: [type: :string, default: ""] ] - alias Jido.Agent.Directive alias AgentJido.GithubIssueBot.Research.ResearchCoordinator + alias Jido.Agent.Directive require Logger @@ -34,9 +34,7 @@ defmodule AgentJido.GithubIssueBot.IssueRun.Actions.TriageResultAction do cond do current_run_id != run_id -> - Logger.warning( - "Received triage result for wrong run: #{run_id} (expected #{current_run_id})" - ) + Logger.warning("Received triage result for wrong run: #{run_id} (expected #{current_run_id})") {:ok, %{}} diff --git a/lib/agent_jido/github_issue_bot/issue_run/coordinator_agent.ex b/lib/agent_jido/github_issue_bot/issue_run/coordinator_agent.ex index d9f6e46..e05157e 100644 --- a/lib/agent_jido/github_issue_bot/issue_run/coordinator_agent.ex +++ b/lib/agent_jido/github_issue_bot/issue_run/coordinator_agent.ex @@ -40,11 +40,11 @@ defmodule AgentJido.GithubIssueBot.IssueRun.CoordinatorAgent do ] alias AgentJido.GithubIssueBot.IssueRun.Actions.{ - StartRunAction, ChildStartedAction, - TriageResultAction, + PullRequestResultAction, ResearchResultAction, - PullRequestResultAction + StartRunAction, + TriageResultAction } def signal_routes do diff --git a/lib/agent_jido/github_issue_bot/pull_request/actions/patch_result_action.ex b/lib/agent_jido/github_issue_bot/pull_request/actions/patch_result_action.ex index efdf034..0a1dc05 100644 --- a/lib/agent_jido/github_issue_bot/pull_request/actions/patch_result_action.ex +++ b/lib/agent_jido/github_issue_bot/pull_request/actions/patch_result_action.ex @@ -12,8 +12,8 @@ defmodule AgentJido.GithubIssueBot.PullRequest.Actions.PatchResultAction do result: [type: :map, required: true] ] - alias Jido.Agent.Directive alias AgentJido.GithubIssueBot.PullRequest.Workers.QualityAgent + alias Jido.Agent.Directive require Logger diff --git a/lib/agent_jido/github_issue_bot/pull_request/actions/pr_submit_result_action.ex b/lib/agent_jido/github_issue_bot/pull_request/actions/pr_submit_result_action.ex index 3739f28..c821b29 100644 --- a/lib/agent_jido/github_issue_bot/pull_request/actions/pr_submit_result_action.ex +++ b/lib/agent_jido/github_issue_bot/pull_request/actions/pr_submit_result_action.ex @@ -70,7 +70,7 @@ defmodule AgentJido.GithubIssueBot.PullRequest.Actions.PRSubmitResultAction do source: "/pull_request_coordinator" ) - emit_directive = Directive.emit_to_parent(%{state: context.state}, result_signal) + emit_directive = Directive.emit_to_parent(context.agent, result_signal) {:ok, %{ diff --git a/lib/agent_jido/github_issue_bot/pull_request/actions/quality_result_action.ex b/lib/agent_jido/github_issue_bot/pull_request/actions/quality_result_action.ex index 49882b3..bace901 100644 --- a/lib/agent_jido/github_issue_bot/pull_request/actions/quality_result_action.ex +++ b/lib/agent_jido/github_issue_bot/pull_request/actions/quality_result_action.ex @@ -161,7 +161,7 @@ defmodule AgentJido.GithubIssueBot.PullRequest.Actions.QualityResultAction do source: "/pull_request_coordinator" ) - emit_directive = Directive.emit_to_parent(%{state: context.state}, result_signal) + emit_directive = Directive.emit_to_parent(context.agent, result_signal) {:ok, %{ diff --git a/lib/agent_jido/github_issue_bot/pull_request/actions/start_pull_request_action.ex b/lib/agent_jido/github_issue_bot/pull_request/actions/start_pull_request_action.ex index 6613999..8f70dd2 100644 --- a/lib/agent_jido/github_issue_bot/pull_request/actions/start_pull_request_action.ex +++ b/lib/agent_jido/github_issue_bot/pull_request/actions/start_pull_request_action.ex @@ -23,8 +23,8 @@ defmodule AgentJido.GithubIssueBot.PullRequest.Actions.StartPullRequestAction do research: [type: :map, required: true] ] - alias Jido.Agent.Directive alias AgentJido.GithubIssueBot.PullRequest.Workers.PatchAgent + alias Jido.Agent.Directive require Logger diff --git a/lib/agent_jido/github_issue_bot/pull_request/pull_request_coordinator.ex b/lib/agent_jido/github_issue_bot/pull_request/pull_request_coordinator.ex index 4729d13..886789e 100644 --- a/lib/agent_jido/github_issue_bot/pull_request/pull_request_coordinator.ex +++ b/lib/agent_jido/github_issue_bot/pull_request/pull_request_coordinator.ex @@ -64,11 +64,11 @@ defmodule AgentJido.GithubIssueBot.PullRequest.PullRequestCoordinator do ] alias AgentJido.GithubIssueBot.PullRequest.Actions.{ - StartPullRequestAction, - WorkerStartedAction, PatchResultAction, + PRSubmitResultAction, QualityResultAction, - PRSubmitResultAction + StartPullRequestAction, + WorkerStartedAction } def signal_routes do diff --git a/lib/agent_jido/github_issue_bot/pull_request/workers/actions/patch_action.ex b/lib/agent_jido/github_issue_bot/pull_request/workers/actions/patch_action.ex index b28b0e8..340f475 100644 --- a/lib/agent_jido/github_issue_bot/pull_request/workers/actions/patch_action.ex +++ b/lib/agent_jido/github_issue_bot/pull_request/workers/actions/patch_action.ex @@ -77,7 +77,7 @@ defmodule AgentJido.GithubIssueBot.PullRequest.Workers.Actions.PatchAction do source: "/patch_agent" ) - emit_directive = Directive.emit_to_parent(%{state: context.state}, result_signal) + emit_directive = Directive.emit_to_parent(context.agent, result_signal) {:ok, %{ diff --git a/lib/agent_jido/github_issue_bot/pull_request/workers/actions/pr_submit_action.ex b/lib/agent_jido/github_issue_bot/pull_request/workers/actions/pr_submit_action.ex index e3476b0..99adf3a 100644 --- a/lib/agent_jido/github_issue_bot/pull_request/workers/actions/pr_submit_action.ex +++ b/lib/agent_jido/github_issue_bot/pull_request/workers/actions/pr_submit_action.ex @@ -60,6 +60,7 @@ defmodule AgentJido.GithubIssueBot.PullRequest.Workers.Actions.PRSubmitAction do # Stub: Generate mock PR data # TODO: Replace with actual GitHub API calls pr_number = generate_pr_number() + result = %{ pr_number: pr_number, pr_url: "https://github.com/#{repo}/pull/#{pr_number}", @@ -83,7 +84,7 @@ defmodule AgentJido.GithubIssueBot.PullRequest.Workers.Actions.PRSubmitAction do source: "/pr_submit_agent" ) - emit_directive = Directive.emit_to_parent(%{state: context.state}, result_signal) + emit_directive = Directive.emit_to_parent(context.agent, result_signal) {:ok, %{ @@ -118,7 +119,10 @@ defmodule AgentJido.GithubIssueBot.PullRequest.Workers.Actions.PRSubmitAction do classification = Map.get(triage, :classification, :unknown) root_cause = get_in(research, [:root_cause, :hypothesis]) || "See research findings" - attempt_note = if attempt > 1, do: "\n\n> **Note**: This fix required #{attempt} iterations to pass all quality checks.", else: "" + attempt_note = + if attempt > 1, + do: "\n\n> **Note**: This fix required #{attempt} iterations to pass all quality checks.", + else: "" """ ## Summary diff --git a/lib/agent_jido/github_issue_bot/pull_request/workers/actions/quality_action.ex b/lib/agent_jido/github_issue_bot/pull_request/workers/actions/quality_action.ex index 97f7f51..8964e67 100644 --- a/lib/agent_jido/github_issue_bot/pull_request/workers/actions/quality_action.ex +++ b/lib/agent_jido/github_issue_bot/pull_request/workers/actions/quality_action.ex @@ -65,7 +65,7 @@ defmodule AgentJido.GithubIssueBot.PullRequest.Workers.Actions.QualityAction do source: "/quality_agent" ) - emit_directive = Directive.emit_to_parent(%{state: context.state}, result_signal) + emit_directive = Directive.emit_to_parent(context.agent, result_signal) {:ok, %{ diff --git a/lib/agent_jido/github_issue_bot/research/actions/start_research_action.ex b/lib/agent_jido/github_issue_bot/research/actions/start_research_action.ex index a01370f..915b8a2 100644 --- a/lib/agent_jido/github_issue_bot/research/actions/start_research_action.ex +++ b/lib/agent_jido/github_issue_bot/research/actions/start_research_action.ex @@ -18,15 +18,15 @@ defmodule AgentJido.GithubIssueBot.Research.Actions.StartResearchAction do triage: [type: :map, required: true] ] - alias Jido.Agent.Directive - alias AgentJido.GithubIssueBot.Research.Workers.{ CodeSearchAgent, + PRSearchAgent, ReproductionAgent, - RootCauseAgent, - PRSearchAgent + RootCauseAgent } + alias Jido.Agent.Directive + require Logger # Workers to spawn - each gets a tag for identification diff --git a/lib/agent_jido/github_issue_bot/research/actions/worker_result_action.ex b/lib/agent_jido/github_issue_bot/research/actions/worker_result_action.ex index c4018d1..4b705c1 100644 --- a/lib/agent_jido/github_issue_bot/research/actions/worker_result_action.ex +++ b/lib/agent_jido/github_issue_bot/research/actions/worker_result_action.ex @@ -94,7 +94,7 @@ defmodule AgentJido.GithubIssueBot.Research.Actions.WorkerResultAction do source: "/research_coordinator" ) - emit_directive = Directive.emit_to_parent(%{state: context.state}, result_signal) + emit_directive = Directive.emit_to_parent(context.agent, result_signal) {:ok, %{ @@ -109,12 +109,10 @@ defmodule AgentJido.GithubIssueBot.Research.Actions.WorkerResultAction do # TODO: Could use LLM to synthesize a coherent narrative defp synthesize_summary(worker_results) do parts = - worker_results - |> Enum.map(fn {worker, result} -> + Enum.map_join(worker_results, "; ", fn {worker, result} -> summary = Map.get(result, :summary, "No summary") "#{worker}: #{summary}" end) - |> Enum.join("; ") "Research complete. #{parts}" end diff --git a/lib/agent_jido/github_issue_bot/research/research_coordinator.ex b/lib/agent_jido/github_issue_bot/research/research_coordinator.ex index a787e4f..5a1c6d2 100644 --- a/lib/agent_jido/github_issue_bot/research/research_coordinator.ex +++ b/lib/agent_jido/github_issue_bot/research/research_coordinator.ex @@ -45,8 +45,8 @@ defmodule AgentJido.GithubIssueBot.Research.ResearchCoordinator do alias AgentJido.GithubIssueBot.Research.Actions.{ StartResearchAction, - WorkerStartedAction, - WorkerResultAction + WorkerResultAction, + WorkerStartedAction } def signal_routes do diff --git a/lib/agent_jido/github_issue_bot/research/workers/actions/code_search_action.ex b/lib/agent_jido/github_issue_bot/research/workers/actions/code_search_action.ex index 2c17707..8988785 100644 --- a/lib/agent_jido/github_issue_bot/research/workers/actions/code_search_action.ex +++ b/lib/agent_jido/github_issue_bot/research/workers/actions/code_search_action.ex @@ -64,7 +64,7 @@ defmodule AgentJido.GithubIssueBot.Research.Workers.Actions.CodeSearchAction do source: "/code_search" ) - emit_directive = Directive.emit_to_parent(%{state: context.state}, result_signal) + emit_directive = Directive.emit_to_parent(context.agent, result_signal) {:ok, %{ diff --git a/lib/agent_jido/github_issue_bot/research/workers/actions/pr_search_action.ex b/lib/agent_jido/github_issue_bot/research/workers/actions/pr_search_action.ex index ac37e25..1115bb0 100644 --- a/lib/agent_jido/github_issue_bot/research/workers/actions/pr_search_action.ex +++ b/lib/agent_jido/github_issue_bot/research/workers/actions/pr_search_action.ex @@ -67,7 +67,7 @@ defmodule AgentJido.GithubIssueBot.Research.Workers.Actions.PRSearchAction do source: "/pr_search" ) - emit_directive = Directive.emit_to_parent(%{state: context.state}, result_signal) + emit_directive = Directive.emit_to_parent(context.agent, result_signal) {:ok, %{ diff --git a/lib/agent_jido/github_issue_bot/research/workers/actions/reproduction_action.ex b/lib/agent_jido/github_issue_bot/research/workers/actions/reproduction_action.ex index dbbb905..7cee009 100644 --- a/lib/agent_jido/github_issue_bot/research/workers/actions/reproduction_action.ex +++ b/lib/agent_jido/github_issue_bot/research/workers/actions/reproduction_action.ex @@ -64,7 +64,7 @@ defmodule AgentJido.GithubIssueBot.Research.Workers.Actions.ReproductionAction d source: "/reproduction" ) - emit_directive = Directive.emit_to_parent(%{state: context.state}, result_signal) + emit_directive = Directive.emit_to_parent(context.agent, result_signal) {:ok, %{ diff --git a/lib/agent_jido/github_issue_bot/research/workers/actions/root_cause_action.ex b/lib/agent_jido/github_issue_bot/research/workers/actions/root_cause_action.ex index 5db542c..608c42d 100644 --- a/lib/agent_jido/github_issue_bot/research/workers/actions/root_cause_action.ex +++ b/lib/agent_jido/github_issue_bot/research/workers/actions/root_cause_action.ex @@ -67,7 +67,7 @@ defmodule AgentJido.GithubIssueBot.Research.Workers.Actions.RootCauseAction do source: "/root_cause" ) - emit_directive = Directive.emit_to_parent(%{state: context.state}, result_signal) + emit_directive = Directive.emit_to_parent(context.agent, result_signal) {:ok, %{ @@ -80,33 +80,21 @@ defmodule AgentJido.GithubIssueBot.Research.Workers.Actions.RootCauseAction do # Generate hypothesis based on keywords and classification defp hypothesize(title, body, classification) do cond do - # State-related issues - String.contains?(body, "state") and String.contains?(body, "persist") -> - {"State serialization/deserialization issue", :medium, "lib/core/state.ex"} - - String.contains?(body, "state") and String.contains?(body, "lost") -> - {"State not being saved properly", :medium, "lib/core/agent.ex"} - - # Concurrency issues - String.contains?(body, "race") or String.contains?(body, "concurrent") -> - {"Race condition in concurrent operations", :low, "lib/core/"} - - # Error handling - String.contains?(title, "error") or String.contains?(body, "exception") -> - {"Unhandled error case", :medium, "lib/"} - - # Classification-based fallbacks - classification == :bug -> - {"Logic error in core functionality", :low, "lib/"} - - classification == :feature -> - {"Missing feature implementation", :high, "lib/"} - - true -> - {"Unable to determine root cause", :low, nil} + state_persist_issue?(body) -> {"State serialization/deserialization issue", :medium, "lib/core/state.ex"} + state_lost_issue?(body) -> {"State not being saved properly", :medium, "lib/core/agent.ex"} + concurrency_issue?(body) -> {"Race condition in concurrent operations", :low, "lib/core/"} + error_handling_issue?(title, body) -> {"Unhandled error case", :medium, "lib/"} + classification == :bug -> {"Logic error in core functionality", :low, "lib/"} + classification == :feature -> {"Missing feature implementation", :high, "lib/"} + true -> {"Unable to determine root cause", :low, nil} end end + defp state_persist_issue?(body), do: String.contains?(body, "state") and String.contains?(body, "persist") + defp state_lost_issue?(body), do: String.contains?(body, "state") and String.contains?(body, "lost") + defp concurrency_issue?(body), do: String.contains?(body, "race") or String.contains?(body, "concurrent") + defp error_handling_issue?(title, body), do: String.contains?(title, "error") or String.contains?(body, "exception") + # Extract evidence from issue body defp extract_evidence(body) do evidence = [] diff --git a/lib/agent_jido/github_issue_bot/triage/actions/triage_action.ex b/lib/agent_jido/github_issue_bot/triage/actions/triage_action.ex index 6c8a5de..3884a33 100644 --- a/lib/agent_jido/github_issue_bot/triage/actions/triage_action.ex +++ b/lib/agent_jido/github_issue_bot/triage/actions/triage_action.ex @@ -43,7 +43,7 @@ defmodule AgentJido.GithubIssueBot.Triage.Actions.TriageAction do source: "/triage" ) - emit_directive = Directive.emit_to_parent(%{state: context.state}, result_signal) + emit_directive = Directive.emit_to_parent(context.agent, result_signal) {:ok, %{ @@ -55,22 +55,29 @@ defmodule AgentJido.GithubIssueBot.Triage.Actions.TriageAction do defp classify(title, _body, labels) do cond do - "bug" in labels or String.contains?(title, "bug") or String.contains?(title, "error") -> - :bug + bug?(title, labels) -> :bug + feature?(title, labels) -> :feature + question?(title, labels) -> :question + documentation?(title, labels) -> :documentation + true -> :unknown + end + end - "feature" in labels or "enhancement" in labels or - String.contains?(title, "feature") or String.contains?(title, "add") -> - :feature + defp bug?(title, labels) do + "bug" in labels or String.contains?(title, "bug") or String.contains?(title, "error") + end - "question" in labels or String.contains?(title, "?") -> - :question + defp feature?(title, labels) do + "feature" in labels or "enhancement" in labels or + String.contains?(title, "feature") or String.contains?(title, "add") + end - "documentation" in labels or String.contains?(title, "docs") -> - :documentation + defp question?(title, labels) do + "question" in labels or String.contains?(title, "?") + end - true -> - :unknown - end + defp documentation?(title, labels) do + "documentation" in labels or String.contains?(title, "docs") end defp needs_more_info?(body) do diff --git a/lib/agent_jido/secrets.ex b/lib/agent_jido/secrets.ex index 6c42606..84e8d68 100644 --- a/lib/agent_jido/secrets.ex +++ b/lib/agent_jido/secrets.ex @@ -1,4 +1,5 @@ defmodule AgentJido.Secrets do + @moduledoc false use AshAuthentication.Secret def secret_for( diff --git a/lib/agent_jido_web.ex b/lib/agent_jido_web.ex index 6d4adea..212b0d0 100644 --- a/lib/agent_jido_web.ex +++ b/lib/agent_jido_web.ex @@ -88,8 +88,8 @@ defmodule AgentJidoWeb do use AgentJidoWeb.Components.MishkaComponents # Common modules used in templates - alias Phoenix.LiveView.JS alias AgentJidoWeb.Layouts + alias Phoenix.LiveView.JS # Routes generation with the ~p sigil unquote(verified_routes()) diff --git a/lib/agent_jido_web/ash_json_api_router.ex b/lib/agent_jido_web/ash_json_api_router.ex index 3b64b48..165d4d6 100644 --- a/lib/agent_jido_web/ash_json_api_router.ex +++ b/lib/agent_jido_web/ash_json_api_router.ex @@ -1,4 +1,5 @@ defmodule AgentJidoWeb.AshJsonApiRouter do + @moduledoc false use AshJsonApi.Router, domains: [], open_api: "/open_api" diff --git a/lib/agent_jido_web/auth_overrides.ex b/lib/agent_jido_web/auth_overrides.ex index 5b388ab..28fc9ab 100644 --- a/lib/agent_jido_web/auth_overrides.ex +++ b/lib/agent_jido_web/auth_overrides.ex @@ -1,4 +1,5 @@ defmodule AgentJidoWeb.AuthOverrides do + @moduledoc false use AshAuthentication.Phoenix.Overrides # configure your UI overrides here diff --git a/lib/agent_jido_web/components/accordion.ex b/lib/agent_jido_web/components/accordion.ex index 792acd4..67776b5 100644 --- a/lib/agent_jido_web/components/accordion.ex +++ b/lib/agent_jido_web/components/accordion.ex @@ -150,8 +150,8 @@ defmodule AgentJidoWeb.Components.Accordion do |> Enum.map(& &1.id) cond do - length(initial_open) > 0 -> Enum.join(initial_open, ",") - length(slot_open_items) > 0 -> Enum.join(slot_open_items, ",") + initial_open != [] -> Enum.join(initial_open, ",") + slot_open_items != [] -> Enum.join(slot_open_items, ",") true -> "" end end diff --git a/lib/agent_jido_web/components/blockquote.ex b/lib/agent_jido_web/components/blockquote.ex index 66ed5ed..ec3c962 100644 --- a/lib/agent_jido_web/components/blockquote.ex +++ b/lib/agent_jido_web/components/blockquote.ex @@ -228,7 +228,8 @@ defmodule AgentJidoWeb.Components.Blockquote do defp space_class(params) when is_binary(params), do: params defp border_class(_, _, variant) - when variant in ["default", "shadow", "transparent", "gradient"], do: nil + when variant in ["default", "shadow", "transparent", "gradient"], + do: nil defp border_class(_, "none", _), do: nil diff --git a/lib/agent_jido_web/components/breadcrumb.ex b/lib/agent_jido_web/components/breadcrumb.ex index 19609cb..e476b4b 100644 --- a/lib/agent_jido_web/components/breadcrumb.ex +++ b/lib/agent_jido_web/components/breadcrumb.ex @@ -249,7 +249,7 @@ defmodule AgentJidoWeb.Components.Breadcrumb do defp size_class(params) when is_binary(params), do: params - defp default_classes() do + defp default_classes do [ "flex items-center transition-all ease-in-out duration-100 group" ] diff --git a/lib/agent_jido_web/components/button.ex b/lib/agent_jido_web/components/button.ex index e9bac8b..b8ac11a 100644 --- a/lib/agent_jido_web/components/button.ex +++ b/lib/agent_jido_web/components/button.ex @@ -191,7 +191,7 @@ defmodule AgentJidoWeb.Components.Button do end def button(assigns) do - assigns = assign_new(assigns, :indicator, fn -> is_indicators?(assigns[:rest]) end) + assigns = assign_new(assigns, :indicator, fn -> has_indicators?(assigns[:rest]) end) ~H"""