Ctrl K
ComunidadGuías…

tests/agents/test_bash_mode_line.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""The main prompt's ``!`` line (Claude Code gives it only in its
interactive terminal): a task whose front end runs ``!`` commands
(``bash_mode`` in its config, the terminal UI's tasks) gets it; every other
task, and a sub-agent of a terminal UI task, does not; a fork keeps its
parent's prompt. The ``# Session-specific guidance`` heading goes only when
the skill line has gone too."""

from __future__ import annotations

from pathlib import Path
from typing import Any

import pytest

from noeta.agents import default_agents
from noeta.backends.execenvs.local import LocalExecEnv
from noeta.backends.stores.sqlite import SqliteStore
from noeta.core.client import Client
from noeta.core.runner import Runtime
from noeta.core.types import Message, ModelRequest, ModelResponse, Text, ToolUse, Usage
from noeta.toolkit.interactive import BASH_MODE_KEY

BANG = "suggest they type `! <command>` in the prompt"
HEADING = "# Session-specific guidance"
SKILL = "`/<skill-name>`"


def _reply(text: str = "", *calls: tuple[str, str, dict[str, Any]]) -> ModelResponse:
    content: list[Any] = [Text(text=text)] if text else []
    content += [ToolUse(id=i, name=n, input=a) for i, n, a in calls]
    return ModelResponse(message=Message(role="assistant", content=content),
                         stop_reason="tool_use" if calls else "end_turn",
                         usage=Usage(uncached=10, output=5))


class Script:
    def __init__(self, replies: list[ModelResponse]) -> None:
        self.replies = replies
        self.requests: list[ModelRequest] = []

    async def complete(self, request: ModelRequest, on_delta: Any = None) -> ModelResponse:
        self.requests.append(request)
        return self.replies.pop(0) if self.replies else _reply("ok")


async def _no_sleep(_: float) -> None:
    return None


async def _systems(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, config: dict[str, Any],
                   replies: list[ModelResponse], *, skill: bool = False
                   ) -> tuple[list[str], dict[str, dict[str, Any]]]:
    """Each request's system text, and each task's recorded config."""
    home, work = tmp_path / "home", tmp_path / "work"
    home.mkdir()
    work.mkdir()
    monkeypatch.setenv("HOME", str(home))
    if skill:
        d = work / ".agents" / "skills" / "deploy"
        d.mkdir(parents=True)
        (d / "SKILL.md").write_text("---\nname: deploy\ndescription: Ship it.\n---\nShip.\n")
    script = Script(replies)
    store = SqliteStore(str(tmp_path / "t.db"))
    rt = Runtime(store=store, agents={a.name: a for a in default_agents("claude-opus-5-5")},
                 provider_for=lambda _m: script,  # type: ignore[arg-type,return-value]
                 env_for=lambda s: LocalExecEnv(str(s.config.get("cwd") or work)),
                 sleep=_no_sleep)
    client = Client(store)
    await client.create("main", "Hi.", config={"cwd": str(work),
                                               "permission_mode": "bypassPermissions",
                                               **config})
    await rt.run_until_idle()
    configs = {r.task_id: (await client.events(r.task_id))[0].event.config  # type: ignore[attr-defined]
               for r in await store.tasks(limit=100)}
    await store.close()
    return ["".join(p.text for p in r.system) for r in script.requests], configs


@pytest.mark.parametrize("skill", [False, True])
async def test_a_terminal_ui_task_gets_the_line(tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
                                                skill: bool) -> None:
    [system], _ = await _systems(tmp_path, monkeypatch, {BASH_MODE_KEY: True}, [_reply("ok")],
                                 skill=skill)
    assert f"{HEADING}\n - If you need the user to run a shell command" in system
    assert BANG in system
    assert (SKILL in system) is skill


@pytest.mark.parametrize("skill", [False, True])
async def test_other_tasks_do_not(tmp_path: Path, monkeypatch: pytest.MonkeyPatch,
                                  skill: bool) -> None:
    # What -p / stream-json, ACP and HTTP create: no key (or ``interactive``
    # alone, which is about who answers, not about ``!`` commands).
    [system], _ = await _systems(tmp_path, monkeypatch, {"interactive": False}, [_reply("ok")],
                                 skill=skill)
    assert "`! <command>`" not in system
    assert (HEADING in system) is skill
    if skill:
        assert f"{HEADING}\n - When the user types {SKILL}" in system


async def test_a_sub_agent_does_not_inherit_it_and_a_fork_keeps_it(
        tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    replies = [
        _reply("", ("a1", "Agent", {"description": "look", "prompt": "Look.",
                                    "subagent_type": "general-purpose"}),
               ("a2", "Agent", {"description": "fork", "prompt": "Go on.",
                                "subagent_type": "fork"})),
    ]
    systems, configs = await _systems(tmp_path, monkeypatch, {BASH_MODE_KEY: True}, replies)
    by_type = {c.get("subagent_type"): c for c in configs.values()}
    assert BASH_MODE_KEY not in by_type["general-purpose"]
    assert by_type["fork"][BASH_MODE_KEY] is True
    main = systems[0]
    assert BANG in main
    # The parent's requests and the fork's.
    assert sum(s == main for s in systems) >= 3
    assert any(BANG not in s for s in systems)  # general-purpose's own prompt