Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion lib/ourocode/cli.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ defmodule Ourocode.CLI do
alias Ourocode.Runtime.LoopBindingInterviewSessionIO
alias Ourocode.Runtime.LoopBindings

@version "0.1.13"
@version "0.1.14"

@doc """
Escript entry point.
Expand Down Expand Up @@ -3238,6 +3238,13 @@ defmodule Ourocode.CLI do

defp workflow_help_prompt?(_task_request), do: false

defp workflow_mode(%{routing_decision: %{adapter_route: :pm}}), do: :pm
defp workflow_mode(%{routing_decision: %{"adapter_route" => "pm"}}), do: :pm
defp workflow_mode(%{routing_decision: %{adapter_route: :interview}}), do: :interview
defp workflow_mode(%{routing_decision: %{"adapter_route" => "interview"}}), do: :interview
defp workflow_mode(%{routing_decision: %{adapter_route: :auto}}), do: :auto
defp workflow_mode(%{routing_decision: %{"adapter_route" => "auto"}}), do: :auto

defp workflow_mode(%{task_input: input}) when is_binary(input) do
normalized = input |> String.trim() |> String.downcase()

Expand Down
9 changes: 8 additions & 1 deletion lib/ourocode/model/profile.ex
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,20 @@ defmodule Ourocode.Model.Profile do
defp runtime_name(_label, model_label), do: short_model_label(model_label)

defp pick_model(models, candidates, active_model, opts) do
ready_candidate(models, candidates) ||
ready_active_candidate(active_model, candidates) ||
ready_candidate(models, candidates) ||
ready_active(active_model) ||
selectable_candidate(models, candidates) ||
active_model(active_model) ||
Keyword.get_lazy(opts, :default_model, &Catalog.default/0)
end

defp ready_active_candidate(%Model{id: id} = model, candidates) do
if id in candidates and Model.ready?(model), do: model
end

defp ready_active_candidate(_model, _candidates), do: nil

defp ready_candidate(models, candidates) do
Enum.find_value(candidates, fn id ->
case Catalog.fetch(models, id) do
Expand Down
5 changes: 5 additions & 0 deletions lib/ourocode/runtime/interview_events.ex
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ defmodule Ourocode.Runtime.InterviewEvents do
|> Map.put(:seed_ready, true)
|> Map.put(:complete, reason)
|> Map.put(:waiting, false)
|> Map.put(:status, "interview complete: #{reason}")
|> Map.put(:question, "")
|> Map.delete(:question_options)
|> Map.delete(:answered)
|> Map.delete(:waiting_started_monotonic_ms)

%{state | interview: interview, interview_session: nil, interview_waiter: nil}
end
Expand Down
5 changes: 3 additions & 2 deletions lib/ourocode/runtime/interview_progress.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ defmodule Ourocode.Runtime.InterviewProgress do
prev
|> Map.merge(%{
parent_call_id: parent_call_id,
question: Map.get(prev, :question, ""),
question: "",
waiting_started_monotonic_ms: monotonic_ms(),
waiting: true,
status: "starting interview session",
dialogue: dialogue
})
|> Map.delete(:answered)
|> Map.delete(:question_options)

session = %{
parent_call_id: parent_call_id,
Expand All @@ -38,7 +39,7 @@ defmodule Ourocode.Runtime.InterviewProgress do
}

state
|> Map.merge(%{interview: iv, interview_session: session, paused: false})
|> Map.merge(%{interview: iv, interview_session: session, paused: false, wonder: nil})
end)
end

Expand Down
6 changes: 3 additions & 3 deletions lib/ourocode/runtime/interview_router/directive.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ defmodule Ourocode.Runtime.InterviewRouter.Directive do
| {:tool, atom(), String.t()}
| :unparseable

@option_re ~r/\A[-*]\s*(.+?)\s*[||]\s*(.+)\z/
@option_re ~r/\A[-*]\s*(.+?)\s*[||]\s*(.+)\z/u
@directive_re ~r/\A(?:TOOL\s+(?:READ|GLOB|GREP)\b|ANSWER\b|ASK_USER\b)/

@spec parse(String.t()) :: t()
Expand Down Expand Up @@ -81,7 +81,7 @@ defmodule Ourocode.Runtime.InterviewRouter.Directive do
end

defp expand_inline_ask_user_option_line(body) do
case Regex.run(~r/\s+-\s+[^|\n]+?\s*[||]/u, body, return: :index) do
case Regex.run(~r/\s+-\s+[^|\n]+?\s*[||]/u, body, return: :index) do
[{start, _length}] ->
{question, rest} = String.split_at(body, start)
option_lines = inline_option_lines(rest)
Expand All @@ -100,7 +100,7 @@ defmodule Ourocode.Runtime.InterviewRouter.Directive do
end

defp inline_option_lines(rest) do
~r/(?:^\s*-\s*|\s+-\s*)([^|\n]+?)\s*[||]\s*(.*?)(?=\s+-\s*[^|\n]+?\s*[||]|$)/u
~r/(?:^\s*-\s*|\s+-\s*)([^|\n]+?)\s*[||]\s*(.*?)(?=\s+-\s*[^|\n]+?\s*[||]|$)/u
|> Regex.scan(rest)
|> Enum.map(fn [_, label, desc] ->
"- #{String.trim(label)} | #{String.trim(desc)}"
Expand Down
83 changes: 66 additions & 17 deletions lib/ourocode/runtime/interview_router/tool_sandbox.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ defmodule Ourocode.Runtime.InterviewRouter.ToolSandbox do
:ok ->
hits =
root
|> Path.join(pat)
|> wildcard_path(pat)
|> Path.wildcard()
|> Enum.map(&Path.relative_to(&1, root))
|> Enum.map(&relative_path(&1, root))
|> Enum.take(@max_glob_hits)

body = if hits == [], do: "(no matches)", else: Enum.join(hits, "\n")
Expand Down Expand Up @@ -56,28 +56,65 @@ defmodule Ourocode.Runtime.InterviewRouter.ToolSandbox do
def run(_tool, arg, _root), do: {"UNKNOWN #{inspect(arg)}", "rejected: unknown tool"}

defp bounded_grep(pattern, glob, root) do
args =
["-rnI", "--"]
|> then(fn base -> if glob, do: ["--include=" <> glob | base], else: base end)
|> Kernel.++([pattern, "."])

task =
Task.async(fn ->
System.cmd("grep", args, cd: root, stderr_to_stdout: true)
end)
task = Task.async(fn -> elixir_grep(pattern, glob, root) end)

case Task.yield(task, @grep_timeout_ms) || Task.shutdown(task, :brutal_kill) do
{:ok, {out, status}} when status in [0, 1] ->
{:ok, out} ->
if String.trim(out) == "", do: "(no matches)", else: cap_bytes(out, @max_grep_bytes)

{:ok, {out, _status}} ->
"error: " <> cap_bytes(out, 256)

_timeout_or_crash ->
"error: grep timed out"
end
rescue
_exception -> "error: grep unavailable"
end

defp elixir_grep(pattern, glob, root) do
matcher = grep_matcher(pattern)

root
|> grep_files(glob)
|> Enum.flat_map(&grep_file(&1, root, matcher))
|> Enum.join("\n")
end

defp grep_matcher(pattern) do
case Regex.compile(pattern) do
{:ok, regex} -> &Regex.match?(regex, &1)
{:error, _reason} -> &String.contains?(&1, pattern)
end
end

defp grep_files(root, nil) do
root
|> wildcard_path("**/*")
|> Path.wildcard()
|> Enum.filter(&File.regular?/1)
end

defp grep_files(root, glob) do
if String.contains?(glob, "/") do
root
|> wildcard_path(glob)
|> Path.wildcard()
else
root
|> wildcard_path("**/" <> glob)
|> Path.wildcard()
end
|> Enum.filter(&File.regular?/1)
end

defp grep_file(path, root, matcher) do
case File.read(path) do
{:ok, content} ->
content
|> String.split("\n")
|> Enum.with_index(1)
|> Enum.filter(fn {line, _index} -> matcher.(line) end)
|> Enum.map(fn {line, index} -> "#{relative_path(path, root)}:#{index}:#{line}" end)

{:error, _reason} ->
[]
end
end

defp split_grep_arg(arg) do
Expand Down Expand Up @@ -141,6 +178,18 @@ defmodule Ourocode.Runtime.InterviewRouter.ToolSandbox do

defp safe_relative?(_path), do: {:error, "invalid path"}

defp wildcard_path(root, pattern) do
root
|> Path.join(pattern)
|> String.replace("\\", "/")
end

defp relative_path(path, root) do
path
|> Path.relative_to(root)
|> String.replace("\\", "/")
end

defp cap_bytes(bin, limit) when byte_size(bin) <= limit, do: bin

defp cap_bytes(bin, limit) do
Expand Down
Loading