diff --git a/apps/arbiter/lib/arbiter/mcp/tools/worker.ex b/apps/arbiter/lib/arbiter/mcp/tools/worker.ex index cf80453b..ecb76889 100644 --- a/apps/arbiter/lib/arbiter/mcp/tools/worker.ex +++ b/apps/arbiter/lib/arbiter/mcp/tools/worker.ex @@ -494,6 +494,8 @@ defmodule Arbiter.MCP.Tools.Worker do model: run.model, provider: run.provider, provider_fallback: run.provider_fallback, + session_id: run.session_id, + resumed_from_run_id: run.resumed_from_run_id, started_at: Tools.iso(run.started_at), completed_at: Tools.iso(run.completed_at), exit_code: run.exit_code, diff --git a/apps/arbiter/test/arbiter/mcp/tools_test.exs b/apps/arbiter/test/arbiter/mcp/tools_test.exs index 65faa387..252929c7 100644 --- a/apps/arbiter/test/arbiter/mcp/tools_test.exs +++ b/apps/arbiter/test/arbiter/mcp/tools_test.exs @@ -3354,6 +3354,50 @@ defmodule Arbiter.MCP.ToolsTest do assert entry.difficulty_at_dispatch == 3 end + # bd-b7e33c post-merge finding (2026-09-22): only `provider` was surfaced + # here; `session_id` and `resumed_from_run_id` were silently dropped, the + # same gap the REST `/api/workers/history` endpoint had — which is what + # made the 04:26Z production verification misread a captured agy + # conversation id as NULL. Surface both so resume continuity is directly + # observable through this tool too. + test "surfaces session_id and resumed_from_run_id", ctx do + {:ok, task} = Ash.create(Issue, %{title: "session fields target", workspace_id: ctx.ws.id}) + + {:ok, prior} = + Ash.create(Arbiter.Workers.Run, %{ + task_id: task.id, + repo: "arbiter", + workspace_id: ctx.ws.id, + status: :completed, + provider: "gemini", + session_id: "25df47b0-054e-434e-84c1-6876fd9f77de", + started_at: DateTime.add(DateTime.utc_now(), -600, :second) + }) + + {:ok, resumed} = + Ash.create(Arbiter.Workers.Run, %{ + task_id: task.id, + repo: "arbiter", + workspace_id: ctx.ws.id, + status: :completed, + provider: "gemini", + session_id: "89a2b784-6bd5-46e6-a971-2178ca58cdcd", + resumed_from_run_id: prior.id, + started_at: DateTime.utc_now() + }) + + assert {:ok, %{runs: [resumed_entry, prior_entry]}} = + Tools.worker_runs(ctx.coordinator, %{"task_id" => task.id}) + + assert prior_entry.id == prior.id + assert prior_entry.session_id == "25df47b0-054e-434e-84c1-6876fd9f77de" + assert prior_entry.resumed_from_run_id == nil + + assert resumed_entry.id == resumed.id + assert resumed_entry.session_id == "89a2b784-6bd5-46e6-a971-2178ca58cdcd" + assert resumed_entry.resumed_from_run_id == prior.id + end + test "honors a bounded limit", ctx do {:ok, task} = Ash.create(Issue, %{title: "many runs", workspace_id: ctx.ws.id}) diff --git a/apps/arbiter_web/lib/arbiter_web/controllers/api/run_json.ex b/apps/arbiter_web/lib/arbiter_web/controllers/api/run_json.ex index 30161bbc..daef53cf 100644 --- a/apps/arbiter_web/lib/arbiter_web/controllers/api/run_json.ex +++ b/apps/arbiter_web/lib/arbiter_web/controllers/api/run_json.ex @@ -34,7 +34,10 @@ defmodule ArbiterWeb.Api.RunJSON do routing_policy: r.routing_policy, model_tier: r.model_tier, thinking: r.thinking, - difficulty_at_dispatch: r.difficulty_at_dispatch + difficulty_at_dispatch: r.difficulty_at_dispatch, + provider: r.provider, + session_id: r.session_id, + resumed_from_run_id: r.resumed_from_run_id } end diff --git a/apps/arbiter_web/test/arbiter_web/controllers/api/run_controller_test.exs b/apps/arbiter_web/test/arbiter_web/controllers/api/run_controller_test.exs index 32cbb573..571a1036 100644 --- a/apps/arbiter_web/test/arbiter_web/controllers/api/run_controller_test.exs +++ b/apps/arbiter_web/test/arbiter_web/controllers/api/run_controller_test.exs @@ -137,6 +137,45 @@ defmodule ArbiterWeb.Api.RunControllerTest do assert entry["thinking"] == "medium" assert entry["difficulty_at_dispatch"] == 2 end + + # bd-b7e33c post-merge finding (2026-09-22): the 04:26Z production + # verification misread an agy run as having `session_id: NULL` / + # `provider: null` because `arb worker runs --json` (this endpoint) never + # surfaced those columns at all — they were silently dropped from the + # summary, not actually null in `worker_runs`. Surface them so a resume's + # conversation continuity (or lack of it) is directly observable from the + # CLI/API without reaching into the DB. + test "lists provider/session_id/resumed_from_run_id", %{conn: conn} do + now = DateTime.utc_now() + + prior = + insert_run!(%{ + task_id: "bd-session-fields", + provider: "gemini", + session_id: "25df47b0-054e-434e-84c1-6876fd9f77de", + started_at: DateTime.add(now, -600, :second) + }) + + _resumed = + insert_run!(%{ + task_id: "bd-session-fields", + provider: "gemini", + session_id: "89a2b784-6bd5-46e6-a971-2178ca58cdcd", + resumed_from_run_id: prior.id, + started_at: now + }) + + conn = get(conn, ~p"/api/workers/history", %{task_id: "bd-session-fields"}) + [resumed_entry, prior_entry] = json_response(conn, 200)["data"] + + assert prior_entry["provider"] == "gemini" + assert prior_entry["session_id"] == "25df47b0-054e-434e-84c1-6876fd9f77de" + assert prior_entry["resumed_from_run_id"] == nil + + assert resumed_entry["provider"] == "gemini" + assert resumed_entry["session_id"] == "89a2b784-6bd5-46e6-a971-2178ca58cdcd" + assert resumed_entry["resumed_from_run_id"] == prior.id + end end describe "GET /api/workers/history/:id" do