diff --git a/CHANGELOG.md b/CHANGELOG.md index 08d364c2..5837e4d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,7 +52,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed repeated generation of GitHub CLI installation tokens by caching the token in the agent session state for subsequent `gh` tool calls. - Fixed skill tool to properly return a `Command` object for state updates instead of returning messages directly. - Fixed `daiv-auto` label to work as a trigger label that both launches the agent and enables auto-approval mode, eliminating the need to add two separate labels. -- Fixed agent post-run failures when git push returns authentication/permission errors by handling push permission failures gracefully in git middleware and adding regression tests. ### Removed diff --git a/daiv/automation/agent/constants.py b/daiv/automation/agent/constants.py index 8a285016..2af34b2b 100644 --- a/daiv/automation/agent/constants.py +++ b/daiv/automation/agent/constants.py @@ -39,6 +39,10 @@ class ModelName(StrEnum): # z-ai models Z_AI_GLM_4_7 = "openrouter:z-ai/glm-4.7" + Z_AI_GLM_5 = "openrouter:z-ai/glm-5" + + # minimax models + MINIMAX_M2_5 = "openrouter:minimax/minimax-m2.5" # MoonshotAI models MOONSHOTAI_KIMI_K2_5 = "openrouter:moonshotai/kimi-k2.5" diff --git a/daiv/automation/agent/graph.py b/daiv/automation/agent/graph.py index 40515736..84b7f6c5 100644 --- a/daiv/automation/agent/graph.py +++ b/daiv/automation/agent/graph.py @@ -6,12 +6,14 @@ from django.utils import timezone from deepagents.backends.filesystem import FilesystemBackend -from deepagents.graph import BASE_AGENT_PROMPT from deepagents.middleware.memory import MemoryMiddleware from deepagents.middleware.patch_tool_calls import PatchToolCallsMiddleware from deepagents.middleware.subagents import SubAgentMiddleware +from deepagents.middleware.summarization import _compute_summarization_defaults from langchain.agents import create_agent from langchain.agents.middleware import ( + HumanInTheLoopMiddleware, + InterruptOnConfig, ModelFallbackMiddleware, ModelRequest, SummarizationMiddleware, @@ -54,10 +56,6 @@ from langgraph.store.base import BaseStore -DEFAULT_SUMMARIZATION_TRIGGER = ("tokens", 170000) -DEFAULT_SUMMARIZATION_KEEP = ("messages", 6) - - OUTPUT_INVARIANTS_SYSTEM_PROMPT = """\ Applies to ALL user-visible text: @@ -93,15 +91,7 @@ async def dynamic_daiv_system_prompt(request: ModelRequest) -> str: bash_tool_enabled=BASH_TOOL_NAME in tool_names, working_directory=f"/{agent_path.name}/", ) - return ( - BASE_AGENT_PROMPT - + "\n\n" - + OUTPUT_INVARIANTS_SYSTEM_PROMPT - + "\n\n" - + request.system_prompt - + "\n\n" - + system_prompt.content.strip() - ) + return OUTPUT_INVARIANTS_SYSTEM_PROMPT + "\n\n" + request.system_prompt + "\n\n" + system_prompt.content.strip() def dynamic_write_todos_system_prompt(bash_tool_enabled: bool) -> str: @@ -121,6 +111,7 @@ async def create_daiv_agent( store: BaseStore | None = None, debug: bool = False, offline: bool = False, + interrupt_on: dict[str, bool | InterruptOnConfig] | None = None, ): """ Create the DAIV agent. @@ -138,34 +129,24 @@ async def create_daiv_agent( Returns: The DAIV agent. """ - agent_path = Path(ctx.repo.working_dir) model = BaseAgent.get_model(model=model_names[0], thinking_level=thinking_level) fallback_models = [ BaseAgent.get_model(model=model_name, thinking_level=thinking_level) for model_name in model_names[1:] ] - summarization_trigger = DEFAULT_SUMMARIZATION_TRIGGER - summarization_keep = DEFAULT_SUMMARIZATION_KEEP - - if isinstance(model.profile, dict) and isinstance(model.profile.get("max_input_tokens"), int): - summarization_trigger = ("fraction", 0.85) - summarization_keep = ("fraction", 0.10) + summarization_defaults = _compute_summarization_defaults(model) + agent_path = Path(ctx.repo.working_dir) backend = FilesystemBackend(root_dir=agent_path.parent, virtual_mode=True) - subagent_default_middlewares = [ - SummarizationMiddleware( - model=model, trigger=summarization_trigger, keep=summarization_keep, trim_tokens_to_summarize=None - ), - AnthropicPromptCachingMiddleware(), - ToolCallLoggingMiddleware(), - PatchToolCallsMiddleware(), + # Create subagents list to be shared between middlewares + subagents = [ + create_general_purpose_subagent(model, backend, ctx, offline=offline), + create_explore_subagent(backend, ctx), + create_changelog_subagent(model, backend, ctx, offline=offline), ] - if fallback_models: - subagent_default_middlewares.append(ModelFallbackMiddleware(fallback_models[0], *fallback_models[1:])) - agent_conditional_middlewares = [] if not offline: @@ -176,13 +157,8 @@ async def create_daiv_agent( agent_conditional_middlewares.append(SandboxMiddleware()) if fallback_models: agent_conditional_middlewares.append(ModelFallbackMiddleware(fallback_models[0], *fallback_models[1:])) - - # Create subagents list to be shared between middlewares - subagents = [ - create_general_purpose_subagent(backend, ctx, offline=offline), - create_explore_subagent(backend, ctx), - create_changelog_subagent(backend, ctx), - ] + if interrupt_on is not None: + agent_conditional_middlewares.append(HumanInTheLoopMiddleware(interrupt_on=interrupt_on)) agent_middleware = [ TodoListMiddleware( @@ -195,18 +171,18 @@ async def create_daiv_agent( SkillsMiddleware( backend=backend, sources=[f"/{agent_path.name}/{source}" for source in SKILLS_SOURCES], subagents=subagents ), - SubAgentMiddleware( - default_model=model, - default_middleware=subagent_default_middlewares, - general_purpose_agent=False, - subagents=subagents, - ), + SubAgentMiddleware(backend=backend, subagents=subagents), *agent_conditional_middlewares, FilesystemMiddleware(backend=backend), GitMiddleware(auto_commit_changes=auto_commit_changes), GitPlatformMiddleware(git_platform=ctx.git_platform), SummarizationMiddleware( - model=model, trigger=summarization_trigger, keep=summarization_keep, trim_tokens_to_summarize=None + model=model, + backend=backend, + trigger=summarization_defaults["trigger"], + keep=summarization_defaults["keep"], + trim_tokens_to_summarize=None, + truncate_args_settings=summarization_defaults["truncate_args_settings"], ), AnthropicPromptCachingMiddleware(), ToolCallLoggingMiddleware(), @@ -239,7 +215,10 @@ async def main(): ) async with set_runtime_ctx(repo_id="srtab/daiv", scope=Scope.GLOBAL, ref="main") as ctx: agent = await create_daiv_agent( - ctx=ctx, model_names=["openrouter:z-ai/glm-5"], store=InMemoryStore(), checkpointer=InMemorySaver() + ctx=ctx, + model_names=["openrouter:minimax/minimax-m2.5"], + store=InMemoryStore(), + checkpointer=InMemorySaver(), ) while True: user_input = await session.prompt_async() diff --git a/daiv/automation/agent/skills/skill-creator/SKILL.md b/daiv/automation/agent/skills/skill-creator/SKILL.md index 60530c26..deb7e10c 100644 --- a/daiv/automation/agent/skills/skill-creator/SKILL.md +++ b/daiv/automation/agent/skills/skill-creator/SKILL.md @@ -23,10 +23,10 @@ equipped with procedural knowledge and domain expertise. ### Skill Location for DAIV -In DAIV, skills are stored in `~/.daiv/skills/` directory. For example, with the default configuration, skills live at: +In DAIV, skills are stored in `~/.agents/skills/` directory. For example, with the default configuration, skills live at: ``` -~/.daiv/skills/ +~/.agents/skills/ ├── skill-name-1/ │ └── SKILL.md ├── skill-name-2/ @@ -282,7 +282,7 @@ scripts/init_skill.py --path For DAIV, use the DAIV's skills directory: ```bash -scripts/init_skill.py --path ~/.daiv/skills +scripts/init_skill.py --path ~/.agents/skills ``` The script: diff --git a/daiv/automation/agent/skills/skill-creator/scripts/init_skill.py b/daiv/automation/agent/skills/skill-creator/scripts/init_skill.py index 7881a93c..42107fbe 100644 --- a/daiv/automation/agent/skills/skill-creator/scripts/init_skill.py +++ b/daiv/automation/agent/skills/skill-creator/scripts/init_skill.py @@ -11,7 +11,7 @@ init_skill.py custom-skill --path /custom/location For DAIV: - init_skill.py my-skill --path ~/.daiv/skills + init_skill.py my-skill --path ~/.agents/skills """ # ruff: NOQA: T201,E501 @@ -283,7 +283,7 @@ def main(): print(" init_skill.py my-api-helper --path skills/private") print(" init_skill.py custom-skill --path /custom/location") print("\nFor DAIV:") - print(" init_skill.py my-skill --path ~/.daiv/skills") + print(" init_skill.py my-skill --path ~/.agents/skills") sys.exit(1) skill_name = sys.argv[1] diff --git a/daiv/automation/agent/skills/skill-creator/scripts/quick_validate.py b/daiv/automation/agent/skills/skill-creator/scripts/quick_validate.py index 03883b29..b881fb58 100644 --- a/daiv/automation/agent/skills/skill-creator/scripts/quick_validate.py +++ b/daiv/automation/agent/skills/skill-creator/scripts/quick_validate.py @@ -3,10 +3,10 @@ Quick validation script for skills - minimal version For DAIV, skills are located at: -~/.daiv/skills// +~/.agents/skills// Example: - python quick_validate.py ~/.daiv/skills/my-skill + python quick_validate.py ~/.agents/skills/my-skill """ # ruff: NOQA: T201 diff --git a/daiv/automation/agent/subagents.py b/daiv/automation/agent/subagents.py index 3b58a531..df87075b 100644 --- a/daiv/automation/agent/subagents.py +++ b/daiv/automation/agent/subagents.py @@ -1,17 +1,23 @@ from typing import TYPE_CHECKING from deepagents.graph import SubAgent +from deepagents.middleware import SummarizationMiddleware +from deepagents.middleware.patch_tool_calls import PatchToolCallsMiddleware +from deepagents.middleware.summarization import _compute_summarization_defaults from langchain.agents.middleware import TodoListMiddleware from automation.agent import BaseAgent from automation.agent.conf import settings from automation.agent.middlewares.file_system import FilesystemMiddleware from automation.agent.middlewares.git_platform import GitPlatformMiddleware +from automation.agent.middlewares.logging import ToolCallLoggingMiddleware +from automation.agent.middlewares.prompt_cache import AnthropicPromptCachingMiddleware from automation.agent.middlewares.sandbox import SandboxMiddleware from automation.agent.middlewares.web_search import WebSearchMiddleware if TYPE_CHECKING: from deepagents.backends import BackendProtocol + from langchain.chat_models import BaseChatModel from codebase.context import RuntimeCtx @@ -205,18 +211,33 @@ Do NOT specify WHAT to write—let the agent examine the diffs and infer user-facing changes. The agent will handle the entire changelog update workflow and return confirmation when complete.""" # noqa: E501 -def create_general_purpose_subagent(backend: BackendProtocol, runtime: RuntimeCtx, offline: bool = False) -> SubAgent: +def create_general_purpose_subagent( + model: BaseChatModel, backend: BackendProtocol, runtime: RuntimeCtx, offline: bool = False +) -> SubAgent: """ Create the general purpose subagent for the DAIV agent. """ from automation.agent.graph import dynamic_write_todos_system_prompt + summarization_defaults = _compute_summarization_defaults(model) + middleware = [ TodoListMiddleware( system_prompt=dynamic_write_todos_system_prompt(bash_tool_enabled=runtime.config.sandbox.enabled) ), FilesystemMiddleware(backend=backend), GitPlatformMiddleware(git_platform=runtime.git_platform), + SummarizationMiddleware( + model=model, + backend=backend, + trigger=summarization_defaults["trigger"], + keep=summarization_defaults["keep"], + trim_tokens_to_summarize=None, + truncate_args_settings=summarization_defaults["truncate_args_settings"], + ), + AnthropicPromptCachingMiddleware(), + ToolCallLoggingMiddleware(), + PatchToolCallsMiddleware(), ] if not offline: @@ -230,6 +251,8 @@ def create_general_purpose_subagent(backend: BackendProtocol, runtime: RuntimeCt description=GENERAL_PURPOSE_DESCRIPTION, system_prompt=GENERAL_PURPOSE_SYSTEM_PROMPT, middleware=middleware, + model=model, + tools=[], ) @@ -239,9 +262,23 @@ def create_explore_subagent(backend: BackendProtocol, runtime: RuntimeCtx) -> Su """ from automation.agent.graph import dynamic_write_todos_system_prompt + model = BaseAgent.get_model(model=settings.EXPLORE_MODEL_NAME) + summarization_defaults = _compute_summarization_defaults(model) + middleware = [ TodoListMiddleware(system_prompt=dynamic_write_todos_system_prompt(bash_tool_enabled=False)), FilesystemMiddleware(backend=backend, read_only=True), + SummarizationMiddleware( + model=model, + backend=backend, + trigger=summarization_defaults["trigger"], + keep=summarization_defaults["keep"], + trim_tokens_to_summarize=None, + truncate_args_settings=summarization_defaults["truncate_args_settings"], + ), + AnthropicPromptCachingMiddleware(), + ToolCallLoggingMiddleware(), + PatchToolCallsMiddleware(), ] return SubAgent( @@ -249,15 +286,34 @@ def create_explore_subagent(backend: BackendProtocol, runtime: RuntimeCtx) -> Su description=EXPLORE_SUBAGENT_DESCRIPTION, system_prompt=EXPLORE_SYSTEM_PROMPT, middleware=middleware, - model=BaseAgent.get_model(model=settings.EXPLORE_MODEL_NAME), + model=model, + tools=[], ) -def create_changelog_subagent(backend: BackendProtocol, runtime: RuntimeCtx, offline: bool = False) -> SubAgent: +def create_changelog_subagent( + model: BaseChatModel, backend: BackendProtocol, runtime: RuntimeCtx, offline: bool = False +) -> SubAgent: """ Create the changelog subagent. """ - middleware = [FilesystemMiddleware(backend=backend), GitPlatformMiddleware(git_platform=runtime.git_platform)] + summarization_defaults = _compute_summarization_defaults(model) + + middleware = [ + FilesystemMiddleware(backend=backend), + GitPlatformMiddleware(git_platform=runtime.git_platform), + SummarizationMiddleware( + model=model, + backend=backend, + trigger=summarization_defaults["trigger"], + keep=summarization_defaults["keep"], + trim_tokens_to_summarize=None, + truncate_args_settings=summarization_defaults["truncate_args_settings"], + ), + AnthropicPromptCachingMiddleware(), + ToolCallLoggingMiddleware(), + PatchToolCallsMiddleware(), + ] if not offline: middleware.append(WebSearchMiddleware()) @@ -270,4 +326,6 @@ def create_changelog_subagent(backend: BackendProtocol, runtime: RuntimeCtx, off description=CHANGELOG_SUBAGENT_DESCRIPTION, system_prompt=CHANGELOG_SYSTEM_PROMPT, middleware=middleware, + model=model, + tools=[], ) diff --git a/docs/ai-agents/overview.md b/docs/ai-agents/overview.md index 709db0e7..ae28fc82 100644 --- a/docs/ai-agents/overview.md +++ b/docs/ai-agents/overview.md @@ -112,7 +112,7 @@ Agents can execute commands in isolated sandbox environments using [daiv-sandbox Agents can leverage [Agent Skills](skills.md) for specialized domain knowledge: - **Progressive Disclosure**: Skills metadata loads at startup, full instructions load on-demand -- **Custom Skills**: Create repository-specific Skills in `.daiv/skills/` +- **Custom Skills**: Create repository-specific Skills in `.agents/skills/` - **Builtin Skills**: Pre-packaged Skills for common tasks (e.g. AGENTS.md generation) - **Scoped Skills**: Target Skills to specific contexts (issues or merge requests) diff --git a/docs/ai-agents/skills.md b/docs/ai-agents/skills.md index 9e941baf..531f3c3d 100644 --- a/docs/ai-agents/skills.md +++ b/docs/ai-agents/skills.md @@ -60,8 +60,7 @@ This architecture ensures minimal context usage—agents only know Skills exist Skills can be placed in any of the following directories at the root of your repository: -- `.daiv/skills/` (default) -- `.agents/skills/` +- `.agents/skills/` (default) - `.cursor/skills/` - `.claude/skills/` @@ -71,7 +70,7 @@ Example structure: ``` your-repository/ -├── .daiv/ +├── .agents/ │ └── skills/ │ ├── code-review/ │ │ ├── SKILL.md # Required: YAML frontmatter + instructions @@ -79,7 +78,7 @@ your-repository/ │ │ └── review.py # Optional: helper script │ └── creating-agents-md-file/ # Builtin skill (auto-copied) │ └── SKILL.md -├── .agents/ +├── .cursor/ │ └── skills/ │ └── web-research/ │ ├── SKILL.md @@ -90,12 +89,12 @@ your-repository/ ### Builtin Skills -DAIV includes builtin Skills that are automatically loaded to the `.daiv/skills/` directory at agent startup: +DAIV includes builtin Skills that are automatically loaded to the `.agents/skills/` directory at agent startup: | Skill | Description | Scope | |-------|-------------|-------| | `generating-agents-md` | Generates or updates an AGENTS.md file reflecting repository structure and conventions | Issues | -| `skills-creator` | Creates a new Skill in the `.daiv/skills/` directory | Issues | +| `skills-creator` | Creates a new Skill in the `.agents/skills/` directory | Issues | You can override builtin skills by creating a Skill with the same name in any of your project's skills directories. @@ -336,7 +335,7 @@ result = function_name("value1", "value2") ### Skill Not Loading -**Check file location**: Ensure the Skill is in `.daiv/skills//SKILL.md` +**Check file location**: Ensure the Skill is in `.agents/skills//SKILL.md` **Verify YAML frontmatter**: The frontmatter must be valid YAML between `---` delimiters: diff --git a/pyproject.toml b/pyproject.toml index 7351a31c..127f0841 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ classifiers = [ ] dependencies = [ "ddgs==9.10.0", - "deepagents==0.4.1", + "deepagents==0.4.2", "django==6.0.2", "django-crontask==1.1.3", "django-extensions==4.1.0", @@ -63,7 +63,7 @@ dev = [ "coverage==7.13.4", "datasets==4.5.0", "prek==0.3.2", - "pyproject-fmt==2.15.2", + "pyproject-fmt==2.15.3", "pytest==9.0.2", "pytest-asyncio==1.3.0", "pytest-cov==7.0.0", diff --git a/tests/unit_tests/automation/agent/test_subagents.py b/tests/unit_tests/automation/agent/test_subagents.py index 2b0c54b3..1e835966 100644 --- a/tests/unit_tests/automation/agent/test_subagents.py +++ b/tests/unit_tests/automation/agent/test_subagents.py @@ -22,6 +22,11 @@ def mock_backend(self): """Create a mock backend.""" return Mock() + @pytest.fixture + def mock_model(self): + """Create a mock model.""" + return Mock() + @pytest.fixture def mock_runtime_ctx(self): """Create a mock runtime context.""" @@ -29,37 +34,37 @@ def mock_runtime_ctx(self): mock_ctx.config.sandbox.enabled = False return mock_ctx - def test_returns_subagent(self, mock_backend, mock_runtime_ctx): + def test_returns_subagent(self, mock_model, mock_backend, mock_runtime_ctx): """Test that create_general_purpose_subagent returns a SubAgent.""" - result = create_general_purpose_subagent(mock_backend, mock_runtime_ctx) + result = create_general_purpose_subagent(mock_model, mock_backend, mock_runtime_ctx) assert isinstance(result, dict) assert result["name"] == "general-purpose" assert result["description"] assert result["system_prompt"] - def test_includes_web_search_middleware(self, mock_backend, mock_runtime_ctx): + def test_includes_web_search_middleware(self, mock_model, mock_backend, mock_runtime_ctx): """Test that general purpose subagent includes web search middleware.""" - result = create_general_purpose_subagent(mock_backend, mock_runtime_ctx) + result = create_general_purpose_subagent(mock_model, mock_backend, mock_runtime_ctx) assert any(isinstance(m, WebSearchMiddleware) for m in result["middleware"]) @patch("automation.agent.middlewares.sandbox.settings.SANDBOX_API_KEY", "test-key") - def test_includes_sandbox_when_enabled(self, mock_backend, mock_runtime_ctx): + def test_includes_sandbox_when_enabled(self, mock_model, mock_backend, mock_runtime_ctx): """Test that sandbox middleware is included when enabled.""" mock_runtime_ctx.config.sandbox.enabled = True - result = create_general_purpose_subagent(mock_backend, mock_runtime_ctx) + result = create_general_purpose_subagent(mock_model, mock_backend, mock_runtime_ctx) sandbox_middlewares = [m for m in result["middleware"] if isinstance(m, SandboxMiddleware)] assert len(sandbox_middlewares) == 1 assert sandbox_middlewares[0].close_session is False - def test_excludes_sandbox_when_disabled(self, mock_backend, mock_runtime_ctx): + def test_excludes_sandbox_when_disabled(self, mock_model, mock_backend, mock_runtime_ctx): """Test that sandbox middleware is excluded when disabled.""" mock_runtime_ctx.config.sandbox.enabled = False - result = create_general_purpose_subagent(mock_backend, mock_runtime_ctx) + result = create_general_purpose_subagent(mock_model, mock_backend, mock_runtime_ctx) assert not any(isinstance(m, SandboxMiddleware) for m in result["middleware"]) @@ -122,6 +127,11 @@ def mock_backend(self): """Create a mock backend.""" return Mock() + @pytest.fixture + def mock_model(self): + """Create a mock model.""" + return Mock() + @pytest.fixture def mock_runtime_ctx(self): """Create a mock runtime context.""" @@ -129,45 +139,45 @@ def mock_runtime_ctx(self): mock_ctx.config.sandbox.enabled = False return mock_ctx - def test_returns_subagent(self, mock_backend, mock_runtime_ctx): + def test_returns_subagent(self, mock_model, mock_backend, mock_runtime_ctx): """Test that create_changelog_subagent returns a SubAgent.""" - result = create_changelog_subagent(mock_backend, mock_runtime_ctx) + result = create_changelog_subagent(mock_model, mock_backend, mock_runtime_ctx) assert isinstance(result, dict) assert result["name"] == "changelog-curator" assert result["description"] assert result["system_prompt"] - def test_description_mentions_changelog_keywords(self, mock_backend, mock_runtime_ctx): + def test_description_mentions_changelog_keywords(self, mock_model, mock_backend, mock_runtime_ctx): """Test that changelog subagent description mentions relevant keywords.""" - result = create_changelog_subagent(mock_backend, mock_runtime_ctx) + result = create_changelog_subagent(mock_model, mock_backend, mock_runtime_ctx) description = result["description"].lower() assert "changelog" in description - def test_system_prompt_mentions_unreleased_and_diff(self, mock_backend, mock_runtime_ctx): + def test_system_prompt_mentions_unreleased_and_diff(self, mock_model, mock_backend, mock_runtime_ctx): """Test that changelog subagent system prompt mentions discovery.""" - result = create_changelog_subagent(mock_backend, mock_runtime_ctx) + result = create_changelog_subagent(mock_model, mock_backend, mock_runtime_ctx) prompt = result["system_prompt"] assert "unreleased" in prompt.lower() assert "git diff" in prompt.lower() @patch("automation.agent.middlewares.sandbox.settings.SANDBOX_API_KEY", "test-key") - def test_includes_sandbox_when_enabled(self, mock_backend, mock_runtime_ctx): + def test_includes_sandbox_when_enabled(self, mock_model, mock_backend, mock_runtime_ctx): """Test that sandbox middleware is included when enabled.""" mock_runtime_ctx.config.sandbox.enabled = True - result = create_changelog_subagent(mock_backend, mock_runtime_ctx) + result = create_changelog_subagent(mock_model, mock_backend, mock_runtime_ctx) sandbox_middlewares = [m for m in result["middleware"] if isinstance(m, SandboxMiddleware)] assert len(sandbox_middlewares) == 1 assert sandbox_middlewares[0].close_session is False - def test_excludes_sandbox_when_disabled(self, mock_backend, mock_runtime_ctx): + def test_excludes_sandbox_when_disabled(self, mock_model, mock_backend, mock_runtime_ctx): """Test that sandbox middleware is excluded when disabled.""" mock_runtime_ctx.config.sandbox.enabled = False - result = create_changelog_subagent(mock_backend, mock_runtime_ctx) + result = create_changelog_subagent(mock_model, mock_backend, mock_runtime_ctx) assert not any(isinstance(m, SandboxMiddleware) for m in result["middleware"]) diff --git a/uv.lock b/uv.lock index c24557d7..f6f28b43 100644 --- a/uv.lock +++ b/uv.lock @@ -497,7 +497,7 @@ docs = [ [package.metadata] requires-dist = [ { name = "ddgs", specifier = "==9.10.0" }, - { name = "deepagents", specifier = "==0.4.1" }, + { name = "deepagents", specifier = "==0.4.2" }, { name = "django", specifier = "==6.0.2" }, { name = "django-crontask", specifier = "==1.1.3" }, { name = "django-extensions", specifier = "==4.1.0" }, @@ -537,7 +537,7 @@ dev = [ { name = "coverage", specifier = "==7.13.4" }, { name = "datasets", specifier = "==4.5.0" }, { name = "prek", specifier = "==0.3.2" }, - { name = "pyproject-fmt", specifier = "==2.15.2" }, + { name = "pyproject-fmt", specifier = "==2.15.3" }, { name = "pytest", specifier = "==9.0.2" }, { name = "pytest-asyncio", specifier = "==1.3.0" }, { name = "pytest-cov", specifier = "==7.0.0" }, @@ -624,7 +624,7 @@ wheels = [ [[package]] name = "deepagents" -version = "0.4.1" +version = "0.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain" }, @@ -633,9 +633,9 @@ dependencies = [ { name = "langchain-google-genai" }, { name = "wcmatch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/71/3aa53332733cbe25e45c30ddc1a337c04b11650ba3b4fad18bddb488ae87/deepagents-0.4.1.tar.gz", hash = "sha256:054f3b3baff2405c5053a2d004e5eaaa06b1a8346018753804ec0292a627fe64", size = 78294, upload-time = "2026-02-11T15:59:38.64Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/2f/46668b426bc9d383e7b9dd0e4c4cf7b4e550b62c82c5217293fd71d14f65/deepagents-0.4.2.tar.gz", hash = "sha256:9b266b4f936bc3a953132183ab75d1ba191f15220c9a70930fe651fca4d9913e", size = 79300, upload-time = "2026-02-12T21:01:42.179Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/fa/64cbb2b3c429d8cfb2417ff7aea67f53c2b507eae135bdeabed6e755f3a0/deepagents-0.4.1-py3-none-any.whl", hash = "sha256:9973c696b452ca050fbba860f4a997d8428dc56f629c5c0f7e01e483d768a4d9", size = 88477, upload-time = "2026-02-11T15:59:37.262Z" }, + { url = "https://files.pythonhosted.org/packages/23/da/80c7b9b7899b30c16a91d29a19d76622ede008a5f0297a0f52f3c129bb29/deepagents-0.4.2-py3-none-any.whl", hash = "sha256:5eff263bf9ba962727caeee052545e1ad329282446bc1bcd9a629a903cf45da2", size = 89260, upload-time = "2026-02-12T21:01:41.161Z" }, ] [[package]] @@ -797,11 +797,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.20.3" +version = "3.21.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/6b/cc63cdbff46eba1ce2fbd058e9699f99c43f7e604da15413ca0331040bff/filelock-3.21.0.tar.gz", hash = "sha256:48c739c73c6fcacd381ed532226991150947c4a76dcd674f84d6807fd55dbaf2", size = 31341, upload-time = "2026-02-12T15:40:48.544Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, + { url = "https://files.pythonhosted.org/packages/da/ab/05190b5a64101fcb743bc63a034c0fac86a515c27c303c69221093565f28/filelock-3.21.0-py3-none-any.whl", hash = "sha256:0f90eee4c62101243df3007db3cf8fc3ebf1bb13541d3e72c687d6e0f3f7d531", size = 21381, upload-time = "2026-02-12T15:40:46.964Z" }, ] [[package]] @@ -931,7 +931,7 @@ requests = [ [[package]] name = "google-genai" -version = "1.62.0" +version = "1.63.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -945,9 +945,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/4c/71b32b5c8db420cf2fd0d5ef8a672adbde97d85e5d44a0b4fca712264ef1/google_genai-1.62.0.tar.gz", hash = "sha256:709468a14c739a080bc240a4f3191df597bf64485b1ca3728e0fb67517774c18", size = 490888, upload-time = "2026-02-04T22:48:41.989Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/d7/07ec5dadd0741f09e89f3ff5f0ce051ce2aa3a76797699d661dc88def077/google_genai-1.63.0.tar.gz", hash = "sha256:dc76cab810932df33cbec6c7ef3ce1538db5bef27aaf78df62ac38666c476294", size = 491970, upload-time = "2026-02-11T23:46:28.472Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/09/5f/4645d8a28c6e431d0dd6011003a852563f3da7037d36af53154925b099fd/google_genai-1.62.0-py3-none-any.whl", hash = "sha256:4c3daeff3d05fafee4b9a1a31f9c07f01bc22051081aa58b4d61f58d16d1bcc0", size = 724166, upload-time = "2026-02-04T22:48:39.956Z" }, + { url = "https://files.pythonhosted.org/packages/82/c8/ba32159e553fab787708c612cf0c3a899dafe7aca81115d841766e3bfe69/google_genai-1.63.0-py3-none-any.whl", hash = "sha256:6206c13fc20f332703ca7375bea7c191c82f95d6781c29936c6982d86599b359", size = 724747, upload-time = "2026-02-11T23:46:26.697Z" }, ] [[package]] @@ -1384,7 +1384,7 @@ wheels = [ [[package]] name = "langchain-core" -version = "1.2.11" +version = "1.2.12" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpatch" }, @@ -1396,9 +1396,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "uuid-utils" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/12/17/1943cedfc118e04b8128e4c3e1dbf0fa0ea58eefddbb6198cfd699d19f01/langchain_core-1.2.11.tar.gz", hash = "sha256:f164bb36602dd74a3a50c1334fca75309ad5ed95767acdfdbb9fa95ce28a1e01", size = 831211, upload-time = "2026-02-10T20:35:28.35Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/1d/08e935d1532fcc90981f6e5bb6825914c9227ea7a962c62b1e18619b49e7/langchain_core-1.2.12.tar.gz", hash = "sha256:4d7fa6643d7ab06fc1905a9b7dcbe96a6f3c181046b56edf9c0c17ecd412d9e9", size = 831329, upload-time = "2026-02-12T20:53:15.01Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/30/1f80e3fc674353cad975ed5294353d42512535d2094ef032c06454c2c873/langchain_core-1.2.11-py3-none-any.whl", hash = "sha256:ae11ceb8dda60d0b9d09e763116e592f1683327c17be5b715f350fd29aee65d3", size = 500062, upload-time = "2026-02-10T20:35:26.698Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a5/678ab0e5cc57794f20ae5ed12c1442506ef1108c9434f950aebc6044e5a3/langchain_core-1.2.12-py3-none-any.whl", hash = "sha256:66ca17a2a9cb007ab29021968e6adfcf4228067151dc2bd6ebfff265ffaf92f5", size = 500132, upload-time = "2026-02-12T20:53:13.806Z" }, ] [[package]] @@ -2106,11 +2106,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.5.1" +version = "4.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/474d0a8508029286b905622e6929470fb84337cfa08f9d09fbb624515249/platformdirs-4.6.0.tar.gz", hash = "sha256:4a13c2db1071e5846c3b3e04e5b095c0de36b2a24be9a3bc0145ca66fce4e328", size = 23433, upload-time = "2026-02-12T14:36:21.288Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, + { url = "https://files.pythonhosted.org/packages/da/10/1b0dcf51427326f70e50d98df21b18c228117a743a1fc515a42f8dc7d342/platformdirs-4.6.0-py3-none-any.whl", hash = "sha256:dd7f808d828e1764a22ebff09e60f175ee3c41876606a6132a688d809c7c9c73", size = 19549, upload-time = "2026-02-12T14:36:19.743Z" }, ] [[package]] @@ -2478,20 +2478,20 @@ wheels = [ [[package]] name = "pyproject-fmt" -version = "2.15.2" +version = "2.15.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "toml-fmt-common" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/34/0586cba8a147011b7708ef08bd24f6e457669b3953bd2ac8c08d18d81395/pyproject_fmt-2.15.2.tar.gz", hash = "sha256:10b22effb4c1ac12033d41b089bee60aded60f2241e0b95f2794917fc7d5dac8", size = 126980, upload-time = "2026-02-10T23:19:03.435Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/2d/9286215ae932894a1787fa10ca8829eda9fdd497106723c4c2b571cd14ca/pyproject_fmt-2.15.3.tar.gz", hash = "sha256:276270e6c6aa21989447e57e1415baa10d282924d1e55168d0b2cc833303217b", size = 130178, upload-time = "2026-02-11T23:29:55.6Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/81/f537fb52345096912ed86dac5805768349758593f7a0112dc25db412010f/pyproject_fmt-2.15.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:7ec5b8cc45125362ac29ef5e79c653a08865a73758bda905920d724f4e806f4b", size = 4708882, upload-time = "2026-02-10T23:18:41.996Z" }, - { url = "https://files.pythonhosted.org/packages/e6/4e/a8a6419a79586254d0f81f682e6de7dc6b49b99aa7e5ee5b9e34c666e9e2/pyproject_fmt-2.15.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:cebc1e2073730e66be7072110c0237bbe9ba1e751045d64397daf21ee4b44f50", size = 4521260, upload-time = "2026-02-10T23:18:44.366Z" }, - { url = "https://files.pythonhosted.org/packages/83/4a/35b9b0b9da2c3799564580af44a5a851773b80fd519e7c2b41e5c939d008/pyproject_fmt-2.15.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7a0d6e2675831d00aa8fc54a028727e51e64fa2936f798c87661542da2945ea9", size = 4666216, upload-time = "2026-02-10T23:18:46.375Z" }, - { url = "https://files.pythonhosted.org/packages/90/c0/bfc9ee58a73820933b7935ac710f96f0cece96dc94527f4bec3b3e796575/pyproject_fmt-2.15.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cdf004e3591e9450f0cbcfb9a39f198f4a5f50d40ad3a26e0f9e9ddf513d7bbc", size = 4970963, upload-time = "2026-02-10T23:18:48.188Z" }, - { url = "https://files.pythonhosted.org/packages/ff/d2/e0a57cb6f2812da6ea0d9c3e1ec83108c2efe737549be603cd7dccd3b4da/pyproject_fmt-2.15.2-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:1f59674432fa93410ca2697d2d868793f411a055b9bb9a5a2127047c77fced40", size = 4707417, upload-time = "2026-02-10T23:18:49.92Z" }, - { url = "https://files.pythonhosted.org/packages/ff/dc/ac96ef4adf722809fb2bf048ec4888a3dfded4e4028072b03631cd4e4d6d/pyproject_fmt-2.15.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dce488a7d99bdac0fde30addb2455ffe220f2063181079653d4754544ce57aed", size = 5173555, upload-time = "2026-02-10T23:18:52.398Z" }, - { url = "https://files.pythonhosted.org/packages/96/db/7c0efc419d846407a966d3ee581505043828c1e43f97f5424f12fb4d1e8d/pyproject_fmt-2.15.2-cp39-abi3-win_amd64.whl", hash = "sha256:3767a4b185490ac65e73e5ff1d4bc304d48cdddde1efe95f8c364ae6ed1867ec", size = 4826862, upload-time = "2026-02-10T23:18:54.141Z" }, + { url = "https://files.pythonhosted.org/packages/b7/04/b0f42e0fffb72fc5f68af0d9e884a42d653f64e0de6765bd7668e4f195ca/pyproject_fmt-2.15.3-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:558564f996c42cf365ae17dd18db8d959ef36d4ef57c78b0265dec09675ca3b4", size = 4712123, upload-time = "2026-02-11T23:29:32.042Z" }, + { url = "https://files.pythonhosted.org/packages/f6/09/565439d9fc8c8e8844b72cfa95bff70af1685c7a2cbb1915fd73cf73a826/pyproject_fmt-2.15.3-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:4ece2998a54557ed15560303f9e1fb56b72d708330500e699cb20c6d83a823fc", size = 4527559, upload-time = "2026-02-11T23:29:33.91Z" }, + { url = "https://files.pythonhosted.org/packages/0b/25/b470b8abbd7d519cefe86e92f63accca7ebaf1cc9ad6d3dc9cac7fb8e95a/pyproject_fmt-2.15.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d23bf897136d4f0ed432f724928fb85b21635f1004abb00457652861287252af", size = 4666133, upload-time = "2026-02-11T23:29:36.365Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6b/afbbe698bea00d34dc6b743a854d40ad68a2440bef135b963996fc557022/pyproject_fmt-2.15.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:85ae4ed12abaa12006b059e2c2204d1465c7cc729d700c6711354f9467c60a08", size = 4970780, upload-time = "2026-02-11T23:29:39.838Z" }, + { url = "https://files.pythonhosted.org/packages/b8/77/4d0967a685a836ffbebbb865ef48f5e401ed8fd99524e11bec05d28dc12b/pyproject_fmt-2.15.3-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:a91ff78500373572a1126203d313004831dbcac89c2523d731eabcf96b633c57", size = 4707728, upload-time = "2026-02-11T23:29:41.751Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/3e259dfe66fb6baaffdeae2232ec9c3946f033be28a4f6e81871551d42e4/pyproject_fmt-2.15.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:71ff57b5efb3e7905c6347a528460b38fa303b48b041b287e92b21fe0a9f1313", size = 5179961, upload-time = "2026-02-11T23:29:44.238Z" }, + { url = "https://files.pythonhosted.org/packages/3d/26/976fabac1cb31da625aaf7cb7ae82c82d47d2effd66a5a5e5748569e848f/pyproject_fmt-2.15.3-cp39-abi3-win_amd64.whl", hash = "sha256:9620e0dddfa2628d9aa39d5b000f29fb15d1b7b57243fed890282ffc1a015390", size = 4826691, upload-time = "2026-02-11T23:29:46.727Z" }, ] [[package]]