@@ -1870,6 +1870,106 @@ async def test_tool_batch_runner_observation_handoff_pushes_mutation_step( |
| 1870 | 1870 | ) |
| 1871 | 1871 | |
| 1872 | 1872 | |
| 1873 | +@pytest.mark.asyncio |
| 1874 | +async def test_tool_batch_runner_missing_artifact_nudge_prefers_pending_index_after_mkdir( |
| 1875 | + temp_dir: Path, |
| 1876 | +) -> None: |
| 1877 | + async def assess_confidence( |
| 1878 | + tool_name: str, |
| 1879 | + tool_args: dict, |
| 1880 | + context: str, |
| 1881 | + ) -> ConfidenceAssessment: |
| 1882 | + raise AssertionError("Confidence scoring should be disabled in this scenario") |
| 1883 | + |
| 1884 | + async def verify_action( |
| 1885 | + tool_name: str, |
| 1886 | + tool_args: dict, |
| 1887 | + result: str, |
| 1888 | + expected: str = "", |
| 1889 | + ) -> ActionVerification: |
| 1890 | + raise AssertionError("Verification should not run for this scenario") |
| 1891 | + |
| 1892 | + nginx_root = temp_dir / "Loader" / "guides" / "nginx" |
| 1893 | + chapters = nginx_root / "chapters" |
| 1894 | + implementation_plan = temp_dir / "implementation.md" |
| 1895 | + implementation_plan.write_text( |
| 1896 | + "\n".join( |
| 1897 | + [ |
| 1898 | + "# Implementation Plan", |
| 1899 | + "", |
| 1900 | + "## File Changes", |
| 1901 | + f"- `{chapters}/`", |
| 1902 | + f"- `{nginx_root / 'index.html'}`", |
| 1903 | + "", |
| 1904 | + ] |
| 1905 | + ) |
| 1906 | + ) |
| 1907 | + |
| 1908 | + context = build_context( |
| 1909 | + temp_dir=temp_dir, |
| 1910 | + messages=[], |
| 1911 | + safeguards=FakeSafeguards(), |
| 1912 | + assess_confidence=assess_confidence, |
| 1913 | + verify_action=verify_action, |
| 1914 | + auto_recover=False, |
| 1915 | + ) |
| 1916 | + queued_messages: list[str] = [] |
| 1917 | + context.queue_steering_message_callback = queued_messages.append |
| 1918 | + runner = ToolBatchRunner(context, DefinitionOfDoneStore(temp_dir)) |
| 1919 | + dod = create_definition_of_done("Create a multi-file nginx guide.") |
| 1920 | + dod.implementation_plan = str(implementation_plan) |
| 1921 | + sync_todos_to_definition_of_done( |
| 1922 | + dod, |
| 1923 | + [ |
| 1924 | + { |
| 1925 | + "content": "Create the nginx directory structure", |
| 1926 | + "active_form": "Creating the nginx directory structure", |
| 1927 | + "status": "pending", |
| 1928 | + }, |
| 1929 | + { |
| 1930 | + "content": "Develop the main index.html file with proper structure", |
| 1931 | + "active_form": "Developing the main index.html file with proper structure", |
| 1932 | + "status": "pending", |
| 1933 | + }, |
| 1934 | + ], |
| 1935 | + ) |
| 1936 | + |
| 1937 | + tool_call = ToolCall( |
| 1938 | + id="mkdir-nginx", |
| 1939 | + name="bash", |
| 1940 | + arguments={"command": f"mkdir -p {chapters}"}, |
| 1941 | + ) |
| 1942 | + executor = FakeExecutor( |
| 1943 | + [ |
| 1944 | + tool_outcome( |
| 1945 | + tool_call=tool_call, |
| 1946 | + output="", |
| 1947 | + is_error=False, |
| 1948 | + ) |
| 1949 | + ] |
| 1950 | + ) |
| 1951 | + |
| 1952 | + summary = TurnSummary(final_response="") |
| 1953 | + await runner.execute_batch( |
| 1954 | + tool_calls=[tool_call], |
| 1955 | + tool_source="assistant", |
| 1956 | + pending_tool_calls_seen=set(), |
| 1957 | + emit=_noop_emit, |
| 1958 | + summary=summary, |
| 1959 | + dod=dod, |
| 1960 | + executor=executor, # type: ignore[arg-type] |
| 1961 | + on_confirmation=None, |
| 1962 | + on_user_question=None, |
| 1963 | + emit_confirmation=None, |
| 1964 | + consecutive_errors=0, |
| 1965 | + ) |
| 1966 | + |
| 1967 | + assert queued_messages |
| 1968 | + message = queued_messages[-1] |
| 1969 | + assert "Resume by creating `index.html` now." in message |
| 1970 | + assert "Resume by creating the next output file under `chapters/` now." not in message |
| 1971 | + |
| 1972 | + |
| 1873 | 1973 | @pytest.mark.asyncio |
| 1874 | 1974 | async def test_duplicate_observation_nudge_prioritizes_missing_artifact_over_review( |
| 1875 | 1975 | temp_dir: Path, |