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.
Files changed (691) hide show
  1. novacode_cli/__init__.py +29 -0
  2. novacode_cli/__main__.py +6 -0
  3. novacode_cli/_lazy_heavy.py +99 -0
  4. novacode_cli/_version.py +3 -0
  5. novacode_cli/agent_stream.py +178 -0
  6. novacode_cli/agents/agent_file.py +250 -0
  7. novacode_cli/agents/agent_mailbox.py +120 -0
  8. novacode_cli/agents/approval_decisions.py +146 -0
  9. novacode_cli/agents/arg_repair.py +196 -0
  10. novacode_cli/agents/async_agents/__init__.py +6 -0
  11. novacode_cli/agents/async_agents/_config.py +213 -0
  12. novacode_cli/agents/async_agents/_mcp_tools.py +192 -0
  13. novacode_cli/agents/async_agents/_specialist.py +84 -0
  14. novacode_cli/agents/async_agents/_test_commands.py +89 -0
  15. novacode_cli/agents/async_agents/_tools.py +432 -0
  16. novacode_cli/agents/async_agents/general_purpose.py +42 -0
  17. novacode_cli/agents/async_agents/langgraph.json +9 -0
  18. novacode_cli/agents/async_agents/research.py +22 -0
  19. novacode_cli/agents/async_context.py +303 -0
  20. novacode_cli/agents/async_on_demand.py +52 -0
  21. novacode_cli/agents/async_task_errors.py +75 -0
  22. novacode_cli/agents/async_workspace.py +27 -0
  23. novacode_cli/agents/attribution.py +165 -0
  24. novacode_cli/agents/browser_mode.py +119 -0
  25. novacode_cli/agents/btw_agent.py +71 -0
  26. novacode_cli/agents/caveman.py +352 -0
  27. novacode_cli/agents/caveman_support.py +370 -0
  28. novacode_cli/agents/code_mode.py +774 -0
  29. novacode_cli/agents/code_runtime.py +160 -0
  30. novacode_cli/agents/context_compaction.py +1339 -0
  31. novacode_cli/agents/core_agent.py +2710 -0
  32. novacode_cli/agents/default_subagents/__init__.py +24 -0
  33. novacode_cli/agents/default_subagents/async_subagents.py +296 -0
  34. novacode_cli/agents/default_subagents/builtin/browser-automation-agent.md +110 -0
  35. novacode_cli/agents/default_subagents/builtin/bug-fix-agent.md +22 -0
  36. novacode_cli/agents/default_subagents/builtin/bug-investigation-agent.md +42 -0
  37. novacode_cli/agents/default_subagents/builtin/code-explorer.md +37 -0
  38. novacode_cli/agents/default_subagents/builtin/code-review-agent.md +33 -0
  39. novacode_cli/agents/default_subagents/builtin/dependency-audit-agent.md +34 -0
  40. novacode_cli/agents/default_subagents/builtin/documentation-update-agent.md +46 -0
  41. novacode_cli/agents/default_subagents/builtin/performance-audit-agent.md +41 -0
  42. novacode_cli/agents/default_subagents/builtin/plan-scout-agent.md +64 -0
  43. novacode_cli/agents/default_subagents/builtin/refactoring-agent.md +35 -0
  44. novacode_cli/agents/default_subagents/builtin/refactoring-specialist-agent.md +175 -0
  45. novacode_cli/agents/default_subagents/builtin/security-audit-agent.md +96 -0
  46. novacode_cli/agents/default_subagents/builtin/test-generation-agent.md +33 -0
  47. novacode_cli/agents/default_subagents/builtin/test-runner-agent.md +55 -0
  48. novacode_cli/agents/default_subagents/prompt.py +88 -0
  49. novacode_cli/agents/default_subagents/subagents.py +138 -0
  50. novacode_cli/agents/dream_agent.py +93 -0
  51. novacode_cli/agents/edit_diagnostics.py +362 -0
  52. novacode_cli/agents/edit_fallback.py +62 -0
  53. novacode_cli/agents/handoff.py +166 -0
  54. novacode_cli/agents/invocation.py +152 -0
  55. novacode_cli/agents/model_budget.py +42 -0
  56. novacode_cli/agents/model_retry.py +113 -0
  57. novacode_cli/agents/model_router.py +420 -0
  58. novacode_cli/agents/openai_decisions.py +186 -0
  59. novacode_cli/agents/plan_agent/__init__.py +12 -0
  60. novacode_cli/agents/plan_agent/plan_agent.py +357 -0
  61. novacode_cli/agents/plan_agent/plan_mode_middleware.py +64 -0
  62. novacode_cli/agents/plan_execution.py +237 -0
  63. novacode_cli/agents/ponytail.py +66 -0
  64. novacode_cli/agents/prime.py +274 -0
  65. novacode_cli/agents/provider_caching.py +99 -0
  66. novacode_cli/agents/server_launcher.py +904 -0
  67. novacode_cli/agents/strict_tools.py +384 -0
  68. novacode_cli/agents/task_discipline.py +294 -0
  69. novacode_cli/agents/tool_call_repair.py +290 -0
  70. novacode_cli/agents/tool_offload.py +291 -0
  71. novacode_cli/agents/tool_search.py +806 -0
  72. novacode_cli/agents/tool_search_variants.py +106 -0
  73. novacode_cli/agents/tool_verdicts.py +1194 -0
  74. novacode_cli/agents/user_async_agents.py +312 -0
  75. novacode_cli/artifacts/__init__.py +10 -0
  76. novacode_cli/artifacts/registry.py +278 -0
  77. novacode_cli/artifacts/server.py +426 -0
  78. novacode_cli/assets/Nova.png +0 -0
  79. novacode_cli/assets/nova-avatar.png +0 -0
  80. novacode_cli/audio/__init__.py +301 -0
  81. novacode_cli/audio/capture.py +137 -0
  82. novacode_cli/audio/downloads.py +273 -0
  83. novacode_cli/audio/pipeline.py +391 -0
  84. novacode_cli/audio/playback.py +87 -0
  85. novacode_cli/audio/providers.py +208 -0
  86. novacode_cli/audio/speakable.py +78 -0
  87. novacode_cli/audio/stt.py +132 -0
  88. novacode_cli/audio/stt_deepgram.py +69 -0
  89. novacode_cli/audio/stt_elevenlabs.py +89 -0
  90. novacode_cli/audio/stt_parakeet.py +94 -0
  91. novacode_cli/audio/stt_whistle.py +61 -0
  92. novacode_cli/audio/summarize.py +92 -0
  93. novacode_cli/audio/tts.py +85 -0
  94. novacode_cli/audio/tts_kokoro.py +73 -0
  95. novacode_cli/audio/tts_orpheus.py +180 -0
  96. novacode_cli/audio/tts_pocket.py +131 -0
  97. novacode_cli/audio/tts_sano.py +69 -0
  98. novacode_cli/audio/vad.py +145 -0
  99. novacode_cli/backends/__init__.py +17 -0
  100. novacode_cli/backends/filesystem.py +894 -0
  101. novacode_cli/backends/native_search.py +92 -0
  102. novacode_cli/bootstrap/__init__.py +9 -0
  103. novacode_cli/bootstrap/env_snapshot.py +240 -0
  104. novacode_cli/bootstrap/steering.py +464 -0
  105. novacode_cli/bootstrap/vision_router.py +604 -0
  106. novacode_cli/brand.py +362 -0
  107. novacode_cli/cli_args.py +718 -0
  108. novacode_cli/commands/__init__.py +237 -0
  109. novacode_cli/commands/agents_commands.py +231 -0
  110. novacode_cli/commands/auth_handler.py +40 -0
  111. novacode_cli/commands/browser_use_handler.py +75 -0
  112. novacode_cli/commands/cache_handler.py +32 -0
  113. novacode_cli/commands/chat_handler.py +1357 -0
  114. novacode_cli/commands/ci_fix.py +171 -0
  115. novacode_cli/commands/claude_plan.py +331 -0
  116. novacode_cli/commands/codex_plan.py +195 -0
  117. novacode_cli/commands/commands.py +1113 -0
  118. novacode_cli/commands/create_handler.py +124 -0
  119. novacode_cli/commands/create_server.py +1887 -0
  120. novacode_cli/commands/cron_handler.py +119 -0
  121. novacode_cli/commands/dream_handler.py +203 -0
  122. novacode_cli/commands/effort_handler.py +65 -0
  123. novacode_cli/commands/evolution_handler.py +214 -0
  124. novacode_cli/commands/file_commands.py +381 -0
  125. novacode_cli/commands/handoff_handler.py +68 -0
  126. novacode_cli/commands/hooks_handler.py +269 -0
  127. novacode_cli/commands/init_handler.py +146 -0
  128. novacode_cli/commands/learning_handler.py +160 -0
  129. novacode_cli/commands/lite_mode_handler.py +76 -0
  130. novacode_cli/commands/log_commands.py +363 -0
  131. novacode_cli/commands/mcp_handler.py +29 -0
  132. novacode_cli/commands/model_handler.py +29 -0
  133. novacode_cli/commands/notifications_handler.py +158 -0
  134. novacode_cli/commands/onboard_handler.py +121 -0
  135. novacode_cli/commands/plan_handler.py +440 -0
  136. novacode_cli/commands/planner_usage.py +250 -0
  137. novacode_cli/commands/plugin_install_handler.py +38 -0
  138. novacode_cli/commands/plugins_handler.py +35 -0
  139. novacode_cli/commands/prime_handler.py +455 -0
  140. novacode_cli/commands/prompt_handler.py +149 -0
  141. novacode_cli/commands/provider_usage.py +160 -0
  142. novacode_cli/commands/ralph_events.py +106 -0
  143. novacode_cli/commands/ralph_handler.py +1293 -0
  144. novacode_cli/commands/refine_handler.py +213 -0
  145. novacode_cli/commands/research_handler.py +202 -0
  146. novacode_cli/commands/rootapp_remote.py +83 -0
  147. novacode_cli/commands/router_handler.py +36 -0
  148. novacode_cli/commands/server_commands.py +249 -0
  149. novacode_cli/commands/session_commands.py +203 -0
  150. novacode_cli/commands/share_handler.py +450 -0
  151. novacode_cli/commands/side_commands.py +366 -0
  152. novacode_cli/commands/skill_invoke.py +420 -0
  153. novacode_cli/commands/skills_commands.py +48 -0
  154. novacode_cli/commands/trace_handler.py +322 -0
  155. novacode_cli/commands/trello_handler.py +226 -0
  156. novacode_cli/commands/trello_remote.py +177 -0
  157. novacode_cli/commands/trello_server.py +1214 -0
  158. novacode_cli/commands/vision_handler.py +127 -0
  159. novacode_cli/commands/voice_handler.py +447 -0
  160. novacode_cli/commands/webhook_handler.py +134 -0
  161. novacode_cli/commands/wiki_commands.py +227 -0
  162. novacode_cli/compaction.py +753 -0
  163. novacode_cli/computation_cache.py +159 -0
  164. novacode_cli/computer/__init__.py +1 -0
  165. novacode_cli/computer/persistent.py +234 -0
  166. novacode_cli/computer/preview.py +253 -0
  167. novacode_cli/computer/runtime.py +474 -0
  168. novacode_cli/computer/windows_console.py +152 -0
  169. novacode_cli/computer/windows_qemu.py +211 -0
  170. novacode_cli/config/browser_mode.py +159 -0
  171. novacode_cli/config/catalog_models.py +95 -0
  172. novacode_cli/config/config.py +1388 -0
  173. novacode_cli/config/credentials.py +540 -0
  174. novacode_cli/config/google_oauth_auth.py +281 -0
  175. novacode_cli/config/model_capabilities.py +343 -0
  176. novacode_cli/config/model_catalog.py +275 -0
  177. novacode_cli/config/model_create.py +1325 -0
  178. novacode_cli/config/model_manager.py +695 -0
  179. novacode_cli/config/nova_config.py +1283 -0
  180. novacode_cli/config/openai_chatgpt_auth.py +384 -0
  181. novacode_cli/config/opencode_gateway.py +25 -0
  182. novacode_cli/config/plan_mode.py +188 -0
  183. novacode_cli/config/provider_auth.py +438 -0
  184. novacode_cli/config/provider_catalog.json +5127 -0
  185. novacode_cli/config/provider_registry.py +38 -0
  186. novacode_cli/config/provider_transports.py +258 -0
  187. novacode_cli/config/role_models.py +175 -0
  188. novacode_cli/context/__init__.py +50 -0
  189. novacode_cli/context/_analysis.py +799 -0
  190. novacode_cli/context/_budget.py +116 -0
  191. novacode_cli/context/_conversation.py +73 -0
  192. novacode_cli/context/_dynamic.py +264 -0
  193. novacode_cli/context/_model_config.py +380 -0
  194. novacode_cli/context/_models_dev.py +99 -0
  195. novacode_cli/context/archives.py +248 -0
  196. novacode_cli/context/estimates.py +65 -0
  197. novacode_cli/context/history.py +17 -0
  198. novacode_cli/context/knowledge_cache.py +292 -0
  199. novacode_cli/context/manager.py +101 -0
  200. novacode_cli/context/observations.py +65 -0
  201. novacode_cli/context/pressure.py +168 -0
  202. novacode_cli/context/provider_accounting.py +109 -0
  203. novacode_cli/context/snapshot.py +493 -0
  204. novacode_cli/context/strategies.py +279 -0
  205. novacode_cli/context/task_progress.py +122 -0
  206. novacode_cli/core/__init__.py +0 -0
  207. novacode_cli/core/agent_loop.py +1821 -0
  208. novacode_cli/core/autonomous_loop.py +277 -0
  209. novacode_cli/core/code_approvals.py +57 -0
  210. novacode_cli/core/input_preparation.py +276 -0
  211. novacode_cli/core/reasoning.py +67 -0
  212. novacode_cli/core/streaming.py +326 -0
  213. novacode_cli/core/subagent_tasks.py +696 -0
  214. novacode_cli/core/subagent_tracking.py +235 -0
  215. novacode_cli/core/tool_results.py +116 -0
  216. novacode_cli/core/verification_loop.py +272 -0
  217. novacode_cli/council.py +595 -0
  218. novacode_cli/council_consensus.py +115 -0
  219. novacode_cli/council_decision.py +157 -0
  220. novacode_cli/council_planner.py +1421 -0
  221. novacode_cli/council_settings.py +208 -0
  222. novacode_cli/council_web_settings.py +41 -0
  223. novacode_cli/council_workspace.py +56 -0
  224. novacode_cli/cowork/__init__.py +10 -0
  225. novacode_cli/cowork/broker_middleware.py +162 -0
  226. novacode_cli/cowork/launcher.py +102 -0
  227. novacode_cli/cowork/policy.py +284 -0
  228. novacode_cli/cowork/ui.py +826 -0
  229. novacode_cli/daemons/__init__.py +1 -0
  230. novacode_cli/daemons/registry.py +342 -0
  231. novacode_cli/desktop_notifications.py +302 -0
  232. novacode_cli/doctor.py +251 -0
  233. novacode_cli/embeddings.py +109 -0
  234. novacode_cli/entrypoint.py +56 -0
  235. novacode_cli/errors/__init__.py +19 -0
  236. novacode_cli/errors/handlers.py +648 -0
  237. novacode_cli/errors/provider_errors.py +249 -0
  238. novacode_cli/errors/taxonomy.py +80 -0
  239. novacode_cli/events.py +179 -0
  240. novacode_cli/file_ops.py +714 -0
  241. novacode_cli/git_safety.py +281 -0
  242. novacode_cli/handoff.py +383 -0
  243. novacode_cli/headless/__init__.py +18 -0
  244. novacode_cli/headless/input.py +58 -0
  245. novacode_cli/headless/output.py +224 -0
  246. novacode_cli/headless/pipe.py +532 -0
  247. novacode_cli/headless/runner.py +461 -0
  248. novacode_cli/hermes/__init__.py +39 -0
  249. novacode_cli/hermes/activation.py +297 -0
  250. novacode_cli/hermes/config.py +129 -0
  251. novacode_cli/hermes/curator.py +199 -0
  252. novacode_cli/hermes/evolution.py +374 -0
  253. novacode_cli/hermes/ledger.py +364 -0
  254. novacode_cli/hermes/memory_tiers.py +969 -0
  255. novacode_cli/hermes/middleware.py +678 -0
  256. novacode_cli/hermes/overview.py +174 -0
  257. novacode_cli/hermes/prompt_evolution.py +503 -0
  258. novacode_cli/hermes/refine_loop.py +643 -0
  259. novacode_cli/hermes/refinement_log.py +190 -0
  260. novacode_cli/hermes/review.py +674 -0
  261. novacode_cli/hermes/skill_debate.py +135 -0
  262. novacode_cli/hermes/skill_discovery.py +450 -0
  263. novacode_cli/hermes/skill_manager.py +335 -0
  264. novacode_cli/hermes/skill_proposals.py +43 -0
  265. novacode_cli/hermes/test_gaming.py +119 -0
  266. novacode_cli/hermes/tracker.py +435 -0
  267. novacode_cli/hermes/tuner.py +206 -0
  268. novacode_cli/hermes/verification_memory.py +134 -0
  269. novacode_cli/hermes/verifier.py +277 -0
  270. novacode_cli/hitl/__init__.py +22 -0
  271. novacode_cli/hitl/interrupts.py +493 -0
  272. novacode_cli/hooks.py +359 -0
  273. novacode_cli/image_utils.py +517 -0
  274. novacode_cli/init/__init__.py +7 -0
  275. novacode_cli/init/events.py +43 -0
  276. novacode_cli/input_utils.py +526 -0
  277. novacode_cli/install_commands.py +87 -0
  278. novacode_cli/integrations/__init__.py +82 -0
  279. novacode_cli/integrations/daytona.py +122 -0
  280. novacode_cli/integrations/docker.py +309 -0
  281. novacode_cli/integrations/host_path.py +111 -0
  282. novacode_cli/integrations/langsmith.py +151 -0
  283. novacode_cli/integrations/modal.py +134 -0
  284. novacode_cli/integrations/runloop.py +130 -0
  285. novacode_cli/integrations/sandbox_factory.py +1388 -0
  286. novacode_cli/integrations/sandbox_registry.py +312 -0
  287. novacode_cli/integrations/workdir_backend.py +589 -0
  288. novacode_cli/main.py +2078 -0
  289. novacode_cli/mcp/__init__.py +73 -0
  290. novacode_cli/mcp/auth.py +189 -0
  291. novacode_cli/mcp/client.py +571 -0
  292. novacode_cli/mcp/code_mode.py +5 -0
  293. novacode_cli/mcp/code_runtime.py +5 -0
  294. novacode_cli/mcp/commands.py +902 -0
  295. novacode_cli/mcp/config.py +406 -0
  296. novacode_cli/mcp/generic.py +128 -0
  297. novacode_cli/mcp/middleware.py +993 -0
  298. novacode_cli/mcp/presets.py +367 -0
  299. novacode_cli/memory/__init__.py +1 -0
  300. novacode_cli/memory/agent_memory.py +1312 -0
  301. novacode_cli/memory/builtin_lessons.py +60 -0
  302. novacode_cli/memory/dream_gates.py +192 -0
  303. novacode_cli/memory/limits.py +59 -0
  304. novacode_cli/memory/parsed_cache.py +106 -0
  305. novacode_cli/memory/recall_stats.py +97 -0
  306. novacode_cli/memory/semantic.py +116 -0
  307. novacode_cli/memory/store.py +335 -0
  308. novacode_cli/memory/wiki.py +274 -0
  309. novacode_cli/migrate.py +185 -0
  310. novacode_cli/onboarding.py +649 -0
  311. novacode_cli/onboarding_check.py +119 -0
  312. novacode_cli/path_approval.py +297 -0
  313. novacode_cli/plan_archive.py +148 -0
  314. novacode_cli/plugins/__init__.py +121 -0
  315. novacode_cli/plugins/claude_plugins.py +538 -0
  316. novacode_cli/plugins/cli.py +168 -0
  317. novacode_cli/plugins/components.py +413 -0
  318. novacode_cli/plugins/hooks.py +356 -0
  319. novacode_cli/plugins/loader.py +317 -0
  320. novacode_cli/plugins/marketplaces.py +199 -0
  321. novacode_cli/plugins/storage.py +70 -0
  322. novacode_cli/prime.py +759 -0
  323. novacode_cli/prime_factory/__init__.py +15 -0
  324. novacode_cli/prime_factory/container.py +198 -0
  325. novacode_cli/prime_factory/controller.py +565 -0
  326. novacode_cli/prime_factory/knowledge.py +261 -0
  327. novacode_cli/prime_factory/publication.py +218 -0
  328. novacode_cli/prime_factory/recovery.py +50 -0
  329. novacode_cli/prime_factory/research.py +370 -0
  330. novacode_cli/prime_factory/runner.py +608 -0
  331. novacode_cli/prime_factory/runtime.py +261 -0
  332. novacode_cli/prime_factory/state.py +201 -0
  333. novacode_cli/prime_factory/store.py +128 -0
  334. novacode_cli/prime_factory/worker.py +129 -0
  335. novacode_cli/prime_factory/workflow.py +899 -0
  336. novacode_cli/prime_factory/workspace.py +317 -0
  337. novacode_cli/prime_memory.py +290 -0
  338. novacode_cli/process_manager.py +585 -0
  339. novacode_cli/project_onboard.py +537 -0
  340. novacode_cli/prompts/System_Prompt_Nova.jinja +23 -0
  341. novacode_cli/prompts/__init__.py +168 -0
  342. novacode_cli/prompts/agent_definitions.jinja +11 -0
  343. novacode_cli/prompts/agent_generation.jinja +125 -0
  344. novacode_cli/prompts/async_agents/general_purpose.jinja +22 -0
  345. novacode_cli/prompts/claude_plan_mode.jinja +12 -0
  346. novacode_cli/prompts/coding_workflow.jinja +18 -0
  347. novacode_cli/prompts/computer_use.jinja +17 -0
  348. novacode_cli/prompts/core_agent_system.jinja +81 -0
  349. novacode_cli/prompts/delegation.jinja +15 -0
  350. novacode_cli/prompts/desktop_power.jinja +4 -0
  351. novacode_cli/prompts/file_paths.jinja +15 -0
  352. novacode_cli/prompts/file_tracker.jinja +23 -0
  353. novacode_cli/prompts/init_agents_md.jinja +56 -0
  354. novacode_cli/prompts/init_exploration.jinja +174 -0
  355. novacode_cli/prompts/init_semantic_orchestrate.jinja +37 -0
  356. novacode_cli/prompts/instruction_scope.jinja +5 -0
  357. novacode_cli/prompts/learnings_extraction.jinja +45 -0
  358. novacode_cli/prompts/longterm_memory.jinja +54 -0
  359. novacode_cli/prompts/mcp.jinja +84 -0
  360. novacode_cli/prompts/memory_consolidation.jinja +40 -0
  361. novacode_cli/prompts/nova_evolution.jinja +73 -0
  362. novacode_cli/prompts/nova_review.jinja +158 -0
  363. novacode_cli/prompts/nova_skill_debate.jinja +36 -0
  364. novacode_cli/prompts/nova_standing_instructions.jinja +26 -0
  365. novacode_cli/prompts/nova_verify.jinja +100 -0
  366. novacode_cli/prompts/onboard_dashboard.jinja +378 -0
  367. novacode_cli/prompts/plan_agent.jinja +87 -0
  368. novacode_cli/prompts/plan_scouts.jinja +5 -0
  369. novacode_cli/prompts/plan_storage.jinja +8 -0
  370. novacode_cli/prompts/planning.jinja +82 -0
  371. novacode_cli/prompts/ponytail/LICENSE +21 -0
  372. novacode_cli/prompts/ponytail/UPSTREAM.json +15 -0
  373. novacode_cli/prompts/ponytail/hooks/ponytail-config.js +175 -0
  374. novacode_cli/prompts/ponytail/hooks/ponytail-instructions.js +98 -0
  375. novacode_cli/prompts/ponytail/skills/ponytail/SKILL.md +52 -0
  376. novacode_cli/prompts/ponytail/skills/ponytail-audit/SKILL.md +103 -0
  377. novacode_cli/prompts/ponytail/skills/ponytail-debt/SKILL.md +44 -0
  378. novacode_cli/prompts/ponytail/skills/ponytail-gain/SKILL.md +52 -0
  379. novacode_cli/prompts/ponytail/skills/ponytail-help/SKILL.md +76 -0
  380. novacode_cli/prompts/ponytail/skills/ponytail-review/SKILL.md +101 -0
  381. novacode_cli/prompts/prompt_evolution.jinja +27 -0
  382. novacode_cli/prompts/ralph_iteration.jinja +75 -0
  383. novacode_cli/prompts/refine_gate.jinja +31 -0
  384. novacode_cli/prompts/refine_plan.jinja +64 -0
  385. novacode_cli/prompts/refine_review.jinja +34 -0
  386. novacode_cli/prompts/research_delivery.jinja +9 -0
  387. novacode_cli/prompts/research_swarm.jinja +130 -0
  388. novacode_cli/prompts/response_style.jinja +5 -0
  389. novacode_cli/prompts/session_summarization.jinja +86 -0
  390. novacode_cli/prompts/skill_creation.jinja +85 -0
  391. novacode_cli/prompts/subagents/fact_checker.jinja +48 -0
  392. novacode_cli/prompts/subagents/financial_analyst.jinja +50 -0
  393. novacode_cli/prompts/subagents/literature_reviewer.jinja +52 -0
  394. novacode_cli/prompts/subagents/market_analyst.jinja +48 -0
  395. novacode_cli/prompts/subagents/research_synthesizer.jinja +57 -0
  396. novacode_cli/prompts/subagents/technical_researcher.jinja +56 -0
  397. novacode_cli/prompts/subagents/web_researcher.jinja +46 -0
  398. novacode_cli/prompts/summarization.jinja +107 -0
  399. novacode_cli/prompts/summarization_answers.jinja +23 -0
  400. novacode_cli/prompts/summarization_questions.jinja +32 -0
  401. novacode_cli/prompts/vision_caption.jinja +21 -0
  402. novacode_cli/prompts/voice_summary.jinja +65 -0
  403. novacode_cli/prompts/wiki_file.jinja +57 -0
  404. novacode_cli/prompts/wiki_ingest.jinja +87 -0
  405. novacode_cli/recovery.py +383 -0
  406. novacode_cli/remote/__init__.py +45 -0
  407. novacode_cli/remote/approvals.py +282 -0
  408. novacode_cli/remote/async_task_clients.py +97 -0
  409. novacode_cli/remote/async_task_watcher.py +240 -0
  410. novacode_cli/remote/bridge.py +905 -0
  411. novacode_cli/remote/command_menu.py +26 -0
  412. novacode_cli/remote/command_ui.py +243 -0
  413. novacode_cli/remote/config.py +199 -0
  414. novacode_cli/remote/discord_bridge.py +530 -0
  415. novacode_cli/remote/discord_format.py +31 -0
  416. novacode_cli/remote/discord_threads.py +225 -0
  417. novacode_cli/remote/images.py +97 -0
  418. novacode_cli/remote/processor.py +601 -0
  419. novacode_cli/remote/rootapp_bot/bot.cjs +16 -0
  420. novacode_cli/remote/rootapp_bot/package-lock.json +2683 -0
  421. novacode_cli/remote/rootapp_bot/package.json +18 -0
  422. novacode_cli/remote/rootapp_bot/transport.cjs +80 -0
  423. novacode_cli/remote/rootapp_bridge.py +370 -0
  424. novacode_cli/remote/rootapp_setup.py +89 -0
  425. novacode_cli/remote/routing.py +103 -0
  426. novacode_cli/remote/scheduler.py +302 -0
  427. novacode_cli/remote/session.py +164 -0
  428. novacode_cli/remote/status.py +411 -0
  429. novacode_cli/remote/streaming.py +188 -0
  430. novacode_cli/remote/telegram_bridge.py +1118 -0
  431. novacode_cli/remote/telegram_format.py +215 -0
  432. novacode_cli/remote/telegram_hub.py +256 -0
  433. novacode_cli/remote/voice_notes.py +120 -0
  434. novacode_cli/remote/webhook_adapters.py +167 -0
  435. novacode_cli/remote/webhook_server.py +154 -0
  436. novacode_cli/rewind.py +150 -0
  437. novacode_cli/rooms/__init__.py +1 -0
  438. novacode_cli/rooms/bridge.py +86 -0
  439. novacode_cli/rooms/cli.py +231 -0
  440. novacode_cli/rooms/nova.py +227 -0
  441. novacode_cli/rooms/runtime.py +299 -0
  442. novacode_cli/rooms/store.py +538 -0
  443. novacode_cli/rooms/workspace.py +94 -0
  444. novacode_cli/security/__init__.py +29 -0
  445. novacode_cli/security/approval_context.py +45 -0
  446. novacode_cli/security/delegated_approval.py +68 -0
  447. novacode_cli/security/local_http.py +85 -0
  448. novacode_cli/security/middleware.py +87 -0
  449. novacode_cli/security/paths.py +35 -0
  450. novacode_cli/security/policy.py +535 -0
  451. novacode_cli/security/policy_writer.py +90 -0
  452. novacode_cli/security/prompt_injection.py +203 -0
  453. novacode_cli/security/remember.py +81 -0
  454. novacode_cli/security/rule_synthesis.py +116 -0
  455. novacode_cli/security/secret_files.py +61 -0
  456. novacode_cli/security/session_allow.py +114 -0
  457. novacode_cli/security/unicode_security.py +516 -0
  458. novacode_cli/security/validator.py +173 -0
  459. novacode_cli/server/__init__.py +9 -0
  460. novacode_cli/server/app.py +632 -0
  461. novacode_cli/server/event_adapter.py +189 -0
  462. novacode_cli/server/session_manager.py +90 -0
  463. novacode_cli/server_main.py +64 -0
  464. novacode_cli/server_runner/dev_server.py +530 -0
  465. novacode_cli/server_runner/test_runner.py +470 -0
  466. novacode_cli/session/__init__.py +1 -0
  467. novacode_cli/session/adapters/__init__.py +240 -0
  468. novacode_cli/session/adapters/claude_code.py +102 -0
  469. novacode_cli/session/adapters/codex.py +77 -0
  470. novacode_cli/session/adapters/harness_native.py +75 -0
  471. novacode_cli/session/checkpoint_coordinator.py +448 -0
  472. novacode_cli/session/context_restore.py +25 -0
  473. novacode_cli/session/history_export.py +228 -0
  474. novacode_cli/session/history_tree.py +174 -0
  475. novacode_cli/session/imported_context.py +267 -0
  476. novacode_cli/session/runtime_identity.py +32 -0
  477. novacode_cli/session/session_persistence.py +1168 -0
  478. novacode_cli/session/session_prompt_builder.py +279 -0
  479. novacode_cli/session/session_restore.py +351 -0
  480. novacode_cli/session/session_summarization.py +20 -0
  481. novacode_cli/session/transcript_journal.py +153 -0
  482. novacode_cli/sessions/__init__.py +11 -0
  483. novacode_cli/sessions/gateway.py +84 -0
  484. novacode_cli/sessions/launch.py +51 -0
  485. novacode_cli/sessions/lease.py +213 -0
  486. novacode_cli/sessions/messaging.py +255 -0
  487. novacode_cli/sessions/protocol.py +216 -0
  488. novacode_cli/sessions/resume.py +31 -0
  489. novacode_cli/sessions/supervisor.py +498 -0
  490. novacode_cli/sessions/tasks.py +73 -0
  491. novacode_cli/sessions/tui_messaging.py +413 -0
  492. novacode_cli/sessions/worker.py +652 -0
  493. novacode_cli/sessions/worktree.py +262 -0
  494. novacode_cli/shell/__init__.py +27 -0
  495. novacode_cli/shell/jobs.py +475 -0
  496. novacode_cli/shell/middleware.py +1568 -0
  497. novacode_cli/shell/os_sandbox.py +205 -0
  498. novacode_cli/shell/output.py +181 -0
  499. novacode_cli/shell/patterns.py +275 -0
  500. novacode_cli/shell/processes.py +87 -0
  501. novacode_cli/shell/utils.py +236 -0
  502. novacode_cli/skills/__init__.py +26 -0
  503. novacode_cli/skills/builtin/handoff/SKILL.md +15 -0
  504. novacode_cli/skills/curation_middleware.py +69 -0
  505. novacode_cli/skills/executable.py +161 -0
  506. novacode_cli/skills/libraries.py +102 -0
  507. novacode_cli/skills/load.py +248 -0
  508. novacode_cli/skills/prune.py +215 -0
  509. novacode_cli/skills/refreshing_middleware.py +896 -0
  510. novacode_cli/skills/retrieval.py +248 -0
  511. novacode_cli/skills/runtime.py +118 -0
  512. novacode_cli/skills/schema.py +254 -0
  513. novacode_cli/skills/skill_creation.py +1549 -0
  514. novacode_cli/skills/skill_lock.py +75 -0
  515. novacode_cli/skills/skill_system_prompt.py +16 -0
  516. novacode_cli/skills/skills_prefs.py +126 -0
  517. novacode_cli/skills/tool_resolver.py +31 -0
  518. novacode_cli/skills/upstream.py +50 -0
  519. novacode_cli/skills/versioning.py +291 -0
  520. novacode_cli/states/Session.py +847 -0
  521. novacode_cli/states/__init__.py +31 -0
  522. novacode_cli/states/slices/__init__.py +17 -0
  523. novacode_cli/states/slices/agent_runtime.py +249 -0
  524. novacode_cli/states/slices/background_tasks.py +21 -0
  525. novacode_cli/states/slices/notifications.py +74 -0
  526. novacode_cli/states/slices/remote_bridge.py +56 -0
  527. novacode_cli/states/slices/ui_settings.py +49 -0
  528. novacode_cli/states/slices/wiki.py +25 -0
  529. novacode_cli/teammates/__init__.py +1 -0
  530. novacode_cli/teammates/controller.py +557 -0
  531. novacode_cli/teammates/graph.py +104 -0
  532. novacode_cli/teammates/registry.py +175 -0
  533. novacode_cli/teammates/service.py +254 -0
  534. novacode_cli/teammates/worker.py +19 -0
  535. novacode_cli/teams/__init__.py +1 -0
  536. novacode_cli/teams/adapters/__init__.py +1 -0
  537. novacode_cli/teams/adapters/container.py +316 -0
  538. novacode_cli/teams/adapters/inprocess.py +206 -0
  539. novacode_cli/teams/adapters/process.py +364 -0
  540. novacode_cli/teams/bootstrap.py +246 -0
  541. novacode_cli/teams/container_git.py +67 -0
  542. novacode_cli/teams/context.py +34 -0
  543. novacode_cli/teams/coordinator.py +815 -0
  544. novacode_cli/teams/gateway.py +200 -0
  545. novacode_cli/teams/host.py +64 -0
  546. novacode_cli/teams/legacy.py +52 -0
  547. novacode_cli/teams/middleware.py +176 -0
  548. novacode_cli/teams/models.py +104 -0
  549. novacode_cli/teams/output.py +47 -0
  550. novacode_cli/teams/protocol.py +50 -0
  551. novacode_cli/teams/recovery.py +80 -0
  552. novacode_cli/teams/scheduler.py +44 -0
  553. novacode_cli/teams/store.py +631 -0
  554. novacode_cli/teams/worker.py +251 -0
  555. novacode_cli/teams/workspace.py +100 -0
  556. novacode_cli/token_utils.py +256 -0
  557. novacode_cli/tools/__init__.py +104 -0
  558. novacode_cli/tools/_shared.py +97 -0
  559. novacode_cli/tools/_types.py +24 -0
  560. novacode_cli/tools/artifact_tools.py +137 -0
  561. novacode_cli/tools/code_search_tools.py +330 -0
  562. novacode_cli/tools/computer_tools.py +77 -0
  563. novacode_cli/tools/cron_tools.py +65 -0
  564. novacode_cli/tools/daemon_tool.py +290 -0
  565. novacode_cli/tools/desktop_tools.py +161 -0
  566. novacode_cli/tools/fetch_tools.py +530 -0
  567. novacode_cli/tools/http_tools.py +279 -0
  568. novacode_cli/tools/inspection_tools.py +107 -0
  569. novacode_cli/tools/job_tools.py +143 -0
  570. novacode_cli/tools/knowledge_tools.py +427 -0
  571. novacode_cli/tools/memory_tools.py +461 -0
  572. novacode_cli/tools/memory_wiki_tools.py +40 -0
  573. novacode_cli/tools/notebook_tools.py +238 -0
  574. novacode_cli/tools/oracle_tool.py +83 -0
  575. novacode_cli/tools/plan_archive_tools.py +80 -0
  576. novacode_cli/tools/plan_mode_tools.py +315 -0
  577. novacode_cli/tools/python_kernel_loop.py +165 -0
  578. novacode_cli/tools/python_kernel_tool.py +222 -0
  579. novacode_cli/tools/reflection_tools.py +34 -0
  580. novacode_cli/tools/scraper_tools.py +377 -0
  581. novacode_cli/tools/skill_tools.py +275 -0
  582. novacode_cli/tools/speak_tool.py +51 -0
  583. novacode_cli/tools/store_memory_tools.py +227 -0
  584. novacode_cli/tools/ui_tools.py +62 -0
  585. novacode_cli/tools/waiter_tools.py +408 -0
  586. novacode_cli/tools/web_tools.py +349 -0
  587. novacode_cli/tools/wiki_tools.py +152 -0
  588. novacode_cli/tools/worktree_tools.py +219 -0
  589. novacode_cli/tracking/__init__.py +10 -0
  590. novacode_cli/tracking/file_tracker.py +826 -0
  591. novacode_cli/tracking/local_metrics.py +170 -0
  592. novacode_cli/tracking/loop_guard.py +507 -0
  593. novacode_cli/tracking/request_metrics.py +60 -0
  594. novacode_cli/tracking/tracing.py +692 -0
  595. novacode_cli/tracking/usage_tree.py +677 -0
  596. novacode_cli/tracking/workspace_anchoring.py +231 -0
  597. novacode_cli/tui/__init__.py +11 -0
  598. novacode_cli/tui/animation_rate.py +9 -0
  599. novacode_cli/tui/animations.py +332 -0
  600. novacode_cli/tui/app.py +19315 -0
  601. novacode_cli/tui/auth_display.py +87 -0
  602. novacode_cli/tui/auth_screens.py +826 -0
  603. novacode_cli/tui/avatar.py +294 -0
  604. novacode_cli/tui/avatar_frames.py +21 -0
  605. novacode_cli/tui/background_tasks.py +35 -0
  606. novacode_cli/tui/browser_settings_screen.py +70 -0
  607. novacode_cli/tui/caveman_screen.py +185 -0
  608. novacode_cli/tui/council_settings_screen.py +182 -0
  609. novacode_cli/tui/council_view.py +535 -0
  610. novacode_cli/tui/decision_settings.py +268 -0
  611. novacode_cli/tui/diff_preview.py +199 -0
  612. novacode_cli/tui/gc_tuning.py +63 -0
  613. novacode_cli/tui/handoff_screen.py +206 -0
  614. novacode_cli/tui/harness.py +244 -0
  615. novacode_cli/tui/history_tree_screen.py +179 -0
  616. novacode_cli/tui/home.py +328 -0
  617. novacode_cli/tui/home_scene.py +94 -0
  618. novacode_cli/tui/icons.py +81 -0
  619. novacode_cli/tui/interface_styles.tcss +327 -0
  620. novacode_cli/tui/matrix3.py +108 -0
  621. novacode_cli/tui/mcp_tools_screen.py +131 -0
  622. novacode_cli/tui/motion.py +178 -0
  623. novacode_cli/tui/output_body.py +136 -0
  624. novacode_cli/tui/output_buffer.py +59 -0
  625. novacode_cli/tui/palette.py +334 -0
  626. novacode_cli/tui/pickers.py +237 -0
  627. novacode_cli/tui/prime_panel.py +506 -0
  628. novacode_cli/tui/provider_signin.py +213 -0
  629. novacode_cli/tui/pty_shell.py +159 -0
  630. novacode_cli/tui/reference_screens.py +444 -0
  631. novacode_cli/tui/resume_picker.py +346 -0
  632. novacode_cli/tui/rooms.py +597 -0
  633. novacode_cli/tui/screens.py +7088 -0
  634. novacode_cli/tui/session_import.py +333 -0
  635. novacode_cli/tui/session_pane.py +242 -0
  636. novacode_cli/tui/session_tab.py +42 -0
  637. novacode_cli/tui/session_transcript.py +118 -0
  638. novacode_cli/tui/shell_output.py +7 -0
  639. novacode_cli/tui/stall_watch.py +174 -0
  640. novacode_cli/tui/statusline.py +40 -0
  641. novacode_cli/tui/statusline_screen.py +91 -0
  642. novacode_cli/tui/teammate_updates.py +162 -0
  643. novacode_cli/tui/teammates.py +548 -0
  644. novacode_cli/tui/terminal.py +289 -0
  645. novacode_cli/tui/terminal_view.py +235 -0
  646. novacode_cli/tui/themes.py +118 -0
  647. novacode_cli/tui/update_notice.py +84 -0
  648. novacode_cli/tui/voice_downloads.py +113 -0
  649. novacode_cli/tui/widgets.py +2038 -0
  650. novacode_cli/tui/windows_input.py +38 -0
  651. novacode_cli/tui/workspace_approval.py +165 -0
  652. novacode_cli/ui/council_web.py +113 -0
  653. novacode_cli/ui/council_workspace.py +72 -0
  654. novacode_cli/ui/cowork_web.py +41 -0
  655. novacode_cli/ui/diff_highlight.py +160 -0
  656. novacode_cli/ui/execution.py +1396 -0
  657. novacode_cli/ui/exit_summary.py +65 -0
  658. novacode_cli/ui/hitl_approval.py +196 -0
  659. novacode_cli/ui/input_preparation.py +2 -0
  660. novacode_cli/ui/interrupt_handlers.py +215 -0
  661. novacode_cli/ui/question_prompt.py +151 -0
  662. novacode_cli/ui/status_phrases.py +157 -0
  663. novacode_cli/ui/stream_preview.py +66 -0
  664. novacode_cli/ui/streaming.py +2 -0
  665. novacode_cli/ui/subagent_tracking.py +2 -0
  666. novacode_cli/ui/ui_elements.py +1585 -0
  667. novacode_cli/ui/web_workspace.py +244 -0
  668. novacode_cli/ui_events.py +299 -0
  669. novacode_cli/update_ui.py +243 -0
  670. novacode_cli/updates.py +489 -0
  671. novacode_cli/utils/backend_patches.py +399 -0
  672. novacode_cli/utils/backend_paths.py +467 -0
  673. novacode_cli/utils/lazy_providers.py +51 -0
  674. novacode_cli/utils/model_info.py +223 -0
  675. novacode_cli/utils/pdf_extraction.py +183 -0
  676. novacode_cli/utils/startup.py +29 -0
  677. novacode_cli/utils/tasks.py +42 -0
  678. novacode_cli/utils/windows.py +9 -0
  679. novacode_cli/vixie/hook_adapter.py +127 -0
  680. novacode_cli/vixie/server.py +375 -0
  681. novacode_cli/wiki/__init__.py +15 -0
  682. novacode_cli/wiki/ask.py +56 -0
  683. novacode_cli/wiki/file.py +71 -0
  684. novacode_cli/wiki/ingest.py +184 -0
  685. novacode_cli/wiki/manager.py +261 -0
  686. novacode_cli-1.2.0.dist-info/METADATA +271 -0
  687. novacode_cli-1.2.0.dist-info/RECORD +691 -0
  688. novacode_cli-1.2.0.dist-info/WHEEL +5 -0
  689. novacode_cli-1.2.0.dist-info/entry_points.txt +2 -0
  690. novacode_cli-1.2.0.dist-info/licenses/LICENSE +21 -0
  691. novacode_cli-1.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,29 @@
1
+ """NOVA CLI - Interactive AI coding assistant."""
2
+
3
+ import os as _os
4
+
5
+ # Must run before numpy is imported anywhere in the process. numpy's OpenBLAS
6
+ # pre-allocates a buffer per CPU thread at import: on a 12-thread machine that
7
+ # is ~380 MB of private memory for nothing — Nova's numeric work is small
8
+ # vector searches (skill and code retrieval over a few thousand 256-d rows),
9
+ # where one thread is measurably FASTER (91 us vs 180 us for a 1000-row
10
+ # search) because thread hand-off costs more than the arithmetic. One thread:
11
+ # ~9 MB. It applies to every Nova process — the TUI and each spawned session.
12
+ # setdefault, so an explicit setting in the environment still wins.
13
+ _os.environ.setdefault("OPENBLAS_NUM_THREADS", "1")
14
+ # Other numerical backends also default to machine-wide thread pools. Multiple
15
+ # Nova processes should not each occupy every core; explicit user values win.
16
+ _os.environ.setdefault("OMP_NUM_THREADS", "1")
17
+ _os.environ.setdefault("MKL_NUM_THREADS", "1")
18
+ _os.environ.setdefault("NUMEXPR_NUM_THREADS", "1")
19
+
20
+ # Must run before langchain_core is imported: it pulls in `transformers` (and
21
+ # therefore torch/numpy/PIL) at module scope for a fallback tokenizer Nova never
22
+ # uses, costing ~160 MB RSS and ~10 s of startup. See novacode_cli._lazy_heavy.
23
+ from novacode_cli import _lazy_heavy as _lazy_heavy
24
+
25
+ _lazy_heavy.install()
26
+
27
+ # from novacode_cli.main import cli_main
28
+
29
+ __all__ = []
@@ -0,0 +1,6 @@
1
+ """Allow running the CLI as: python -m deepagents.cli."""
2
+
3
+ from novacode_cli.entrypoint import cli_main
4
+
5
+ if __name__ == "__main__":
6
+ cli_main()
@@ -0,0 +1,99 @@
1
+ """Keep `transformers`/`torch` out of Nova's startup import graph.
2
+
3
+ ``langchain_core.language_models.base`` does, at module scope::
4
+
5
+ try:
6
+ from transformers import GPT2TokenizerFast
7
+ _HAS_TRANSFORMERS = True
8
+ except ImportError:
9
+ _HAS_TRANSFORMERS = False
10
+
11
+ That import is only ever used for a *fallback* GPT-2 token counter
12
+ (``_get_token_ids_default_method``), but ``transformers`` eagerly pulls in
13
+ ``torch``, ``numpy``, ``huggingface_hub`` and ``PIL``. Measured on this project:
14
+
15
+ full import of novacode_cli.main 305 MB RSS, ~13 s, 4056 modules
16
+ with transformers/torch excluded 145 MB RSS, ~2.4 s, 3053 modules
17
+
18
+ Nova talks to hosted model APIs and takes token counts from the provider
19
+ (``get_num_tokens_from_messages``, with char-based fallbacks at every call
20
+ site), so it never needs the GPT-2 tokenizer. Paying 160 MB of resident memory
21
+ and ~10 s of startup for it caused real problems on memory-constrained machines
22
+ (paging-file exhaustion, failed subprocess spawns).
23
+
24
+ This installs a meta-path finder that makes ``import transformers`` raise
25
+ ``ImportError`` — the exact exception langchain_core already handles — so it
26
+ cleanly takes the ``_HAS_TRANSFORMERS = False`` branch.
27
+
28
+ Escape hatch: set ``NOVA_ALLOW_TRANSFORMERS=1`` to disable the guard. Anything
29
+ that genuinely needs the library (a local HF model, say) can also call
30
+ :func:`allow_transformers` first.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import os
36
+ import sys
37
+ from typing import Any
38
+
39
+ #: Top-level packages kept out of the startup graph.
40
+ _BLOCKED = frozenset({"transformers", "torch"})
41
+
42
+ _ENV_OPT_OUT = "NOVA_ALLOW_TRANSFORMERS"
43
+
44
+
45
+ class _HeavyImportGuard:
46
+ """Meta-path finder that refuses the heavy optional deps.
47
+
48
+ Raising ``ImportError`` (rather than returning ``None``) is deliberate: the
49
+ consumers of these packages all wrap the import in ``try/except
50
+ ImportError`` and degrade gracefully. Returning ``None`` would just let the
51
+ real finder load them.
52
+ """
53
+
54
+ def find_spec(self, name: str, path: Any = None, target: Any = None) -> None: # noqa: ANN401, ARG002
55
+ if name.split(".")[0] in _BLOCKED:
56
+ msg = (
57
+ f"{name!r} is not imported at Nova startup (it costs ~160 MB of "
58
+ f"resident memory and is only needed for a fallback tokenizer). "
59
+ f"Set {_ENV_OPT_OUT}=1 if you really need it."
60
+ )
61
+ raise ImportError(msg)
62
+ return None
63
+
64
+
65
+ _guard: _HeavyImportGuard | None = None
66
+
67
+
68
+ def install() -> bool:
69
+ """Install the guard. Returns True if it is now active.
70
+
71
+ No-ops when the user opted out, or when the packages are already imported
72
+ (blocking then would be pointless — the memory is already committed).
73
+ """
74
+ global _guard
75
+ if os.environ.get(_ENV_OPT_OUT, "").strip().lower() in ("1", "true", "yes"):
76
+ return False
77
+ if _guard is not None:
78
+ return True
79
+ if any(mod in sys.modules for mod in _BLOCKED):
80
+ return False
81
+ _guard = _HeavyImportGuard()
82
+ sys.meta_path.insert(0, _guard)
83
+ return True
84
+
85
+
86
+ def allow_transformers() -> None:
87
+ """Remove the guard so ``transformers``/``torch`` can be imported again."""
88
+ global _guard
89
+ if _guard is not None:
90
+ try:
91
+ sys.meta_path.remove(_guard)
92
+ except ValueError:
93
+ pass
94
+ _guard = None
95
+
96
+
97
+ def is_active() -> bool:
98
+ """Whether the guard is currently installed."""
99
+ return _guard is not None
@@ -0,0 +1,3 @@
1
+ """Version information for NOVA CLI."""
2
+
3
+ __version__ = "1.2.0"
@@ -0,0 +1,178 @@
1
+ """UI-agnostic agent streaming.
2
+
3
+ Thin wrapper around :func:`novacode_cli.core.agent_loop.iterate_agent_events`
4
+ that preserves the public ``run_agent_stream`` API for backward compatibility.
5
+
6
+ Human-in-the-loop interrupts are surfaced as :class:`~novacode_cli.ui_events.InterruptRequest`
7
+ events carrying an ``asyncio.Future`` that the consumer must resolve with the
8
+ decision; the generator awaits it and resumes the graph.
9
+
10
+ When the learning loop is enabled, each completed turn is graded out-of-band and
11
+ the verdict is logged — that verdict is the quality signal ``/prompt`` evolution
12
+ (prompt-template A/B testing) and ``verification_log`` feed on. Historically only
13
+ the legacy CLI path (``ui/execution.py`` via ``run_with_verification``) produced
14
+ this signal, so the TUI — which calls ``iterate_agent_events`` through here —
15
+ generated nothing, leaving ``verification_log`` empty and auto prompt-evolution
16
+ starved. The grade runs as a fire-and-forget background task: no retries and no
17
+ UI events, so it never delays or alters the turn.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import asyncio
23
+ from typing import TYPE_CHECKING, Any
24
+
25
+ from novacode_cli import ui_events as ev
26
+ from novacode_cli.core.autonomous_loop import run_with_goal
27
+ from novacode_cli.sessions.lease import lease_holder, lease_session
28
+
29
+ if TYPE_CHECKING:
30
+ from collections.abc import AsyncIterator, Callable
31
+
32
+ # Hold references to in-flight grade tasks so they aren't GC'd mid-run.
33
+ _verification_tasks: set[asyncio.Task] = set()
34
+
35
+
36
+ class _NullAsyncCM:
37
+ """A no-op async context manager (used when there is no thread to lease)."""
38
+
39
+ async def __aenter__(self) -> None:
40
+ return None
41
+
42
+ async def __aexit__(self, *exc: object) -> None:
43
+ return None
44
+
45
+
46
+ def _null_async_cm() -> _NullAsyncCM:
47
+ return _NullAsyncCM()
48
+
49
+
50
+ def _maybe_verifier() -> Any | None:
51
+ """An ``InlineVerifier`` when the learning loop is enabled, else ``None``.
52
+
53
+ Gated behind ``learning_enabled`` (opt-in) because grading adds one
54
+ out-of-band model call per turn. Best-effort — any setup failure returns
55
+ ``None`` and the turn runs unverified.
56
+ """
57
+ try:
58
+ from novacode_cli.config.nova_config import NovaConfig
59
+
60
+ if not NovaConfig().get_learning_enabled():
61
+ return None
62
+ from novacode_cli.hermes.verifier import InlineVerifier
63
+ from novacode_cli.memory.store import get_durable_store
64
+
65
+ return InlineVerifier(get_durable_store(), enabled=True)
66
+ except Exception: # noqa: BLE001 — learning is best-effort, never break a turn
67
+ return None
68
+
69
+
70
+ def _spawn_verification_signal(
71
+ verifier: Any,
72
+ user_input: str,
73
+ agent_output: str,
74
+ file_ops: list,
75
+ session_state: Any,
76
+ tool_results: list | None = None,
77
+ ) -> None:
78
+ """Grade the finished turn and log the verdict (verification_log + prompt A/B).
79
+
80
+ Fire-and-forget: no retries, no UI events. Skipped for a pure no-op turn
81
+ (nothing produced to grade).
82
+ """
83
+ if verifier is None or (not agent_output and not file_ops):
84
+ return
85
+
86
+ async def _grade() -> None:
87
+ try:
88
+ from novacode_cli.core.verification_loop import (
89
+ _extract_diffs,
90
+ _extract_test_evidence,
91
+ _record_prompt_ab_outcome,
92
+ )
93
+
94
+ test_evidence = _extract_test_evidence(tool_results or [])
95
+ diffs = _extract_diffs(file_ops)
96
+ verdict = await verifier.grade(
97
+ user_input,
98
+ agent_output,
99
+ file_ops,
100
+ test_evidence=test_evidence,
101
+ diffs=diffs,
102
+ )
103
+ await verifier.log_outcome(getattr(session_state, "thread_id", ""), verdict, 0)
104
+ # Feed prompt-evolution (Enhancement 2) its quality signal.
105
+ await _record_prompt_ab_outcome(passed=verdict.passed)
106
+ except Exception: # noqa: BLE001 — a grading failure must never surface
107
+ pass
108
+
109
+ task = asyncio.create_task(_grade())
110
+ _verification_tasks.add(task)
111
+ task.add_done_callback(_verification_tasks.discard)
112
+
113
+
114
+ async def run_agent_stream(
115
+ user_input: str,
116
+ agent,
117
+ assistant_id: str | None,
118
+ session_state,
119
+ *,
120
+ backend=None,
121
+ image_tracker=None,
122
+ seen_message_ids: set[str] | None = None,
123
+ skip_file_mentions: bool = False,
124
+ can_continue: Callable[[], bool] | None = None,
125
+ ) -> AsyncIterator[Any]:
126
+ """Run the agent and yield UI events.
127
+
128
+ Delegates to :func:`~novacode_cli.core.agent_loop.iterate_agent_events` (or
129
+ :func:`~novacode_cli.core.autonomous_loop.run_with_goal` when a goal is
130
+ active, so goal mode works from the TUI). Yields instances from
131
+ :mod:`novacode_cli.ui_events`. Terminates with a
132
+ :class:`~novacode_cli.ui_events.Done`, :class:`~novacode_cli.ui_events.Cancelled`,
133
+ or :class:`~novacode_cli.ui_events.Error` event. When learning is enabled,
134
+ grades the completed turn out-of-band (see module docstring).
135
+
136
+ The turn runs under a best-effort cross-process session lease (never blocks;
137
+ proceeds unleased on conflict).
138
+ """
139
+ verifier = _maybe_verifier()
140
+ texts: list[str] = []
141
+ file_ops: list[Any] = []
142
+ tool_results: list[Any] = []
143
+
144
+ thread_id = getattr(session_state, "thread_id", None) or getattr(
145
+ session_state, "session_id", None
146
+ )
147
+ holder = lease_holder("tui")
148
+
149
+ # Best-effort cross-process lease. Only acquired when we have a real
150
+ # thread_id; never blocks (proceeds unleased on conflict).
151
+ _lease_cm = lease_session(thread_id, holder) if thread_id else _null_async_cm()
152
+
153
+ async with _lease_cm:
154
+ async for event in run_with_goal(
155
+ user_input,
156
+ agent,
157
+ assistant_id,
158
+ session_state,
159
+ backend=backend,
160
+ image_tracker=image_tracker,
161
+ seen_message_ids=seen_message_ids,
162
+ skip_file_mentions=skip_file_mentions,
163
+ can_continue=can_continue,
164
+ ):
165
+ if verifier is not None:
166
+ if isinstance(event, ev.AssistantMessage):
167
+ texts.append(event.text)
168
+ elif isinstance(event, ev.FileOp) and getattr(event, "record", None) is not None:
169
+ file_ops.append(event.record)
170
+ elif isinstance(event, ev.ToolResult):
171
+ tool_results.append(event)
172
+ yield event
173
+
174
+ if verifier is not None:
175
+ agent_output = "\n\n".join(t for t in texts if t).strip()
176
+ _spawn_verification_signal(
177
+ verifier, user_input, agent_output, file_ops, session_state, tool_results
178
+ )
@@ -0,0 +1,250 @@
1
+ """Custom subagent definitions and the tools a subagent may be given.
2
+
3
+ An ``agent.md`` is YAML frontmatter plus the system prompt::
4
+
5
+ ---
6
+ color: "#0ea5e9"
7
+ description: "Reviews Python for security bugs"
8
+ tools: ["web_search", "serena_find_symbol"]
9
+ ---
10
+ You are ...
11
+
12
+ ``tools`` is optional. Absent means every tool (what agents always had);
13
+ a list means exactly those, on top of the file/shell tools every subagent gets
14
+ from deepagents itself (:data:`ALWAYS_INCLUDED`).
15
+
16
+ Create new definitions as flat ``.nova/agents/<name>.md`` files in a project or
17
+ ``~/.nova/agents/<name>.md`` globally. Existing ``<name>/agent.md`` folders and
18
+ Claude Code agent files remain supported::
19
+
20
+ ---
21
+ name: approver
22
+ description: Final gate; writes decision.md.
23
+ tools: Read, Grep, Glob, Bash, Write
24
+ model: opus
25
+ ---
26
+
27
+ Claude tool names map to Nova's (:data:`CLAUDE_TOOL_NAMES`). A list that names
28
+ any file/shell tool is a Claude-style allowlist: the file/shell tools it leaves
29
+ out are taken away (:class:`FilesystemToolFilter`), so ``approver`` has no
30
+ ``edit_file``.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import json
36
+ from pathlib import Path
37
+ from typing import TYPE_CHECKING, Any, cast
38
+
39
+ from langchain.agents.middleware import AgentMiddleware
40
+ from langchain_core.messages import ToolMessage
41
+
42
+ if TYPE_CHECKING:
43
+ from collections.abc import Awaitable, Callable
44
+
45
+ from langchain.agents.middleware.types import ModelRequest, ModelResponse
46
+ from langchain.tools.tool_node import ToolCallRequest
47
+ from langgraph.types import Command
48
+
49
+ #: Given to every subagent by deepagents' own middleware; not selectable.
50
+ ALWAYS_INCLUDED = (
51
+ "ls", "read_file", "write_file", "edit_file", "delete", "glob", "grep", "execute",
52
+ )
53
+
54
+ #: Claude Code tool names in an agent file's ``tools:`` → Nova's tool names.
55
+ CLAUDE_TOOL_NAMES = {
56
+ "Read": "read_file",
57
+ "Write": "write_file",
58
+ "Edit": "edit_file",
59
+ "MultiEdit": "edit_file",
60
+ "NotebookEdit": "NotebookEditTool",
61
+ "Grep": "grep",
62
+ "Glob": "glob",
63
+ "GlobTool": "glob",
64
+ "GrepTool": "grep",
65
+ "LS": "ls",
66
+ "Bash": "execute",
67
+ "WebFetch": "fetch_url",
68
+ "WebSearch": "web_search",
69
+ "TodoWrite": "write_todos",
70
+ }
71
+
72
+
73
+ def _parse(content: str) -> tuple[dict[str, Any], str]:
74
+ if not content.startswith("---"):
75
+ return {}, content
76
+ parts = content.split("---", 2)
77
+ if len(parts) < 3:
78
+ return {}, content
79
+ front: dict[str, Any] = {}
80
+ try:
81
+ import yaml
82
+
83
+ loaded = yaml.safe_load(parts[1])
84
+ if isinstance(loaded, dict):
85
+ front = loaded
86
+ except Exception: # noqa: BLE001 — hand-written files: fall back to key: value lines
87
+ for line in parts[1].splitlines():
88
+ if ":" in line:
89
+ key, value = line.split(":", 1)
90
+ front[key.strip()] = value.strip().strip('"').strip("'")
91
+ return front, parts[2].lstrip("\n")
92
+
93
+
94
+ def _split(content: str) -> tuple[dict[str, Any], str]:
95
+ """Reuse parsed frontmatter by exact content, never resolved capabilities."""
96
+ from time import perf_counter_ns
97
+
98
+ from novacode_cli.computation_cache import digest, get, put, record_timing
99
+
100
+ started = perf_counter_ns()
101
+ key = digest("agent-definition-v1\0" + content)
102
+ record_timing("agent_definitions", "validation", perf_counter_ns() - started)
103
+ cached = get("agent_definitions", key)
104
+ if cached is not None:
105
+ cached = cast("list[Any]", cached)
106
+ return cached[0], cached[1]
107
+ started = perf_counter_ns()
108
+ front, body = _parse(content)
109
+ record_timing("agent_definitions", "computation", perf_counter_ns() - started)
110
+ if set(front) <= {"name", "description", "tools", "model", "color", "skill_names"}:
111
+ put("agent_definitions", key, [front, body])
112
+ return front, body
113
+
114
+
115
+ def read_agent(path: Path) -> tuple[dict[str, Any], str]:
116
+ """``(frontmatter, system_prompt)`` of an ``agent.md``."""
117
+ return _split(path.read_text(encoding="utf-8"))
118
+
119
+
120
+ def agent_tools(content: str) -> list[str] | None:
121
+ """The agent's chosen tool names, or None when it did not choose (= all)."""
122
+ tools = _split(content)[0].get("tools")
123
+ if tools is None:
124
+ return None
125
+ if isinstance(tools, str):
126
+ tools = [t.strip() for t in tools.split(",")]
127
+ names = [str(t).strip() for t in tools if str(t).strip()]
128
+ return list(dict.fromkeys(CLAUDE_TOOL_NAMES.get(n, n) for n in names))
129
+
130
+
131
+ def filesystem_allowlist(chosen: list[str] | None) -> set[str] | None:
132
+ """The file/shell tools an agent keeps, or None when it keeps all of them.
133
+
134
+ Only a list naming at least one of them restricts: Nova's own agent files
135
+ never list them (the /agents picker hides them), so they keep every one.
136
+ """
137
+ allowed = set(chosen or ()) & set(ALWAYS_INCLUDED)
138
+ return allowed or None
139
+
140
+
141
+ class FilesystemToolFilter(AgentMiddleware):
142
+ """Takes away the file/shell tools an agent's ``tools:`` list left out.
143
+
144
+ deepagents gives every subagent all of :data:`ALWAYS_INCLUDED`; this hides
145
+ the others from the model and refuses a call to one anyway.
146
+ """
147
+
148
+ def __init__(self, allowed: set[str]) -> None:
149
+ """Keep *allowed* of :data:`ALWAYS_INCLUDED`; block the rest."""
150
+ super().__init__()
151
+ self._blocked = set(ALWAYS_INCLUDED) - allowed
152
+
153
+ def _bind(self, request: ModelRequest) -> ModelRequest:
154
+ keep = [t for t in request.tools if getattr(t, "name", None) not in self._blocked]
155
+ return request.override(tools=keep)
156
+
157
+ def _refusal(self, request: ToolCallRequest) -> ToolMessage | None:
158
+ call = request.tool_call
159
+ if call["name"] not in self._blocked:
160
+ return None
161
+ return ToolMessage(
162
+ f"{call['name']} is not in this agent's tools.",
163
+ tool_call_id=call["id"],
164
+ name=call["name"],
165
+ status="error",
166
+ )
167
+
168
+ def wrap_model_call(
169
+ self,
170
+ request: ModelRequest,
171
+ handler: Callable[[ModelRequest], ModelResponse],
172
+ ) -> ModelResponse:
173
+ """Bind only the allowed file/shell tools."""
174
+ return handler(self._bind(request))
175
+
176
+ async def awrap_model_call(
177
+ self,
178
+ request: ModelRequest,
179
+ handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
180
+ ) -> ModelResponse:
181
+ """Bind only the allowed file/shell tools."""
182
+ return await handler(self._bind(request))
183
+
184
+ def wrap_tool_call(
185
+ self,
186
+ request: ToolCallRequest,
187
+ handler: Callable[[ToolCallRequest], ToolMessage | Command],
188
+ ) -> ToolMessage | Command:
189
+ """Refuse a blocked tool the model called anyway."""
190
+ return self._refusal(request) or handler(request)
191
+
192
+ async def awrap_tool_call(
193
+ self,
194
+ request: ToolCallRequest,
195
+ handler: Callable[[ToolCallRequest], Awaitable[ToolMessage | Command]],
196
+ ) -> ToolMessage | Command:
197
+ """Refuse a blocked tool the model called anyway."""
198
+ return self._refusal(request) or await handler(request)
199
+
200
+
201
+ def write_agent(path: Path, front: dict[str, Any], body: str) -> None:
202
+ """Write ``agent.md``. Values are JSON-quoted, which is valid YAML: a
203
+ description holding ``: `` or a Windows path can no longer break it."""
204
+ lines = ["---"]
205
+ for key, value in front.items():
206
+ if value is None:
207
+ continue
208
+ lines.append(f"{key}: {json.dumps(value, ensure_ascii=False)}")
209
+ lines += ["---", "", body.strip(), ""]
210
+ path.parent.mkdir(parents=True, exist_ok=True)
211
+ path.write_text("\n".join(lines), encoding="utf-8")
212
+
213
+
214
+ def set_agent_tools(path: Path, tools: list[str] | None) -> None:
215
+ """Replace an agent's tool selection (None = back to every tool)."""
216
+ front, body = read_agent(path)
217
+ front.pop("tools", None)
218
+ if tools is not None:
219
+ front["tools"] = sorted(tools)
220
+ write_agent(path, front, body)
221
+
222
+
223
+ def tool_arsenal(session_tools: list[Any] | None = None) -> list[tuple[str, str]]:
224
+ """Every selectable tool as ``(name, one-line description)``, sorted.
225
+
226
+ Nova's own tools come from the live session; MCP tools from the schema
227
+ cache of each enabled server (named ``<server>_<tool>``, as bound).
228
+ """
229
+ found: dict[str, str] = {}
230
+ for tool in session_tools or []:
231
+ name = getattr(tool, "name", None)
232
+ if name and name not in ALWAYS_INCLUDED:
233
+ found[name] = (getattr(tool, "description", "") or "").strip().split("\n")[0]
234
+ try:
235
+ from novacode_cli.mcp.config import MCPConfig
236
+ from novacode_cli.mcp.middleware import _load_schema_cache
237
+
238
+ enabled = {
239
+ name for name, cfg in MCPConfig().list_servers().items()
240
+ if not getattr(cfg, "disabled", False)
241
+ }
242
+ for server, entry in _load_schema_cache().items():
243
+ if server not in enabled or not isinstance(entry, dict):
244
+ continue
245
+ for raw in entry.get("tools") or []:
246
+ desc = (raw.get("description") or "").strip().split("\n")[0]
247
+ found[f"{server}_{raw.get('name')}"] = desc
248
+ except Exception: # noqa: BLE001 — MCP listing is best effort
249
+ pass
250
+ return sorted(found.items())
@@ -0,0 +1,120 @@
1
+ """Agent-to-agent mailbox — a lightweight in-process message bus.
2
+
3
+ Nova's subagents run synchronously (one at a time, via the `task` tool), so they
4
+ can't hold a live conversation. This mailbox gives peer messaging instead: any
5
+ agent (orchestrator or subagent) drops a message into another agent's inbox by
6
+ name, and reads its own inbox when it runs. The orchestrator sequences the turns
7
+ (dispatch A → A messages B → dispatch B → B reads + replies → A reads).
8
+
9
+ Process-global and thread-safe: subagents run in the same process as the
10
+ orchestrator, so a module-level store is all that's needed. It is NOT durable
11
+ across restarts (a conversation lives within one session), by design.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import threading
17
+ import time
18
+
19
+ from langchain.tools import tool
20
+
21
+ _lock = threading.RLock()
22
+ # recipient agent name -> list of {"from": str, "text": str, "ts": float}
23
+ _mailbox: dict[str, list[dict]] = {}
24
+ _scoped_mailbox: dict[tuple[str, str, str, str], list[dict]] = {}
25
+ _MAX_PER_INBOX = 100 # cap so a runaway loop can't grow memory unbounded
26
+
27
+
28
+ @tool
29
+ def send_agent_message(to_agent: str, message: str, from_agent: str = "orchestrator") -> str:
30
+ """Send a message to another agent's inbox (agent-to-agent communication).
31
+
32
+ Use this to hand a question, result, or instruction to a specific peer agent
33
+ by name. The recipient reads it with `read_agent_messages` when it next runs.
34
+
35
+ Args:
36
+ to_agent: The exact name of the recipient agent (e.g. "code-explorer").
37
+ message: The message to deliver.
38
+ from_agent: Your own agent name, so the recipient knows who wrote it.
39
+
40
+ Returns:
41
+ Confirmation that the message was queued.
42
+ """
43
+ to_agent = (to_agent or "").strip()
44
+ if not to_agent:
45
+ return "Error: to_agent is required."
46
+ from novacode_cli.teams.legacy import mailbox_identity
47
+
48
+ scope = mailbox_identity.get()
49
+ if scope is not None:
50
+ from_agent = scope[3]
51
+ with _lock:
52
+ inbox = (
53
+ _scoped_mailbox.setdefault((*scope[:3], to_agent), [])
54
+ if scope
55
+ else _mailbox.setdefault(to_agent, [])
56
+ )
57
+ if scope and (len(inbox) >= _MAX_PER_INBOX or len(message.encode()) > 16384):
58
+ return "Error: scoped inbox limit reached; read pending messages first."
59
+ inbox.append({"from": from_agent or "unknown", "text": message, "ts": time.time()})
60
+ if len(inbox) > _MAX_PER_INBOX:
61
+ del inbox[: len(inbox) - _MAX_PER_INBOX]
62
+ return f"Message delivered to '{to_agent}' inbox."
63
+
64
+
65
+ @tool
66
+ def read_agent_messages(as_agent: str, clear: bool = True) -> str:
67
+ """Read messages addressed to you (agent-to-agent communication).
68
+
69
+ Args:
70
+ as_agent: Your own agent name — the inbox to read.
71
+ clear: When True (default), consume the messages so they aren't re-read.
72
+
73
+ Returns:
74
+ The messages addressed to you, newest last, or "No messages.".
75
+ """
76
+ as_agent = (as_agent or "").strip()
77
+ if not as_agent:
78
+ return "Error: as_agent is required (pass your own agent name)."
79
+ from novacode_cli.teams.legacy import mailbox_identity
80
+
81
+ scope = mailbox_identity.get()
82
+ with _lock:
83
+ if scope:
84
+ if as_agent != scope[3]:
85
+ return "Error: cannot read another agent's inbox."
86
+ msgs = list(_scoped_mailbox.get(scope, []))
87
+ if clear:
88
+ _scoped_mailbox.pop(scope, None)
89
+ else:
90
+ msgs = list(_mailbox.get(as_agent, []))
91
+ if clear:
92
+ _mailbox[as_agent] = []
93
+ if not msgs:
94
+ return "No messages."
95
+ return "\n".join(f"[from {m['from']}] {m['text']}" for m in msgs)
96
+
97
+
98
+ def clear_mailbox() -> None:
99
+ """Drop every inbox (used on /clear so a new conversation starts empty)."""
100
+ with _lock:
101
+ _mailbox.clear()
102
+
103
+
104
+ if __name__ == "__main__":
105
+ clear_mailbox()
106
+ assert send_agent_message.invoke({"to_agent": "b", "message": "hi B", "from_agent": "a"}).startswith(
107
+ "Message delivered"
108
+ )
109
+ assert read_agent_messages.invoke({"as_agent": "b"}) == "[from a] hi B"
110
+ assert read_agent_messages.invoke({"as_agent": "b"}) == "No messages." # cleared
111
+ assert read_agent_messages.invoke({"as_agent": "nobody"}) == "No messages."
112
+ print("agent_mailbox self-check ok")
113
+
114
+
115
+ def clear_scoped_mailbox(project: str, root: str) -> None:
116
+ """Release one root's legacy inboxes after its owned workers stop."""
117
+ with _lock:
118
+ for key in list(_scoped_mailbox):
119
+ if key[:2] == (project, root):
120
+ _scoped_mailbox.pop(key, None)