mana-agent 0.0.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mana_agent/__init__.py +6 -0
- mana_agent/_version.py +46 -0
- mana_agent/agent/__init__.py +13 -0
- mana_agent/agent/evaluation_gate.py +174 -0
- mana_agent/agent/evidence_queue.py +72 -0
- mana_agent/agent/flow.py +109 -0
- mana_agent/agent/orchestrator.py +212 -0
- mana_agent/agent/selection.py +56 -0
- mana_agent/agent/task_classifier.py +172 -0
- mana_agent/agent/task_context.py +70 -0
- mana_agent/agent/verification.py +36 -0
- mana_agent/agent/verification_planner.py +150 -0
- mana_agent/analysis/__init__.py +0 -0
- mana_agent/analysis/checks.py +156 -0
- mana_agent/analysis/chunker.py +92 -0
- mana_agent/analysis/models.py +369 -0
- mana_agent/api/__init__.py +2 -0
- mana_agent/api/app.py +86 -0
- mana_agent/api/exceptions.py +11 -0
- mana_agent/api/routes/__init__.py +2 -0
- mana_agent/api/routes/analyze.py +46 -0
- mana_agent/api/routes/conversations.py +189 -0
- mana_agent/api/routes/events_ws.py +140 -0
- mana_agent/api/routes/repository_analyze.py +226 -0
- mana_agent/api/routes/workspaces.py +212 -0
- mana_agent/api/services/__init__.py +2 -0
- mana_agent/api/services/analyze_service.py +200 -0
- mana_agent/api/services/job_service.py +58 -0
- mana_agent/api/services/zip_service.py +80 -0
- mana_agent/automations/__init__.py +60 -0
- mana_agent/automations/github_integration.py +23 -0
- mana_agent/automations/scheduler.py +28 -0
- mana_agent/automations/self_improvement.py +134 -0
- mana_agent/automations/service.py +392 -0
- mana_agent/builtin_skills/__init__.py +2 -0
- mana_agent/builtin_skills/skill_creator/SKILL.md +54 -0
- mana_agent/builtin_skills/skill_creator/__init__.py +35 -0
- mana_agent/builtin_skills/skill_creator/completion.py +50 -0
- mana_agent/builtin_skills/skill_creator/evaluator.py +106 -0
- mana_agent/builtin_skills/skill_creator/generator.py +271 -0
- mana_agent/builtin_skills/skill_creator/renderer.py +101 -0
- mana_agent/builtin_skills/skill_creator/schema.py +268 -0
- mana_agent/builtin_skills/skill_creator/session_loader.py +75 -0
- mana_agent/builtin_skills/skill_creator/storage.py +260 -0
- mana_agent/builtin_skills/skill_creator/validator.py +132 -0
- mana_agent/chat/__init__.py +28 -0
- mana_agent/chat/events.py +150 -0
- mana_agent/chat/history.py +173 -0
- mana_agent/cli/__init__.py +5 -0
- mana_agent/cli/chat_ui.py +557 -0
- mana_agent/cli/events.py +456 -0
- mana_agent/cli/menu.py +5 -0
- mana_agent/cli/renderers.py +771 -0
- mana_agent/coding/__init__.py +23 -0
- mana_agent/coding/backend.py +26 -0
- mana_agent/coding/models.py +121 -0
- mana_agent/coding/orchestrator.py +32 -0
- mana_agent/coding/registry.py +45 -0
- mana_agent/coding/routing_policy.py +22 -0
- mana_agent/commands/__init__.py +1 -0
- mana_agent/commands/analyze_formats.py +174 -0
- mana_agent/commands/browser_cli.py +19 -0
- mana_agent/commands/chat_analyze_command.py +572 -0
- mana_agent/commands/chat_cli.py +3629 -0
- mana_agent/commands/chat_input.py +140 -0
- mana_agent/commands/cli.py +54 -0
- mana_agent/commands/cli_internal.py +1741 -0
- mana_agent/commands/codex_cli.py +66 -0
- mana_agent/commands/email_cli.py +68 -0
- mana_agent/commands/main_cli.py +133 -0
- mana_agent/commands/output.py +153 -0
- mana_agent/commands/telegram_cli.py +170 -0
- mana_agent/commands/ui_helpers.py +2610 -0
- mana_agent/commands/workspace_cli.py +204 -0
- mana_agent/commands/worktree_cli.py +221 -0
- mana_agent/config/__init__.py +3 -0
- mana_agent/config/settings.py +167 -0
- mana_agent/config/skills.py +39 -0
- mana_agent/config/user_config.py +500 -0
- mana_agent/connectors/__init__.py +0 -0
- mana_agent/connectors/browser/__init__.py +7 -0
- mana_agent/connectors/browser/approval.py +36 -0
- mana_agent/connectors/browser/contracts.py +35 -0
- mana_agent/connectors/browser/models.py +43 -0
- mana_agent/connectors/browser/runtime_tools.py +37 -0
- mana_agent/connectors/browser/session.py +305 -0
- mana_agent/connectors/email/__init__.py +4 -0
- mana_agent/connectors/email/approval.py +23 -0
- mana_agent/connectors/email/auth/__init__.py +2 -0
- mana_agent/connectors/email/auth/credential_store.py +32 -0
- mana_agent/connectors/email/auth/oauth.py +35 -0
- mana_agent/connectors/email/config.py +19 -0
- mana_agent/connectors/email/exceptions.py +80 -0
- mana_agent/connectors/email/models.py +216 -0
- mana_agent/connectors/email/permissions.py +6 -0
- mana_agent/connectors/email/providers/__init__.py +3 -0
- mana_agent/connectors/email/providers/base.py +43 -0
- mana_agent/connectors/email/providers/gmail.py +193 -0
- mana_agent/connectors/email/registry.py +15 -0
- mana_agent/connectors/email/runtime_tools.py +175 -0
- mana_agent/connectors/email/sanitizer.py +40 -0
- mana_agent/connectors/email/tools/__init__.py +2 -0
- mana_agent/connectors/email/tools/contracts.py +9 -0
- mana_agent/connectors/telegram/__init__.py +6 -0
- mana_agent/connectors/telegram/access.py +47 -0
- mana_agent/connectors/telegram/admin.py +119 -0
- mana_agent/connectors/telegram/attachments.py +59 -0
- mana_agent/connectors/telegram/chat.py +168 -0
- mana_agent/connectors/telegram/client.py +110 -0
- mana_agent/connectors/telegram/commands.py +77 -0
- mana_agent/connectors/telegram/config.py +155 -0
- mana_agent/connectors/telegram/connector.py +131 -0
- mana_agent/connectors/telegram/errors.py +33 -0
- mana_agent/connectors/telegram/models.py +59 -0
- mana_agent/connectors/telegram/normalizer.py +42 -0
- mana_agent/connectors/telegram/observability.py +16 -0
- mana_agent/connectors/telegram/pipeline.py +83 -0
- mana_agent/connectors/telegram/queue.py +75 -0
- mana_agent/connectors/telegram/renderer.py +54 -0
- mana_agent/connectors/telegram/store.py +224 -0
- mana_agent/connectors/telegram/transports/__init__.py +4 -0
- mana_agent/connectors/telegram/transports/polling.py +123 -0
- mana_agent/connectors/telegram/transports/webhook.py +64 -0
- mana_agent/dashboard/__init__.py +1 -0
- mana_agent/dashboard/app.py +79 -0
- mana_agent/dashboard/components/__init__.py +1 -0
- mana_agent/dashboard/components/chat_timeline.py +190 -0
- mana_agent/dashboard/pages/__init__.py +1 -0
- mana_agent/dashboard/pages/analyze.py +174 -0
- mana_agent/dashboard/pages/automations.py +73 -0
- mana_agent/dashboard/pages/chat.py +186 -0
- mana_agent/dashboard/pages/cron.py +52 -0
- mana_agent/dashboard/pages/metrics.py +23 -0
- mana_agent/dashboard/pages/observability.py +42 -0
- mana_agent/dashboard/pages/overview.py +44 -0
- mana_agent/dashboard/pages/reports.py +25 -0
- mana_agent/dashboard/pages/skills.py +91 -0
- mana_agent/dashboard/pages/taskboard.py +42 -0
- mana_agent/default_skills/__init__.py +1 -0
- mana_agent/default_skills/celery.md +18 -0
- mana_agent/default_skills/cli.md +24 -0
- mana_agent/default_skills/coding-agent.md +22 -0
- mana_agent/default_skills/django.md +18 -0
- mana_agent/default_skills/fastapi.md +17 -0
- mana_agent/default_skills/git.md +23 -0
- mana_agent/default_skills/laravel.md +17 -0
- mana_agent/default_skills/nestjs.md +17 -0
- mana_agent/default_skills/nextjs.md +17 -0
- mana_agent/default_skills/planning.md +22 -0
- mana_agent/default_skills/reactjs.md +17 -0
- mana_agent/default_skills/security.md +50 -0
- mana_agent/default_skills/telegram-bot.md +18 -0
- mana_agent/default_skills/testing.md +22 -0
- mana_agent/default_skills/vue.md +18 -0
- mana_agent/dependencies/__init__.py +3 -0
- mana_agent/dependencies/dependency_service.py +309 -0
- mana_agent/describe/describe_service.py +304 -0
- mana_agent/documents/__init__.py +7 -0
- mana_agent/documents/cache.py +53 -0
- mana_agent/documents/detector.py +40 -0
- mana_agent/documents/query.py +50 -0
- mana_agent/documents/readers.py +258 -0
- mana_agent/documents/service.py +548 -0
- mana_agent/documents/types.py +75 -0
- mana_agent/documents/writers.py +1085 -0
- mana_agent/gateway/__init__.py +30 -0
- mana_agent/gateway/chat_gateway.py +448 -0
- mana_agent/gateway/config.py +124 -0
- mana_agent/gateway/stack.py +458 -0
- mana_agent/gateway/turn_engine.py +874 -0
- mana_agent/integrations/__init__.py +1 -0
- mana_agent/integrations/codex/__init__.py +14 -0
- mana_agent/integrations/codex/backend.py +268 -0
- mana_agent/integrations/codex/client.py +210 -0
- mana_agent/integrations/codex/coding_agent_shim.py +298 -0
- mana_agent/integrations/codex/config.py +43 -0
- mana_agent/integrations/codex/event_adapter.py +70 -0
- mana_agent/integrations/codex/exceptions.py +20 -0
- mana_agent/integrations/codex/health.py +83 -0
- mana_agent/integrations/codex/prompt_builder.py +59 -0
- mana_agent/integrations/codex/result_parser.py +103 -0
- mana_agent/mcp/__init__.py +19 -0
- mana_agent/mcp/client.py +138 -0
- mana_agent/mcp/config.py +183 -0
- mana_agent/mcp/server.py +95 -0
- mana_agent/mcp/tools.py +65 -0
- mana_agent/models.py +284 -0
- mana_agent/multi_agent/__init__.py +15 -0
- mana_agent/multi_agent/agents/base_agent.py +143 -0
- mana_agent/multi_agent/agents/coding_agent.py +59 -0
- mana_agent/multi_agent/agents/head_decision_agent.py +55 -0
- mana_agent/multi_agent/agents/main_agent.py +888 -0
- mana_agent/multi_agent/agents/planner_agent.py +36 -0
- mana_agent/multi_agent/agents/research_agent.py +6 -0
- mana_agent/multi_agent/agents/reviewer_agent.py +102 -0
- mana_agent/multi_agent/agents/summarizer_agent.py +20 -0
- mana_agent/multi_agent/agents/tool_agent.py +15 -0
- mana_agent/multi_agent/agents/verifier_agent.py +154 -0
- mana_agent/multi_agent/codex_pool.py +94 -0
- mana_agent/multi_agent/communication/decision_room.py +111 -0
- mana_agent/multi_agent/communication/discussion.py +65 -0
- mana_agent/multi_agent/communication/mailbox.py +5 -0
- mana_agent/multi_agent/communication/message_bus.py +96 -0
- mana_agent/multi_agent/communication/protocols.py +9 -0
- mana_agent/multi_agent/core/errors.py +14 -0
- mana_agent/multi_agent/core/events.py +3 -0
- mana_agent/multi_agent/core/ids.py +54 -0
- mana_agent/multi_agent/core/state.py +9 -0
- mana_agent/multi_agent/core/types.py +558 -0
- mana_agent/multi_agent/events.py +3 -0
- mana_agent/multi_agent/memory/__init__.py +27 -0
- mana_agent/multi_agent/memory/agent_memory.py +13 -0
- mana_agent/multi_agent/memory/memory_bundle.py +143 -0
- mana_agent/multi_agent/memory/repo_context.py +9 -0
- mana_agent/multi_agent/memory/service.py +35 -0
- mana_agent/multi_agent/memory/task_memory.py +5 -0
- mana_agent/multi_agent/observability/logger.py +5 -0
- mana_agent/multi_agent/observability/metrics.py +12 -0
- mana_agent/multi_agent/observability/trace.py +64 -0
- mana_agent/multi_agent/queue/locks.py +15 -0
- mana_agent/multi_agent/queue/queue_manager.py +401 -0
- mana_agent/multi_agent/queue/queue_models.py +3 -0
- mana_agent/multi_agent/queue/scheduler.py +10 -0
- mana_agent/multi_agent/registry/agent_registry.py +129 -0
- mana_agent/multi_agent/registry/capability_registry.py +16 -0
- mana_agent/multi_agent/registry/route_registry.py +9 -0
- mana_agent/multi_agent/routing/agent_decision.py +455 -0
- mana_agent/multi_agent/routing/handoff.py +3 -0
- mana_agent/multi_agent/routing/hierarchy.py +234 -0
- mana_agent/multi_agent/routing/policies.py +24 -0
- mana_agent/multi_agent/routing/router.py +50 -0
- mana_agent/multi_agent/runtime/__init__.py +0 -0
- mana_agent/multi_agent/runtime/agent_session.py +92 -0
- mana_agent/multi_agent/runtime/agent_work_queue.py +2767 -0
- mana_agent/multi_agent/runtime/agent_work_queue_adapters.py +782 -0
- mana_agent/multi_agent/runtime/ask_agent.py +2601 -0
- mana_agent/multi_agent/runtime/auto_chat.py +303 -0
- mana_agent/multi_agent/runtime/automation_hooks.py +71 -0
- mana_agent/multi_agent/runtime/coding_agent.py +2484 -0
- mana_agent/multi_agent/runtime/coding_agent_models.py +114 -0
- mana_agent/multi_agent/runtime/coding_agent_prompt.py +223 -0
- mana_agent/multi_agent/runtime/compatibility.py +220 -0
- mana_agent/multi_agent/runtime/delegation.py +42 -0
- mana_agent/multi_agent/runtime/edit_scope.py +134 -0
- mana_agent/multi_agent/runtime/entry_router.py +251 -0
- mana_agent/multi_agent/runtime/evidence_ledger.py +179 -0
- mana_agent/multi_agent/runtime/evidence_memory.py +3 -0
- mana_agent/multi_agent/runtime/execution_scope.py +313 -0
- mana_agent/multi_agent/runtime/gate_command.py +542 -0
- mana_agent/multi_agent/runtime/goal_profiles.py +167 -0
- mana_agent/multi_agent/runtime/model_levels.py +68 -0
- mana_agent/multi_agent/runtime/mutation_plan.py +602 -0
- mana_agent/multi_agent/runtime/prompts.py +859 -0
- mana_agent/multi_agent/runtime/qna_chain.py +50 -0
- mana_agent/multi_agent/runtime/redis_tool_tasks.py +101 -0
- mana_agent/multi_agent/runtime/repo_chain.py +311 -0
- mana_agent/multi_agent/runtime/route_executor.py +562 -0
- mana_agent/multi_agent/runtime/run_logger.py +61 -0
- mana_agent/multi_agent/runtime/small_direct_edit.py +219 -0
- mana_agent/multi_agent/runtime/tool_worker_process.py +2046 -0
- mana_agent/multi_agent/runtime/tools_executor.py +448 -0
- mana_agent/multi_agent/runtime/tools_manager.py +2717 -0
- mana_agent/multi_agent/taskboard/models.py +3 -0
- mana_agent/multi_agent/taskboard/selectors.py +11 -0
- mana_agent/multi_agent/taskboard/store.py +143 -0
- mana_agent/multi_agent/taskboard/taskboard.py +328 -0
- mana_agent/multi_agent/taskboard/validators.py +33 -0
- mana_agent/multi_agent/tools/git_tools.py +622 -0
- mana_agent/multi_agent/tools/permissions.py +58 -0
- mana_agent/multi_agent/tools/tool_manager.py +397 -0
- mana_agent/multi_agent/tools/tool_request.py +3 -0
- mana_agent/multi_agent/tools/tool_result.py +3 -0
- mana_agent/multi_agent/worktrees/__init__.py +20 -0
- mana_agent/multi_agent/worktrees/events.py +76 -0
- mana_agent/multi_agent/worktrees/manager.py +742 -0
- mana_agent/multi_agent/worktrees/models.py +278 -0
- mana_agent/multi_agent/worktrees/review.py +98 -0
- mana_agent/multi_agent/worktrees/store.py +114 -0
- mana_agent/observability/__init__.py +5 -0
- mana_agent/observability/service.py +265 -0
- mana_agent/parsers/__init__.py +0 -0
- mana_agent/parsers/multi_parser.py +131 -0
- mana_agent/parsers/python_parser.py +106 -0
- mana_agent/prompting/__init__.py +2 -0
- mana_agent/prompting/builder.py +353 -0
- mana_agent/prompting/layers.py +56 -0
- mana_agent/prompting/memory_snapshot.py +39 -0
- mana_agent/prompting/mode_rules.py +18 -0
- mana_agent/prompting/output_contract.py +42 -0
- mana_agent/prompting/repo_rules.py +35 -0
- mana_agent/prompting/skills_index.py +56 -0
- mana_agent/renderers/__init__.py +3 -0
- mana_agent/renderers/html_report.py +613 -0
- mana_agent/search/__init__.py +20 -0
- mana_agent/search/config.py +85 -0
- mana_agent/search/decision.py +265 -0
- mana_agent/search/github_provider.py +164 -0
- mana_agent/search/memory.py +178 -0
- mana_agent/search/models.py +110 -0
- mana_agent/search/prompts.py +35 -0
- mana_agent/search/providers.py +33 -0
- mana_agent/search/ranker.py +49 -0
- mana_agent/search/router.py +166 -0
- mana_agent/search/web_provider.py +175 -0
- mana_agent/services/__init__.py +0 -0
- mana_agent/services/ask_service.py +394 -0
- mana_agent/services/chat_service.py +200 -0
- mana_agent/services/coding_memory_service.py +1106 -0
- mana_agent/services/coding_todo_service.py +139 -0
- mana_agent/services/conversation_service.py +373 -0
- mana_agent/services/dependency_service.py +3 -0
- mana_agent/services/describe_service.py +3 -0
- mana_agent/services/execution_event_hub.py +294 -0
- mana_agent/services/index_service.py +238 -0
- mana_agent/services/memory_service.py +831 -0
- mana_agent/services/parsers/__init__.py +19 -0
- mana_agent/services/parsers/base.py +18 -0
- mana_agent/services/parsers/dart_parser.py +37 -0
- mana_agent/services/parsers/js_ts_parser.py +46 -0
- mana_agent/services/parsers/jvm_parser.py +32 -0
- mana_agent/services/parsers/markup_parser.py +14 -0
- mana_agent/services/parsers/native_parser.py +135 -0
- mana_agent/services/parsers/python_parser.py +105 -0
- mana_agent/services/parsers/scripting_parser.py +27 -0
- mana_agent/services/project_analyze_service.py +1514 -0
- mana_agent/services/project_llm_analyze_service.py +426 -0
- mana_agent/services/report_service.py +734 -0
- mana_agent/services/search_service.py +244 -0
- mana_agent/services/structure_service.py +468 -0
- mana_agent/services/vulnerability_service.py +199 -0
- mana_agent/skills/__init__.py +21 -0
- mana_agent/skills/adaptive.py +362 -0
- mana_agent/skills/chat.py +177 -0
- mana_agent/skills/manager.py +473 -0
- mana_agent/telemetry/session_trace.py +44 -0
- mana_agent/telemetry/tokens.py +257 -0
- mana_agent/tools/__init__.py +88 -0
- mana_agent/tools/apply_patch.py +1413 -0
- mana_agent/tools/catalog.py +497 -0
- mana_agent/tools/contracts.py +721 -0
- mana_agent/tools/edit_file.py +236 -0
- mana_agent/tools/repository.py +665 -0
- mana_agent/tools/write_file.py +605 -0
- mana_agent/tui/__init__.py +23 -0
- mana_agent/tui/app.py +1158 -0
- mana_agent/tui/app.tcss +131 -0
- mana_agent/tui/banner.py +9 -0
- mana_agent/tui/forms.py +31 -0
- mana_agent/tui/menu.py +61 -0
- mana_agent/tui/model_picker.py +160 -0
- mana_agent/tui/search_provider_picker.py +78 -0
- mana_agent/tui/status.py +26 -0
- mana_agent/tui/widgets/__init__.py +6 -0
- mana_agent/tui/widgets/chat_log.py +360 -0
- mana_agent/tui/widgets/tool_card.py +236 -0
- mana_agent/tui/wizard.py +263 -0
- mana_agent/ui/__init__.py +7 -0
- mana_agent/ui/banner.py +56 -0
- mana_agent/ui/streamlit_helpers.py +616 -0
- mana_agent/utils/__init__.py +0 -0
- mana_agent/utils/guards.py +32 -0
- mana_agent/utils/index_discovery.py +51 -0
- mana_agent/utils/io.py +225 -0
- mana_agent/utils/logging.py +35 -0
- mana_agent/utils/project_discovery.py +137 -0
- mana_agent/utils/project_search.py +239 -0
- mana_agent/utils/redaction.py +96 -0
- mana_agent/utils/tool_policy.py +147 -0
- mana_agent/utils/tool_results.py +45 -0
- mana_agent/utils/tools_run.py +55 -0
- mana_agent/vector_store/__init__.py +0 -0
- mana_agent/vector_store/embeddings.py +88 -0
- mana_agent/vector_store/faiss_store.py +172 -0
- mana_agent/workspaces/__init__.py +25 -0
- mana_agent/workspaces/context.py +40 -0
- mana_agent/workspaces/discovery.py +47 -0
- mana_agent/workspaces/impact.py +73 -0
- mana_agent/workspaces/models.py +184 -0
- mana_agent/workspaces/paths.py +85 -0
- mana_agent/workspaces/relationships.py +124 -0
- mana_agent/workspaces/routing.py +101 -0
- mana_agent/workspaces/search.py +76 -0
- mana_agent/workspaces/service.py +351 -0
- mana_agent/workspaces/store.py +95 -0
- mana_agent-0.0.16.dist-info/LICENSE +21 -0
- mana_agent-0.0.16.dist-info/METADATA +1051 -0
- mana_agent-0.0.16.dist-info/RECORD +390 -0
- mana_agent-0.0.16.dist-info/WHEEL +5 -0
- mana_agent-0.0.16.dist-info/entry_points.txt +2 -0
- mana_agent-0.0.16.dist-info/top_level.txt +1 -0
mana_agent/__init__.py
ADDED
mana_agent/_version.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Single-source package version resolution from pyproject.toml."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
_DIST_NAME = "mana-agent"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _read_pyproject_version(start: Path) -> str | None:
|
|
12
|
+
"""Walk parents from *start* and return ``[project].version`` if present."""
|
|
13
|
+
for directory in (start, *start.parents):
|
|
14
|
+
path = directory / "pyproject.toml"
|
|
15
|
+
if not path.is_file():
|
|
16
|
+
continue
|
|
17
|
+
try:
|
|
18
|
+
import tomllib
|
|
19
|
+
|
|
20
|
+
data = tomllib.loads(path.read_text(encoding="utf-8"))
|
|
21
|
+
version = data.get("project", {}).get("version")
|
|
22
|
+
if isinstance(version, str) and version.strip():
|
|
23
|
+
return version.strip()
|
|
24
|
+
except Exception:
|
|
25
|
+
continue
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@lru_cache(maxsize=1)
|
|
30
|
+
def get_version() -> str:
|
|
31
|
+
"""Return the package version.
|
|
32
|
+
|
|
33
|
+
Preference order:
|
|
34
|
+
1. ``[project].version`` from the nearest ``pyproject.toml`` (source tree).
|
|
35
|
+
2. Installed distribution metadata for ``mana-agent``.
|
|
36
|
+
3. ``\"dev\"`` when neither is available.
|
|
37
|
+
"""
|
|
38
|
+
from_pyproject = _read_pyproject_version(Path(__file__).resolve().parent)
|
|
39
|
+
if from_pyproject:
|
|
40
|
+
return from_pyproject
|
|
41
|
+
try:
|
|
42
|
+
from importlib.metadata import version
|
|
43
|
+
|
|
44
|
+
return version(_DIST_NAME)
|
|
45
|
+
except Exception:
|
|
46
|
+
return "dev"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Agent flow primitives shared by ManaAgent orchestration paths."""
|
|
2
|
+
|
|
3
|
+
from mana_agent.agent.orchestrator import AgentOrchestrator
|
|
4
|
+
from mana_agent.agent.task_classifier import TaskDecision, classify_task
|
|
5
|
+
from mana_agent.agent.verification_planner import VerificationDecision, plan_verification
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"AgentOrchestrator",
|
|
9
|
+
"TaskDecision",
|
|
10
|
+
"VerificationDecision",
|
|
11
|
+
"classify_task",
|
|
12
|
+
"plan_verification",
|
|
13
|
+
]
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
|
|
8
|
+
from mana_agent.agent.evidence_queue import EvidenceQueue
|
|
9
|
+
from mana_agent.agent.task_classifier import TaskDecision
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AgentRunPhase(str, Enum):
|
|
13
|
+
RECEIVED = "RECEIVED"
|
|
14
|
+
CLASSIFIED = "CLASSIFIED"
|
|
15
|
+
PLANNED = "PLANNED"
|
|
16
|
+
GATHERING_EVIDENCE = "GATHERING_EVIDENCE"
|
|
17
|
+
READY_TO_MUTATE = "READY_TO_MUTATE"
|
|
18
|
+
MUTATING = "MUTATING"
|
|
19
|
+
VERIFYING = "VERIFYING"
|
|
20
|
+
READY_TO_FINALIZE = "READY_TO_FINALIZE"
|
|
21
|
+
DONE = "DONE"
|
|
22
|
+
FAILED_RECOVERABLE = "FAILED_RECOVERABLE"
|
|
23
|
+
FAILED_FINAL = "FAILED_FINAL"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
ExecutionDecision = Literal[
|
|
27
|
+
"execute_next_tool",
|
|
28
|
+
"skip_tool",
|
|
29
|
+
"stop_discovery",
|
|
30
|
+
"start_mutation",
|
|
31
|
+
"run_verification",
|
|
32
|
+
"ask_user",
|
|
33
|
+
"finalize",
|
|
34
|
+
"abort_due_to_error",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(slots=True)
|
|
39
|
+
class AgentRunState:
|
|
40
|
+
phase: AgentRunPhase = AgentRunPhase.RECEIVED
|
|
41
|
+
files_read: set[str] = field(default_factory=set)
|
|
42
|
+
changed_files: set[str] = field(default_factory=set)
|
|
43
|
+
failed_tool_calls: list[str] = field(default_factory=list)
|
|
44
|
+
warnings: list[str] = field(default_factory=list)
|
|
45
|
+
verification_status: str = ""
|
|
46
|
+
loop_counters: dict[str, int] = field(default_factory=dict)
|
|
47
|
+
evidence_sufficient: bool = False
|
|
48
|
+
mutation_succeeded: bool = False
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass(frozen=True, slots=True)
|
|
52
|
+
class GateResult:
|
|
53
|
+
decision: ExecutionDecision
|
|
54
|
+
reason: str
|
|
55
|
+
next_action: str = ""
|
|
56
|
+
confidence: float = 0.8
|
|
57
|
+
warnings: tuple[str, ...] = ()
|
|
58
|
+
|
|
59
|
+
def trace_row(self, *, changed_files: list[str] | None = None) -> dict[str, Any]:
|
|
60
|
+
return {
|
|
61
|
+
"layer": "evaluation_gate",
|
|
62
|
+
"decision": self.decision,
|
|
63
|
+
"reason": self.reason,
|
|
64
|
+
"next_action": self.next_action,
|
|
65
|
+
"confidence": self.confidence,
|
|
66
|
+
"changed_files": list(changed_files or []),
|
|
67
|
+
"warnings": list(self.warnings),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class EvaluationGate:
|
|
72
|
+
def __init__(self, *, repo_root: str | Path, max_repeated_tool: int = 1, max_repeated_error: int = 1) -> None:
|
|
73
|
+
self.repo_root = Path(repo_root).resolve()
|
|
74
|
+
self.max_repeated_tool = max(1, int(max_repeated_tool))
|
|
75
|
+
self.max_repeated_error = max(1, int(max_repeated_error))
|
|
76
|
+
|
|
77
|
+
def target_section_found(self, decision: TaskDecision, *, path: str) -> bool:
|
|
78
|
+
if not decision.target_sections:
|
|
79
|
+
return True
|
|
80
|
+
target = self.repo_root / path
|
|
81
|
+
try:
|
|
82
|
+
text = target.read_text(encoding="utf-8", errors="replace")
|
|
83
|
+
except OSError:
|
|
84
|
+
return False
|
|
85
|
+
headings = {
|
|
86
|
+
" ".join(line.lstrip("#").strip().split()).lower()
|
|
87
|
+
for line in text.splitlines()
|
|
88
|
+
if line.lstrip().startswith("#")
|
|
89
|
+
}
|
|
90
|
+
return any(section.lower() in headings for section in decision.target_sections)
|
|
91
|
+
|
|
92
|
+
def evidence_is_sufficient(
|
|
93
|
+
self,
|
|
94
|
+
*,
|
|
95
|
+
decision: TaskDecision,
|
|
96
|
+
state: AgentRunState,
|
|
97
|
+
) -> bool:
|
|
98
|
+
if decision.scope == "single_file_section" and decision.target_files:
|
|
99
|
+
target = decision.target_files[0]
|
|
100
|
+
return target in state.files_read and self.target_section_found(decision, path=target)
|
|
101
|
+
if decision.scope == "single_file" and decision.target_files:
|
|
102
|
+
return decision.target_files[0] in state.files_read
|
|
103
|
+
if decision.scope == "multi_file" and decision.target_files:
|
|
104
|
+
return set(decision.target_files).issubset(state.files_read)
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
def after_tool(
|
|
108
|
+
self,
|
|
109
|
+
*,
|
|
110
|
+
decision: TaskDecision,
|
|
111
|
+
state: AgentRunState,
|
|
112
|
+
evidence_queue: EvidenceQueue,
|
|
113
|
+
tool_name: str,
|
|
114
|
+
ok: bool,
|
|
115
|
+
files_read: list[str],
|
|
116
|
+
changed_files: list[str],
|
|
117
|
+
error: str = "",
|
|
118
|
+
) -> GateResult:
|
|
119
|
+
key = f"{tool_name}:{','.join(files_read or changed_files)}"
|
|
120
|
+
state.loop_counters[key] = state.loop_counters.get(key, 0) + 1
|
|
121
|
+
state.files_read.update(files_read)
|
|
122
|
+
state.changed_files.update(changed_files)
|
|
123
|
+
if error:
|
|
124
|
+
state.failed_tool_calls.append(error)
|
|
125
|
+
if not ok and error and state.failed_tool_calls.count(error) > self.max_repeated_error:
|
|
126
|
+
state.phase = AgentRunPhase.FAILED_RECOVERABLE
|
|
127
|
+
return GateResult("abort_due_to_error", f"repeated tool error stopped: {error}", confidence=0.9)
|
|
128
|
+
if state.loop_counters[key] > self.max_repeated_tool and tool_name:
|
|
129
|
+
return GateResult("skip_tool", f"repeated tool call stopped: {tool_name}", confidence=0.9)
|
|
130
|
+
for path in files_read:
|
|
131
|
+
evidence_queue.mark_done("read", path, evidence={"source": "tool"})
|
|
132
|
+
state.evidence_sufficient = self.evidence_is_sufficient(decision=decision, state=state)
|
|
133
|
+
if state.evidence_sufficient and decision.needs_mutation:
|
|
134
|
+
skipped = evidence_queue.skip_where(
|
|
135
|
+
lambda item: item.action_type in {"search", "read"} and item.target not in decision.target_files,
|
|
136
|
+
reason="target evidence is sufficient",
|
|
137
|
+
)
|
|
138
|
+
state.phase = AgentRunPhase.READY_TO_MUTATE
|
|
139
|
+
return GateResult(
|
|
140
|
+
"start_mutation",
|
|
141
|
+
"target file was read and requested section exists; unrelated discovery is optional",
|
|
142
|
+
next_action="apply_patch",
|
|
143
|
+
confidence=0.91,
|
|
144
|
+
warnings=(f"skipped_pending_discovery:{skipped}",) if skipped else (),
|
|
145
|
+
)
|
|
146
|
+
if changed_files and decision.needs_verification:
|
|
147
|
+
state.phase = AgentRunPhase.VERIFYING
|
|
148
|
+
return GateResult("run_verification", "mutation changed files; verification is now possible")
|
|
149
|
+
if changed_files:
|
|
150
|
+
state.phase = AgentRunPhase.READY_TO_FINALIZE
|
|
151
|
+
return GateResult("finalize", "mutation changed files and no verification was required")
|
|
152
|
+
return GateResult("execute_next_tool", "continue with the next approved tool")
|
|
153
|
+
|
|
154
|
+
def before_tool(
|
|
155
|
+
self,
|
|
156
|
+
*,
|
|
157
|
+
decision: TaskDecision,
|
|
158
|
+
state: AgentRunState,
|
|
159
|
+
tool_name: str,
|
|
160
|
+
target: str = "",
|
|
161
|
+
) -> GateResult:
|
|
162
|
+
if state.evidence_sufficient and tool_name in {"repo_search", "repo_batch_search", "semantic_search", "list_files"}:
|
|
163
|
+
return GateResult("skip_tool", "evidence is already sufficient; broad discovery is skipped")
|
|
164
|
+
if (
|
|
165
|
+
state.evidence_sufficient
|
|
166
|
+
and tool_name == "read_file"
|
|
167
|
+
and decision.target_files
|
|
168
|
+
and target not in set(decision.target_files)
|
|
169
|
+
):
|
|
170
|
+
return GateResult("skip_tool", "target evidence is already sufficient; unrelated read is skipped")
|
|
171
|
+
return GateResult("execute_next_tool", "tool call is necessary for the current state")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
__all__ = ["AgentRunPhase", "AgentRunState", "EvaluationGate", "ExecutionDecision", "GateResult"]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Callable, Literal
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
EvidenceAction = Literal["search", "read", "patch", "verify", "summarize"]
|
|
8
|
+
EvidenceStatus = Literal["pending", "running", "done", "skipped", "failed"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(slots=True)
|
|
12
|
+
class EvidenceQueueItem:
|
|
13
|
+
action_type: EvidenceAction
|
|
14
|
+
target: str
|
|
15
|
+
reason: str
|
|
16
|
+
priority: int = 50
|
|
17
|
+
required: bool = False
|
|
18
|
+
status: EvidenceStatus = "pending"
|
|
19
|
+
evidence: dict[str, Any] = field(default_factory=dict)
|
|
20
|
+
changed_files: list[str] = field(default_factory=list)
|
|
21
|
+
retry_count: int = 0
|
|
22
|
+
skip_reason: str = ""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class EvidenceQueue:
|
|
26
|
+
def __init__(self, items: list[EvidenceQueueItem] | None = None) -> None:
|
|
27
|
+
self._items = list(items or [])
|
|
28
|
+
|
|
29
|
+
def add(self, item: EvidenceQueueItem) -> None:
|
|
30
|
+
self._items.append(item)
|
|
31
|
+
|
|
32
|
+
def items(self) -> list[EvidenceQueueItem]:
|
|
33
|
+
return list(self._items)
|
|
34
|
+
|
|
35
|
+
def pending(self) -> list[EvidenceQueueItem]:
|
|
36
|
+
return [item for item in self._items if item.status == "pending"]
|
|
37
|
+
|
|
38
|
+
def mark_done(self, action_type: str, target: str, *, evidence: dict[str, Any] | None = None) -> None:
|
|
39
|
+
for item in self._items:
|
|
40
|
+
if item.action_type == action_type and item.target == target and item.status in {"pending", "running"}:
|
|
41
|
+
item.status = "done"
|
|
42
|
+
item.evidence.update(evidence or {})
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
def skip_where(self, predicate: Callable[[EvidenceQueueItem], bool], *, reason: str) -> int:
|
|
46
|
+
skipped = 0
|
|
47
|
+
for item in self._items:
|
|
48
|
+
if item.status == "pending" and predicate(item):
|
|
49
|
+
item.status = "skipped"
|
|
50
|
+
item.skip_reason = reason
|
|
51
|
+
skipped += 1
|
|
52
|
+
return skipped
|
|
53
|
+
|
|
54
|
+
def trace_row(self) -> dict[str, Any]:
|
|
55
|
+
return {
|
|
56
|
+
"layer": "evidence_queue",
|
|
57
|
+
"decision": "queue_state",
|
|
58
|
+
"reason": "evidence queue snapshot",
|
|
59
|
+
"items": [
|
|
60
|
+
{
|
|
61
|
+
"action_type": item.action_type,
|
|
62
|
+
"target": item.target,
|
|
63
|
+
"required": item.required,
|
|
64
|
+
"status": item.status,
|
|
65
|
+
"skip_reason": item.skip_reason,
|
|
66
|
+
}
|
|
67
|
+
for item in self._items
|
|
68
|
+
],
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
__all__ = ["EvidenceAction", "EvidenceQueue", "EvidenceQueueItem", "EvidenceStatus"]
|
mana_agent/agent/flow.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from mana_agent.agent.selection import AgentPhase, select_task
|
|
8
|
+
from mana_agent.agent.task_context import TaskContext
|
|
9
|
+
from mana_agent.agent.verification import VerificationPlan, default_verification_plan
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
FLOW_ORDER: tuple[AgentPhase, ...] = (
|
|
13
|
+
AgentPhase.DISCOVER,
|
|
14
|
+
AgentPhase.SELECT,
|
|
15
|
+
AgentPhase.READ,
|
|
16
|
+
AgentPhase.ACT,
|
|
17
|
+
AgentPhase.VERIFY,
|
|
18
|
+
AgentPhase.SUMMARIZE,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True, slots=True)
|
|
23
|
+
class AgentFlow:
|
|
24
|
+
context: TaskContext
|
|
25
|
+
verification: VerificationPlan
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _short_goal(request: str) -> str:
|
|
29
|
+
cleaned = " ".join(str(request or "").split())
|
|
30
|
+
if not cleaned:
|
|
31
|
+
return "Handle the current user request."
|
|
32
|
+
return cleaned[:180]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _candidate_search_terms(request: str, candidate_files: tuple[str, ...]) -> tuple[str, ...]:
|
|
36
|
+
terms: list[str] = []
|
|
37
|
+
terms.extend(candidate_files[:3])
|
|
38
|
+
for match in re.findall(r"`([^`]{2,120})`", request):
|
|
39
|
+
terms.append(match.strip())
|
|
40
|
+
for match in re.findall(r"\b[A-Za-z_][A-Za-z0-9_]{2,}\b", request):
|
|
41
|
+
if match.lower() not in {"the", "and", "for", "with", "this", "that", "from", "into", "when"}:
|
|
42
|
+
terms.append(match)
|
|
43
|
+
if len(terms) >= 8:
|
|
44
|
+
break
|
|
45
|
+
seen: set[str] = set()
|
|
46
|
+
unique: list[str] = []
|
|
47
|
+
for term in terms:
|
|
48
|
+
if term and term not in seen:
|
|
49
|
+
seen.add(term)
|
|
50
|
+
unique.append(term)
|
|
51
|
+
return tuple(unique[:8])
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _done_criteria(mode: str) -> tuple[str, ...]:
|
|
55
|
+
if mode == "plan_only":
|
|
56
|
+
return (
|
|
57
|
+
"plan includes goal, requirements, implementation steps, verification, and done criteria",
|
|
58
|
+
"no repository files are edited",
|
|
59
|
+
)
|
|
60
|
+
if mode == "analyze":
|
|
61
|
+
return (
|
|
62
|
+
"analysis is backed by repository evidence",
|
|
63
|
+
"risks and recommended next steps are explicit",
|
|
64
|
+
)
|
|
65
|
+
if mode == "verify":
|
|
66
|
+
return (
|
|
67
|
+
"relevant checks are run or a concrete blocker is reported",
|
|
68
|
+
"final response includes exact command outcomes",
|
|
69
|
+
)
|
|
70
|
+
if mode == "edit":
|
|
71
|
+
return (
|
|
72
|
+
"requested code changes are implemented",
|
|
73
|
+
"changed files are reviewed",
|
|
74
|
+
"relevant verification is run or a blocker is reported",
|
|
75
|
+
)
|
|
76
|
+
return ("the user receives a direct, useful, honest answer",)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def build_agent_flow(
|
|
80
|
+
request: str,
|
|
81
|
+
*,
|
|
82
|
+
repo_root: str | Path | None = None,
|
|
83
|
+
explicit_mode: str | None = None,
|
|
84
|
+
candidate_files: tuple[str, ...] = (),
|
|
85
|
+
files_read: tuple[str, ...] = (),
|
|
86
|
+
flow_context: str | None = None,
|
|
87
|
+
) -> AgentFlow:
|
|
88
|
+
selection = select_task(
|
|
89
|
+
request,
|
|
90
|
+
explicit_mode=explicit_mode,
|
|
91
|
+
candidate_files=candidate_files,
|
|
92
|
+
files_read=files_read,
|
|
93
|
+
)
|
|
94
|
+
verification = default_verification_plan(mode=selection.mode.value, request=request)
|
|
95
|
+
context = TaskContext(
|
|
96
|
+
request=request,
|
|
97
|
+
mode=selection.mode.value,
|
|
98
|
+
phase=selection.phase,
|
|
99
|
+
goal=_short_goal(request),
|
|
100
|
+
explicit_requirements=(request.strip(),) if request.strip() else (),
|
|
101
|
+
repo_root=Path(repo_root).resolve() if repo_root is not None else None,
|
|
102
|
+
candidate_files=selection.candidate_files,
|
|
103
|
+
files_read=tuple(files_read),
|
|
104
|
+
candidate_search_terms=_candidate_search_terms(request, selection.candidate_files),
|
|
105
|
+
done_criteria=_done_criteria(selection.mode.value),
|
|
106
|
+
verification_plan=verification.commands or verification.notes,
|
|
107
|
+
flow_context=flow_context,
|
|
108
|
+
)
|
|
109
|
+
return AgentFlow(context=context, verification=verification)
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Sequence
|
|
6
|
+
|
|
7
|
+
from mana_agent.agent.evaluation_gate import AgentRunPhase, AgentRunState, EvaluationGate, GateResult
|
|
8
|
+
from mana_agent.agent.evidence_queue import EvidenceQueue, EvidenceQueueItem
|
|
9
|
+
from mana_agent.agent.task_classifier import TaskDecision, classify_task
|
|
10
|
+
from mana_agent.agent.verification_planner import VerificationDecision, plan_verification
|
|
11
|
+
from mana_agent.multi_agent.runtime.execution_scope import ExecutionScopeDecision, ScopeLevel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True, slots=True)
|
|
15
|
+
class TaskPlan:
|
|
16
|
+
decision: TaskDecision
|
|
17
|
+
evidence_queue: EvidenceQueue
|
|
18
|
+
trace: tuple[dict[str, Any], ...] = ()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PlanBuilder:
|
|
22
|
+
def build(self, decision: TaskDecision) -> TaskPlan:
|
|
23
|
+
queue = EvidenceQueue()
|
|
24
|
+
if decision.needs_file_read:
|
|
25
|
+
for path in decision.target_files:
|
|
26
|
+
queue.add(
|
|
27
|
+
EvidenceQueueItem(
|
|
28
|
+
action_type="read",
|
|
29
|
+
target=path,
|
|
30
|
+
reason="explicit target file named by the user",
|
|
31
|
+
priority=10,
|
|
32
|
+
required=True,
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
if decision.needs_repo_search:
|
|
36
|
+
queue.add(
|
|
37
|
+
EvidenceQueueItem(
|
|
38
|
+
action_type="search",
|
|
39
|
+
target="repo",
|
|
40
|
+
reason="no explicit target file was available",
|
|
41
|
+
priority=20,
|
|
42
|
+
required=True,
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
if decision.needs_mutation:
|
|
46
|
+
queue.add(
|
|
47
|
+
EvidenceQueueItem(
|
|
48
|
+
action_type="patch",
|
|
49
|
+
target=",".join(decision.target_files) or "resolved target",
|
|
50
|
+
reason="mutation is required after sufficient evidence",
|
|
51
|
+
priority=80,
|
|
52
|
+
required=True,
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
if decision.needs_verification:
|
|
56
|
+
queue.add(
|
|
57
|
+
EvidenceQueueItem(
|
|
58
|
+
action_type="verify",
|
|
59
|
+
target="changed files",
|
|
60
|
+
reason="verify the requested mutation",
|
|
61
|
+
priority=90,
|
|
62
|
+
required=True,
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
trace = (
|
|
66
|
+
{
|
|
67
|
+
"layer": "plan_builder",
|
|
68
|
+
"decision": "minimal_plan_built",
|
|
69
|
+
"reason": "explicit targets are prioritized before repository-wide discovery",
|
|
70
|
+
"next_action": "read_file" if decision.needs_file_read else "repo_search",
|
|
71
|
+
"confidence": decision.confidence,
|
|
72
|
+
},
|
|
73
|
+
queue.trace_row(),
|
|
74
|
+
)
|
|
75
|
+
return TaskPlan(decision=decision, evidence_queue=queue, trace=trace)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass(slots=True)
|
|
79
|
+
class CriticDecision:
|
|
80
|
+
progressed: bool
|
|
81
|
+
enough_evidence: bool
|
|
82
|
+
should_finalize: bool
|
|
83
|
+
reason: str
|
|
84
|
+
|
|
85
|
+
def trace_row(self) -> dict[str, Any]:
|
|
86
|
+
return {
|
|
87
|
+
"layer": "post_tool_critic",
|
|
88
|
+
"decision": "finalize" if self.should_finalize else "continue",
|
|
89
|
+
"reason": self.reason,
|
|
90
|
+
"progressed": self.progressed,
|
|
91
|
+
"enough_evidence": self.enough_evidence,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class PostToolCritic:
|
|
96
|
+
def review(self, *, gate: GateResult, state: AgentRunState) -> CriticDecision:
|
|
97
|
+
should_finalize = gate.decision == "finalize" or state.phase == AgentRunPhase.DONE
|
|
98
|
+
progressed = gate.decision in {"start_mutation", "run_verification", "finalize", "execute_next_tool"}
|
|
99
|
+
reason = gate.reason
|
|
100
|
+
return CriticDecision(
|
|
101
|
+
progressed=progressed,
|
|
102
|
+
enough_evidence=state.evidence_sufficient,
|
|
103
|
+
should_finalize=should_finalize,
|
|
104
|
+
reason=reason,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass(slots=True)
|
|
109
|
+
class AgentOrchestrator:
|
|
110
|
+
repo_root: Path
|
|
111
|
+
decision: TaskDecision
|
|
112
|
+
plan: TaskPlan
|
|
113
|
+
state: AgentRunState = field(default_factory=AgentRunState)
|
|
114
|
+
gate: EvaluationGate = field(init=False)
|
|
115
|
+
critic: PostToolCritic = field(default_factory=PostToolCritic)
|
|
116
|
+
trace: list[dict[str, Any]] = field(default_factory=list)
|
|
117
|
+
|
|
118
|
+
def __post_init__(self) -> None:
|
|
119
|
+
self.repo_root = Path(self.repo_root).resolve()
|
|
120
|
+
self.gate = EvaluationGate(repo_root=self.repo_root)
|
|
121
|
+
self.state.phase = AgentRunPhase.PLANNED
|
|
122
|
+
self.trace.append(self.decision.as_trace_row())
|
|
123
|
+
self.trace.extend(self.plan.trace)
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def start(
|
|
127
|
+
cls,
|
|
128
|
+
request: str,
|
|
129
|
+
*,
|
|
130
|
+
repo_root: str | Path,
|
|
131
|
+
target_files: Sequence[str] = (),
|
|
132
|
+
requires_edit: bool | None = None,
|
|
133
|
+
execution_scope: ExecutionScopeDecision | None = None,
|
|
134
|
+
) -> "AgentOrchestrator":
|
|
135
|
+
root = Path(repo_root).resolve()
|
|
136
|
+
if execution_scope is not None:
|
|
137
|
+
task_type = {
|
|
138
|
+
"answer": "answer_only",
|
|
139
|
+
"inspect": "repo_inspection",
|
|
140
|
+
"edit": "mutation_required",
|
|
141
|
+
"verify": "verification_only",
|
|
142
|
+
"plan": "answer_only",
|
|
143
|
+
}[execution_scope.task_type]
|
|
144
|
+
scope = {
|
|
145
|
+
ScopeLevel.DIRECT: "single_file" if len(execution_scope.explicit_target_files) == 1 else "multi_file",
|
|
146
|
+
ScopeLevel.BOUNDED: "multi_file",
|
|
147
|
+
ScopeLevel.IMPACT: "multi_file",
|
|
148
|
+
ScopeLevel.BROAD: "project_wide",
|
|
149
|
+
}[execution_scope.scope_level]
|
|
150
|
+
decision = TaskDecision(
|
|
151
|
+
task_type=task_type, # type: ignore[arg-type]
|
|
152
|
+
target_files=tuple(execution_scope.explicit_target_files),
|
|
153
|
+
mutation_intent=str(request).strip() if execution_scope.task_type == "edit" else "",
|
|
154
|
+
constraints=tuple(execution_scope.out_of_bounds),
|
|
155
|
+
needs_repo_search=execution_scope.max_search_operations > 0,
|
|
156
|
+
needs_file_read=bool(execution_scope.all_evidence_files),
|
|
157
|
+
needs_mutation=execution_scope.task_type == "edit",
|
|
158
|
+
needs_verification=execution_scope.verification_strategy != "none",
|
|
159
|
+
scope=scope, # type: ignore[arg-type]
|
|
160
|
+
confidence=execution_scope.confidence,
|
|
161
|
+
)
|
|
162
|
+
else:
|
|
163
|
+
decision = classify_task(request, repo_root=root, target_files=target_files, requires_edit=requires_edit)
|
|
164
|
+
plan = PlanBuilder().build(decision)
|
|
165
|
+
return cls(repo_root=root, decision=decision, plan=plan)
|
|
166
|
+
|
|
167
|
+
def before_tool(self, *, tool_name: str, target: str = "") -> GateResult:
|
|
168
|
+
result = self.gate.before_tool(decision=self.decision, state=self.state, tool_name=tool_name, target=target)
|
|
169
|
+
self.trace.append(result.trace_row(changed_files=sorted(self.state.changed_files)))
|
|
170
|
+
return result
|
|
171
|
+
|
|
172
|
+
def after_tool(
|
|
173
|
+
self,
|
|
174
|
+
*,
|
|
175
|
+
tool_name: str,
|
|
176
|
+
ok: bool,
|
|
177
|
+
files_read: list[str],
|
|
178
|
+
changed_files: list[str],
|
|
179
|
+
error: str = "",
|
|
180
|
+
) -> GateResult:
|
|
181
|
+
result = self.gate.after_tool(
|
|
182
|
+
decision=self.decision,
|
|
183
|
+
state=self.state,
|
|
184
|
+
evidence_queue=self.plan.evidence_queue,
|
|
185
|
+
tool_name=tool_name,
|
|
186
|
+
ok=ok,
|
|
187
|
+
files_read=files_read,
|
|
188
|
+
changed_files=changed_files,
|
|
189
|
+
error=error,
|
|
190
|
+
)
|
|
191
|
+
self.trace.append(result.trace_row(changed_files=sorted(self.state.changed_files)))
|
|
192
|
+
self.trace.append(self.critic.review(gate=result, state=self.state).trace_row())
|
|
193
|
+
return result
|
|
194
|
+
|
|
195
|
+
def verification_decision(self, *, changed_files: Sequence[str], core_agent_change: bool = False) -> VerificationDecision:
|
|
196
|
+
decision = plan_verification(changed_files=changed_files, core_agent_change=core_agent_change)
|
|
197
|
+
self.trace.append(decision.trace_row())
|
|
198
|
+
return decision
|
|
199
|
+
|
|
200
|
+
def finalize_trace(self) -> None:
|
|
201
|
+
self.trace.append(
|
|
202
|
+
{
|
|
203
|
+
"layer": "final_response_builder",
|
|
204
|
+
"decision": "finalize",
|
|
205
|
+
"reason": "authoritative queue state is ready for the final response",
|
|
206
|
+
"changed_files": sorted(self.state.changed_files),
|
|
207
|
+
"warnings": list(self.state.warnings),
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
__all__ = ["AgentOrchestrator", "AgentRunState", "PlanBuilder", "PostToolCritic", "TaskPlan"]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
from mana_agent.multi_agent.runtime.auto_chat import AutoChatMode, classify_auto_chat_intent
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AgentPhase(str, Enum):
|
|
10
|
+
DISCOVER = "discover"
|
|
11
|
+
SELECT = "select"
|
|
12
|
+
READ = "read"
|
|
13
|
+
ACT = "act"
|
|
14
|
+
VERIFY = "verify"
|
|
15
|
+
SUMMARIZE = "summarize"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True, slots=True)
|
|
19
|
+
class SelectionResult:
|
|
20
|
+
mode: AutoChatMode
|
|
21
|
+
phase: AgentPhase
|
|
22
|
+
candidate_files: tuple[str, ...] = ()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def select_mode(request: str, *, explicit_mode: str | None = None) -> AutoChatMode:
|
|
26
|
+
"""Resolve the task mode using the same bounded classifier as chat."""
|
|
27
|
+
if explicit_mode:
|
|
28
|
+
normalized = str(explicit_mode).strip().lower().replace("-", "_")
|
|
29
|
+
for mode in AutoChatMode:
|
|
30
|
+
if normalized in {mode.value, mode.name.lower()}:
|
|
31
|
+
return mode
|
|
32
|
+
return classify_auto_chat_intent(request)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def select_initial_phase(*, candidate_files: tuple[str, ...] = (), files_read: tuple[str, ...] = ()) -> AgentPhase:
|
|
36
|
+
"""Choose the prompt-visible phase from known task context."""
|
|
37
|
+
if files_read:
|
|
38
|
+
return AgentPhase.ACT
|
|
39
|
+
if candidate_files:
|
|
40
|
+
return AgentPhase.READ
|
|
41
|
+
return AgentPhase.DISCOVER
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def select_task(
|
|
45
|
+
request: str,
|
|
46
|
+
*,
|
|
47
|
+
explicit_mode: str | None = None,
|
|
48
|
+
candidate_files: tuple[str, ...] = (),
|
|
49
|
+
files_read: tuple[str, ...] = (),
|
|
50
|
+
) -> SelectionResult:
|
|
51
|
+
return SelectionResult(
|
|
52
|
+
mode=select_mode(request, explicit_mode=explicit_mode),
|
|
53
|
+
phase=select_initial_phase(candidate_files=candidate_files, files_read=files_read),
|
|
54
|
+
candidate_files=tuple(candidate_files),
|
|
55
|
+
)
|
|
56
|
+
|