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,119 @@
|
|
|
1
|
+
"""``/cron`` command — manage scheduled (heartbeat) tasks (Enhancement 3).
|
|
2
|
+
|
|
3
|
+
Subcommands::
|
|
4
|
+
|
|
5
|
+
/cron list scheduled jobs
|
|
6
|
+
/cron list
|
|
7
|
+
/cron add "0 9 * * *" "review the project and summarise what's left"
|
|
8
|
+
/cron remove <job_id>
|
|
9
|
+
/cron now "check if CI is broken" fire a one-off task immediately
|
|
10
|
+
|
|
11
|
+
Jobs are fired by :class:`~novacode_cli.remote.scheduler.CronScheduler`, which
|
|
12
|
+
puts them on the same queue the remote bridges use, so they run like any prompt.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import shlex
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from rich.console import Console
|
|
22
|
+
|
|
23
|
+
from novacode_cli.remote.scheduler import CronScheduler
|
|
24
|
+
from novacode_cli.states.Session import SessionState
|
|
25
|
+
|
|
26
|
+
_ADD_PARTS = 2
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def _ensure_scheduler(session_state: SessionState) -> CronScheduler:
|
|
30
|
+
"""Return the session's scheduler, creating + starting it on first use."""
|
|
31
|
+
scheduler = getattr(session_state, "_cron_scheduler", None)
|
|
32
|
+
if scheduler is None:
|
|
33
|
+
from novacode_cli.memory.store import get_durable_store
|
|
34
|
+
from novacode_cli.remote.scheduler import CronScheduler
|
|
35
|
+
|
|
36
|
+
queue = getattr(session_state, "_remote_message_queue", None)
|
|
37
|
+
if queue is None:
|
|
38
|
+
import asyncio
|
|
39
|
+
|
|
40
|
+
queue = asyncio.Queue(maxsize=100)
|
|
41
|
+
session_state._remote_message_queue = queue
|
|
42
|
+
scheduler = CronScheduler(queue, store=get_durable_store())
|
|
43
|
+
session_state._cron_scheduler = scheduler
|
|
44
|
+
if not scheduler.running:
|
|
45
|
+
await scheduler.start()
|
|
46
|
+
return scheduler
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def handle_cron_command( # noqa: PLR0911 — command dispatcher, one return per subcommand
|
|
50
|
+
cmd_args: str | None, session_state: SessionState, console: Console
|
|
51
|
+
) -> bool:
|
|
52
|
+
"""Dispatch a ``/cron`` subcommand. Returns ``True`` (command handled)."""
|
|
53
|
+
scheduler = await _ensure_scheduler(session_state)
|
|
54
|
+
args = (cmd_args or "").strip()
|
|
55
|
+
|
|
56
|
+
if not args or args.split()[0] == "list":
|
|
57
|
+
_print_jobs(scheduler, console)
|
|
58
|
+
return True
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
tokens = shlex.split(args)
|
|
62
|
+
except ValueError as exc:
|
|
63
|
+
console.print(f"[red]Could not parse arguments: {exc}[/red]")
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
action = tokens[0]
|
|
67
|
+
rest = tokens[1:]
|
|
68
|
+
|
|
69
|
+
if action == "add":
|
|
70
|
+
if len(rest) < _ADD_PARTS:
|
|
71
|
+
console.print('[yellow]Usage:[/yellow] /cron add "<cron expr>" "<task>"')
|
|
72
|
+
return True
|
|
73
|
+
cron_expr, task = rest[0], rest[1]
|
|
74
|
+
try:
|
|
75
|
+
job_id = await scheduler.add_job(cron_expr, task)
|
|
76
|
+
except ValueError as exc:
|
|
77
|
+
console.print(f"[red]Invalid cron expression:[/red] {exc}")
|
|
78
|
+
return True
|
|
79
|
+
console.print(
|
|
80
|
+
f" [green]✓[/green] Scheduled job [cyan]{job_id}[/cyan] "
|
|
81
|
+
f"([dim]{cron_expr}[/dim]): {task}"
|
|
82
|
+
)
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
if action == "remove":
|
|
86
|
+
if not rest:
|
|
87
|
+
console.print("[yellow]Usage:[/yellow] /cron remove <job_id>")
|
|
88
|
+
return True
|
|
89
|
+
removed = await scheduler.remove_job(rest[0])
|
|
90
|
+
if removed:
|
|
91
|
+
console.print(f" [green]✓[/green] Removed job [cyan]{rest[0]}[/cyan]")
|
|
92
|
+
else:
|
|
93
|
+
console.print(f" [dim]No job with id {rest[0]}[/dim]")
|
|
94
|
+
return True
|
|
95
|
+
|
|
96
|
+
if action == "now":
|
|
97
|
+
if not rest:
|
|
98
|
+
console.print('[yellow]Usage:[/yellow] /cron now "<task>"')
|
|
99
|
+
return True
|
|
100
|
+
await scheduler.fire_now(rest[0])
|
|
101
|
+
console.print(f" [green]✓[/green] Fired one-off task: {rest[0]}")
|
|
102
|
+
return True
|
|
103
|
+
|
|
104
|
+
console.print(f"[yellow]Unknown /cron subcommand:[/yellow] {action}")
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _print_jobs(scheduler: CronScheduler, console: Console) -> None:
|
|
109
|
+
"""Render the active job list."""
|
|
110
|
+
jobs = scheduler.list_jobs()
|
|
111
|
+
if not jobs:
|
|
112
|
+
console.print('[dim]No scheduled jobs. Add one with /cron add "0 9 * * *" "task".[/dim]')
|
|
113
|
+
return
|
|
114
|
+
console.print("[bold]Scheduled jobs[/bold]")
|
|
115
|
+
for job in jobs:
|
|
116
|
+
console.print(
|
|
117
|
+
f" [cyan]{job.get('job_id', '?')}[/cyan] "
|
|
118
|
+
f"[dim]{job.get('cron_expr', '?')}[/dim] — {job.get('task', '')}"
|
|
119
|
+
)
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""Handler for the /dream command - memory consolidation.
|
|
2
|
+
|
|
3
|
+
This module provides the handle_dream_command function that runs
|
|
4
|
+
a memory consolidation pass to organize and clean up memory files.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from novacode_cli.config.config import COLORS, console
|
|
15
|
+
from novacode_cli.prompts import render_template
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from novacode_cli.commands import CommandContext, CommandRegistry
|
|
19
|
+
|
|
20
|
+
_CONTEXT_TOPIC_PREVIEW = 8
|
|
21
|
+
_STATUS_TOPIC_PREVIEW = 6
|
|
22
|
+
|
|
23
|
+
# A UI sink: called with one Rich-markup string per line ("" = blank line).
|
|
24
|
+
EmitFn = Callable[[str], None]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _console_emit(message: str = "") -> None:
|
|
28
|
+
"""Default emitter (CLI): render a Rich-markup string to the console."""
|
|
29
|
+
console.print(message)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def handle_dream_command(
|
|
33
|
+
session_state: object,
|
|
34
|
+
assistant_id: str | None = None,
|
|
35
|
+
agent_dir: Path | None = None,
|
|
36
|
+
emit: EmitFn | None = None,
|
|
37
|
+
) -> bool | str:
|
|
38
|
+
"""Handle the /dream command - run memory consolidation.
|
|
39
|
+
|
|
40
|
+
Performs a multi-phase memory consolidation pass over the agent's *real*
|
|
41
|
+
memory directory (``~/.nova/agents/{assistant_id}/``): agent.md (user model +
|
|
42
|
+
preferences) and the topic files under ``memories/`` (indexed by INDEX.md).
|
|
43
|
+
|
|
44
|
+
Returns the consolidation prompt (a ``str``) for the caller to stream to the
|
|
45
|
+
agent when memory exists, or ``True`` (handled, nothing to do) when it does
|
|
46
|
+
not. The agent acts on the **virtual** ``/memories/`` route — which maps to
|
|
47
|
+
this directory — so its read/edit tools work regardless of OS path form.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
session_state: Current session state.
|
|
51
|
+
assistant_id: The active agent id (defaults to the main agent).
|
|
52
|
+
agent_dir: Optional explicit agent directory (overrides the resolved one).
|
|
53
|
+
emit: Optional renderer-specific status sink.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
The consolidation prompt string, or True if there is nothing to consolidate.
|
|
57
|
+
"""
|
|
58
|
+
from novacode_cli.config.config import MAIN_AGENT_ID, settings
|
|
59
|
+
|
|
60
|
+
if emit is None:
|
|
61
|
+
emit = _console_emit
|
|
62
|
+
if getattr(session_state, "plan_mode_enabled", False):
|
|
63
|
+
emit("[yellow]Exit plan mode before running memory wiki maintenance.[/yellow]")
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
# Resolve the REAL memory directory (the one Nova actually writes to).
|
|
67
|
+
if agent_dir is None:
|
|
68
|
+
try:
|
|
69
|
+
agent_dir = settings.get_agent_dir(assistant_id or MAIN_AGENT_ID)
|
|
70
|
+
except Exception: # noqa: BLE001 - bad agent id, fall back to main
|
|
71
|
+
agent_dir = settings.get_agent_dir(MAIN_AGENT_ID)
|
|
72
|
+
|
|
73
|
+
# Nova's semantic surface: agent.md (user model + prefs) + topic files
|
|
74
|
+
# under memories/ (indexed by INDEX.md).
|
|
75
|
+
tier_files = {
|
|
76
|
+
"agent.md": "user model, preferences & general notes",
|
|
77
|
+
"HABITS.md": "stable operating checklist; incident lessons belong in topic memory",
|
|
78
|
+
}
|
|
79
|
+
memories_dir = agent_dir / "memories"
|
|
80
|
+
|
|
81
|
+
present = [(name, role) for name, role in tier_files.items() if (agent_dir / name).exists()]
|
|
82
|
+
topic_files = sorted(
|
|
83
|
+
[
|
|
84
|
+
*memories_dir.glob("*.md"),
|
|
85
|
+
*(memories_dir / "projects").glob("*/*.md"),
|
|
86
|
+
]
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if not present and not topic_files:
|
|
90
|
+
emit("")
|
|
91
|
+
emit("[yellow]No memory files found yet.[/yellow]")
|
|
92
|
+
emit(f"[dim]Looked in: {agent_dir}[/dim]")
|
|
93
|
+
emit(
|
|
94
|
+
"[dim]Ask me to 'remember this' during a session and the memory tiers"
|
|
95
|
+
" fill in; then /dream consolidates them.[/dim]"
|
|
96
|
+
)
|
|
97
|
+
emit("")
|
|
98
|
+
return True
|
|
99
|
+
|
|
100
|
+
transcripts_dir = agent_dir / "transcripts"
|
|
101
|
+
|
|
102
|
+
# Describe Nova's concrete memory layout so the agent consolidates the right
|
|
103
|
+
# files. Use the VIRTUAL /memories/ route — the agent's file tools resolve it
|
|
104
|
+
# to the real agent dir; passing the raw OS path would be rejected by the
|
|
105
|
+
# virtual-mode filesystem backend.
|
|
106
|
+
lines = ["These memory files exist (operate on them under `/memories/`):"]
|
|
107
|
+
for name, role in present:
|
|
108
|
+
lines.append(f"- `/memories/{name}` — {role}")
|
|
109
|
+
if topic_files:
|
|
110
|
+
lines.append(
|
|
111
|
+
f"- `/memories/memories/` — {len(topic_files)} topic file(s): "
|
|
112
|
+
+ ", ".join(f.name for f in topic_files[:_CONTEXT_TOPIC_PREVIEW])
|
|
113
|
+
+ ("…" if len(topic_files) > _CONTEXT_TOPIC_PREVIEW else "")
|
|
114
|
+
)
|
|
115
|
+
memory_dir_context = "\n".join(lines)
|
|
116
|
+
|
|
117
|
+
# The wiki has one canonical home, including on its first maintenance pass.
|
|
118
|
+
index_file = "/memories/memories/INDEX.md"
|
|
119
|
+
|
|
120
|
+
from novacode_cli.memory.wiki import maintain_wiki, snapshot_wiki
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
wiki = await asyncio.to_thread(maintain_wiki, agent_dir)
|
|
124
|
+
backup = wiki["backup"] or str(await asyncio.to_thread(snapshot_wiki, agent_dir))
|
|
125
|
+
except (OSError, ValueError, RuntimeError) as exc:
|
|
126
|
+
emit(f"[yellow]Memory maintenance stopped: {exc}[/yellow]")
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
emit("")
|
|
130
|
+
emit(f"[dim]Wiki: {wiki['topics']} topics; {len(wiki['broken_links'])} unresolved links.[/dim]")
|
|
131
|
+
emit(f"[bold {COLORS['primary']}]💭 Memory Consolidation (Dream)[/bold {COLORS['primary']}]")
|
|
132
|
+
for name, role in present:
|
|
133
|
+
emit(f" [cyan]{name}[/cyan] [dim]— {role}[/dim]")
|
|
134
|
+
if topic_files:
|
|
135
|
+
names = ", ".join(f.name for f in topic_files[:_STATUS_TOPIC_PREVIEW]) + (
|
|
136
|
+
"…" if len(topic_files) > _STATUS_TOPIC_PREVIEW else ""
|
|
137
|
+
)
|
|
138
|
+
emit(f" [cyan]memories/[/cyan] [dim]— {len(topic_files)} topic file(s): {names}[/dim]")
|
|
139
|
+
emit("")
|
|
140
|
+
|
|
141
|
+
return render_template(
|
|
142
|
+
"memory_consolidation.jinja",
|
|
143
|
+
memory_dir="/memories/",
|
|
144
|
+
memory_dir_context=memory_dir_context,
|
|
145
|
+
index_file=index_file,
|
|
146
|
+
index_max_lines=100,
|
|
147
|
+
transcripts_dir="/memories/transcripts/" if transcripts_dir.exists() else None,
|
|
148
|
+
backup_dir="/memories/" + Path(backup).relative_to(agent_dir).as_posix(),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def get_dream_prompt(
|
|
153
|
+
memory_dir: Path,
|
|
154
|
+
transcripts_dir: Path | None = None,
|
|
155
|
+
index_file: str = "/memories/memories/INDEX.md",
|
|
156
|
+
index_max_lines: int = 100,
|
|
157
|
+
) -> str:
|
|
158
|
+
"""Generate the memory consolidation prompt.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
memory_dir: Path to the memory directory
|
|
162
|
+
transcripts_dir: Optional path to transcripts directory
|
|
163
|
+
index_file: Virtual wiki home path.
|
|
164
|
+
index_max_lines: Maximum lines for the index (default: 100)
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
The rendered prompt string
|
|
168
|
+
"""
|
|
169
|
+
return render_template(
|
|
170
|
+
"memory_consolidation.jinja",
|
|
171
|
+
memory_dir=memory_dir.as_posix().rstrip("/") + "/",
|
|
172
|
+
memory_dir_context=f"Memory files are stored in {memory_dir}",
|
|
173
|
+
index_file=index_file,
|
|
174
|
+
index_max_lines=index_max_lines,
|
|
175
|
+
transcripts_dir=str(transcripts_dir) if transcripts_dir else None,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
# ---------------------------------------------------------------------------
|
|
180
|
+
# Registry
|
|
181
|
+
# ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def register_commands(registry: CommandRegistry) -> None:
|
|
185
|
+
"""Register the shared command handler for every renderer."""
|
|
186
|
+
|
|
187
|
+
async def _handle(ctx: CommandContext) -> bool:
|
|
188
|
+
result = await handle_dream_command(ctx.session_state, ctx.assistant_id)
|
|
189
|
+
# When memory exists, the handler returns a consolidation prompt to run.
|
|
190
|
+
if isinstance(result, str) and result.strip() and ctx.agent is not None:
|
|
191
|
+
from novacode_cli.ui.execution import execute_task
|
|
192
|
+
|
|
193
|
+
console.print("[dim]💭 Dreaming over memories…[/dim]")
|
|
194
|
+
await execute_task(
|
|
195
|
+
result,
|
|
196
|
+
ctx.agent,
|
|
197
|
+
ctx.assistant_id,
|
|
198
|
+
ctx.session_state,
|
|
199
|
+
ctx.token_tracker,
|
|
200
|
+
)
|
|
201
|
+
return True
|
|
202
|
+
|
|
203
|
+
registry.register("dream", _handle)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Handler for the /effort command to manage reasoning effort configuration."""
|
|
2
|
+
|
|
3
|
+
from novacode_cli.commands import CommandContext
|
|
4
|
+
from novacode_cli.config.config import COLORS, console
|
|
5
|
+
from novacode_cli.config.nova_config import NovaConfig
|
|
6
|
+
from novacode_cli.config.model_create import create_model
|
|
7
|
+
|
|
8
|
+
async def handle_effort_command(ctx: CommandContext) -> bool:
|
|
9
|
+
"""Handle the /effort command."""
|
|
10
|
+
args = ctx.cmd_args
|
|
11
|
+
nova_config = NovaConfig()
|
|
12
|
+
current_effort = nova_config.get("reasoning_effort", "off")
|
|
13
|
+
|
|
14
|
+
if not args:
|
|
15
|
+
console.print()
|
|
16
|
+
console.print(f"[bold]Reasoning Effort Configuration[/bold]", style=COLORS["primary"])
|
|
17
|
+
console.print(f"Current reasoning effort: [bold cyan]{current_effort}[/bold cyan]")
|
|
18
|
+
console.print()
|
|
19
|
+
console.print("[bold]Usage:[/bold]")
|
|
20
|
+
console.print(" /effort <low|medium|high|off> - Set the reasoning effort level")
|
|
21
|
+
console.print()
|
|
22
|
+
console.print("[bold]Supported Providers:[/bold]")
|
|
23
|
+
console.print(" • [bold]OpenAI[/bold] (o1, o3-mini models): supports low, medium, high")
|
|
24
|
+
console.print(" • [bold]Google Gemini[/bold] (Gemini 2.5/3 models): supports low, medium, high")
|
|
25
|
+
console.print(" • [bold]Anthropic Claude[/bold] (Claude 3.7 Sonnet): maps to token budget")
|
|
26
|
+
console.print(" • [bold]Ollama[/bold]: Local models (like DeepSeek-R1) generate reasoning automatically")
|
|
27
|
+
console.print()
|
|
28
|
+
return True
|
|
29
|
+
|
|
30
|
+
val = args.strip().lower()
|
|
31
|
+
if val not in ("low", "medium", "high", "off", "none", "default"):
|
|
32
|
+
console.print(f"[red]Error: Invalid effort level '{args}'.[/red]")
|
|
33
|
+
console.print("Choose one of: low, medium, high, off")
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
# Normalize none / default to off
|
|
37
|
+
if val in ("none", "default"):
|
|
38
|
+
val = "off"
|
|
39
|
+
|
|
40
|
+
# Save configuration
|
|
41
|
+
nova_config.set("reasoning_effort", val)
|
|
42
|
+
console.print()
|
|
43
|
+
console.print(f"[green]✓ Reasoning effort set to '{val}' and saved to config.[/green]")
|
|
44
|
+
|
|
45
|
+
# Trigger hot-swap
|
|
46
|
+
session_state = ctx.session_state
|
|
47
|
+
if session_state is not None:
|
|
48
|
+
try:
|
|
49
|
+
new_model = create_model()
|
|
50
|
+
await session_state.switch_model(new_model)
|
|
51
|
+
console.print("[green]✓ Model recreated with new reasoning effort dynamically![/green]")
|
|
52
|
+
except Exception as e:
|
|
53
|
+
console.print(f"[yellow]⚠ Could not recreate model dynamically: {e}[/yellow]")
|
|
54
|
+
console.print("[dim]The change will take effect on next model switch or restart.[/dim]")
|
|
55
|
+
else:
|
|
56
|
+
console.print("[dim]The change will take effect on restart.[/dim]")
|
|
57
|
+
|
|
58
|
+
console.print()
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
def register_commands(registry) -> None:
|
|
62
|
+
async def _handle(ctx: CommandContext) -> bool:
|
|
63
|
+
return await handle_effort_command(ctx)
|
|
64
|
+
|
|
65
|
+
registry.register("effort", _handle)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Read-only learning activity for /evolution, including labelled legacy history."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from datetime import UTC, datetime
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
from rich.markup import escape
|
|
11
|
+
|
|
12
|
+
from novacode_cli.config.config import COLORS, MAIN_AGENT_ID, console
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from novacode_cli.commands import CommandContext, CommandRegistry
|
|
16
|
+
|
|
17
|
+
# A UI sink: called with one Rich-markup string per line ("" = blank line).
|
|
18
|
+
# Mirrors dream_handler so the TUI can render natively instead of printing to
|
|
19
|
+
# the Rich console (which corrupts the Textual display).
|
|
20
|
+
EmitFn = Callable[[str], None]
|
|
21
|
+
|
|
22
|
+
LEARNING_STATUSES = (
|
|
23
|
+
"candidate",
|
|
24
|
+
"approved",
|
|
25
|
+
"applied",
|
|
26
|
+
"rejected",
|
|
27
|
+
"rollback_pending",
|
|
28
|
+
"rolled_back",
|
|
29
|
+
"failed",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def evolution_summary(counters: dict[str, int]) -> str:
|
|
34
|
+
"""Describe settings and current ledger state without claiming proposals are applied."""
|
|
35
|
+
enabled = "on" if counters.get("learning_enabled") else "off"
|
|
36
|
+
automatic = "on" if counters.get("auto_approve") else "off"
|
|
37
|
+
return (
|
|
38
|
+
f"Learning {enabled} · auto approval {automatic}\n"
|
|
39
|
+
f"{counters.get('candidate', 0)} pending review · "
|
|
40
|
+
f"{counters.get('approved', 0)} approved · {counters.get('applied', 0)} applied"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def evolution_label(entry: dict[str, Any]) -> str:
|
|
45
|
+
"""Return the current decision state, or an explicitly historical label."""
|
|
46
|
+
status = entry.get("status", "historical")
|
|
47
|
+
if status == "historical":
|
|
48
|
+
kind = {"unlock": "unlock", "levelup": "level-up"}.get(
|
|
49
|
+
str(entry.get("kind", "")), "activity"
|
|
50
|
+
)
|
|
51
|
+
return f"Historical {kind} (application unverified)"
|
|
52
|
+
return {
|
|
53
|
+
"candidate": "Pending review",
|
|
54
|
+
"approved": "Approved; awaiting application",
|
|
55
|
+
"applied": "Applied",
|
|
56
|
+
"rejected": "Rejected",
|
|
57
|
+
"rollback_pending": "Rollback pending",
|
|
58
|
+
"rolled_back": "Rolled back",
|
|
59
|
+
"failed": "Application failed",
|
|
60
|
+
}.get(status, str(status))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def evolution_detail(entry: dict[str, Any]) -> str:
|
|
64
|
+
"""Build literal report text shared by the console and native dashboard."""
|
|
65
|
+
summary = str(entry.get("task_summary") or "No summary recorded.")
|
|
66
|
+
if entry.get("source") != "ledger":
|
|
67
|
+
return f"{evolution_label(entry)}\n{summary}"
|
|
68
|
+
return (
|
|
69
|
+
f"{entry['reference']} · {entry['kind']} · {evolution_label(entry)}\n"
|
|
70
|
+
f"Scope: {entry['scope']} · Project: {entry['project'] or 'shared'}\n"
|
|
71
|
+
"Use /learning review to inspect and manage proposals.\n\n" + summary
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _learning_activity(
|
|
76
|
+
agent_id: str = MAIN_AGENT_ID,
|
|
77
|
+
) -> tuple[dict[str, int], list[dict[str, Any]]]:
|
|
78
|
+
"""Read the current learning ledger and settings; never approve or apply anything."""
|
|
79
|
+
from novacode_cli.config.config import Settings
|
|
80
|
+
from novacode_cli.config.nova_config import NovaConfig
|
|
81
|
+
from novacode_cli.hermes.ledger import LearningLedger
|
|
82
|
+
|
|
83
|
+
cfg = NovaConfig()
|
|
84
|
+
path = Settings.from_environment().get_agent_dir(agent_id) / "learning.sqlite3"
|
|
85
|
+
counters = dict.fromkeys(LEARNING_STATUSES, 0)
|
|
86
|
+
counters.update(
|
|
87
|
+
learning_enabled=int(cfg.get_learning_enabled()),
|
|
88
|
+
auto_approve=int(cfg.get_learning_auto_approve()),
|
|
89
|
+
)
|
|
90
|
+
entries = []
|
|
91
|
+
for record, timestamp in LearningLedger.read_activity(path):
|
|
92
|
+
counters[record.status] = counters.get(record.status, 0) + 1
|
|
93
|
+
entries.append(
|
|
94
|
+
{
|
|
95
|
+
"source": "ledger",
|
|
96
|
+
"status": record.status,
|
|
97
|
+
"kind": record.kind,
|
|
98
|
+
"skill": record.target or record.kind.replace("_", " ").title(),
|
|
99
|
+
"reference": record.reference,
|
|
100
|
+
"ts": timestamp,
|
|
101
|
+
"task_summary": record.content[:8000],
|
|
102
|
+
"scope": record.scope,
|
|
103
|
+
"project": record.project,
|
|
104
|
+
"origin": record.origin,
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
return counters, entries
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _console_emit(message: str = "") -> None:
|
|
111
|
+
"""Default emitter (CLI/REPL): render a Rich-markup string to the console."""
|
|
112
|
+
console.print(message)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
async def _load_evolution(
|
|
116
|
+
*, strict: bool = False, agent_id: str = MAIN_AGENT_ID
|
|
117
|
+
) -> tuple[dict[str, int], list[dict[str, Any]]]:
|
|
118
|
+
"""Return current proposals followed by explicitly historical evolution records."""
|
|
119
|
+
from novacode_cli.memory.store import get_durable_store
|
|
120
|
+
|
|
121
|
+
store = await asyncio.to_thread(get_durable_store)
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
counters, current = await asyncio.to_thread(_learning_activity, agent_id)
|
|
125
|
+
except Exception:
|
|
126
|
+
if strict:
|
|
127
|
+
raise
|
|
128
|
+
counters, current = {}, []
|
|
129
|
+
counters.update(unlocked=0, leveled=0)
|
|
130
|
+
try:
|
|
131
|
+
entry = await store.aget(("nova", "meta"), "evolution")
|
|
132
|
+
if entry and isinstance(entry.value, dict):
|
|
133
|
+
counters.update(
|
|
134
|
+
unlocked=int(entry.value.get("unlocked", 0)),
|
|
135
|
+
leveled=int(entry.value.get("leveled", 0)),
|
|
136
|
+
)
|
|
137
|
+
except Exception:
|
|
138
|
+
if strict:
|
|
139
|
+
raise
|
|
140
|
+
|
|
141
|
+
entries: list[dict[str, Any]] = []
|
|
142
|
+
try:
|
|
143
|
+
results = await store.asearch(("nova", "evolution_log"), limit=1000)
|
|
144
|
+
for item in results or []:
|
|
145
|
+
value = getattr(item, "value", None)
|
|
146
|
+
if isinstance(value, dict):
|
|
147
|
+
entries.append({**value, "source": "legacy", "status": "historical"})
|
|
148
|
+
except Exception:
|
|
149
|
+
if strict:
|
|
150
|
+
raise
|
|
151
|
+
entries.sort(key=lambda e: e.get("ts", 0.0), reverse=True)
|
|
152
|
+
return counters, [*current, *entries]
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
async def handle_evolution_command(
|
|
156
|
+
limit: int = 15,
|
|
157
|
+
emit: EmitFn | None = None,
|
|
158
|
+
*,
|
|
159
|
+
agent_id: str = MAIN_AGENT_ID,
|
|
160
|
+
) -> bool:
|
|
161
|
+
"""Render the evolution summary + recent unlocks/level-ups.
|
|
162
|
+
|
|
163
|
+
Writes through ``emit`` (one Rich-markup line at a time) so the TUI can
|
|
164
|
+
render natively; defaults to the Rich console for the CLI/REPL.
|
|
165
|
+
"""
|
|
166
|
+
if emit is None:
|
|
167
|
+
emit = _console_emit
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
counters, entries = await _load_evolution(strict=True, agent_id=agent_id)
|
|
171
|
+
except Exception as error: # noqa: BLE001 — report storage failure without failing the task
|
|
172
|
+
emit(f"Evolution history unavailable: {escape(str(error))}")
|
|
173
|
+
return True
|
|
174
|
+
|
|
175
|
+
emit("")
|
|
176
|
+
emit(f"[bold {COLORS['primary']}]🧬 Self-Evolution[/bold {COLORS['primary']}]")
|
|
177
|
+
emit(escape(evolution_summary(counters)))
|
|
178
|
+
|
|
179
|
+
if not entries:
|
|
180
|
+
emit(
|
|
181
|
+
" [dim]No activity yet. /learning on enables task-end reviews; "
|
|
182
|
+
"/learning review lists proposals before application.[/dim]"
|
|
183
|
+
)
|
|
184
|
+
emit("")
|
|
185
|
+
return True
|
|
186
|
+
|
|
187
|
+
emit("")
|
|
188
|
+
for e in entries[:limit]:
|
|
189
|
+
icon = "•"
|
|
190
|
+
verb = evolution_label(e)
|
|
191
|
+
skill = e.get("skill", "?")
|
|
192
|
+
when = ""
|
|
193
|
+
ts = e.get("ts")
|
|
194
|
+
if isinstance(ts, (int, float)) and ts > 0:
|
|
195
|
+
when = datetime.fromtimestamp(ts, UTC).strftime("%Y-%m-%d %H:%M")
|
|
196
|
+
summary = (e.get("task_summary") or "").strip()
|
|
197
|
+
line = f" {icon} [bold]{escape(str(skill))}[/bold] [dim]{escape(verb)}"
|
|
198
|
+
if when:
|
|
199
|
+
line += f" · {when}"
|
|
200
|
+
line += "[/dim]"
|
|
201
|
+
emit(line)
|
|
202
|
+
if summary:
|
|
203
|
+
emit(escape(evolution_detail(e)))
|
|
204
|
+
emit("")
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def register_commands(registry: CommandRegistry) -> None:
|
|
209
|
+
"""Register the read-only evolution report for console transports."""
|
|
210
|
+
|
|
211
|
+
async def _handle(ctx: CommandContext) -> bool:
|
|
212
|
+
return await handle_evolution_command(agent_id=ctx.assistant_id)
|
|
213
|
+
|
|
214
|
+
registry.register("evolution", _handle)
|