newcode 0.2.5__tar.gz → 0.2.8__tar.gz

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 (287) hide show
  1. {newcode-0.2.5 → newcode-0.2.8}/PKG-INFO +2 -3
  2. {newcode-0.2.5 → newcode-0.2.8}/README.md +1 -2
  3. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/base_agent.py +184 -94
  4. {newcode-0.2.5 → newcode-0.2.8}/newcode/claude_cache_client.py +13 -3
  5. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/attachments.py +4 -0
  6. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/clipboard.py +5 -1
  7. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/core_commands.py +15 -1
  8. {newcode-0.2.5 → newcode-0.2.8}/newcode/hook_engine/executor.py +1 -1
  9. newcode-0.2.8/newcode/image_utils.py +85 -0
  10. {newcode-0.2.5 → newcode-0.2.8}/newcode/models.json +4 -4
  11. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/chatgpt_oauth/oauth_flow.py +3 -2
  12. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/chatgpt_oauth/test_plugin.py +7 -1
  13. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/chatgpt_oauth/utils.py +25 -1
  14. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_oauth/register_callbacks.py +51 -0
  15. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_oauth/token_refresh_heartbeat.py +4 -0
  16. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_oauth/utils.py +39 -3
  17. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/__init__.py +0 -23
  18. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/browser_screenshot.py +5 -1
  19. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/terminal_screenshot_tools.py +6 -2
  20. {newcode-0.2.5 → newcode-0.2.8}/pyproject.toml +1 -1
  21. newcode-0.2.5/newcode/agents/agent_scheduler.py +0 -120
  22. newcode-0.2.5/newcode/plugins/scheduler/__init__.py +0 -1
  23. newcode-0.2.5/newcode/plugins/scheduler/register_callbacks.py +0 -88
  24. newcode-0.2.5/newcode/plugins/scheduler/scheduler_menu.py +0 -520
  25. newcode-0.2.5/newcode/plugins/scheduler/scheduler_wizard.py +0 -341
  26. newcode-0.2.5/newcode/scheduler/__init__.py +0 -41
  27. newcode-0.2.5/newcode/scheduler/__main__.py +0 -9
  28. newcode-0.2.5/newcode/scheduler/cli.py +0 -118
  29. newcode-0.2.5/newcode/scheduler/config.py +0 -126
  30. newcode-0.2.5/newcode/scheduler/daemon.py +0 -280
  31. newcode-0.2.5/newcode/scheduler/executor.py +0 -155
  32. newcode-0.2.5/newcode/scheduler/platform.py +0 -19
  33. newcode-0.2.5/newcode/scheduler/platform_unix.py +0 -22
  34. newcode-0.2.5/newcode/scheduler/platform_win.py +0 -32
  35. newcode-0.2.5/newcode/tools/scheduler_tools.py +0 -412
  36. {newcode-0.2.5 → newcode-0.2.8}/.gitignore +0 -0
  37. {newcode-0.2.5 → newcode-0.2.8}/LICENSE +0 -0
  38. {newcode-0.2.5 → newcode-0.2.8}/newcode/__init__.py +0 -0
  39. {newcode-0.2.5 → newcode-0.2.8}/newcode/__main__.py +0 -0
  40. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/__init__.py +0 -0
  41. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_c_reviewer.py +0 -0
  42. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_code_agent.py +0 -0
  43. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_code_reviewer.py +0 -0
  44. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_cpp_reviewer.py +0 -0
  45. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_creator_agent.py +0 -0
  46. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_golang_reviewer.py +0 -0
  47. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_javascript_reviewer.py +0 -0
  48. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_manager.py +0 -0
  49. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_pack_leader.py +0 -0
  50. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_planning.py +0 -0
  51. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_python_programmer.py +0 -0
  52. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_python_reviewer.py +0 -0
  53. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_qa_browser.py +0 -0
  54. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_qa_expert.py +0 -0
  55. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_security_auditor.py +0 -0
  56. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_terminal_qa.py +0 -0
  57. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_typescript_reviewer.py +0 -0
  58. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/event_stream_handler.py +0 -0
  59. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/json_agent.py +0 -0
  60. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/pack/__init__.py +0 -0
  61. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/pack/bloodhound.py +0 -0
  62. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/pack/husky.py +0 -0
  63. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/pack/retriever.py +0 -0
  64. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/pack/shepherd.py +0 -0
  65. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/pack/terrier.py +0 -0
  66. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/pack/watchdog.py +0 -0
  67. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/prompt_reviewer.py +0 -0
  68. {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/subagent_stream_handler.py +0 -0
  69. {newcode-0.2.5 → newcode-0.2.8}/newcode/api/__init__.py +0 -0
  70. {newcode-0.2.5 → newcode-0.2.8}/newcode/api/app.py +0 -0
  71. {newcode-0.2.5 → newcode-0.2.8}/newcode/api/main.py +0 -0
  72. {newcode-0.2.5 → newcode-0.2.8}/newcode/api/pty_manager.py +0 -0
  73. {newcode-0.2.5 → newcode-0.2.8}/newcode/api/routers/__init__.py +0 -0
  74. {newcode-0.2.5 → newcode-0.2.8}/newcode/api/routers/agents.py +0 -0
  75. {newcode-0.2.5 → newcode-0.2.8}/newcode/api/routers/commands.py +0 -0
  76. {newcode-0.2.5 → newcode-0.2.8}/newcode/api/routers/config.py +0 -0
  77. {newcode-0.2.5 → newcode-0.2.8}/newcode/api/routers/sessions.py +0 -0
  78. {newcode-0.2.5 → newcode-0.2.8}/newcode/api/templates/terminal.html +0 -0
  79. {newcode-0.2.5 → newcode-0.2.8}/newcode/api/websocket.py +0 -0
  80. {newcode-0.2.5 → newcode-0.2.8}/newcode/callbacks.py +0 -0
  81. {newcode-0.2.5 → newcode-0.2.8}/newcode/chatgpt_codex_client.py +0 -0
  82. {newcode-0.2.5 → newcode-0.2.8}/newcode/cli_runner.py +0 -0
  83. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/__init__.py +0 -0
  84. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/add_model_menu.py +0 -0
  85. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/agent_menu.py +0 -0
  86. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/autosave_menu.py +0 -0
  87. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/colors_menu.py +0 -0
  88. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/command_handler.py +0 -0
  89. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/command_registry.py +0 -0
  90. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/config_commands.py +0 -0
  91. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/diff_menu.py +0 -0
  92. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/file_path_completion.py +0 -0
  93. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/load_context_completion.py +0 -0
  94. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/__init__.py +0 -0
  95. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/base.py +0 -0
  96. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/catalog_server_installer.py +0 -0
  97. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/custom_server_form.py +0 -0
  98. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/custom_server_installer.py +0 -0
  99. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/edit_command.py +0 -0
  100. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/handler.py +0 -0
  101. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/help_command.py +0 -0
  102. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/install_command.py +0 -0
  103. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/install_menu.py +0 -0
  104. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/list_command.py +0 -0
  105. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/logs_command.py +0 -0
  106. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/remove_command.py +0 -0
  107. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/restart_command.py +0 -0
  108. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/search_command.py +0 -0
  109. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/start_all_command.py +0 -0
  110. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/start_command.py +0 -0
  111. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/status_command.py +0 -0
  112. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/stop_all_command.py +0 -0
  113. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/stop_command.py +0 -0
  114. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/test_command.py +0 -0
  115. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/utils.py +0 -0
  116. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/wizard_utils.py +0 -0
  117. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp_completion.py +0 -0
  118. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/model_picker_completion.py +0 -0
  119. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/model_settings_menu.py +0 -0
  120. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/motd.py +0 -0
  121. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/onboarding_slides.py +0 -0
  122. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/onboarding_wizard.py +0 -0
  123. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/pin_command_completion.py +0 -0
  124. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/prompt_toolkit_completion.py +0 -0
  125. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/session_commands.py +0 -0
  126. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/skills_completion.py +0 -0
  127. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/uc_menu.py +0 -0
  128. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/utils.py +0 -0
  129. {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/wiggum_state.py +0 -0
  130. {newcode-0.2.5 → newcode-0.2.8}/newcode/config.py +0 -0
  131. {newcode-0.2.5 → newcode-0.2.8}/newcode/error_logging.py +0 -0
  132. {newcode-0.2.5 → newcode-0.2.8}/newcode/gemini_code_assist.py +0 -0
  133. {newcode-0.2.5 → newcode-0.2.8}/newcode/gemini_model.py +0 -0
  134. {newcode-0.2.5 → newcode-0.2.8}/newcode/hook_engine/README.md +0 -0
  135. {newcode-0.2.5 → newcode-0.2.8}/newcode/hook_engine/__init__.py +0 -0
  136. {newcode-0.2.5 → newcode-0.2.8}/newcode/hook_engine/aliases.py +0 -0
  137. {newcode-0.2.5 → newcode-0.2.8}/newcode/hook_engine/engine.py +0 -0
  138. {newcode-0.2.5 → newcode-0.2.8}/newcode/hook_engine/matcher.py +0 -0
  139. {newcode-0.2.5 → newcode-0.2.8}/newcode/hook_engine/models.py +0 -0
  140. {newcode-0.2.5 → newcode-0.2.8}/newcode/hook_engine/registry.py +0 -0
  141. {newcode-0.2.5 → newcode-0.2.8}/newcode/hook_engine/validator.py +0 -0
  142. {newcode-0.2.5 → newcode-0.2.8}/newcode/http_utils.py +0 -0
  143. {newcode-0.2.5 → newcode-0.2.8}/newcode/keymap.py +0 -0
  144. {newcode-0.2.5 → newcode-0.2.8}/newcode/main.py +0 -0
  145. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/__init__.py +0 -0
  146. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/async_lifecycle.py +0 -0
  147. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/blocking_startup.py +0 -0
  148. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/captured_stdio_server.py +0 -0
  149. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/circuit_breaker.py +0 -0
  150. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/config_wizard.py +0 -0
  151. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/dashboard.py +0 -0
  152. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/error_isolation.py +0 -0
  153. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/examples/retry_example.py +0 -0
  154. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/health_monitor.py +0 -0
  155. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/managed_server.py +0 -0
  156. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/manager.py +0 -0
  157. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/mcp_logs.py +0 -0
  158. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/registry.py +0 -0
  159. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/retry_manager.py +0 -0
  160. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/server_registry_catalog.py +0 -0
  161. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/status_tracker.py +0 -0
  162. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/system_tools.py +0 -0
  163. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_prompts/__init__.py +0 -0
  164. {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_prompts/hook_creator.py +0 -0
  165. {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/__init__.py +0 -0
  166. {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/bus.py +0 -0
  167. {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/commands.py +0 -0
  168. {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/markdown_patches.py +0 -0
  169. {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/message_queue.py +0 -0
  170. {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/messages.py +0 -0
  171. {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/queue_console.py +0 -0
  172. {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/renderers.py +0 -0
  173. {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/rich_renderer.py +0 -0
  174. {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/spinner/__init__.py +0 -0
  175. {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/spinner/console_spinner.py +0 -0
  176. {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/spinner/spinner_base.py +0 -0
  177. {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/subagent_console.py +0 -0
  178. {newcode-0.2.5 → newcode-0.2.8}/newcode/model_factory.py +0 -0
  179. {newcode-0.2.5 → newcode-0.2.8}/newcode/model_switching.py +0 -0
  180. {newcode-0.2.5 → newcode-0.2.8}/newcode/model_utils.py +0 -0
  181. {newcode-0.2.5 → newcode-0.2.8}/newcode/models_dev_api.json +0 -0
  182. {newcode-0.2.5 → newcode-0.2.8}/newcode/models_dev_parser.py +0 -0
  183. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/__init__.py +0 -0
  184. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/__init__.py +0 -0
  185. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/config.py +0 -0
  186. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/discovery.py +0 -0
  187. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/downloader.py +0 -0
  188. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/installer.py +0 -0
  189. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/metadata.py +0 -0
  190. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/prompt_builder.py +0 -0
  191. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/register_callbacks.py +0 -0
  192. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/remote_catalog.py +0 -0
  193. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/skill_catalog.py +0 -0
  194. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/skills_install_menu.py +0 -0
  195. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/skills_menu.py +0 -0
  196. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/__init__.py +0 -0
  197. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/accounts.py +0 -0
  198. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/antigravity_model.py +0 -0
  199. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/config.py +0 -0
  200. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/constants.py +0 -0
  201. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/oauth.py +0 -0
  202. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/register_callbacks.py +0 -0
  203. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/storage.py +0 -0
  204. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/test_plugin.py +0 -0
  205. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/token.py +0 -0
  206. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/transport.py +0 -0
  207. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/utils.py +0 -0
  208. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/chatgpt_oauth/__init__.py +0 -0
  209. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/chatgpt_oauth/config.py +0 -0
  210. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/chatgpt_oauth/register_callbacks.py +0 -0
  211. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_hooks/__init__.py +0 -0
  212. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_hooks/config.py +0 -0
  213. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_hooks/register_callbacks.py +0 -0
  214. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_oauth/README.md +0 -0
  215. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_oauth/SETUP.md +0 -0
  216. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_oauth/__init__.py +0 -0
  217. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_oauth/config.py +0 -0
  218. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_oauth/test_plugin.py +0 -0
  219. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/customizable_commands/__init__.py +0 -0
  220. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/customizable_commands/register_callbacks.py +0 -0
  221. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/example_custom_command/README.md +0 -0
  222. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/example_custom_command/register_callbacks.py +0 -0
  223. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/file_permission_handler/__init__.py +0 -0
  224. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/file_permission_handler/register_callbacks.py +0 -0
  225. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/frontend_emitter/__init__.py +0 -0
  226. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/frontend_emitter/emitter.py +0 -0
  227. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/frontend_emitter/register_callbacks.py +0 -0
  228. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/hook_creator/__init__.py +0 -0
  229. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/hook_creator/register_callbacks.py +0 -0
  230. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/hook_manager/__init__.py +0 -0
  231. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/hook_manager/config.py +0 -0
  232. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/hook_manager/hooks_menu.py +0 -0
  233. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/hook_manager/register_callbacks.py +0 -0
  234. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/oauth_puppy_html.py +0 -0
  235. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/shell_safety/__init__.py +0 -0
  236. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/shell_safety/agent_shell_safety.py +0 -0
  237. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/shell_safety/command_cache.py +0 -0
  238. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/shell_safety/register_callbacks.py +0 -0
  239. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/synthetic_status/__init__.py +0 -0
  240. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/synthetic_status/register_callbacks.py +0 -0
  241. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/synthetic_status/status_api.py +0 -0
  242. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/universal_constructor/__init__.py +0 -0
  243. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/universal_constructor/models.py +0 -0
  244. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/universal_constructor/register_callbacks.py +0 -0
  245. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/universal_constructor/registry.py +0 -0
  246. {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/universal_constructor/sandbox.py +0 -0
  247. {newcode-0.2.5 → newcode-0.2.8}/newcode/prompts/antigravity_system_prompt.md +0 -0
  248. {newcode-0.2.5 → newcode-0.2.8}/newcode/pydantic_patches.py +0 -0
  249. {newcode-0.2.5 → newcode-0.2.8}/newcode/reopenable_async_client.py +0 -0
  250. {newcode-0.2.5 → newcode-0.2.8}/newcode/round_robin_model.py +0 -0
  251. {newcode-0.2.5 → newcode-0.2.8}/newcode/session_storage.py +0 -0
  252. {newcode-0.2.5 → newcode-0.2.8}/newcode/status_display.py +0 -0
  253. {newcode-0.2.5 → newcode-0.2.8}/newcode/summarization_agent.py +0 -0
  254. {newcode-0.2.5 → newcode-0.2.8}/newcode/terminal_utils.py +0 -0
  255. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/agent_tools.py +0 -0
  256. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/__init__.py +0 -0
  257. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/constants.py +0 -0
  258. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/demo_tui.py +0 -0
  259. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/handler.py +0 -0
  260. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/models.py +0 -0
  261. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/registration.py +0 -0
  262. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/renderers.py +0 -0
  263. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/terminal_ui.py +0 -0
  264. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/theme.py +0 -0
  265. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/tui_loop.py +0 -0
  266. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/__init__.py +0 -0
  267. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/browser_control.py +0 -0
  268. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/browser_interactions.py +0 -0
  269. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/browser_locators.py +0 -0
  270. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/browser_manager.py +0 -0
  271. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/browser_navigation.py +0 -0
  272. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/browser_scripts.py +0 -0
  273. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/browser_workflows.py +0 -0
  274. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/chromium_terminal_manager.py +0 -0
  275. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/terminal_command_tools.py +0 -0
  276. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/terminal_tools.py +0 -0
  277. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/command_runner.py +0 -0
  278. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/common.py +0 -0
  279. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/display.py +0 -0
  280. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/file_modifications.py +0 -0
  281. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/file_operations.py +0 -0
  282. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/skills_tools.py +0 -0
  283. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/subagent_context.py +0 -0
  284. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/tools_content.py +0 -0
  285. {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/universal_constructor.py +0 -0
  286. {newcode-0.2.5 → newcode-0.2.8}/newcode/uvx_detection.py +0 -0
  287. {newcode-0.2.5 → newcode-0.2.8}/newcode/version_checker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: newcode
3
- Version: 0.2.5
3
+ Version: 0.2.8
4
4
  Summary: AI-powered code generation agent platform
5
5
  Project-URL: repository, https://github.com/janfeddersen-wq/new_code
6
6
  Project-URL: HomePage, https://github.com/janfeddersen-wq/new_code
@@ -57,7 +57,7 @@ NewCode is designed for practical software workflows in the terminal: inspect fi
57
57
  - Built-in agent system with a default coding agent and optional specialist workflows
58
58
  - MCP (Model Context Protocol) integration and server management
59
59
  - Interactive command-driven UX for model, agent, and settings management
60
- - Session autosave/restore and scheduler utilities
60
+ - Session autosave/restore utilities
61
61
  - Plugin/callback extensibility hooks
62
62
 
63
63
  ## Quick start
@@ -94,7 +94,6 @@ On first run, NewCode starts onboarding to help configure API keys and defaults.
94
94
  /config # Show current configuration
95
95
  /model # Select or switch model
96
96
  /agent # Select or switch agent
97
- /scheduler # Manage scheduled tasks
98
97
  /colors # Customize terminal UI colors
99
98
  /api # Manage built-in API server (start|stop|status)
100
99
  ```
@@ -15,7 +15,7 @@ NewCode is designed for practical software workflows in the terminal: inspect fi
15
15
  - Built-in agent system with a default coding agent and optional specialist workflows
16
16
  - MCP (Model Context Protocol) integration and server management
17
17
  - Interactive command-driven UX for model, agent, and settings management
18
- - Session autosave/restore and scheduler utilities
18
+ - Session autosave/restore utilities
19
19
  - Plugin/callback extensibility hooks
20
20
 
21
21
  ## Quick start
@@ -52,7 +52,6 @@ On first run, NewCode starts onboarding to help configure API keys and defaults.
52
52
  /config # Show current configuration
53
53
  /model # Select or switch model
54
54
  /agent # Select or switch agent
55
- /scheduler # Manage scheduled tasks
56
55
  /colors # Customize terminal UI colors
57
56
  /api # Manage built-in API server (start|stop|status)
58
57
  ```
@@ -71,6 +71,7 @@ from newcode.config import (
71
71
  get_value,
72
72
  )
73
73
  from newcode.error_logging import log_error
74
+ from newcode.image_utils import constrain_image_dimensions
74
75
  from newcode.keymap import cancel_agent_uses_signal, get_cancel_agent_char_code
75
76
  from newcode.mcp_ import get_mcp_manager
76
77
  from newcode.messaging import (
@@ -95,6 +96,20 @@ _delayed_compaction_requested = False
95
96
  _reload_count = 0
96
97
 
97
98
 
99
+ def _is_cloudflare_auth_error(exc: Exception) -> bool:
100
+ """Detect Cloudflare 400 auth failures from exception text."""
101
+ message = str(exc)
102
+ if not message:
103
+ return False
104
+
105
+ message_lower = message.lower()
106
+ return (
107
+ "cloudflare" in message_lower
108
+ and "400" in message_lower
109
+ and "bad request" in message_lower
110
+ )
111
+
112
+
98
113
  def _log_error_to_file(exc: Exception) -> Optional[str]:
99
114
  """Log detailed error information to ~/.newcode/error_logs/log_{timestamp}.txt.
100
115
 
@@ -1304,6 +1319,12 @@ class BaseAgent(ABC):
1304
1319
  """Force-reload the pydantic-ai Agent based on current config and model."""
1305
1320
  from newcode.tools import register_tools_for_agent
1306
1321
 
1322
+ # Invalidate the project-local rules cache so a fresh read from the
1323
+ # current working directory is performed on the next load_agent_rules()
1324
+ # call. This is critical for /cd: the user may have switched to a
1325
+ # different project that has its own AGENT.md (or none at all).
1326
+ self._agent_rules = None
1327
+
1307
1328
  if message_group is None:
1308
1329
  message_group = str(uuid.uuid4())
1309
1330
 
@@ -1836,7 +1857,25 @@ class BaseAgent(ABC):
1836
1857
  # Build combined prompt payload when attachments are provided.
1837
1858
  attachment_parts: List[Any] = []
1838
1859
  if attachments:
1839
- attachment_parts.extend(list(attachments))
1860
+ # Constrain image dimensions for API compliance (Claude max 2000px for many-image requests)
1861
+ constrained = []
1862
+ for att in attachments:
1863
+ if (
1864
+ isinstance(att, BinaryContent)
1865
+ and hasattr(att, "media_type")
1866
+ and hasattr(att, "data")
1867
+ ):
1868
+ media_type = getattr(att, "media_type", "") or ""
1869
+ if media_type.startswith("image/"):
1870
+ new_data = constrain_image_dimensions(
1871
+ att.data, media_type=media_type
1872
+ )
1873
+ if new_data is not att.data:
1874
+ att = BinaryContent(data=new_data, media_type=media_type)
1875
+ constrained.append(att)
1876
+ else:
1877
+ constrained.append(att)
1878
+ attachment_parts.extend(constrained)
1840
1879
  if link_attachments:
1841
1880
  attachment_parts.extend(list(link_attachments))
1842
1881
 
@@ -1849,41 +1888,58 @@ class BaseAgent(ABC):
1849
1888
  prompt_payload = prompt
1850
1889
 
1851
1890
  async def run_agent_task():
1852
- try:
1853
- self.set_message_history(
1854
- self.prune_interrupted_tool_calls(self.get_message_history())
1855
- )
1891
+ _cloudflare_retry_attempted = False
1856
1892
 
1857
- # DELAYED COMPACTION: Check if we should attempt delayed compaction
1858
- if self.should_attempt_delayed_compaction():
1859
- emit_info(
1860
- "🔄 Attempting delayed compaction (tool calls completed)",
1861
- message_group="token_context_status",
1893
+ while True:
1894
+ _retry_after_cloudflare_refresh = False
1895
+ try:
1896
+ self.set_message_history(
1897
+ self.prune_interrupted_tool_calls(self.get_message_history())
1862
1898
  )
1863
- current_messages = self.get_message_history()
1864
- compacted_messages, _ = self.compact_messages(current_messages)
1865
- if compacted_messages != current_messages:
1866
- self.set_message_history(compacted_messages)
1899
+
1900
+ # DELAYED COMPACTION: Check if we should attempt delayed compaction
1901
+ if self.should_attempt_delayed_compaction():
1867
1902
  emit_info(
1868
- " Delayed compaction completed successfully",
1903
+ "🔄 Attempting delayed compaction (tool calls completed)",
1869
1904
  message_group="token_context_status",
1870
1905
  )
1906
+ current_messages = self.get_message_history()
1907
+ compacted_messages, _ = self.compact_messages(current_messages)
1908
+ if compacted_messages != current_messages:
1909
+ self.set_message_history(compacted_messages)
1910
+ emit_info(
1911
+ "✅ Delayed compaction completed successfully",
1912
+ message_group="token_context_status",
1913
+ )
1871
1914
 
1872
- usage_limits = UsageLimits(request_limit=get_message_limit())
1915
+ usage_limits = UsageLimits(request_limit=get_message_limit())
1873
1916
 
1874
- # Handle MCP servers - add them temporarily when using DBOS
1875
- if (
1876
- get_use_dbos()
1877
- and hasattr(self, "_mcp_servers")
1878
- and self._mcp_servers
1879
- ):
1880
- # Temporarily add MCP servers to the DBOS agent using internal _toolsets
1881
- original_toolsets = pydantic_agent._toolsets
1882
- pydantic_agent._toolsets = original_toolsets + self._mcp_servers
1883
- pydantic_agent._toolsets = original_toolsets + self._mcp_servers
1917
+ # Handle MCP servers - add them temporarily when using DBOS
1918
+ if (
1919
+ get_use_dbos()
1920
+ and hasattr(self, "_mcp_servers")
1921
+ and self._mcp_servers
1922
+ ):
1923
+ # Temporarily add MCP servers to the DBOS agent using internal _toolsets
1924
+ original_toolsets = pydantic_agent._toolsets
1925
+ pydantic_agent._toolsets = original_toolsets + self._mcp_servers
1926
+ pydantic_agent._toolsets = original_toolsets + self._mcp_servers
1884
1927
 
1885
- try:
1886
- # Set the workflow ID for DBOS context so DBOS and the agent ID match
1928
+ try:
1929
+ # Set the workflow ID for DBOS context so DBOS and the agent ID match
1930
+ with SetWorkflowID(group_id):
1931
+ result_ = await pydantic_agent.run(
1932
+ prompt_payload,
1933
+ message_history=self.get_message_history(),
1934
+ usage_limits=usage_limits,
1935
+ event_stream_handler=event_stream_handler,
1936
+ **kwargs,
1937
+ )
1938
+ return result_
1939
+ finally:
1940
+ # Always restore original toolsets
1941
+ pydantic_agent._toolsets = original_toolsets
1942
+ elif get_use_dbos():
1887
1943
  with SetWorkflowID(group_id):
1888
1944
  result_ = await pydantic_agent.run(
1889
1945
  prompt_payload,
@@ -1893,11 +1949,8 @@ class BaseAgent(ABC):
1893
1949
  **kwargs,
1894
1950
  )
1895
1951
  return result_
1896
- finally:
1897
- # Always restore original toolsets
1898
- pydantic_agent._toolsets = original_toolsets
1899
- elif get_use_dbos():
1900
- with SetWorkflowID(group_id):
1952
+ else:
1953
+ # Non-DBOS path (MCP servers are already included)
1901
1954
  result_ = await pydantic_agent.run(
1902
1955
  prompt_payload,
1903
1956
  message_history=self.get_message_history(),
@@ -1906,74 +1959,111 @@ class BaseAgent(ABC):
1906
1959
  **kwargs,
1907
1960
  )
1908
1961
  return result_
1909
- else:
1910
- # Non-DBOS path (MCP servers are already included)
1911
- result_ = await pydantic_agent.run(
1912
- prompt_payload,
1913
- message_history=self.get_message_history(),
1914
- usage_limits=usage_limits,
1915
- event_stream_handler=event_stream_handler,
1916
- **kwargs,
1962
+ except* UsageLimitExceeded as ule:
1963
+ emit_info(f"Usage limit exceeded: {str(ule)}", group_id=group_id)
1964
+ emit_info(
1965
+ "The agent has reached its usage limit. You can ask it to continue by saying 'please continue' or similar.",
1966
+ group_id=group_id,
1917
1967
  )
1918
- return result_
1919
- except* UsageLimitExceeded as ule:
1920
- emit_info(f"Usage limit exceeded: {str(ule)}", group_id=group_id)
1921
- emit_info(
1922
- "The agent has reached its usage limit. You can ask it to continue by saying 'please continue' or similar.",
1923
- group_id=group_id,
1924
- )
1925
- except* mcp.shared.exceptions.McpError as mcp_error:
1926
- emit_info(f"MCP server error: {str(mcp_error)}", group_id=group_id)
1927
- emit_info(f"{str(mcp_error)}", group_id=group_id)
1928
- emit_info(
1929
- "Try disabling any malfunctioning MCP servers", group_id=group_id
1930
- )
1931
- except* asyncio.exceptions.CancelledError:
1932
- emit_info("Cancelled")
1933
- if get_use_dbos():
1934
- await DBOS.cancel_workflow_async(group_id)
1935
- except* InterruptedError as ie:
1936
- emit_info(f"Interrupted: {str(ie)}")
1937
- if get_use_dbos():
1938
- await DBOS.cancel_workflow_async(group_id)
1939
- except* Exception as other_error:
1940
- # Filter out CancelledError and UsageLimitExceeded from the exception group - let it propagate
1941
- remaining_exceptions = []
1942
-
1943
- def collect_non_cancelled_exceptions(exc):
1944
- if isinstance(exc, ExceptionGroup):
1945
- for sub_exc in exc.exceptions:
1946
- collect_non_cancelled_exceptions(sub_exc)
1947
- elif not isinstance(
1948
- exc, (asyncio.CancelledError, UsageLimitExceeded)
1968
+ except* mcp.shared.exceptions.McpError as mcp_error:
1969
+ emit_info(f"MCP server error: {str(mcp_error)}", group_id=group_id)
1970
+ emit_info(f"{str(mcp_error)}", group_id=group_id)
1971
+ emit_info(
1972
+ "Try disabling any malfunctioning MCP servers",
1973
+ group_id=group_id,
1974
+ )
1975
+ except* asyncio.exceptions.CancelledError:
1976
+ emit_info("Cancelled")
1977
+ if get_use_dbos():
1978
+ await DBOS.cancel_workflow_async(group_id)
1979
+ except* InterruptedError as ie:
1980
+ emit_info(f"Interrupted: {str(ie)}")
1981
+ if get_use_dbos():
1982
+ await DBOS.cancel_workflow_async(group_id)
1983
+ except* Exception as other_error:
1984
+
1985
+ def contains_cloudflare_auth_error(exc: Exception) -> bool:
1986
+ if isinstance(exc, ExceptionGroup):
1987
+ return any(
1988
+ contains_cloudflare_auth_error(sub_exc)
1989
+ for sub_exc in exc.exceptions
1990
+ )
1991
+ return _is_cloudflare_auth_error(exc)
1992
+
1993
+ if (
1994
+ not _cloudflare_retry_attempted
1995
+ and contains_cloudflare_auth_error(other_error)
1949
1996
  ):
1950
- remaining_exceptions.append(exc)
1951
- emit_info(f"Unexpected error: {str(exc)}", group_id=group_id)
1952
- emit_info(f"{str(exc.args)}", group_id=group_id)
1953
- # Log to file for debugging
1954
- log_error(
1955
- exc,
1956
- context=f"Agent run (group_id={group_id})",
1957
- include_traceback=True,
1997
+ _cloudflare_retry_attempted = True
1998
+ emit_info(
1999
+ "Detected Cloudflare 400 error (likely expired token), attempting refresh...",
2000
+ group_id=group_id,
1958
2001
  )
2002
+ refreshed_token = None
2003
+ try:
2004
+ from newcode.plugins.claude_code_oauth.utils import (
2005
+ refresh_access_token,
2006
+ )
2007
+
2008
+ refreshed_token = refresh_access_token(force=True)
2009
+ except Exception as refresh_error:
2010
+ emit_info(
2011
+ f"Token refresh failed: {str(refresh_error)}",
2012
+ group_id=group_id,
2013
+ )
2014
+
2015
+ if refreshed_token:
2016
+ emit_info(
2017
+ "Token refresh successful, retrying request...",
2018
+ group_id=group_id,
2019
+ )
2020
+ _retry_after_cloudflare_refresh = True
2021
+
2022
+ if not _retry_after_cloudflare_refresh:
2023
+ # Filter out CancelledError and UsageLimitExceeded from the exception group - let it propagate
2024
+ remaining_exceptions = []
2025
+
2026
+ def collect_non_cancelled_exceptions(exc):
2027
+ if isinstance(exc, ExceptionGroup):
2028
+ for sub_exc in exc.exceptions:
2029
+ collect_non_cancelled_exceptions(sub_exc)
2030
+ elif not isinstance(
2031
+ exc, (asyncio.CancelledError, UsageLimitExceeded)
2032
+ ):
2033
+ remaining_exceptions.append(exc)
2034
+ emit_info(
2035
+ f"Unexpected error: {str(exc)}", group_id=group_id
2036
+ )
2037
+ emit_info(f"{str(exc.args)}", group_id=group_id)
2038
+ # Log to file for debugging
2039
+ log_error(
2040
+ exc,
2041
+ context=f"Agent run (group_id={group_id})",
2042
+ include_traceback=True,
2043
+ )
1959
2044
 
1960
- collect_non_cancelled_exceptions(other_error)
2045
+ collect_non_cancelled_exceptions(other_error)
1961
2046
 
1962
- # If there are CancelledError exceptions in the group, re-raise them
1963
- cancelled_exceptions = []
2047
+ # If there are CancelledError exceptions in the group, re-raise them
2048
+ cancelled_exceptions = []
1964
2049
 
1965
- def collect_cancelled_exceptions(exc):
1966
- if isinstance(exc, ExceptionGroup):
1967
- for sub_exc in exc.exceptions:
1968
- collect_cancelled_exceptions(sub_exc)
1969
- elif isinstance(exc, asyncio.CancelledError):
1970
- cancelled_exceptions.append(exc)
2050
+ def collect_cancelled_exceptions(exc):
2051
+ if isinstance(exc, ExceptionGroup):
2052
+ for sub_exc in exc.exceptions:
2053
+ collect_cancelled_exceptions(sub_exc)
2054
+ elif isinstance(exc, asyncio.CancelledError):
2055
+ cancelled_exceptions.append(exc)
1971
2056
 
1972
- collect_cancelled_exceptions(other_error)
1973
- finally:
1974
- self.set_message_history(
1975
- self.prune_interrupted_tool_calls(self.get_message_history())
1976
- )
2057
+ collect_cancelled_exceptions(other_error)
2058
+ finally:
2059
+ self.set_message_history(
2060
+ self.prune_interrupted_tool_calls(self.get_message_history())
2061
+ )
2062
+
2063
+ if _retry_after_cloudflare_refresh:
2064
+ continue
2065
+
2066
+ break
1977
2067
 
1978
2068
  # Create the task FIRST
1979
2069
  agent_task = asyncio.create_task(run_agent_task())
@@ -371,7 +371,7 @@ class ClaudeCacheAsyncClient(httpx.AsyncClient):
371
371
  is_auth_error = response.status_code in (401, 403)
372
372
 
373
373
  if response.status_code == 400:
374
- is_auth_error = self._is_cloudflare_html_error(response)
374
+ is_auth_error = await self._is_cloudflare_html_error(response)
375
375
  if is_auth_error:
376
376
  logger.info(
377
377
  "Detected Cloudflare 400 error (expired token), attempting token refresh"
@@ -531,7 +531,7 @@ class ClaudeCacheAsyncClient(httpx.AsyncClient):
531
531
  headers["Authorization"] = bearer_value
532
532
 
533
533
  @staticmethod
534
- def _is_cloudflare_html_error(response: httpx.Response) -> bool:
534
+ async def _is_cloudflare_html_error(response: httpx.Response) -> bool:
535
535
  """Check if this is a Cloudflare HTML error response.
536
536
 
537
537
  Cloudflare often returns HTML error pages with status 400 when
@@ -546,10 +546,20 @@ class ClaudeCacheAsyncClient(httpx.AsyncClient):
546
546
  """
547
547
  try:
548
548
  body = None
549
- # Try reading the body from _content first (already consumed)
549
+
550
+ # For async httpx, read the body if content is not available yet.
551
+ if not hasattr(response, "_content") or not response._content:
552
+ try:
553
+ await response.aread()
554
+ except Exception as read_exc:
555
+ logger.debug("Failed to read response body: %s", read_exc)
556
+ return False
557
+
558
+ # Prefer raw _content if present (already consumed responses).
550
559
  if hasattr(response, "_content") and response._content:
551
560
  body = response._content.decode("utf-8", errors="ignore")
552
561
  else:
562
+ # Fallback to text property.
553
563
  try:
554
564
  body = response.text
555
565
  except Exception:
@@ -11,6 +11,8 @@ from typing import Iterable, List, Sequence
11
11
 
12
12
  from pydantic_ai import BinaryContent, DocumentUrl, ImageUrl
13
13
 
14
+ from newcode.image_utils import constrain_image_dimensions
15
+
14
16
  SUPPORTED_INLINE_SCHEMES = {"http", "https"}
15
17
 
16
18
  # Maximum path length to consider - conservative limit to avoid OS errors
@@ -341,6 +343,8 @@ def parse_prompt_attachments(prompt: str) -> ProcessedPrompt:
341
343
  except AttachmentParsingError:
342
344
  # Silently ignore unreadable attachments to reduce prompt noise
343
345
  continue
346
+ # Constrain image dimensions for API compliance (Claude max 2000px for many-image requests)
347
+ data = constrain_image_dimensions(data, media_type=media_type)
344
348
  attachments.append(
345
349
  PromptAttachment(
346
350
  placeholder=detection.placeholder,
@@ -16,6 +16,8 @@ import threading
16
16
  import time
17
17
  from typing import Optional
18
18
 
19
+ from newcode.image_utils import constrain_image_dimensions
20
+
19
21
  # Try to import PIL - it's optional but needed for clipboard image support
20
22
  try:
21
23
  from PIL import Image, ImageGrab
@@ -42,7 +44,7 @@ logger = logging.getLogger(__name__)
42
44
 
43
45
  # Constants
44
46
  MAX_IMAGE_SIZE_BYTES = 10 * 1024 * 1024 # 10MB
45
- MAX_IMAGE_DIMENSION = 4096 # Max width/height for resize
47
+ MAX_IMAGE_DIMENSION = 1920 # Max width/height for resize
46
48
  MAX_PENDING_IMAGES = (
47
49
  10 # SEC-CLIP-001: Limit pending images to prevent memory exhaustion
48
50
  )
@@ -317,6 +319,7 @@ def get_clipboard_image() -> Optional[bytes]:
317
319
  )
318
320
  return None
319
321
 
322
+ image_bytes = constrain_image_dimensions(image_bytes)
320
323
  return image_bytes
321
324
 
322
325
  # Windows/macOS path - use PIL
@@ -356,6 +359,7 @@ def get_clipboard_image() -> Optional[bytes]:
356
359
  image_bytes = buffer.getvalue()
357
360
 
358
361
  logger.info(f"Clipboard image size: {len(image_bytes) / 1024:.1f}KB")
362
+ image_bytes = constrain_image_dimensions(image_bytes)
359
363
  return image_bytes
360
364
 
361
365
  except Exception as e:
@@ -55,7 +55,7 @@ def handle_cd_command(command: str) -> bool:
55
55
  # Use shlex.split to handle quoted paths properly
56
56
  import shlex
57
57
 
58
- from newcode.messaging import emit_error, emit_info, emit_success
58
+ from newcode.messaging import emit_error, emit_info, emit_success, emit_warning
59
59
 
60
60
  try:
61
61
  tokens = shlex.split(command)
@@ -77,6 +77,20 @@ def handle_cd_command(command: str) -> bool:
77
77
  if os.path.isdir(target):
78
78
  os.chdir(target)
79
79
  emit_success(f"Changed directory to: {target}")
80
+ # Reload the agent so the system prompt and project-local
81
+ # AGENT.md rules reflect the new working directory. Without
82
+ # this, the LLM keeps receiving stale path information for the
83
+ # remainder of the session (the PydanticAgent instructions are
84
+ # baked in at construction time and never refreshed otherwise).
85
+ try:
86
+ from newcode.agents import get_current_agent
87
+
88
+ get_current_agent().reload_code_generation_agent()
89
+ except Exception as e:
90
+ emit_warning(
91
+ f"Directory changed, but agent reload failed: {e}. "
92
+ "You may need to run /agent or /model to force a refresh."
93
+ )
80
94
  else:
81
95
  emit_error(f"Not a directory: {dirname}")
82
96
  return True
@@ -193,7 +193,7 @@ def _substitute_variables(
193
193
  result = command
194
194
  for var, value in substitutions.items():
195
195
  result = result.replace(f"${{{var}}}", str(value))
196
- result = re.sub(rf"\${re.escape(var)}(?=\W|$)", str(value), result)
196
+ result = re.sub(rf"\${re.escape(var)}(?=\W|$)", lambda m: str(value), result)
197
197
  return result
198
198
 
199
199
 
@@ -0,0 +1,85 @@
1
+ """Image processing utilities for API compliance.
2
+
3
+ Ensures images sent to LLM APIs respect dimension limits.
4
+ The Claude API enforces a 2000px max dimension for many-image requests.
5
+ We use 1920px as default to provide a safety margin.
6
+ """
7
+
8
+ import io
9
+ import logging
10
+ from typing import Optional
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ # Claude API max dimension for many-image requests is 2000px.
15
+ # Use 1920 for safety margin.
16
+ MAX_IMAGE_DIMENSION = 1920
17
+
18
+ try:
19
+ from PIL import Image
20
+
21
+ PIL_AVAILABLE = True
22
+ except ImportError:
23
+ PIL_AVAILABLE = False
24
+ Image = None # type: ignore[misc, assignment]
25
+
26
+
27
+ def constrain_image_dimensions(
28
+ image_bytes: bytes,
29
+ max_dim: int = MAX_IMAGE_DIMENSION,
30
+ media_type: Optional[str] = None,
31
+ ) -> bytes:
32
+ """Resize image if any dimension exceeds max_dim, preserving aspect ratio.
33
+
34
+ Args:
35
+ image_bytes: Raw image bytes (PNG, JPEG, etc.).
36
+ max_dim: Maximum allowed width or height in pixels.
37
+ media_type: Optional MIME type hint. If provided and not an image
38
+ type, returns bytes unchanged.
39
+
40
+ Returns:
41
+ Original bytes if within limits or PIL unavailable, resized PNG bytes otherwise.
42
+ """
43
+ # Skip non-image content
44
+ if media_type and not media_type.startswith("image/"):
45
+ return image_bytes
46
+
47
+ if not PIL_AVAILABLE or Image is None:
48
+ logger.debug("PIL not available, skipping image dimension check")
49
+ return image_bytes
50
+
51
+ if not image_bytes:
52
+ return image_bytes
53
+
54
+ try:
55
+ img = Image.open(io.BytesIO(image_bytes))
56
+ width, height = img.size
57
+
58
+ if width <= max_dim and height <= max_dim:
59
+ return image_bytes
60
+
61
+ # Calculate scale factor based on the larger dimension
62
+ scale = max_dim / max(width, height)
63
+ new_width = int(width * scale)
64
+ new_height = int(height * scale)
65
+
66
+ # Ensure minimum dimensions
67
+ new_width = max(new_width, 1)
68
+ new_height = max(new_height, 1)
69
+
70
+ resized = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
71
+
72
+ # Save as PNG to preserve quality
73
+ output = io.BytesIO()
74
+ resized.save(output, format="PNG", optimize=True)
75
+ output.seek(0)
76
+
77
+ logger.info(
78
+ f"Constrained image dimensions from {width}x{height} "
79
+ f"to {new_width}x{new_height} (max_dim={max_dim})"
80
+ )
81
+ return output.read()
82
+
83
+ except Exception as e:
84
+ logger.warning(f"Failed to constrain image dimensions: {e}")
85
+ return image_bytes
@@ -9,9 +9,9 @@
9
9
  "context_length": 200000,
10
10
  "supported_settings": ["temperature", "seed", "top_p"]
11
11
  },
12
- "synthetic-MiniMax-M2.1": {
12
+ "synthetic-MiniMax-M2.5": {
13
13
  "type": "custom_openai",
14
- "name": "hf:MiniMaxAI/MiniMax-M2.1",
14
+ "name": "hf:MiniMaxAI/MiniMax-M2.5",
15
15
  "custom_endpoint": {
16
16
  "url": "https://api.synthetic.new/openai/v1/",
17
17
  "api_key": "$SYN_API_KEY"
@@ -19,9 +19,9 @@
19
19
  "context_length": 195000,
20
20
  "supported_settings": ["temperature", "seed", "top_p"]
21
21
  },
22
- "synthetic-Kimi-K2-Thinking": {
22
+ "synthetic-qwen3.5-397b": {
23
23
  "type": "custom_openai",
24
- "name": "hf:moonshotai/Kimi-K2-Thinking",
24
+ "name": "hf:Qwen/Qwen3.5-397B-A17B",
25
25
  "custom_endpoint": {
26
26
  "url": "https://api.synthetic.new/openai/v1/",
27
27
  "api_key": "$SYN_API_KEY"
@@ -318,9 +318,10 @@ def run_oauth_flow() -> None:
318
318
 
319
319
  if api_key:
320
320
  emit_info("Registering ChatGPT Codex models…")
321
- from .utils import DEFAULT_CODEX_MODELS
321
+ from .utils import fetch_chatgpt_models
322
322
 
323
- models = DEFAULT_CODEX_MODELS
323
+ account_id = tokens.get("account_id", "")
324
+ models = fetch_chatgpt_models(api_key, account_id)
324
325
  if models:
325
326
  if add_models_to_extra_config(models):
326
327
  emit_success(