novacode-cli 1.2.0__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.
- novacode_cli/__init__.py +29 -0
- novacode_cli/__main__.py +6 -0
- novacode_cli/_lazy_heavy.py +99 -0
- novacode_cli/_version.py +3 -0
- novacode_cli/agent_stream.py +178 -0
- novacode_cli/agents/agent_file.py +250 -0
- novacode_cli/agents/agent_mailbox.py +120 -0
- novacode_cli/agents/approval_decisions.py +146 -0
- novacode_cli/agents/arg_repair.py +196 -0
- novacode_cli/agents/async_agents/__init__.py +6 -0
- novacode_cli/agents/async_agents/_config.py +213 -0
- novacode_cli/agents/async_agents/_mcp_tools.py +192 -0
- novacode_cli/agents/async_agents/_specialist.py +84 -0
- novacode_cli/agents/async_agents/_test_commands.py +89 -0
- novacode_cli/agents/async_agents/_tools.py +432 -0
- novacode_cli/agents/async_agents/general_purpose.py +42 -0
- novacode_cli/agents/async_agents/langgraph.json +9 -0
- novacode_cli/agents/async_agents/research.py +22 -0
- novacode_cli/agents/async_context.py +303 -0
- novacode_cli/agents/async_on_demand.py +52 -0
- novacode_cli/agents/async_task_errors.py +75 -0
- novacode_cli/agents/async_workspace.py +27 -0
- novacode_cli/agents/attribution.py +165 -0
- novacode_cli/agents/browser_mode.py +119 -0
- novacode_cli/agents/btw_agent.py +71 -0
- novacode_cli/agents/caveman.py +352 -0
- novacode_cli/agents/caveman_support.py +370 -0
- novacode_cli/agents/code_mode.py +774 -0
- novacode_cli/agents/code_runtime.py +160 -0
- novacode_cli/agents/context_compaction.py +1339 -0
- novacode_cli/agents/core_agent.py +2710 -0
- novacode_cli/agents/default_subagents/__init__.py +24 -0
- novacode_cli/agents/default_subagents/async_subagents.py +296 -0
- novacode_cli/agents/default_subagents/builtin/browser-automation-agent.md +110 -0
- novacode_cli/agents/default_subagents/builtin/bug-fix-agent.md +22 -0
- novacode_cli/agents/default_subagents/builtin/bug-investigation-agent.md +42 -0
- novacode_cli/agents/default_subagents/builtin/code-explorer.md +37 -0
- novacode_cli/agents/default_subagents/builtin/code-review-agent.md +33 -0
- novacode_cli/agents/default_subagents/builtin/dependency-audit-agent.md +34 -0
- novacode_cli/agents/default_subagents/builtin/documentation-update-agent.md +46 -0
- novacode_cli/agents/default_subagents/builtin/performance-audit-agent.md +41 -0
- novacode_cli/agents/default_subagents/builtin/plan-scout-agent.md +64 -0
- novacode_cli/agents/default_subagents/builtin/refactoring-agent.md +35 -0
- novacode_cli/agents/default_subagents/builtin/refactoring-specialist-agent.md +175 -0
- novacode_cli/agents/default_subagents/builtin/security-audit-agent.md +96 -0
- novacode_cli/agents/default_subagents/builtin/test-generation-agent.md +33 -0
- novacode_cli/agents/default_subagents/builtin/test-runner-agent.md +55 -0
- novacode_cli/agents/default_subagents/prompt.py +88 -0
- novacode_cli/agents/default_subagents/subagents.py +138 -0
- novacode_cli/agents/dream_agent.py +93 -0
- novacode_cli/agents/edit_diagnostics.py +362 -0
- novacode_cli/agents/edit_fallback.py +62 -0
- novacode_cli/agents/handoff.py +166 -0
- novacode_cli/agents/invocation.py +152 -0
- novacode_cli/agents/model_budget.py +42 -0
- novacode_cli/agents/model_retry.py +113 -0
- novacode_cli/agents/model_router.py +420 -0
- novacode_cli/agents/openai_decisions.py +186 -0
- novacode_cli/agents/plan_agent/__init__.py +12 -0
- novacode_cli/agents/plan_agent/plan_agent.py +357 -0
- novacode_cli/agents/plan_agent/plan_mode_middleware.py +64 -0
- novacode_cli/agents/plan_execution.py +237 -0
- novacode_cli/agents/ponytail.py +66 -0
- novacode_cli/agents/prime.py +274 -0
- novacode_cli/agents/provider_caching.py +99 -0
- novacode_cli/agents/server_launcher.py +904 -0
- novacode_cli/agents/strict_tools.py +384 -0
- novacode_cli/agents/task_discipline.py +294 -0
- novacode_cli/agents/tool_call_repair.py +290 -0
- novacode_cli/agents/tool_offload.py +291 -0
- novacode_cli/agents/tool_search.py +806 -0
- novacode_cli/agents/tool_search_variants.py +106 -0
- novacode_cli/agents/tool_verdicts.py +1194 -0
- novacode_cli/agents/user_async_agents.py +312 -0
- novacode_cli/artifacts/__init__.py +10 -0
- novacode_cli/artifacts/registry.py +278 -0
- novacode_cli/artifacts/server.py +426 -0
- novacode_cli/assets/Nova.png +0 -0
- novacode_cli/assets/nova-avatar.png +0 -0
- novacode_cli/audio/__init__.py +301 -0
- novacode_cli/audio/capture.py +137 -0
- novacode_cli/audio/downloads.py +273 -0
- novacode_cli/audio/pipeline.py +391 -0
- novacode_cli/audio/playback.py +87 -0
- novacode_cli/audio/providers.py +208 -0
- novacode_cli/audio/speakable.py +78 -0
- novacode_cli/audio/stt.py +132 -0
- novacode_cli/audio/stt_deepgram.py +69 -0
- novacode_cli/audio/stt_elevenlabs.py +89 -0
- novacode_cli/audio/stt_parakeet.py +94 -0
- novacode_cli/audio/stt_whistle.py +61 -0
- novacode_cli/audio/summarize.py +92 -0
- novacode_cli/audio/tts.py +85 -0
- novacode_cli/audio/tts_kokoro.py +73 -0
- novacode_cli/audio/tts_orpheus.py +180 -0
- novacode_cli/audio/tts_pocket.py +131 -0
- novacode_cli/audio/tts_sano.py +69 -0
- novacode_cli/audio/vad.py +145 -0
- novacode_cli/backends/__init__.py +17 -0
- novacode_cli/backends/filesystem.py +894 -0
- novacode_cli/backends/native_search.py +92 -0
- novacode_cli/bootstrap/__init__.py +9 -0
- novacode_cli/bootstrap/env_snapshot.py +240 -0
- novacode_cli/bootstrap/steering.py +464 -0
- novacode_cli/bootstrap/vision_router.py +604 -0
- novacode_cli/brand.py +362 -0
- novacode_cli/cli_args.py +718 -0
- novacode_cli/commands/__init__.py +237 -0
- novacode_cli/commands/agents_commands.py +231 -0
- novacode_cli/commands/auth_handler.py +40 -0
- novacode_cli/commands/browser_use_handler.py +75 -0
- novacode_cli/commands/cache_handler.py +32 -0
- novacode_cli/commands/chat_handler.py +1357 -0
- novacode_cli/commands/ci_fix.py +171 -0
- novacode_cli/commands/claude_plan.py +331 -0
- novacode_cli/commands/codex_plan.py +195 -0
- novacode_cli/commands/commands.py +1113 -0
- novacode_cli/commands/create_handler.py +124 -0
- novacode_cli/commands/create_server.py +1887 -0
- novacode_cli/commands/cron_handler.py +119 -0
- novacode_cli/commands/dream_handler.py +203 -0
- novacode_cli/commands/effort_handler.py +65 -0
- novacode_cli/commands/evolution_handler.py +214 -0
- novacode_cli/commands/file_commands.py +381 -0
- novacode_cli/commands/handoff_handler.py +68 -0
- novacode_cli/commands/hooks_handler.py +269 -0
- novacode_cli/commands/init_handler.py +146 -0
- novacode_cli/commands/learning_handler.py +160 -0
- novacode_cli/commands/lite_mode_handler.py +76 -0
- novacode_cli/commands/log_commands.py +363 -0
- novacode_cli/commands/mcp_handler.py +29 -0
- novacode_cli/commands/model_handler.py +29 -0
- novacode_cli/commands/notifications_handler.py +158 -0
- novacode_cli/commands/onboard_handler.py +121 -0
- novacode_cli/commands/plan_handler.py +440 -0
- novacode_cli/commands/planner_usage.py +250 -0
- novacode_cli/commands/plugin_install_handler.py +38 -0
- novacode_cli/commands/plugins_handler.py +35 -0
- novacode_cli/commands/prime_handler.py +455 -0
- novacode_cli/commands/prompt_handler.py +149 -0
- novacode_cli/commands/provider_usage.py +160 -0
- novacode_cli/commands/ralph_events.py +106 -0
- novacode_cli/commands/ralph_handler.py +1293 -0
- novacode_cli/commands/refine_handler.py +213 -0
- novacode_cli/commands/research_handler.py +202 -0
- novacode_cli/commands/rootapp_remote.py +83 -0
- novacode_cli/commands/router_handler.py +36 -0
- novacode_cli/commands/server_commands.py +249 -0
- novacode_cli/commands/session_commands.py +203 -0
- novacode_cli/commands/share_handler.py +450 -0
- novacode_cli/commands/side_commands.py +366 -0
- novacode_cli/commands/skill_invoke.py +420 -0
- novacode_cli/commands/skills_commands.py +48 -0
- novacode_cli/commands/trace_handler.py +322 -0
- novacode_cli/commands/trello_handler.py +226 -0
- novacode_cli/commands/trello_remote.py +177 -0
- novacode_cli/commands/trello_server.py +1214 -0
- novacode_cli/commands/vision_handler.py +127 -0
- novacode_cli/commands/voice_handler.py +447 -0
- novacode_cli/commands/webhook_handler.py +134 -0
- novacode_cli/commands/wiki_commands.py +227 -0
- novacode_cli/compaction.py +753 -0
- novacode_cli/computation_cache.py +159 -0
- novacode_cli/computer/__init__.py +1 -0
- novacode_cli/computer/persistent.py +234 -0
- novacode_cli/computer/preview.py +253 -0
- novacode_cli/computer/runtime.py +474 -0
- novacode_cli/computer/windows_console.py +152 -0
- novacode_cli/computer/windows_qemu.py +211 -0
- novacode_cli/config/browser_mode.py +159 -0
- novacode_cli/config/catalog_models.py +95 -0
- novacode_cli/config/config.py +1388 -0
- novacode_cli/config/credentials.py +540 -0
- novacode_cli/config/google_oauth_auth.py +281 -0
- novacode_cli/config/model_capabilities.py +343 -0
- novacode_cli/config/model_catalog.py +275 -0
- novacode_cli/config/model_create.py +1325 -0
- novacode_cli/config/model_manager.py +695 -0
- novacode_cli/config/nova_config.py +1283 -0
- novacode_cli/config/openai_chatgpt_auth.py +384 -0
- novacode_cli/config/opencode_gateway.py +25 -0
- novacode_cli/config/plan_mode.py +188 -0
- novacode_cli/config/provider_auth.py +438 -0
- novacode_cli/config/provider_catalog.json +5127 -0
- novacode_cli/config/provider_registry.py +38 -0
- novacode_cli/config/provider_transports.py +258 -0
- novacode_cli/config/role_models.py +175 -0
- novacode_cli/context/__init__.py +50 -0
- novacode_cli/context/_analysis.py +799 -0
- novacode_cli/context/_budget.py +116 -0
- novacode_cli/context/_conversation.py +73 -0
- novacode_cli/context/_dynamic.py +264 -0
- novacode_cli/context/_model_config.py +380 -0
- novacode_cli/context/_models_dev.py +99 -0
- novacode_cli/context/archives.py +248 -0
- novacode_cli/context/estimates.py +65 -0
- novacode_cli/context/history.py +17 -0
- novacode_cli/context/knowledge_cache.py +292 -0
- novacode_cli/context/manager.py +101 -0
- novacode_cli/context/observations.py +65 -0
- novacode_cli/context/pressure.py +168 -0
- novacode_cli/context/provider_accounting.py +109 -0
- novacode_cli/context/snapshot.py +493 -0
- novacode_cli/context/strategies.py +279 -0
- novacode_cli/context/task_progress.py +122 -0
- novacode_cli/core/__init__.py +0 -0
- novacode_cli/core/agent_loop.py +1821 -0
- novacode_cli/core/autonomous_loop.py +277 -0
- novacode_cli/core/code_approvals.py +57 -0
- novacode_cli/core/input_preparation.py +276 -0
- novacode_cli/core/reasoning.py +67 -0
- novacode_cli/core/streaming.py +326 -0
- novacode_cli/core/subagent_tasks.py +696 -0
- novacode_cli/core/subagent_tracking.py +235 -0
- novacode_cli/core/tool_results.py +116 -0
- novacode_cli/core/verification_loop.py +272 -0
- novacode_cli/council.py +595 -0
- novacode_cli/council_consensus.py +115 -0
- novacode_cli/council_decision.py +157 -0
- novacode_cli/council_planner.py +1421 -0
- novacode_cli/council_settings.py +208 -0
- novacode_cli/council_web_settings.py +41 -0
- novacode_cli/council_workspace.py +56 -0
- novacode_cli/cowork/__init__.py +10 -0
- novacode_cli/cowork/broker_middleware.py +162 -0
- novacode_cli/cowork/launcher.py +102 -0
- novacode_cli/cowork/policy.py +284 -0
- novacode_cli/cowork/ui.py +826 -0
- novacode_cli/daemons/__init__.py +1 -0
- novacode_cli/daemons/registry.py +342 -0
- novacode_cli/desktop_notifications.py +302 -0
- novacode_cli/doctor.py +251 -0
- novacode_cli/embeddings.py +109 -0
- novacode_cli/entrypoint.py +56 -0
- novacode_cli/errors/__init__.py +19 -0
- novacode_cli/errors/handlers.py +648 -0
- novacode_cli/errors/provider_errors.py +249 -0
- novacode_cli/errors/taxonomy.py +80 -0
- novacode_cli/events.py +179 -0
- novacode_cli/file_ops.py +714 -0
- novacode_cli/git_safety.py +281 -0
- novacode_cli/handoff.py +383 -0
- novacode_cli/headless/__init__.py +18 -0
- novacode_cli/headless/input.py +58 -0
- novacode_cli/headless/output.py +224 -0
- novacode_cli/headless/pipe.py +532 -0
- novacode_cli/headless/runner.py +461 -0
- novacode_cli/hermes/__init__.py +39 -0
- novacode_cli/hermes/activation.py +297 -0
- novacode_cli/hermes/config.py +129 -0
- novacode_cli/hermes/curator.py +199 -0
- novacode_cli/hermes/evolution.py +374 -0
- novacode_cli/hermes/ledger.py +364 -0
- novacode_cli/hermes/memory_tiers.py +969 -0
- novacode_cli/hermes/middleware.py +678 -0
- novacode_cli/hermes/overview.py +174 -0
- novacode_cli/hermes/prompt_evolution.py +503 -0
- novacode_cli/hermes/refine_loop.py +643 -0
- novacode_cli/hermes/refinement_log.py +190 -0
- novacode_cli/hermes/review.py +674 -0
- novacode_cli/hermes/skill_debate.py +135 -0
- novacode_cli/hermes/skill_discovery.py +450 -0
- novacode_cli/hermes/skill_manager.py +335 -0
- novacode_cli/hermes/skill_proposals.py +43 -0
- novacode_cli/hermes/test_gaming.py +119 -0
- novacode_cli/hermes/tracker.py +435 -0
- novacode_cli/hermes/tuner.py +206 -0
- novacode_cli/hermes/verification_memory.py +134 -0
- novacode_cli/hermes/verifier.py +277 -0
- novacode_cli/hitl/__init__.py +22 -0
- novacode_cli/hitl/interrupts.py +493 -0
- novacode_cli/hooks.py +359 -0
- novacode_cli/image_utils.py +517 -0
- novacode_cli/init/__init__.py +7 -0
- novacode_cli/init/events.py +43 -0
- novacode_cli/input_utils.py +526 -0
- novacode_cli/install_commands.py +87 -0
- novacode_cli/integrations/__init__.py +82 -0
- novacode_cli/integrations/daytona.py +122 -0
- novacode_cli/integrations/docker.py +309 -0
- novacode_cli/integrations/host_path.py +111 -0
- novacode_cli/integrations/langsmith.py +151 -0
- novacode_cli/integrations/modal.py +134 -0
- novacode_cli/integrations/runloop.py +130 -0
- novacode_cli/integrations/sandbox_factory.py +1388 -0
- novacode_cli/integrations/sandbox_registry.py +312 -0
- novacode_cli/integrations/workdir_backend.py +589 -0
- novacode_cli/main.py +2078 -0
- novacode_cli/mcp/__init__.py +73 -0
- novacode_cli/mcp/auth.py +189 -0
- novacode_cli/mcp/client.py +571 -0
- novacode_cli/mcp/code_mode.py +5 -0
- novacode_cli/mcp/code_runtime.py +5 -0
- novacode_cli/mcp/commands.py +902 -0
- novacode_cli/mcp/config.py +406 -0
- novacode_cli/mcp/generic.py +128 -0
- novacode_cli/mcp/middleware.py +993 -0
- novacode_cli/mcp/presets.py +367 -0
- novacode_cli/memory/__init__.py +1 -0
- novacode_cli/memory/agent_memory.py +1312 -0
- novacode_cli/memory/builtin_lessons.py +60 -0
- novacode_cli/memory/dream_gates.py +192 -0
- novacode_cli/memory/limits.py +59 -0
- novacode_cli/memory/parsed_cache.py +106 -0
- novacode_cli/memory/recall_stats.py +97 -0
- novacode_cli/memory/semantic.py +116 -0
- novacode_cli/memory/store.py +335 -0
- novacode_cli/memory/wiki.py +274 -0
- novacode_cli/migrate.py +185 -0
- novacode_cli/onboarding.py +649 -0
- novacode_cli/onboarding_check.py +119 -0
- novacode_cli/path_approval.py +297 -0
- novacode_cli/plan_archive.py +148 -0
- novacode_cli/plugins/__init__.py +121 -0
- novacode_cli/plugins/claude_plugins.py +538 -0
- novacode_cli/plugins/cli.py +168 -0
- novacode_cli/plugins/components.py +413 -0
- novacode_cli/plugins/hooks.py +356 -0
- novacode_cli/plugins/loader.py +317 -0
- novacode_cli/plugins/marketplaces.py +199 -0
- novacode_cli/plugins/storage.py +70 -0
- novacode_cli/prime.py +759 -0
- novacode_cli/prime_factory/__init__.py +15 -0
- novacode_cli/prime_factory/container.py +198 -0
- novacode_cli/prime_factory/controller.py +565 -0
- novacode_cli/prime_factory/knowledge.py +261 -0
- novacode_cli/prime_factory/publication.py +218 -0
- novacode_cli/prime_factory/recovery.py +50 -0
- novacode_cli/prime_factory/research.py +370 -0
- novacode_cli/prime_factory/runner.py +608 -0
- novacode_cli/prime_factory/runtime.py +261 -0
- novacode_cli/prime_factory/state.py +201 -0
- novacode_cli/prime_factory/store.py +128 -0
- novacode_cli/prime_factory/worker.py +129 -0
- novacode_cli/prime_factory/workflow.py +899 -0
- novacode_cli/prime_factory/workspace.py +317 -0
- novacode_cli/prime_memory.py +290 -0
- novacode_cli/process_manager.py +585 -0
- novacode_cli/project_onboard.py +537 -0
- novacode_cli/prompts/System_Prompt_Nova.jinja +23 -0
- novacode_cli/prompts/__init__.py +168 -0
- novacode_cli/prompts/agent_definitions.jinja +11 -0
- novacode_cli/prompts/agent_generation.jinja +125 -0
- novacode_cli/prompts/async_agents/general_purpose.jinja +22 -0
- novacode_cli/prompts/claude_plan_mode.jinja +12 -0
- novacode_cli/prompts/coding_workflow.jinja +18 -0
- novacode_cli/prompts/computer_use.jinja +17 -0
- novacode_cli/prompts/core_agent_system.jinja +81 -0
- novacode_cli/prompts/delegation.jinja +15 -0
- novacode_cli/prompts/desktop_power.jinja +4 -0
- novacode_cli/prompts/file_paths.jinja +15 -0
- novacode_cli/prompts/file_tracker.jinja +23 -0
- novacode_cli/prompts/init_agents_md.jinja +56 -0
- novacode_cli/prompts/init_exploration.jinja +174 -0
- novacode_cli/prompts/init_semantic_orchestrate.jinja +37 -0
- novacode_cli/prompts/instruction_scope.jinja +5 -0
- novacode_cli/prompts/learnings_extraction.jinja +45 -0
- novacode_cli/prompts/longterm_memory.jinja +54 -0
- novacode_cli/prompts/mcp.jinja +84 -0
- novacode_cli/prompts/memory_consolidation.jinja +40 -0
- novacode_cli/prompts/nova_evolution.jinja +73 -0
- novacode_cli/prompts/nova_review.jinja +158 -0
- novacode_cli/prompts/nova_skill_debate.jinja +36 -0
- novacode_cli/prompts/nova_standing_instructions.jinja +26 -0
- novacode_cli/prompts/nova_verify.jinja +100 -0
- novacode_cli/prompts/onboard_dashboard.jinja +378 -0
- novacode_cli/prompts/plan_agent.jinja +87 -0
- novacode_cli/prompts/plan_scouts.jinja +5 -0
- novacode_cli/prompts/plan_storage.jinja +8 -0
- novacode_cli/prompts/planning.jinja +82 -0
- novacode_cli/prompts/ponytail/LICENSE +21 -0
- novacode_cli/prompts/ponytail/UPSTREAM.json +15 -0
- novacode_cli/prompts/ponytail/hooks/ponytail-config.js +175 -0
- novacode_cli/prompts/ponytail/hooks/ponytail-instructions.js +98 -0
- novacode_cli/prompts/ponytail/skills/ponytail/SKILL.md +52 -0
- novacode_cli/prompts/ponytail/skills/ponytail-audit/SKILL.md +103 -0
- novacode_cli/prompts/ponytail/skills/ponytail-debt/SKILL.md +44 -0
- novacode_cli/prompts/ponytail/skills/ponytail-gain/SKILL.md +52 -0
- novacode_cli/prompts/ponytail/skills/ponytail-help/SKILL.md +76 -0
- novacode_cli/prompts/ponytail/skills/ponytail-review/SKILL.md +101 -0
- novacode_cli/prompts/prompt_evolution.jinja +27 -0
- novacode_cli/prompts/ralph_iteration.jinja +75 -0
- novacode_cli/prompts/refine_gate.jinja +31 -0
- novacode_cli/prompts/refine_plan.jinja +64 -0
- novacode_cli/prompts/refine_review.jinja +34 -0
- novacode_cli/prompts/research_delivery.jinja +9 -0
- novacode_cli/prompts/research_swarm.jinja +130 -0
- novacode_cli/prompts/response_style.jinja +5 -0
- novacode_cli/prompts/session_summarization.jinja +86 -0
- novacode_cli/prompts/skill_creation.jinja +85 -0
- novacode_cli/prompts/subagents/fact_checker.jinja +48 -0
- novacode_cli/prompts/subagents/financial_analyst.jinja +50 -0
- novacode_cli/prompts/subagents/literature_reviewer.jinja +52 -0
- novacode_cli/prompts/subagents/market_analyst.jinja +48 -0
- novacode_cli/prompts/subagents/research_synthesizer.jinja +57 -0
- novacode_cli/prompts/subagents/technical_researcher.jinja +56 -0
- novacode_cli/prompts/subagents/web_researcher.jinja +46 -0
- novacode_cli/prompts/summarization.jinja +107 -0
- novacode_cli/prompts/summarization_answers.jinja +23 -0
- novacode_cli/prompts/summarization_questions.jinja +32 -0
- novacode_cli/prompts/vision_caption.jinja +21 -0
- novacode_cli/prompts/voice_summary.jinja +65 -0
- novacode_cli/prompts/wiki_file.jinja +57 -0
- novacode_cli/prompts/wiki_ingest.jinja +87 -0
- novacode_cli/recovery.py +383 -0
- novacode_cli/remote/__init__.py +45 -0
- novacode_cli/remote/approvals.py +282 -0
- novacode_cli/remote/async_task_clients.py +97 -0
- novacode_cli/remote/async_task_watcher.py +240 -0
- novacode_cli/remote/bridge.py +905 -0
- novacode_cli/remote/command_menu.py +26 -0
- novacode_cli/remote/command_ui.py +243 -0
- novacode_cli/remote/config.py +199 -0
- novacode_cli/remote/discord_bridge.py +530 -0
- novacode_cli/remote/discord_format.py +31 -0
- novacode_cli/remote/discord_threads.py +225 -0
- novacode_cli/remote/images.py +97 -0
- novacode_cli/remote/processor.py +601 -0
- novacode_cli/remote/rootapp_bot/bot.cjs +16 -0
- novacode_cli/remote/rootapp_bot/package-lock.json +2683 -0
- novacode_cli/remote/rootapp_bot/package.json +18 -0
- novacode_cli/remote/rootapp_bot/transport.cjs +80 -0
- novacode_cli/remote/rootapp_bridge.py +370 -0
- novacode_cli/remote/rootapp_setup.py +89 -0
- novacode_cli/remote/routing.py +103 -0
- novacode_cli/remote/scheduler.py +302 -0
- novacode_cli/remote/session.py +164 -0
- novacode_cli/remote/status.py +411 -0
- novacode_cli/remote/streaming.py +188 -0
- novacode_cli/remote/telegram_bridge.py +1118 -0
- novacode_cli/remote/telegram_format.py +215 -0
- novacode_cli/remote/telegram_hub.py +256 -0
- novacode_cli/remote/voice_notes.py +120 -0
- novacode_cli/remote/webhook_adapters.py +167 -0
- novacode_cli/remote/webhook_server.py +154 -0
- novacode_cli/rewind.py +150 -0
- novacode_cli/rooms/__init__.py +1 -0
- novacode_cli/rooms/bridge.py +86 -0
- novacode_cli/rooms/cli.py +231 -0
- novacode_cli/rooms/nova.py +227 -0
- novacode_cli/rooms/runtime.py +299 -0
- novacode_cli/rooms/store.py +538 -0
- novacode_cli/rooms/workspace.py +94 -0
- novacode_cli/security/__init__.py +29 -0
- novacode_cli/security/approval_context.py +45 -0
- novacode_cli/security/delegated_approval.py +68 -0
- novacode_cli/security/local_http.py +85 -0
- novacode_cli/security/middleware.py +87 -0
- novacode_cli/security/paths.py +35 -0
- novacode_cli/security/policy.py +535 -0
- novacode_cli/security/policy_writer.py +90 -0
- novacode_cli/security/prompt_injection.py +203 -0
- novacode_cli/security/remember.py +81 -0
- novacode_cli/security/rule_synthesis.py +116 -0
- novacode_cli/security/secret_files.py +61 -0
- novacode_cli/security/session_allow.py +114 -0
- novacode_cli/security/unicode_security.py +516 -0
- novacode_cli/security/validator.py +173 -0
- novacode_cli/server/__init__.py +9 -0
- novacode_cli/server/app.py +632 -0
- novacode_cli/server/event_adapter.py +189 -0
- novacode_cli/server/session_manager.py +90 -0
- novacode_cli/server_main.py +64 -0
- novacode_cli/server_runner/dev_server.py +530 -0
- novacode_cli/server_runner/test_runner.py +470 -0
- novacode_cli/session/__init__.py +1 -0
- novacode_cli/session/adapters/__init__.py +240 -0
- novacode_cli/session/adapters/claude_code.py +102 -0
- novacode_cli/session/adapters/codex.py +77 -0
- novacode_cli/session/adapters/harness_native.py +75 -0
- novacode_cli/session/checkpoint_coordinator.py +448 -0
- novacode_cli/session/context_restore.py +25 -0
- novacode_cli/session/history_export.py +228 -0
- novacode_cli/session/history_tree.py +174 -0
- novacode_cli/session/imported_context.py +267 -0
- novacode_cli/session/runtime_identity.py +32 -0
- novacode_cli/session/session_persistence.py +1168 -0
- novacode_cli/session/session_prompt_builder.py +279 -0
- novacode_cli/session/session_restore.py +351 -0
- novacode_cli/session/session_summarization.py +20 -0
- novacode_cli/session/transcript_journal.py +153 -0
- novacode_cli/sessions/__init__.py +11 -0
- novacode_cli/sessions/gateway.py +84 -0
- novacode_cli/sessions/launch.py +51 -0
- novacode_cli/sessions/lease.py +213 -0
- novacode_cli/sessions/messaging.py +255 -0
- novacode_cli/sessions/protocol.py +216 -0
- novacode_cli/sessions/resume.py +31 -0
- novacode_cli/sessions/supervisor.py +498 -0
- novacode_cli/sessions/tasks.py +73 -0
- novacode_cli/sessions/tui_messaging.py +413 -0
- novacode_cli/sessions/worker.py +652 -0
- novacode_cli/sessions/worktree.py +262 -0
- novacode_cli/shell/__init__.py +27 -0
- novacode_cli/shell/jobs.py +475 -0
- novacode_cli/shell/middleware.py +1568 -0
- novacode_cli/shell/os_sandbox.py +205 -0
- novacode_cli/shell/output.py +181 -0
- novacode_cli/shell/patterns.py +275 -0
- novacode_cli/shell/processes.py +87 -0
- novacode_cli/shell/utils.py +236 -0
- novacode_cli/skills/__init__.py +26 -0
- novacode_cli/skills/builtin/handoff/SKILL.md +15 -0
- novacode_cli/skills/curation_middleware.py +69 -0
- novacode_cli/skills/executable.py +161 -0
- novacode_cli/skills/libraries.py +102 -0
- novacode_cli/skills/load.py +248 -0
- novacode_cli/skills/prune.py +215 -0
- novacode_cli/skills/refreshing_middleware.py +896 -0
- novacode_cli/skills/retrieval.py +248 -0
- novacode_cli/skills/runtime.py +118 -0
- novacode_cli/skills/schema.py +254 -0
- novacode_cli/skills/skill_creation.py +1549 -0
- novacode_cli/skills/skill_lock.py +75 -0
- novacode_cli/skills/skill_system_prompt.py +16 -0
- novacode_cli/skills/skills_prefs.py +126 -0
- novacode_cli/skills/tool_resolver.py +31 -0
- novacode_cli/skills/upstream.py +50 -0
- novacode_cli/skills/versioning.py +291 -0
- novacode_cli/states/Session.py +847 -0
- novacode_cli/states/__init__.py +31 -0
- novacode_cli/states/slices/__init__.py +17 -0
- novacode_cli/states/slices/agent_runtime.py +249 -0
- novacode_cli/states/slices/background_tasks.py +21 -0
- novacode_cli/states/slices/notifications.py +74 -0
- novacode_cli/states/slices/remote_bridge.py +56 -0
- novacode_cli/states/slices/ui_settings.py +49 -0
- novacode_cli/states/slices/wiki.py +25 -0
- novacode_cli/teammates/__init__.py +1 -0
- novacode_cli/teammates/controller.py +557 -0
- novacode_cli/teammates/graph.py +104 -0
- novacode_cli/teammates/registry.py +175 -0
- novacode_cli/teammates/service.py +254 -0
- novacode_cli/teammates/worker.py +19 -0
- novacode_cli/teams/__init__.py +1 -0
- novacode_cli/teams/adapters/__init__.py +1 -0
- novacode_cli/teams/adapters/container.py +316 -0
- novacode_cli/teams/adapters/inprocess.py +206 -0
- novacode_cli/teams/adapters/process.py +364 -0
- novacode_cli/teams/bootstrap.py +246 -0
- novacode_cli/teams/container_git.py +67 -0
- novacode_cli/teams/context.py +34 -0
- novacode_cli/teams/coordinator.py +815 -0
- novacode_cli/teams/gateway.py +200 -0
- novacode_cli/teams/host.py +64 -0
- novacode_cli/teams/legacy.py +52 -0
- novacode_cli/teams/middleware.py +176 -0
- novacode_cli/teams/models.py +104 -0
- novacode_cli/teams/output.py +47 -0
- novacode_cli/teams/protocol.py +50 -0
- novacode_cli/teams/recovery.py +80 -0
- novacode_cli/teams/scheduler.py +44 -0
- novacode_cli/teams/store.py +631 -0
- novacode_cli/teams/worker.py +251 -0
- novacode_cli/teams/workspace.py +100 -0
- novacode_cli/token_utils.py +256 -0
- novacode_cli/tools/__init__.py +104 -0
- novacode_cli/tools/_shared.py +97 -0
- novacode_cli/tools/_types.py +24 -0
- novacode_cli/tools/artifact_tools.py +137 -0
- novacode_cli/tools/code_search_tools.py +330 -0
- novacode_cli/tools/computer_tools.py +77 -0
- novacode_cli/tools/cron_tools.py +65 -0
- novacode_cli/tools/daemon_tool.py +290 -0
- novacode_cli/tools/desktop_tools.py +161 -0
- novacode_cli/tools/fetch_tools.py +530 -0
- novacode_cli/tools/http_tools.py +279 -0
- novacode_cli/tools/inspection_tools.py +107 -0
- novacode_cli/tools/job_tools.py +143 -0
- novacode_cli/tools/knowledge_tools.py +427 -0
- novacode_cli/tools/memory_tools.py +461 -0
- novacode_cli/tools/memory_wiki_tools.py +40 -0
- novacode_cli/tools/notebook_tools.py +238 -0
- novacode_cli/tools/oracle_tool.py +83 -0
- novacode_cli/tools/plan_archive_tools.py +80 -0
- novacode_cli/tools/plan_mode_tools.py +315 -0
- novacode_cli/tools/python_kernel_loop.py +165 -0
- novacode_cli/tools/python_kernel_tool.py +222 -0
- novacode_cli/tools/reflection_tools.py +34 -0
- novacode_cli/tools/scraper_tools.py +377 -0
- novacode_cli/tools/skill_tools.py +275 -0
- novacode_cli/tools/speak_tool.py +51 -0
- novacode_cli/tools/store_memory_tools.py +227 -0
- novacode_cli/tools/ui_tools.py +62 -0
- novacode_cli/tools/waiter_tools.py +408 -0
- novacode_cli/tools/web_tools.py +349 -0
- novacode_cli/tools/wiki_tools.py +152 -0
- novacode_cli/tools/worktree_tools.py +219 -0
- novacode_cli/tracking/__init__.py +10 -0
- novacode_cli/tracking/file_tracker.py +826 -0
- novacode_cli/tracking/local_metrics.py +170 -0
- novacode_cli/tracking/loop_guard.py +507 -0
- novacode_cli/tracking/request_metrics.py +60 -0
- novacode_cli/tracking/tracing.py +692 -0
- novacode_cli/tracking/usage_tree.py +677 -0
- novacode_cli/tracking/workspace_anchoring.py +231 -0
- novacode_cli/tui/__init__.py +11 -0
- novacode_cli/tui/animation_rate.py +9 -0
- novacode_cli/tui/animations.py +332 -0
- novacode_cli/tui/app.py +19315 -0
- novacode_cli/tui/auth_display.py +87 -0
- novacode_cli/tui/auth_screens.py +826 -0
- novacode_cli/tui/avatar.py +294 -0
- novacode_cli/tui/avatar_frames.py +21 -0
- novacode_cli/tui/background_tasks.py +35 -0
- novacode_cli/tui/browser_settings_screen.py +70 -0
- novacode_cli/tui/caveman_screen.py +185 -0
- novacode_cli/tui/council_settings_screen.py +182 -0
- novacode_cli/tui/council_view.py +535 -0
- novacode_cli/tui/decision_settings.py +268 -0
- novacode_cli/tui/diff_preview.py +199 -0
- novacode_cli/tui/gc_tuning.py +63 -0
- novacode_cli/tui/handoff_screen.py +206 -0
- novacode_cli/tui/harness.py +244 -0
- novacode_cli/tui/history_tree_screen.py +179 -0
- novacode_cli/tui/home.py +328 -0
- novacode_cli/tui/home_scene.py +94 -0
- novacode_cli/tui/icons.py +81 -0
- novacode_cli/tui/interface_styles.tcss +327 -0
- novacode_cli/tui/matrix3.py +108 -0
- novacode_cli/tui/mcp_tools_screen.py +131 -0
- novacode_cli/tui/motion.py +178 -0
- novacode_cli/tui/output_body.py +136 -0
- novacode_cli/tui/output_buffer.py +59 -0
- novacode_cli/tui/palette.py +334 -0
- novacode_cli/tui/pickers.py +237 -0
- novacode_cli/tui/prime_panel.py +506 -0
- novacode_cli/tui/provider_signin.py +213 -0
- novacode_cli/tui/pty_shell.py +159 -0
- novacode_cli/tui/reference_screens.py +444 -0
- novacode_cli/tui/resume_picker.py +346 -0
- novacode_cli/tui/rooms.py +597 -0
- novacode_cli/tui/screens.py +7088 -0
- novacode_cli/tui/session_import.py +333 -0
- novacode_cli/tui/session_pane.py +242 -0
- novacode_cli/tui/session_tab.py +42 -0
- novacode_cli/tui/session_transcript.py +118 -0
- novacode_cli/tui/shell_output.py +7 -0
- novacode_cli/tui/stall_watch.py +174 -0
- novacode_cli/tui/statusline.py +40 -0
- novacode_cli/tui/statusline_screen.py +91 -0
- novacode_cli/tui/teammate_updates.py +162 -0
- novacode_cli/tui/teammates.py +548 -0
- novacode_cli/tui/terminal.py +289 -0
- novacode_cli/tui/terminal_view.py +235 -0
- novacode_cli/tui/themes.py +118 -0
- novacode_cli/tui/update_notice.py +84 -0
- novacode_cli/tui/voice_downloads.py +113 -0
- novacode_cli/tui/widgets.py +2038 -0
- novacode_cli/tui/windows_input.py +38 -0
- novacode_cli/tui/workspace_approval.py +165 -0
- novacode_cli/ui/council_web.py +113 -0
- novacode_cli/ui/council_workspace.py +72 -0
- novacode_cli/ui/cowork_web.py +41 -0
- novacode_cli/ui/diff_highlight.py +160 -0
- novacode_cli/ui/execution.py +1396 -0
- novacode_cli/ui/exit_summary.py +65 -0
- novacode_cli/ui/hitl_approval.py +196 -0
- novacode_cli/ui/input_preparation.py +2 -0
- novacode_cli/ui/interrupt_handlers.py +215 -0
- novacode_cli/ui/question_prompt.py +151 -0
- novacode_cli/ui/status_phrases.py +157 -0
- novacode_cli/ui/stream_preview.py +66 -0
- novacode_cli/ui/streaming.py +2 -0
- novacode_cli/ui/subagent_tracking.py +2 -0
- novacode_cli/ui/ui_elements.py +1585 -0
- novacode_cli/ui/web_workspace.py +244 -0
- novacode_cli/ui_events.py +299 -0
- novacode_cli/update_ui.py +243 -0
- novacode_cli/updates.py +489 -0
- novacode_cli/utils/backend_patches.py +399 -0
- novacode_cli/utils/backend_paths.py +467 -0
- novacode_cli/utils/lazy_providers.py +51 -0
- novacode_cli/utils/model_info.py +223 -0
- novacode_cli/utils/pdf_extraction.py +183 -0
- novacode_cli/utils/startup.py +29 -0
- novacode_cli/utils/tasks.py +42 -0
- novacode_cli/utils/windows.py +9 -0
- novacode_cli/vixie/hook_adapter.py +127 -0
- novacode_cli/vixie/server.py +375 -0
- novacode_cli/wiki/__init__.py +15 -0
- novacode_cli/wiki/ask.py +56 -0
- novacode_cli/wiki/file.py +71 -0
- novacode_cli/wiki/ingest.py +184 -0
- novacode_cli/wiki/manager.py +261 -0
- novacode_cli-1.2.0.dist-info/METADATA +271 -0
- novacode_cli-1.2.0.dist-info/RECORD +691 -0
- novacode_cli-1.2.0.dist-info/WHEEL +5 -0
- novacode_cli-1.2.0.dist-info/entry_points.txt +2 -0
- novacode_cli-1.2.0.dist-info/licenses/LICENSE +21 -0
- novacode_cli-1.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""MCP tools for a background (async) agent graph.
|
|
2
|
+
|
|
3
|
+
A background graph is built by :func:`build_specialist_graph`, which resolves the
|
|
4
|
+
agent's declared tool names against ``novacode_cli.tools``. MCP tools are not in
|
|
5
|
+
that module — they are discovered at runtime from ``~/.nova/mcp.json`` — so every
|
|
6
|
+
``serena_*`` / ``playwright_*`` / ``cua-driver_*`` name an agent declares used to
|
|
7
|
+
be dropped without a word. An agent whose prompt is written around symbol
|
|
8
|
+
navigation then ran in the background with no symbol tool at all.
|
|
9
|
+
|
|
10
|
+
The server process can fix this itself: it is a subprocess of the same
|
|
11
|
+
interpreter with the same environment (``server_launcher.build_env``), and the
|
|
12
|
+
MCP config is a plain file, so there is no IPC and nothing to pass between
|
|
13
|
+
processes. This module is the one place that turns "the names in an agent's
|
|
14
|
+
frontmatter" into real MCP tools, reusing the same discovery the in-process path
|
|
15
|
+
uses (``MCPMiddleware``) and the same server-prefix convention the in-process
|
|
16
|
+
grant uses (``core_agent._grant_mcp_tools``).
|
|
17
|
+
|
|
18
|
+
**Failure is never fatal here.** ``langgraph`` loads every graph in the config at
|
|
19
|
+
startup and refuses to start if one fails to load, so an MCP server that is
|
|
20
|
+
missing, slow or broken must degrade to "this agent has fewer tools" rather than
|
|
21
|
+
"no background agent can run". Every path in :func:`mcp_tools_for` returns a
|
|
22
|
+
list, and the empty list is a normal answer.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import logging
|
|
28
|
+
from typing import TYPE_CHECKING
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from langchain_core.tools import BaseTool
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
|
|
35
|
+
#: Discovery results, keyed by the sorted tuple of requested tool names.
|
|
36
|
+
#:
|
|
37
|
+
#: The server imports every registered graph at startup, and each graph would
|
|
38
|
+
#: otherwise run its own discovery — spawning the same MCP servers once per
|
|
39
|
+
#: agent. Cold-starting a stdio server (``npx``/``uvx``) is seconds, so that
|
|
40
|
+
#: multiplies badly. Keyed by the request rather than by server so two agents
|
|
41
|
+
#: asking for different subsets each get exactly their own tools.
|
|
42
|
+
_CACHE: dict[tuple[str, ...], list[BaseTool]] = {}
|
|
43
|
+
|
|
44
|
+
#: How long discovery may take before the agent is built without MCP tools.
|
|
45
|
+
#:
|
|
46
|
+
#: Generous, because a cold stdio server (``npx``/``uvx``) may download a package
|
|
47
|
+
#: on first run, and the same 90 s the in-process path allows is the right order
|
|
48
|
+
#: of magnitude. It is a ceiling on a *background* thread, so a slow server costs
|
|
49
|
+
#: this agent its MCP tools rather than the server its startup.
|
|
50
|
+
_DISCOVERY_TIMEOUT = 90.0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def mcp_tools_for(names: list[str] | tuple[str, ...]) -> list[BaseTool]:
|
|
54
|
+
"""The MCP tools among *names*, discovered from the user's MCP config.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
names: Tool names as written in an ``agent.md``'s ``tools:`` frontmatter,
|
|
58
|
+
e.g. ``["serena_find_symbol", "code_search"]``. Names that are not MCP
|
|
59
|
+
tools are ignored here — the caller resolves those against
|
|
60
|
+
``novacode_cli.tools``.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
The discovered MCP tools whose names appear in *names*, in discovery
|
|
64
|
+
order. Empty when there is no MCP config, no server could be reached, or
|
|
65
|
+
none of *names* is an MCP tool. Never raises: a background agent with
|
|
66
|
+
fewer tools is strictly better than a server that will not boot.
|
|
67
|
+
"""
|
|
68
|
+
wanted = tuple(sorted({n for n in names if n}))
|
|
69
|
+
if not wanted:
|
|
70
|
+
return []
|
|
71
|
+
if wanted in _CACHE:
|
|
72
|
+
return _CACHE[wanted]
|
|
73
|
+
|
|
74
|
+
tools, ok = _discover(wanted)
|
|
75
|
+
# Only a *successful* discovery is cached. A failure is usually transient — a
|
|
76
|
+
# server that was slow to start, or a timeout — and caching it would deny the
|
|
77
|
+
# agent its tools for the life of the process, with no way back short of a
|
|
78
|
+
# restart. A retry costs one more spawn; a permanent empty answer costs the
|
|
79
|
+
# agent the tools its prompt is written around.
|
|
80
|
+
if ok:
|
|
81
|
+
_CACHE[wanted] = tools
|
|
82
|
+
return tools
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _discover(wanted: tuple[str, ...]) -> tuple[list[BaseTool], bool]:
|
|
86
|
+
"""Run MCP discovery and keep the tools named in *wanted*.
|
|
87
|
+
|
|
88
|
+
Returns the tools and whether discovery itself succeeded, so the caller can
|
|
89
|
+
tell "this agent has no MCP tools" from "discovery did not work this time"
|
|
90
|
+
and cache only the former.
|
|
91
|
+
|
|
92
|
+
Split out so :func:`mcp_tools_for` stays a cache lookup plus one call, and so
|
|
93
|
+
the whole discovery can be wrapped in a single guard.
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
from novacode_cli.mcp import get_shared_mcp_middleware
|
|
97
|
+
|
|
98
|
+
middleware = get_shared_mcp_middleware()
|
|
99
|
+
available = list(_discover_off_loop(middleware))
|
|
100
|
+
except Exception: # noqa: BLE001 — no MCP tools is a valid outcome
|
|
101
|
+
logger.warning(
|
|
102
|
+
"could not discover MCP tools for a background agent; it will run without them",
|
|
103
|
+
exc_info=True,
|
|
104
|
+
)
|
|
105
|
+
return [], False
|
|
106
|
+
|
|
107
|
+
wanted_set = set(wanted)
|
|
108
|
+
kept = [t for t in available if _tool_name(t) in wanted_set]
|
|
109
|
+
if not kept and wanted:
|
|
110
|
+
# Worth a line: the agent asked for tools that exist in no configured
|
|
111
|
+
# server, which is usually a typo or a server the user has not added.
|
|
112
|
+
logger.info(
|
|
113
|
+
"none of the %d requested MCP tool(s) were found in the MCP config: %s",
|
|
114
|
+
len(wanted),
|
|
115
|
+
", ".join(wanted),
|
|
116
|
+
)
|
|
117
|
+
# "Discovery worked" is not the same as "no server answered". With servers
|
|
118
|
+
# configured but not one tool discovered, the servers are down or still
|
|
119
|
+
# starting, and caching that would turn a transient outage into a permanent
|
|
120
|
+
# "this agent has no MCP tools". Only a discovery that actually produced
|
|
121
|
+
# something, or a config with nothing to discover, is worth caching.
|
|
122
|
+
return kept, bool(available) or not _configured_servers()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _configured_servers() -> bool:
|
|
126
|
+
"""Whether the user has any MCP server configured at all.
|
|
127
|
+
|
|
128
|
+
Used to tell "there is nothing to discover, so an empty result is the real
|
|
129
|
+
answer" from "servers are configured but none answered, so the empty result
|
|
130
|
+
is an outage". Never raises: an unreadable config is treated as "none", which
|
|
131
|
+
makes the caller cache the empty result rather than retry forever.
|
|
132
|
+
"""
|
|
133
|
+
try:
|
|
134
|
+
from novacode_cli.mcp import get_shared_mcp_middleware
|
|
135
|
+
|
|
136
|
+
return bool(get_shared_mcp_middleware().mcp_config.list_servers())
|
|
137
|
+
except Exception: # noqa: BLE001 — an unreadable config is not an outage
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _discover_off_loop(middleware: object) -> list[BaseTool]:
|
|
142
|
+
"""Discover MCP tools without blocking the caller's event loop.
|
|
143
|
+
|
|
144
|
+
``langgraph dev`` installs ``blockbuster``, which turns a blocking call on the
|
|
145
|
+
event loop into an exception. Spawning a stdio MCP server does exactly that:
|
|
146
|
+
the ``mcp`` library resolves the command with ``shutil.which``, which calls
|
|
147
|
+
``os.access``. So discovery is run in a worker thread with its own loop, the
|
|
148
|
+
same way the in-process path does it (``MCPMiddleware._discover_tools_sync``
|
|
149
|
+
already uses a ``ThreadPoolExecutor`` for this reason).
|
|
150
|
+
|
|
151
|
+
A graph is imported on the server's loop, so calling the sync entry point
|
|
152
|
+
directly here would trip blockbuster and take the whole server down with it.
|
|
153
|
+
|
|
154
|
+
The pool is shut down with ``wait=False`` on timeout, deliberately. A
|
|
155
|
+
``with`` block would call ``shutdown(wait=True)`` and block until the worker
|
|
156
|
+
finished, so a hung MCP server would still stall the server's startup and
|
|
157
|
+
``_DISCOVERY_TIMEOUT`` would bound nothing. A thread left running is the
|
|
158
|
+
lesser evil: it is one per distinct request, and the process is a dev server.
|
|
159
|
+
"""
|
|
160
|
+
import asyncio
|
|
161
|
+
import concurrent.futures
|
|
162
|
+
|
|
163
|
+
def _run() -> list[BaseTool]:
|
|
164
|
+
# A fresh loop in this thread: the caller's loop is not ours to touch.
|
|
165
|
+
asyncio.run(middleware._discover_tools_async()) # type: ignore[attr-defined]
|
|
166
|
+
return list(middleware.tools) # type: ignore[attr-defined]
|
|
167
|
+
|
|
168
|
+
pool = concurrent.futures.ThreadPoolExecutor(max_workers=1)
|
|
169
|
+
try:
|
|
170
|
+
return pool.submit(_run).result(timeout=_DISCOVERY_TIMEOUT)
|
|
171
|
+
finally:
|
|
172
|
+
# wait=False: never block the caller on a worker that overran the budget.
|
|
173
|
+
pool.shutdown(wait=False)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _tool_name(tool: object) -> str:
|
|
177
|
+
"""A tool's name, or ``""`` when it has none.
|
|
178
|
+
|
|
179
|
+
Mirrors ``core_agent._tool_name``: the name is what the prefix match and the
|
|
180
|
+
caller's request are both expressed in, so a tool without one is simply not
|
|
181
|
+
a match rather than an error.
|
|
182
|
+
"""
|
|
183
|
+
return str(getattr(tool, "name", "") or "")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def reset_cache() -> None:
|
|
187
|
+
"""Drop the discovery cache.
|
|
188
|
+
|
|
189
|
+
For tests, and for a caller that has just changed the MCP config and wants
|
|
190
|
+
the next graph build to see it.
|
|
191
|
+
"""
|
|
192
|
+
_CACHE.clear()
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Build a background graph from a prompt and a list of tool names.
|
|
2
|
+
|
|
3
|
+
Shared by the async agents that are just that: a prompt, Nova tools named by
|
|
4
|
+
string, and file access to the session's workspace. An agent that already exists
|
|
5
|
+
in-process (``default_subagents/prompt.py``) can be handed over as it is, so the
|
|
6
|
+
two forms never drift apart.
|
|
7
|
+
|
|
8
|
+
A declared name is resolved against two sources, because the in-process subagent
|
|
9
|
+
path does the same: ``novacode_cli.tools`` for Nova's own tools, and the user's
|
|
10
|
+
MCP config for ``serena_*`` / ``playwright_*`` / ``cua-driver_*`` (see
|
|
11
|
+
``_mcp_tools``). Only the first was consulted here, so an agent whose prompt is
|
|
12
|
+
written around symbol navigation ran in the background with no symbol tool.
|
|
13
|
+
|
|
14
|
+
Only work that is long and self-contained belongs here; an agent that edits code
|
|
15
|
+
interactively needs the approvals that do not reach this server.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
import deepagents
|
|
23
|
+
from novacode_cli.context.snapshot import background_context_middleware
|
|
24
|
+
from deepagents.backends.filesystem import FilesystemBackend
|
|
25
|
+
|
|
26
|
+
from novacode_cli.agents.async_context import AsyncModelOverrideMiddleware, NovaAsyncContext
|
|
27
|
+
from novacode_cli.agents.async_workspace import workspace_root
|
|
28
|
+
from novacode_cli.config.model_create import build_async_agent_model
|
|
29
|
+
|
|
30
|
+
BACKGROUND_NOTE = """
|
|
31
|
+
|
|
32
|
+
## You are running in the background
|
|
33
|
+
|
|
34
|
+
Nobody is watching this run and nobody can answer a question, so never ask one:
|
|
35
|
+
make the reasonable assumption, state it, and carry on. Your final message is the
|
|
36
|
+
whole deliverable. Whoever dispatched you sees only that, so make it a complete,
|
|
37
|
+
self-contained report rather than a note that work was done.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build_specialist_graph(
|
|
42
|
+
name: str,
|
|
43
|
+
definition: dict[str, Any],
|
|
44
|
+
*,
|
|
45
|
+
extra_tools: list[Any] | None = None,
|
|
46
|
+
note: str = "",
|
|
47
|
+
) -> Any:
|
|
48
|
+
"""A background graph for the agent described by *definition*.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
name: The graph's agent name (its id in ``langgraph.json``).
|
|
52
|
+
definition: A ``{"prompt", "tools"}`` entry.
|
|
53
|
+
extra_tools: Tools this background form needs beyond the named ones.
|
|
54
|
+
note: Anything the background form does differently from the prompt.
|
|
55
|
+
"""
|
|
56
|
+
import novacode_cli.tools as nova_tools
|
|
57
|
+
from novacode_cli.agents.async_agents import _tools as background_tools
|
|
58
|
+
from novacode_cli.agents.async_agents._mcp_tools import mcp_tools_for
|
|
59
|
+
from novacode_cli.security.delegated_approval import DelegatedApprovalMiddleware
|
|
60
|
+
from novacode_cli.skills.libraries import background_library
|
|
61
|
+
|
|
62
|
+
declared = definition.get("tools", [])
|
|
63
|
+
# File tools (ls / read_file / glob / grep / write_file) come from the
|
|
64
|
+
# backend, so only the names that are real Nova tools are looked up: the
|
|
65
|
+
# session's own, then the background-only ones (git, linters, run_tests).
|
|
66
|
+
named = [
|
|
67
|
+
getattr(source, n)
|
|
68
|
+
for n in declared
|
|
69
|
+
if (source := next((m for m in (nova_tools, background_tools) if hasattr(m, n)), None))
|
|
70
|
+
]
|
|
71
|
+
# MCP tools are not in novacode_cli.tools — they are discovered from the
|
|
72
|
+
# user's MCP config — so they are resolved separately. Best-effort: an
|
|
73
|
+
# unreachable MCP server yields fewer tools, never a graph that fails to
|
|
74
|
+
# build (which would stop the whole server from starting).
|
|
75
|
+
mcp = mcp_tools_for(declared)
|
|
76
|
+
return deepagents.create_deep_agent(
|
|
77
|
+
name=name,
|
|
78
|
+
model=build_async_agent_model(),
|
|
79
|
+
tools=[*named, *mcp, *(extra_tools or [])],
|
|
80
|
+
system_prompt=definition["prompt"] + BACKGROUND_NOTE + note,
|
|
81
|
+
backend=FilesystemBackend(root_dir=str(workspace_root()), virtual_mode=True),
|
|
82
|
+
middleware=[AsyncModelOverrideMiddleware(), DelegatedApprovalMiddleware(), background_library(name, definition), *background_context_middleware()],
|
|
83
|
+
context_schema=NovaAsyncContext,
|
|
84
|
+
)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""The background test runner's one tool: test-runner commands only, no shell.
|
|
2
|
+
|
|
3
|
+
Kept apart from ``test_runner.py`` because that module builds a graph (and a
|
|
4
|
+
model) on import, and this is the part that has to be testable on its own.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import shlex
|
|
10
|
+
import subprocess
|
|
11
|
+
|
|
12
|
+
from langchain_core.tools import tool
|
|
13
|
+
|
|
14
|
+
from novacode_cli.agents.async_workspace import workspace_root
|
|
15
|
+
|
|
16
|
+
#: What a command may start with. This runs unattended, so it is a list of test
|
|
17
|
+
#: runners rather than a shell: nothing here can be told to delete or push.
|
|
18
|
+
ALLOWED_PREFIXES: tuple[tuple[str, ...], ...] = (
|
|
19
|
+
("pytest",),
|
|
20
|
+
("python", "-m", "pytest"),
|
|
21
|
+
("python", "-m", "unittest"),
|
|
22
|
+
("uv", "run", "pytest"),
|
|
23
|
+
("uv", "run", "python", "-m", "pytest"),
|
|
24
|
+
("npm", "test"),
|
|
25
|
+
("npm", "run", "test"),
|
|
26
|
+
("pnpm", "test"),
|
|
27
|
+
("yarn", "test"),
|
|
28
|
+
("npx", "vitest"),
|
|
29
|
+
("npx", "jest"),
|
|
30
|
+
("go", "test"),
|
|
31
|
+
("cargo", "test"),
|
|
32
|
+
("dotnet", "test"),
|
|
33
|
+
("mvn", "test"),
|
|
34
|
+
("gradle", "test"),
|
|
35
|
+
("make", "test"),
|
|
36
|
+
)
|
|
37
|
+
_SHELL_CHARS = set("|&;<>`$\n\r")
|
|
38
|
+
_TAIL_CHARS = 12_000
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def parse_test_command(command: str) -> list[str] | str:
|
|
42
|
+
"""The argv for *command*, or a message saying why it will not be run."""
|
|
43
|
+
if _SHELL_CHARS & set(command):
|
|
44
|
+
return "Refused: pipes, redirects and chained commands are not allowed. Give one test command."
|
|
45
|
+
try:
|
|
46
|
+
argv = shlex.split(command, posix=True)
|
|
47
|
+
except ValueError as exc:
|
|
48
|
+
return f"Refused: could not parse the command ({exc})."
|
|
49
|
+
head = [a.lower().removesuffix(".exe") for a in argv]
|
|
50
|
+
if head and head[0] in ("python3", "py"):
|
|
51
|
+
head[0] = "python"
|
|
52
|
+
if not any(tuple(head[: len(p)]) == p for p in ALLOWED_PREFIXES):
|
|
53
|
+
allowed = ", ".join(" ".join(p) for p in ALLOWED_PREFIXES)
|
|
54
|
+
return f"Refused: only test runners can be run here. Start the command with one of: {allowed}."
|
|
55
|
+
return argv
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@tool
|
|
59
|
+
def run_tests(command: str, timeout_seconds: int = 900) -> str:
|
|
60
|
+
"""Run a test command in the project and return its exit code and output.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
command: One test-runner command, e.g. ``pytest tests/ -x -q`` or ``npm test``.
|
|
64
|
+
No pipes, redirects or chaining.
|
|
65
|
+
timeout_seconds: Give up after this long (default 15 minutes, at most 60).
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
The exit code and the end of the combined output.
|
|
69
|
+
"""
|
|
70
|
+
argv = parse_test_command(command)
|
|
71
|
+
if isinstance(argv, str):
|
|
72
|
+
return argv
|
|
73
|
+
try:
|
|
74
|
+
result = subprocess.run( # noqa: S603 — argv is allowlisted above, no shell
|
|
75
|
+
argv,
|
|
76
|
+
capture_output=True,
|
|
77
|
+
text=True,
|
|
78
|
+
errors="replace",
|
|
79
|
+
cwd=workspace_root(),
|
|
80
|
+
timeout=max(10, min(int(timeout_seconds), 3600)),
|
|
81
|
+
)
|
|
82
|
+
except FileNotFoundError:
|
|
83
|
+
return f"Could not run `{argv[0]}`: it is not installed or not on PATH."
|
|
84
|
+
except subprocess.TimeoutExpired:
|
|
85
|
+
return f"Timed out after {timeout_seconds}s. Run a smaller selection of tests."
|
|
86
|
+
output = (result.stdout or "") + (result.stderr or "")
|
|
87
|
+
if len(output) > _TAIL_CHARS:
|
|
88
|
+
output = "… (earlier output omitted)\n" + output[-_TAIL_CHARS:]
|
|
89
|
+
return f"exit code {result.returncode}\n{output}"
|