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,116 @@
|
|
|
1
|
+
"""Context budget tracking across middleware layers.
|
|
2
|
+
|
|
3
|
+
Private to the ``context`` package. Tracks token usage per middleware layer to
|
|
4
|
+
prevent overflow and show which layers consume the most context. Surfaced
|
|
5
|
+
through ``ContextManager.budget``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import logging
|
|
12
|
+
import threading
|
|
13
|
+
from contextvars import ContextVar
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ContextBudget:
|
|
20
|
+
"""Track and manage context token usage across middleware layers.
|
|
21
|
+
|
|
22
|
+
This helps prevent context overflow by monitoring token usage and
|
|
23
|
+
providing visibility into which middleware layers consume the most context.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, max_tokens: int = 50000):
|
|
27
|
+
"""Initialize context budget tracker.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
max_tokens: Maximum allowed tokens before compression/warning.
|
|
31
|
+
"""
|
|
32
|
+
self.max_tokens = max_tokens
|
|
33
|
+
self.middleware_usage: dict[str, int] = {}
|
|
34
|
+
self.total_tokens = 0
|
|
35
|
+
|
|
36
|
+
def track_middleware(self, middleware_name: str, context: str | list[Any]) -> int:
|
|
37
|
+
"""Track context usage for a middleware layer.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
middleware_name: Name of the middleware.
|
|
41
|
+
context: Context content (string or list of messages).
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Number of tokens used by this middleware.
|
|
45
|
+
"""
|
|
46
|
+
tokens = self._count_tokens(context)
|
|
47
|
+
self.middleware_usage[middleware_name] = tokens
|
|
48
|
+
# Sum, don't accumulate: this is called once per model call with the same
|
|
49
|
+
# (cached) section, so `+=` grew the total forever and logged a bogus
|
|
50
|
+
# "budget exceeded" every turn until the log was nothing else.
|
|
51
|
+
self.total_tokens = sum(self.middleware_usage.values())
|
|
52
|
+
|
|
53
|
+
if self.total_tokens > self.max_tokens:
|
|
54
|
+
logger.warning(
|
|
55
|
+
f"Context budget exceeded: {self.total_tokens} > {self.max_tokens} "
|
|
56
|
+
f"tokens. Consider compressing context or reducing middleware layers."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return tokens
|
|
60
|
+
|
|
61
|
+
def _count_tokens(self, context: str | list[Any]) -> int:
|
|
62
|
+
"""Estimate token count (~4 chars per token for English text)."""
|
|
63
|
+
if isinstance(context, str):
|
|
64
|
+
return len(context) // 4
|
|
65
|
+
elif isinstance(context, list):
|
|
66
|
+
total = 0
|
|
67
|
+
for msg in context:
|
|
68
|
+
if isinstance(msg, dict):
|
|
69
|
+
content = msg.get("content", "")
|
|
70
|
+
if isinstance(content, str):
|
|
71
|
+
total += len(content) // 4
|
|
72
|
+
elif isinstance(content, list):
|
|
73
|
+
for block in content:
|
|
74
|
+
if isinstance(block, dict):
|
|
75
|
+
total += len(str(block.get("text", ""))) // 4
|
|
76
|
+
elif isinstance(msg, str):
|
|
77
|
+
total += len(msg) // 4
|
|
78
|
+
return total
|
|
79
|
+
return 0
|
|
80
|
+
|
|
81
|
+
def reset(self) -> None:
|
|
82
|
+
"""Reset tracking for a new request."""
|
|
83
|
+
self.middleware_usage.clear()
|
|
84
|
+
self.total_tokens = 0
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# Global context budget tracker
|
|
88
|
+
_context_budget: ContextVar[tuple[object, ContextBudget] | None] = ContextVar(
|
|
89
|
+
"nova_context_budget", default=None
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_context_budget(max_tokens: int = 50000) -> ContextBudget:
|
|
94
|
+
"""Get or create the global context budget tracker.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
max_tokens: Maximum allowed tokens (only used on first call).
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Global ContextBudget instance.
|
|
101
|
+
"""
|
|
102
|
+
try:
|
|
103
|
+
owner = asyncio.current_task() or threading.current_thread()
|
|
104
|
+
except RuntimeError:
|
|
105
|
+
owner = threading.current_thread()
|
|
106
|
+
entry = _context_budget.get()
|
|
107
|
+
budget = entry[1] if entry and entry[0] is owner else None
|
|
108
|
+
if budget is None or budget.max_tokens != max_tokens:
|
|
109
|
+
budget = ContextBudget(max_tokens)
|
|
110
|
+
_context_budget.set((owner, budget))
|
|
111
|
+
return budget
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def reset_context_budget() -> None:
|
|
115
|
+
"""Reset the global context budget tracker."""
|
|
116
|
+
_context_budget.set(None)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Recent-conversation digest from agent state.
|
|
2
|
+
|
|
3
|
+
Private to the ``context`` package. Used by long-running commands (/research,
|
|
4
|
+
/ralph) so the task they kick off is grounded in what was already discussed with
|
|
5
|
+
the core agent — including for the subagents they spawn, which otherwise run on
|
|
6
|
+
fresh threads with no history. Surfaced through ``ContextManager.digest``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _message_text(msg: Any) -> str:
|
|
15
|
+
"""Extract plain text from a LangChain message's content (str or blocks)."""
|
|
16
|
+
content = getattr(msg, "content", "")
|
|
17
|
+
if isinstance(content, str):
|
|
18
|
+
return content
|
|
19
|
+
if isinstance(content, list):
|
|
20
|
+
parts: list[str] = []
|
|
21
|
+
for block in content:
|
|
22
|
+
if isinstance(block, dict):
|
|
23
|
+
if block.get("type") == "text":
|
|
24
|
+
parts.append(block.get("text", ""))
|
|
25
|
+
elif isinstance(block, str):
|
|
26
|
+
parts.append(block)
|
|
27
|
+
return "\n".join(parts)
|
|
28
|
+
return str(content)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def get_recent_conversation_digest(
|
|
32
|
+
agent: Any,
|
|
33
|
+
thread_id: str,
|
|
34
|
+
*,
|
|
35
|
+
max_turns: int = 10,
|
|
36
|
+
max_chars: int = 4000,
|
|
37
|
+
) -> str:
|
|
38
|
+
"""Return a compact transcript of the most recent user/assistant turns.
|
|
39
|
+
|
|
40
|
+
Reads the agent's checkpointed state for ``thread_id`` and formats the last
|
|
41
|
+
``max_turns`` Human/AI turns (tool noise skipped) into a bounded text block.
|
|
42
|
+
Returns an empty string if there's no history or the state can't be read.
|
|
43
|
+
"""
|
|
44
|
+
config = {"configurable": {"thread_id": thread_id}}
|
|
45
|
+
try:
|
|
46
|
+
state = await agent.aget_state(config)
|
|
47
|
+
except Exception: # noqa: BLE001
|
|
48
|
+
return ""
|
|
49
|
+
|
|
50
|
+
values = getattr(state, "values", None) or {}
|
|
51
|
+
messages = values.get("messages", []) or []
|
|
52
|
+
if not messages:
|
|
53
|
+
return ""
|
|
54
|
+
|
|
55
|
+
turns: list[str] = []
|
|
56
|
+
for msg in messages:
|
|
57
|
+
role = getattr(msg, "type", "") or ""
|
|
58
|
+
if role not in ("human", "ai"):
|
|
59
|
+
continue
|
|
60
|
+
text = _message_text(msg).strip()
|
|
61
|
+
if not text:
|
|
62
|
+
continue
|
|
63
|
+
speaker = "User" if role == "human" else "Assistant"
|
|
64
|
+
turns.append(f"{speaker}: {text}")
|
|
65
|
+
|
|
66
|
+
if not turns:
|
|
67
|
+
return ""
|
|
68
|
+
|
|
69
|
+
turns = turns[-max_turns:]
|
|
70
|
+
digest = "\n\n".join(turns)
|
|
71
|
+
if len(digest) > max_chars:
|
|
72
|
+
digest = "…(earlier turns trimmed)…\n\n" + digest[-max_chars:]
|
|
73
|
+
return digest
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""Dynamic context-window detection for Ollama models.
|
|
2
|
+
|
|
3
|
+
Private to the ``context`` package. Probes a locally-installed Ollama for a
|
|
4
|
+
model's real context length via ``ollama show``, cached for the process
|
|
5
|
+
lifetime. Used by :mod:`novacode_cli.context._analysis` as the first source for
|
|
6
|
+
``get_context_window_size`` (Ollama models only).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import subprocess
|
|
15
|
+
from functools import lru_cache
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
# A LAST-RESORT assumption about a local model's context, used only when neither
|
|
21
|
+
# its Modelfile nor ``OLLAMA_CONTEXT_LENGTH`` says (see predicted_local_num_ctx).
|
|
22
|
+
#
|
|
23
|
+
# It used to be the value Nova REQUESTED when loading a model, and so the real
|
|
24
|
+
# limit. Nova now uses Ollama's OpenAI-compatible endpoint, which cannot send
|
|
25
|
+
# ``num_ctx``: the server decides, and this number is no longer enforced by
|
|
26
|
+
# anything. Overridable via ``OLLAMA_NUM_CTX`` to tell Nova what the server uses.
|
|
27
|
+
DEFAULT_OLLAMA_NUM_CTX = 200_000
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def is_ollama_cloud_model(model_name: str) -> bool:
|
|
31
|
+
"""Whether a model name refers to an Ollama *cloud* model (``:cloud`` suffix).
|
|
32
|
+
|
|
33
|
+
Cloud models (e.g. ``qwen3-coder:480b-cloud``, ``deepseek-v4-pro:cloud``) run
|
|
34
|
+
entirely on Ollama's servers — they are never loaded into local VRAM, so they
|
|
35
|
+
don't appear in ``ollama ps`` and the local ``num_ctx`` allocation doesn't
|
|
36
|
+
apply. Their context length comes from ``ollama show`` (which works for cloud
|
|
37
|
+
models) and is the model's maximum.
|
|
38
|
+
"""
|
|
39
|
+
name = model_name.lower()
|
|
40
|
+
return name.endswith("-cloud") or name.endswith(":cloud")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_ollama_num_ctx() -> int:
|
|
44
|
+
"""The ``num_ctx`` Nova loads Ollama models with (env ``OLLAMA_NUM_CTX``).
|
|
45
|
+
|
|
46
|
+
Returns ``DEFAULT_OLLAMA_NUM_CTX`` when unset or malformed.
|
|
47
|
+
"""
|
|
48
|
+
raw = os.environ.get("OLLAMA_NUM_CTX", "").strip()
|
|
49
|
+
if raw:
|
|
50
|
+
try:
|
|
51
|
+
value = int(raw)
|
|
52
|
+
if value > 0:
|
|
53
|
+
return value
|
|
54
|
+
except ValueError:
|
|
55
|
+
logger.warning("Invalid OLLAMA_NUM_CTX=%r; using default", raw)
|
|
56
|
+
return DEFAULT_OLLAMA_NUM_CTX
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@lru_cache(maxsize=128)
|
|
60
|
+
def get_ollama_modelfile_num_ctx(model_name: str) -> Optional[int]:
|
|
61
|
+
"""``num_ctx`` baked into a model's Modelfile, or None when it sets none.
|
|
62
|
+
|
|
63
|
+
Nova talks to Ollama through its OpenAI-compatible endpoint, which cannot
|
|
64
|
+
carry ``num_ctx``, so the server decides a local model's context — and a
|
|
65
|
+
Modelfile value is the first thing it uses. Measured: ``tev1:0.8b`` declares
|
|
66
|
+
``num_ctx 2050`` and was allocated exactly 2,050 tokens, where Nova had been
|
|
67
|
+
assuming the 200,000 it used to request.
|
|
68
|
+
"""
|
|
69
|
+
try:
|
|
70
|
+
result = subprocess.run(
|
|
71
|
+
["ollama", "show", model_name, "--parameters"],
|
|
72
|
+
capture_output=True,
|
|
73
|
+
text=True,
|
|
74
|
+
timeout=10,
|
|
75
|
+
)
|
|
76
|
+
except (OSError, subprocess.SubprocessError):
|
|
77
|
+
return None
|
|
78
|
+
if result.returncode != 0:
|
|
79
|
+
return None
|
|
80
|
+
for line in (result.stdout or "").splitlines():
|
|
81
|
+
parts = line.split()
|
|
82
|
+
if len(parts) >= 2 and parts[0] == "num_ctx" and parts[-1].isdigit():
|
|
83
|
+
return int(parts[-1]) or None
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def predicted_local_num_ctx(model_name: str, *, use_dynamic: bool = True) -> int:
|
|
88
|
+
"""The context the server will give a LOCAL model that is not loaded yet.
|
|
89
|
+
|
|
90
|
+
In the order Ollama itself applies them: the Modelfile's ``num_ctx``, then
|
|
91
|
+
the server's ``OLLAMA_CONTEXT_LENGTH``. If neither can be seen from here the
|
|
92
|
+
answer falls back to :func:`get_ollama_num_ctx`, which is then only a guess:
|
|
93
|
+
the server's built-in default applies and Nova cannot read it until the
|
|
94
|
+
model is loaded (after which ``ollama ps`` reports the real figure).
|
|
95
|
+
"""
|
|
96
|
+
if use_dynamic:
|
|
97
|
+
baked = get_ollama_modelfile_num_ctx(model_name)
|
|
98
|
+
if baked:
|
|
99
|
+
return baked
|
|
100
|
+
raw = os.environ.get("OLLAMA_CONTEXT_LENGTH", "").strip()
|
|
101
|
+
if raw.isdigit() and int(raw) > 0:
|
|
102
|
+
return int(raw)
|
|
103
|
+
return get_ollama_num_ctx()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@lru_cache(maxsize=128)
|
|
107
|
+
def get_ollama_context_length(model_name: str) -> Optional[int]:
|
|
108
|
+
"""Get context length for an Ollama model dynamically.
|
|
109
|
+
|
|
110
|
+
Uses caching to avoid repeated Ollama queries. Results are cached
|
|
111
|
+
for the lifetime of the process.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
model_name: Name of the Ollama model (e.g., "glm-5:cloud")
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Context length in tokens, or None if not found.
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
result = subprocess.run(
|
|
121
|
+
["ollama", "show", model_name],
|
|
122
|
+
capture_output=True,
|
|
123
|
+
text=True,
|
|
124
|
+
# Answers in ~0.1s when healthy; a busy daemon can hang. A failure is
|
|
125
|
+
# cached for the session anyway, so waiting longer buys nothing.
|
|
126
|
+
timeout=3,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if result.returncode != 0:
|
|
130
|
+
logger.warning(f"Failed to get context for {model_name}: {result.stderr}")
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
output = result.stdout
|
|
134
|
+
|
|
135
|
+
# Look for a "context length" line, e.g. "context length 202752"
|
|
136
|
+
for line in output.split("\n"):
|
|
137
|
+
if "context" in line.lower() and "length" in line.lower():
|
|
138
|
+
match = re.search(r"(\d+)", line)
|
|
139
|
+
if match:
|
|
140
|
+
context_length = int(match.group(1))
|
|
141
|
+
logger.info(
|
|
142
|
+
f"Detected context length for {model_name}: "
|
|
143
|
+
f"{context_length:,} tokens"
|
|
144
|
+
)
|
|
145
|
+
return context_length
|
|
146
|
+
|
|
147
|
+
logger.warning(f"No context length found for {model_name}")
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
except subprocess.TimeoutExpired:
|
|
151
|
+
logger.error(f"Timeout getting context for {model_name}")
|
|
152
|
+
return None
|
|
153
|
+
except FileNotFoundError:
|
|
154
|
+
logger.error("Ollama not found. Make sure Ollama is installed and in PATH.")
|
|
155
|
+
return None
|
|
156
|
+
except Exception as e: # noqa: BLE001
|
|
157
|
+
logger.error(f"Error getting context for {model_name}: {e}")
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _names_match(ps_name: str, model_name: str) -> bool:
|
|
162
|
+
"""Whether an ``ollama ps`` NAME refers to the requested model.
|
|
163
|
+
|
|
164
|
+
Handles tag differences: ``gemma3`` vs ``gemma3:latest``,
|
|
165
|
+
``qwen3-coder:480b-cloud`` vs ``qwen3-coder``.
|
|
166
|
+
"""
|
|
167
|
+
ps_name = ps_name.strip()
|
|
168
|
+
model_name = model_name.strip()
|
|
169
|
+
if ps_name == model_name:
|
|
170
|
+
return True
|
|
171
|
+
return ps_name.split(":", 1)[0] == model_name.split(":", 1)[0]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def get_ollama_runtime_info(model_name: str) -> Optional[dict]:
|
|
175
|
+
"""Parse ``ollama ps`` for the running model's actual allocated state.
|
|
176
|
+
|
|
177
|
+
``ollama ps`` reports the *real* runtime values for a loaded model — the
|
|
178
|
+
``CONTEXT`` actually allocated (which Ollama may clamp below the requested
|
|
179
|
+
``num_ctx`` to fit VRAM) and the ``PROCESSOR`` split (e.g. ``100% GPU`` vs a
|
|
180
|
+
CPU-offloaded ``48%/52% CPU/GPU``). This is the most accurate source for both
|
|
181
|
+
context-usage tracking and performance diagnostics.
|
|
182
|
+
|
|
183
|
+
Not cached: a model loads after the first request and its allocation can
|
|
184
|
+
change between runs, so each call reflects the current state.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
A dict with any of ``{"name", "context", "processor"}`` for the matching
|
|
188
|
+
model, or ``None`` if Ollama isn't running, has no such model loaded, or
|
|
189
|
+
the installed Ollama predates the ``CONTEXT`` column.
|
|
190
|
+
"""
|
|
191
|
+
try:
|
|
192
|
+
result = subprocess.run(
|
|
193
|
+
["ollama", "ps"], capture_output=True, text=True, timeout=3
|
|
194
|
+
)
|
|
195
|
+
if result.returncode != 0:
|
|
196
|
+
return None
|
|
197
|
+
output = result.stdout
|
|
198
|
+
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
# On Windows `capture_output=True` can yield stdout=None even with a 0
|
|
202
|
+
# returncode, so guard before splitting rather than crashing on None.
|
|
203
|
+
if not output:
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
lines = [ln for ln in output.splitlines() if ln.strip()]
|
|
207
|
+
if len(lines) < 2: # header only (nothing loaded) or empty
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
# Columns are separated by runs of 2+ spaces; single spaces inside a field
|
|
211
|
+
# ("6.6 GB", "100% GPU", "2 minutes from now") are preserved.
|
|
212
|
+
headers = re.split(r"\s{2,}", lines[0].strip())
|
|
213
|
+
col = {h.upper(): i for i, h in enumerate(headers)}
|
|
214
|
+
name_i = col.get("NAME", 0)
|
|
215
|
+
ctx_i = col.get("CONTEXT")
|
|
216
|
+
proc_i = col.get("PROCESSOR")
|
|
217
|
+
|
|
218
|
+
for line in lines[1:]:
|
|
219
|
+
fields = re.split(r"\s{2,}", line.strip())
|
|
220
|
+
if name_i >= len(fields):
|
|
221
|
+
continue
|
|
222
|
+
if not _names_match(fields[name_i], model_name):
|
|
223
|
+
continue
|
|
224
|
+
info: dict = {"name": fields[name_i]}
|
|
225
|
+
if ctx_i is not None and ctx_i < len(fields):
|
|
226
|
+
m = re.search(r"\d+", fields[ctx_i])
|
|
227
|
+
if m:
|
|
228
|
+
info["context"] = int(m.group())
|
|
229
|
+
if proc_i is not None and proc_i < len(fields):
|
|
230
|
+
info["processor"] = fields[proc_i].strip()
|
|
231
|
+
return info
|
|
232
|
+
|
|
233
|
+
return None
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def get_ollama_running_context(model_name: str) -> Optional[int]:
|
|
237
|
+
"""The actual allocated context length for a loaded Ollama model, or None.
|
|
238
|
+
|
|
239
|
+
Sourced from ``ollama ps`` (see :func:`get_ollama_runtime_info`); this is the
|
|
240
|
+
authoritative window — what the model was really loaded with — superseding
|
|
241
|
+
both the architecture max and the requested ``num_ctx``.
|
|
242
|
+
"""
|
|
243
|
+
info = get_ollama_runtime_info(model_name)
|
|
244
|
+
return info.get("context") if info else None
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def check_ollama_offloading(model_name: str) -> Optional[str]:
|
|
248
|
+
"""Return a warning if a loaded Ollama model isn't fully on the GPU.
|
|
249
|
+
|
|
250
|
+
CPU offloading (PROCESSOR not ``100% GPU``) drastically slows generation.
|
|
251
|
+
Returns ``None`` when the model is fully GPU-resident, not loaded, or the
|
|
252
|
+
PROCESSOR split is unavailable.
|
|
253
|
+
"""
|
|
254
|
+
info = get_ollama_runtime_info(model_name)
|
|
255
|
+
if not info:
|
|
256
|
+
return None
|
|
257
|
+
processor = info.get("processor")
|
|
258
|
+
if not processor or "CPU" not in processor.upper():
|
|
259
|
+
return None # fully on GPU (or no split reported)
|
|
260
|
+
return (
|
|
261
|
+
f"Ollama model '{info.get('name', model_name)}' is partially offloaded to "
|
|
262
|
+
f"CPU (PROCESSOR: {processor}) — generation will be slow. Free VRAM, lower "
|
|
263
|
+
f"OLLAMA_NUM_CTX, or use a smaller model to keep it 100% on GPU."
|
|
264
|
+
)
|