Gemini question tool and awaiting linger

Task 7484 · Done · Coder · created · updated

Child C4 of epic 7425 (ACP runtime: every provider lane on ACP). Planned by Task 7428. ## Requirement (verbatim) Epic goal: "Goal: every provider and auth lane can run on jaah-acp-host. On it, ... a question waits for the operator ... Each fix removes its catalogue gap or legacy-only row in `apps/jaahd/agent_providers.py`, so Settings › Providers tells the truth. While a legacy path still exists, it keeps working." Outcome 3 (A7, A8): "Questions work without elicitation. Engines with no native question surface (Gemini) get the question tool listed, gated by the launch, and the host never idles out while the task is `awaiting_response`. This deletes `acp-no-structured-interview`." Current behaviour (PRD §3): A7 — `input_request` is `listed=False` (`agent_vocabulary.py:366`), so the real MCP server (`mcp/jaah/server.py:159-185`) neither lists nor serves it. Gemini ACP has `requests.user_input=None` while `can_interview=True`, a mismatch. The Gemini replay fixture's stand-in server lists the tool; the real one does not. A8 — the host skips the nudge and the idle end while `awaiting_response`, but reads task state only after a turn (`host.py:628-641`, called only at `:688`). The idle end (`:703-704`) checks only `_open_inputs`. A task that becomes `awaiting_response` **during** the 30-minute linger (`DEFAULT_IDLE_LINGER_S`, `:65`) is therefore ended `idle_autoclose`. No test covers that transition. Decided (PRD): the question-tool gate is a launch-declared flag, set by Gemini's ACP launch only; Pi keeps its shell verb. The idle rule covers the mid-linger transition. ## Approach (architecture §4, verbatim) ### R4: the question tool, listed by launch - **Pinned seam:** the control MCP server's env, `JAAH_QUESTION_TOOL=1`. It is the per-launch slot `control_mcp_server(command, args, env)` (`agents/base.py:643-650`), which every caller passes as `{}` today. This mirrors `JAAH_PROJECT_MEMORY` (`mcp/jaah/server.py:149-156`). - `agent_vocabulary.ToolSpec` gains `launch_listed: bool = False` (`input_request` keeps `listed=False` and sets `launch_listed=True`). `tool_verbs(memory_armed, question_armed=False)` includes `launch_listed` tools only when armed. `BY_TOOL`/`call_tool` refuse such a tool unarmed with the same `unknown_tool` answer the memory gate uses. - **Who sets it:** the node, from the attempt's dispatched runtime, on the ACP launch and on any legacy fallback launch alike ("Question-tool arming" rule below). When the provider's ACP capability says `requests.user_input == "mcp_tool"`, the adapter passes `{"JAAH_QUESTION_TOOL": "1"}` to `control_mcp_server`. Put this in a **base helper** so it is not a Gemini branch (C5 reuses this helper). Today only GEMINI's ACP row gets `requests.user_input="mcp_tool"`. Pi builds its own control entry (`agents/pi.py:80-84`) and keeps `jaah-call input.request`. - **Charter respelling (R4.3), pinned:** the respelling site is `apps/api/app/dispatch_compose.py::apply_provider_values` (:2824). It replaces `_CHARTER_ASK_TOOL` with `provider.ask_tool` (:2847) after swapping the MCP prefix (:2843-2844). Add a keyword `runtime: str = "legacy"`. When `runtime == "acp"` and the provider's ACP `requests.user_input == "mcp_tool"`, the ask tool is `provider.mcp_tool_prefix + "input_request"`. GEMINI's `mcp_tool_prefix` is `"mcp_control_"`, so the tool is exactly **`mcp_control_input_request`** (agrees with the control server's ACP name `"control"`, `agents/base.py:649`). The dismissed-picker sub (:2850) is skipped for that tool. Callers pass the runtime the dispatch already resolved once per attempt (`lease_drain.py:1236` `acp_runtime.dispatch_runtime(...)`), threaded to every `apply_provider_values` call for the attempt — never resolved a second time (could race a switch flip). Chose to derive the name over a new `acp_ask_tool` string field: one fact drives both listing and respelling. ### R5: no idle end while `awaiting_response` - **Chose:** a bounded poll during the linger. The idle wait wakes at `min(deadline, now + TASK_POLL_S)`, with `TASK_POLL_S = 60` as a module constant, and calls `_task_open()`. If the task is `awaiting_response`, it clears the deadline and waits without one (the `_awaiting_answer` branch, :692-695), still re-polling every `TASK_POLL_S`. If the state returns to `active` without an answer intent, it re-arms a fresh linger. If the task is closed, it returns `END_TASK_CLOSED`. The nudge is also suppressed while `_awaiting_answer`. The answer path is unchanged: an `answer` intent becomes `_pending` (:737-741). - **Catalogue:** GEMINI `requests.user_input="mcp_tool"`. Delete gap `acp-no-structured-interview` and its test row `(44, (GEMINI,), "acp-no-structured-interview")`; fix the comment at `agent_providers.py:~491`. ### Question-tool arming (architecture §9 row, verbatim) **One fact drives both:** the attempt's dispatched `runtime` (lease intent, `lease_drain.py:1236`). The API respells the charter from it. The node arms `JAAH_QUESTION_TOOL=1` from the **same intent field**, not from the launch path actually taken: whenever `intent.runtime == "acp"` and the provider's ACP `requests.user_input == "mcp_tool"`, the control MCP server's env carries the flag in the ACP launch **and** in a legacy launch the node fell back to. Gemini's legacy control-server entry is written by its legacy settings rendering, which this child extends with the env. R4.2 therefore reads "a session whose dispatch resolved legacy" lists no `input_request`. (The MicroVM API-key downgrade test of this rule is owned by C5, which depends on this child.) Seams (architecture §9): `acp/host.py` shared with C5 (pre-`session/new` authenticate) — disjoint functions. `GeminiAdapter.acp_launch`: this child edits only the `control_mcp_server(..., env)` argument (:572); C7 owns the bind lines, C5 owns :540-541. GEMINI catalogue row: flip only `requests.user_input`, delete only `acp-no-structured-interview`; keep both sides on rebase. Credentials & reach: - Replay, unit, API and golden tests; `check_provider_branches.py`; `./dc check full` — Nothing beyond the worktree — verified - Dev control plane — `app.dev.jaah.ai` — verified: HTTP 200 and a UI sign-in from h5-3 (every dev oracle uses `app.dev.jaah.ai`, not `dev.jaah.ai`) - Dev console login (operator) — `jaah/debug/main/operator-login` — verified - Gemini OAuth task (R5.4) — dev account `gem1` — **deferred until a healthy probe**: open the account's page, refresh its health and proceed only when healthy. If it stays unhealthy, ask once with the `Credential` header and settle the dev flow as unverified, named in the PR. Out of scope: Do NOT touch "Pi's question path (`jaah-call input.request` from the shell, `ask_tool`). Do not change it. R4's listing gate does not apply to Pi." Do NOT list `input_request` for any session other than the armed one. Do NOT add a CP-pushed state intent (deferred follow-up). Do NOT touch the bind lines of `GeminiAdapter.acp_launch` (C7) or the API-key refusal (C5). Do NOT touch the chat ↔ terminal hand-off (Task 7370: `Console.toggle`, `_CHAT_AND_TERMINAL`, `app/routes/console_runtime.py`, `ChatView.tsx`), registry and SDK v2 agents (Task 7429), billing rules, Settings › Providers layout. "No new provider-name branches (`tools/provider_branches.toml` allowlist growth)." Done means: - R4.1 The control MCP `input_request` tool is listed and callable in a session only when its launch declares the session's engine has no native question surface. Gemini's ACP launch declares it. — verify: a test of the server roster: with the launch flag set, `tools/list` includes `input_request` and a call reaches the `input.request` verb. Without it, the tool is neither listed nor callable (as today). - R4.2 Every other session's roster is unchanged: Claude, DeepSeek, Codex, legacy Gemini, and Pi (which keeps asking via `jaah-call input.request` from its shell). — verify: roster tests for those sessions show no `input_request`. - R4.3 An ACP Gemini runner's charter names a question tool that exists in its roster. The charter's `AskUserQuestion` is respelled to the control tool under Gemini's MCP prefix (`mcp_control_input_request`) on ACP. Legacy Gemini keeps `ask_user`. — verify: a compose test for a Gemini ACP dispatch and a Gemini legacy dispatch, each naming its own tool; **merge-blocking:** a test composes a Gemini ACP charter and asserts its question-tool name is in the `tools/list` roster of a control server launched with `JAAH_QUESTION_TOOL=1`, after Gemini's prefix is applied. - R4.4 The Gemini row's `requests.user_input` becomes `"mcp_tool"`. Gap `acp-no-structured-interview` is deleted. — verify: `grep -rn acp-no-structured-interview apps tools docs` returns nothing, and the catalogue test passes asserting the new value. - R5.1 The host never ends an attempt `idle_autoclose`, and never nudges, while the control plane reports the task `awaiting_response`. That holds whether the state was set before the turn ended or **during** the idle linger. — verify: a replay test with a fake clock where the CP stub turns the task `awaiting_response` after `end_turn`, during the linger. Advance the clock past 30 minutes (and to 10 hours). Observe no nudge and no `END_IDLE`. - R5.2 An answer delivered after the wait reaches the session as its next prompt, and the run continues. — verify: the same replay delivers an `answer` intent at T+45 minutes. Observe the prompt is sent and the turn completes. - R5.3 (states) `active` with no open input still idles out after the linger as today. A task closed by the CP still ends `END_TASK_CLOSED`. An open elicitation request still holds the session. — verify: existing `test_replay.py` and `test_acp_host.py` cases pass, plus one new case for `active` idling out. - R5.4 (availability) — verify on dev (`app.dev.jaah.ai`): create a Gemini task with autonomous OFF (guided) on an OAuth account with the Gemini ACP switch ON. Wait for its question card, answer it 31 or more minutes later, and observe the run continue and settle (`done`). Its session row in Audit › Sessions shows runtime ACP. (deferred — `gem1` health; see Credentials & reach) - D.1 `apps/jaahd/README.md` §"Gemini CLI on the ACP runtime" and §"The ACP host runtime" describe the present (question tool, linger poll). — verify: grep for `acp-no-structured-interview` returns nothing; `python3 tools/check_context.py` passes. - T.3 No new gap is claimed fixed unless it is. — verify: review in the PR, plus the catalogue test. - T.5 `python3 tools/check_provider_branches.py` passes, and no provider-name branch is added to its allowlist. — verify: run it and observe "OK"; `git diff tools/provider_branches.toml` adds no `[[allow]]`. Required checks: ./dc check full

Outcome

Gemini ACP lists input_request via the launch-armed JAAH_QUESTION_TOOL; its charter names mcp_control_input_request; the host never idles out while awaiting_response; gap deleted. PR #6067 merged (a3dfaca05), deploy green; full gate not run pre-merge (lane off). R5.4 dev flow deferred.

Pull request

Artifacts

Activity

  • Draft
  • Ready
  • Runner started
  • Runner ended (done)