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.
- {newcode-0.2.5 → newcode-0.2.8}/PKG-INFO +2 -3
- {newcode-0.2.5 → newcode-0.2.8}/README.md +1 -2
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/base_agent.py +184 -94
- {newcode-0.2.5 → newcode-0.2.8}/newcode/claude_cache_client.py +13 -3
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/attachments.py +4 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/clipboard.py +5 -1
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/core_commands.py +15 -1
- {newcode-0.2.5 → newcode-0.2.8}/newcode/hook_engine/executor.py +1 -1
- newcode-0.2.8/newcode/image_utils.py +85 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/models.json +4 -4
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/chatgpt_oauth/oauth_flow.py +3 -2
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/chatgpt_oauth/test_plugin.py +7 -1
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/chatgpt_oauth/utils.py +25 -1
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_oauth/register_callbacks.py +51 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_oauth/token_refresh_heartbeat.py +4 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_oauth/utils.py +39 -3
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/__init__.py +0 -23
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/browser_screenshot.py +5 -1
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/terminal_screenshot_tools.py +6 -2
- {newcode-0.2.5 → newcode-0.2.8}/pyproject.toml +1 -1
- newcode-0.2.5/newcode/agents/agent_scheduler.py +0 -120
- newcode-0.2.5/newcode/plugins/scheduler/__init__.py +0 -1
- newcode-0.2.5/newcode/plugins/scheduler/register_callbacks.py +0 -88
- newcode-0.2.5/newcode/plugins/scheduler/scheduler_menu.py +0 -520
- newcode-0.2.5/newcode/plugins/scheduler/scheduler_wizard.py +0 -341
- newcode-0.2.5/newcode/scheduler/__init__.py +0 -41
- newcode-0.2.5/newcode/scheduler/__main__.py +0 -9
- newcode-0.2.5/newcode/scheduler/cli.py +0 -118
- newcode-0.2.5/newcode/scheduler/config.py +0 -126
- newcode-0.2.5/newcode/scheduler/daemon.py +0 -280
- newcode-0.2.5/newcode/scheduler/executor.py +0 -155
- newcode-0.2.5/newcode/scheduler/platform.py +0 -19
- newcode-0.2.5/newcode/scheduler/platform_unix.py +0 -22
- newcode-0.2.5/newcode/scheduler/platform_win.py +0 -32
- newcode-0.2.5/newcode/tools/scheduler_tools.py +0 -412
- {newcode-0.2.5 → newcode-0.2.8}/.gitignore +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/LICENSE +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/__main__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_c_reviewer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_code_agent.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_code_reviewer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_cpp_reviewer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_creator_agent.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_golang_reviewer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_javascript_reviewer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_manager.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_pack_leader.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_planning.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_python_programmer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_python_reviewer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_qa_browser.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_qa_expert.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_security_auditor.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_terminal_qa.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/agent_typescript_reviewer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/event_stream_handler.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/json_agent.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/pack/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/pack/bloodhound.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/pack/husky.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/pack/retriever.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/pack/shepherd.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/pack/terrier.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/pack/watchdog.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/prompt_reviewer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/agents/subagent_stream_handler.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/api/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/api/app.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/api/main.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/api/pty_manager.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/api/routers/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/api/routers/agents.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/api/routers/commands.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/api/routers/config.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/api/routers/sessions.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/api/templates/terminal.html +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/api/websocket.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/chatgpt_codex_client.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/cli_runner.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/add_model_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/agent_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/autosave_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/colors_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/command_handler.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/command_registry.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/config_commands.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/diff_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/file_path_completion.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/load_context_completion.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/base.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/catalog_server_installer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/custom_server_form.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/custom_server_installer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/edit_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/handler.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/help_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/install_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/install_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/list_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/logs_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/remove_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/restart_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/search_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/start_all_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/start_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/status_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/stop_all_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/stop_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/test_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/utils.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp/wizard_utils.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/mcp_completion.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/model_picker_completion.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/model_settings_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/motd.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/onboarding_slides.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/onboarding_wizard.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/pin_command_completion.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/prompt_toolkit_completion.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/session_commands.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/skills_completion.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/uc_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/utils.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/command_line/wiggum_state.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/config.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/error_logging.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/gemini_code_assist.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/gemini_model.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/hook_engine/README.md +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/hook_engine/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/hook_engine/aliases.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/hook_engine/engine.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/hook_engine/matcher.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/hook_engine/models.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/hook_engine/registry.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/hook_engine/validator.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/http_utils.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/keymap.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/main.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/async_lifecycle.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/blocking_startup.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/captured_stdio_server.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/circuit_breaker.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/config_wizard.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/dashboard.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/error_isolation.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/examples/retry_example.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/health_monitor.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/managed_server.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/manager.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/mcp_logs.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/registry.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/retry_manager.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/server_registry_catalog.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/status_tracker.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_/system_tools.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_prompts/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/mcp_prompts/hook_creator.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/bus.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/commands.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/markdown_patches.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/message_queue.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/messages.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/queue_console.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/renderers.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/rich_renderer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/spinner/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/spinner/console_spinner.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/spinner/spinner_base.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/messaging/subagent_console.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/model_factory.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/model_switching.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/model_utils.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/models_dev_api.json +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/models_dev_parser.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/config.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/discovery.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/downloader.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/installer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/metadata.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/prompt_builder.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/remote_catalog.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/skill_catalog.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/skills_install_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/agent_skills/skills_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/accounts.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/antigravity_model.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/config.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/constants.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/oauth.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/storage.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/test_plugin.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/token.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/transport.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/antigravity_oauth/utils.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/chatgpt_oauth/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/chatgpt_oauth/config.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/chatgpt_oauth/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_hooks/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_hooks/config.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_hooks/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_oauth/README.md +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_oauth/SETUP.md +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_oauth/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_oauth/config.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/claude_code_oauth/test_plugin.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/customizable_commands/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/customizable_commands/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/example_custom_command/README.md +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/example_custom_command/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/file_permission_handler/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/file_permission_handler/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/frontend_emitter/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/frontend_emitter/emitter.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/frontend_emitter/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/hook_creator/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/hook_creator/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/hook_manager/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/hook_manager/config.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/hook_manager/hooks_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/hook_manager/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/oauth_puppy_html.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/shell_safety/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/shell_safety/agent_shell_safety.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/shell_safety/command_cache.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/shell_safety/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/synthetic_status/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/synthetic_status/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/synthetic_status/status_api.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/universal_constructor/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/universal_constructor/models.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/universal_constructor/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/universal_constructor/registry.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/plugins/universal_constructor/sandbox.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/prompts/antigravity_system_prompt.md +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/pydantic_patches.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/reopenable_async_client.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/round_robin_model.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/session_storage.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/status_display.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/summarization_agent.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/terminal_utils.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/agent_tools.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/constants.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/demo_tui.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/handler.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/models.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/registration.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/renderers.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/terminal_ui.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/theme.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/ask_user_question/tui_loop.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/browser_control.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/browser_interactions.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/browser_locators.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/browser_manager.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/browser_navigation.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/browser_scripts.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/browser_workflows.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/chromium_terminal_manager.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/terminal_command_tools.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/browser/terminal_tools.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/command_runner.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/common.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/display.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/file_modifications.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/file_operations.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/skills_tools.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/subagent_context.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/tools_content.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/tools/universal_constructor.py +0 -0
- {newcode-0.2.5 → newcode-0.2.8}/newcode/uvx_detection.py +0 -0
- {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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1853
|
-
self.set_message_history(
|
|
1854
|
-
self.prune_interrupted_tool_calls(self.get_message_history())
|
|
1855
|
-
)
|
|
1891
|
+
_cloudflare_retry_attempted = False
|
|
1856
1892
|
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
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
|
-
|
|
1864
|
-
|
|
1865
|
-
if
|
|
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
|
-
"
|
|
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
|
-
|
|
1915
|
+
usage_limits = UsageLimits(request_limit=get_message_limit())
|
|
1873
1916
|
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
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
|
-
|
|
1886
|
-
|
|
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
|
-
|
|
1897
|
-
#
|
|
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
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
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
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
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
|
-
|
|
1951
|
-
emit_info(
|
|
1952
|
-
|
|
1953
|
-
|
|
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
|
-
|
|
2045
|
+
collect_non_cancelled_exceptions(other_error)
|
|
1961
2046
|
|
|
1962
|
-
|
|
1963
|
-
|
|
2047
|
+
# If there are CancelledError exceptions in the group, re-raise them
|
|
2048
|
+
cancelled_exceptions = []
|
|
1964
2049
|
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
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
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
12
|
+
"synthetic-MiniMax-M2.5": {
|
|
13
13
|
"type": "custom_openai",
|
|
14
|
-
"name": "hf:MiniMaxAI/MiniMax-M2.
|
|
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-
|
|
22
|
+
"synthetic-qwen3.5-397b": {
|
|
23
23
|
"type": "custom_openai",
|
|
24
|
-
"name": "hf:
|
|
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
|
|
321
|
+
from .utils import fetch_chatgpt_models
|
|
322
322
|
|
|
323
|
-
|
|
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(
|