newcode 0.2.4__tar.gz → 0.2.6__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.4 → newcode-0.2.6}/PKG-INFO +2 -3
- {newcode-0.2.4 → newcode-0.2.6}/README.md +1 -2
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/base_agent.py +178 -94
- {newcode-0.2.4 → newcode-0.2.6}/newcode/claude_cache_client.py +15 -61
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/attachments.py +4 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/clipboard.py +5 -1
- newcode-0.2.6/newcode/image_utils.py +85 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/claude_code_oauth/register_callbacks.py +51 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/claude_code_oauth/token_refresh_heartbeat.py +4 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/__init__.py +0 -23
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/browser/browser_screenshot.py +5 -1
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/browser/terminal_screenshot_tools.py +6 -2
- {newcode-0.2.4 → newcode-0.2.6}/pyproject.toml +1 -1
- newcode-0.2.4/newcode/agents/agent_scheduler.py +0 -120
- newcode-0.2.4/newcode/plugins/scheduler/__init__.py +0 -1
- newcode-0.2.4/newcode/plugins/scheduler/register_callbacks.py +0 -88
- newcode-0.2.4/newcode/plugins/scheduler/scheduler_menu.py +0 -520
- newcode-0.2.4/newcode/plugins/scheduler/scheduler_wizard.py +0 -341
- newcode-0.2.4/newcode/scheduler/__init__.py +0 -41
- newcode-0.2.4/newcode/scheduler/__main__.py +0 -9
- newcode-0.2.4/newcode/scheduler/cli.py +0 -118
- newcode-0.2.4/newcode/scheduler/config.py +0 -126
- newcode-0.2.4/newcode/scheduler/daemon.py +0 -280
- newcode-0.2.4/newcode/scheduler/executor.py +0 -155
- newcode-0.2.4/newcode/scheduler/platform.py +0 -19
- newcode-0.2.4/newcode/scheduler/platform_unix.py +0 -22
- newcode-0.2.4/newcode/scheduler/platform_win.py +0 -32
- newcode-0.2.4/newcode/tools/scheduler_tools.py +0 -412
- {newcode-0.2.4 → newcode-0.2.6}/.gitignore +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/LICENSE +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/__main__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/agent_c_reviewer.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/agent_code_agent.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/agent_code_reviewer.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/agent_cpp_reviewer.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/agent_creator_agent.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/agent_golang_reviewer.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/agent_javascript_reviewer.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/agent_manager.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/agent_pack_leader.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/agent_planning.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/agent_python_programmer.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/agent_python_reviewer.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/agent_qa_browser.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/agent_qa_expert.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/agent_security_auditor.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/agent_terminal_qa.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/agent_typescript_reviewer.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/event_stream_handler.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/json_agent.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/pack/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/pack/bloodhound.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/pack/husky.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/pack/retriever.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/pack/shepherd.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/pack/terrier.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/pack/watchdog.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/prompt_reviewer.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/agents/subagent_stream_handler.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/api/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/api/app.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/api/main.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/api/pty_manager.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/api/routers/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/api/routers/agents.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/api/routers/commands.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/api/routers/config.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/api/routers/sessions.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/api/templates/terminal.html +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/api/websocket.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/callbacks.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/chatgpt_codex_client.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/cli_runner.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/add_model_menu.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/agent_menu.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/autosave_menu.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/colors_menu.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/command_handler.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/command_registry.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/config_commands.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/core_commands.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/diff_menu.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/file_path_completion.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/load_context_completion.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/base.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/catalog_server_installer.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/custom_server_form.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/custom_server_installer.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/edit_command.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/handler.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/help_command.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/install_command.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/install_menu.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/list_command.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/logs_command.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/remove_command.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/restart_command.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/search_command.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/start_all_command.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/start_command.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/status_command.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/stop_all_command.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/stop_command.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/test_command.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/utils.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp/wizard_utils.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/mcp_completion.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/model_picker_completion.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/model_settings_menu.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/motd.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/onboarding_slides.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/onboarding_wizard.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/pin_command_completion.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/prompt_toolkit_completion.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/session_commands.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/skills_completion.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/uc_menu.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/utils.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/command_line/wiggum_state.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/config.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/error_logging.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/gemini_code_assist.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/gemini_model.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/hook_engine/README.md +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/hook_engine/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/hook_engine/aliases.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/hook_engine/engine.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/hook_engine/executor.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/hook_engine/matcher.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/hook_engine/models.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/hook_engine/registry.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/hook_engine/validator.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/http_utils.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/keymap.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/main.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_/async_lifecycle.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_/blocking_startup.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_/captured_stdio_server.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_/circuit_breaker.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_/config_wizard.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_/dashboard.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_/error_isolation.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_/examples/retry_example.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_/health_monitor.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_/managed_server.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_/manager.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_/mcp_logs.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_/registry.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_/retry_manager.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_/server_registry_catalog.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_/status_tracker.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_/system_tools.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_prompts/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/mcp_prompts/hook_creator.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/messaging/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/messaging/bus.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/messaging/commands.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/messaging/markdown_patches.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/messaging/message_queue.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/messaging/messages.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/messaging/queue_console.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/messaging/renderers.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/messaging/rich_renderer.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/messaging/spinner/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/messaging/spinner/console_spinner.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/messaging/spinner/spinner_base.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/messaging/subagent_console.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/model_factory.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/model_switching.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/model_utils.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/models.json +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/models_dev_api.json +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/models_dev_parser.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/agent_skills/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/agent_skills/config.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/agent_skills/discovery.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/agent_skills/downloader.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/agent_skills/installer.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/agent_skills/metadata.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/agent_skills/prompt_builder.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/agent_skills/register_callbacks.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/agent_skills/remote_catalog.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/agent_skills/skill_catalog.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/agent_skills/skills_install_menu.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/agent_skills/skills_menu.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/accounts.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/antigravity_model.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/config.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/constants.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/oauth.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/register_callbacks.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/storage.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/test_plugin.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/token.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/transport.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/utils.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/chatgpt_oauth/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/chatgpt_oauth/config.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/chatgpt_oauth/oauth_flow.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/chatgpt_oauth/register_callbacks.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/chatgpt_oauth/test_plugin.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/chatgpt_oauth/utils.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/claude_code_hooks/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/claude_code_hooks/config.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/claude_code_hooks/register_callbacks.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/claude_code_oauth/README.md +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/claude_code_oauth/SETUP.md +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/claude_code_oauth/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/claude_code_oauth/config.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/claude_code_oauth/test_plugin.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/claude_code_oauth/utils.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/customizable_commands/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/customizable_commands/register_callbacks.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/example_custom_command/README.md +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/example_custom_command/register_callbacks.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/file_permission_handler/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/file_permission_handler/register_callbacks.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/frontend_emitter/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/frontend_emitter/emitter.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/frontend_emitter/register_callbacks.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/hook_creator/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/hook_creator/register_callbacks.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/hook_manager/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/hook_manager/config.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/hook_manager/hooks_menu.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/hook_manager/register_callbacks.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/oauth_puppy_html.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/shell_safety/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/shell_safety/agent_shell_safety.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/shell_safety/command_cache.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/shell_safety/register_callbacks.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/synthetic_status/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/synthetic_status/register_callbacks.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/synthetic_status/status_api.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/universal_constructor/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/universal_constructor/models.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/universal_constructor/register_callbacks.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/universal_constructor/registry.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/plugins/universal_constructor/sandbox.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/prompts/antigravity_system_prompt.md +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/pydantic_patches.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/reopenable_async_client.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/round_robin_model.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/session_storage.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/status_display.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/summarization_agent.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/terminal_utils.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/agent_tools.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/ask_user_question/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/ask_user_question/constants.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/ask_user_question/demo_tui.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/ask_user_question/handler.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/ask_user_question/models.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/ask_user_question/registration.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/ask_user_question/renderers.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/ask_user_question/terminal_ui.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/ask_user_question/theme.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/ask_user_question/tui_loop.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/browser/__init__.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/browser/browser_control.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/browser/browser_interactions.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/browser/browser_locators.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/browser/browser_manager.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/browser/browser_navigation.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/browser/browser_scripts.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/browser/browser_workflows.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/browser/chromium_terminal_manager.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/browser/terminal_command_tools.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/browser/terminal_tools.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/command_runner.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/common.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/display.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/file_modifications.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/file_operations.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/skills_tools.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/subagent_context.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/tools_content.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/tools/universal_constructor.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/newcode/uvx_detection.py +0 -0
- {newcode-0.2.4 → newcode-0.2.6}/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.6
|
|
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
|
|
|
@@ -1836,7 +1851,25 @@ class BaseAgent(ABC):
|
|
|
1836
1851
|
# Build combined prompt payload when attachments are provided.
|
|
1837
1852
|
attachment_parts: List[Any] = []
|
|
1838
1853
|
if attachments:
|
|
1839
|
-
|
|
1854
|
+
# Constrain image dimensions for API compliance (Claude max 2000px for many-image requests)
|
|
1855
|
+
constrained = []
|
|
1856
|
+
for att in attachments:
|
|
1857
|
+
if (
|
|
1858
|
+
isinstance(att, BinaryContent)
|
|
1859
|
+
and hasattr(att, "media_type")
|
|
1860
|
+
and hasattr(att, "data")
|
|
1861
|
+
):
|
|
1862
|
+
media_type = getattr(att, "media_type", "") or ""
|
|
1863
|
+
if media_type.startswith("image/"):
|
|
1864
|
+
new_data = constrain_image_dimensions(
|
|
1865
|
+
att.data, media_type=media_type
|
|
1866
|
+
)
|
|
1867
|
+
if new_data is not att.data:
|
|
1868
|
+
att = BinaryContent(data=new_data, media_type=media_type)
|
|
1869
|
+
constrained.append(att)
|
|
1870
|
+
else:
|
|
1871
|
+
constrained.append(att)
|
|
1872
|
+
attachment_parts.extend(constrained)
|
|
1840
1873
|
if link_attachments:
|
|
1841
1874
|
attachment_parts.extend(list(link_attachments))
|
|
1842
1875
|
|
|
@@ -1849,41 +1882,58 @@ class BaseAgent(ABC):
|
|
|
1849
1882
|
prompt_payload = prompt
|
|
1850
1883
|
|
|
1851
1884
|
async def run_agent_task():
|
|
1852
|
-
|
|
1853
|
-
self.set_message_history(
|
|
1854
|
-
self.prune_interrupted_tool_calls(self.get_message_history())
|
|
1855
|
-
)
|
|
1885
|
+
_cloudflare_retry_attempted = False
|
|
1856
1886
|
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1887
|
+
while True:
|
|
1888
|
+
_retry_after_cloudflare_refresh = False
|
|
1889
|
+
try:
|
|
1890
|
+
self.set_message_history(
|
|
1891
|
+
self.prune_interrupted_tool_calls(self.get_message_history())
|
|
1862
1892
|
)
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
if
|
|
1866
|
-
self.set_message_history(compacted_messages)
|
|
1893
|
+
|
|
1894
|
+
# DELAYED COMPACTION: Check if we should attempt delayed compaction
|
|
1895
|
+
if self.should_attempt_delayed_compaction():
|
|
1867
1896
|
emit_info(
|
|
1868
|
-
"
|
|
1897
|
+
"🔄 Attempting delayed compaction (tool calls completed)",
|
|
1869
1898
|
message_group="token_context_status",
|
|
1870
1899
|
)
|
|
1900
|
+
current_messages = self.get_message_history()
|
|
1901
|
+
compacted_messages, _ = self.compact_messages(current_messages)
|
|
1902
|
+
if compacted_messages != current_messages:
|
|
1903
|
+
self.set_message_history(compacted_messages)
|
|
1904
|
+
emit_info(
|
|
1905
|
+
"✅ Delayed compaction completed successfully",
|
|
1906
|
+
message_group="token_context_status",
|
|
1907
|
+
)
|
|
1871
1908
|
|
|
1872
|
-
|
|
1909
|
+
usage_limits = UsageLimits(request_limit=get_message_limit())
|
|
1873
1910
|
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1911
|
+
# Handle MCP servers - add them temporarily when using DBOS
|
|
1912
|
+
if (
|
|
1913
|
+
get_use_dbos()
|
|
1914
|
+
and hasattr(self, "_mcp_servers")
|
|
1915
|
+
and self._mcp_servers
|
|
1916
|
+
):
|
|
1917
|
+
# Temporarily add MCP servers to the DBOS agent using internal _toolsets
|
|
1918
|
+
original_toolsets = pydantic_agent._toolsets
|
|
1919
|
+
pydantic_agent._toolsets = original_toolsets + self._mcp_servers
|
|
1920
|
+
pydantic_agent._toolsets = original_toolsets + self._mcp_servers
|
|
1884
1921
|
|
|
1885
|
-
|
|
1886
|
-
|
|
1922
|
+
try:
|
|
1923
|
+
# Set the workflow ID for DBOS context so DBOS and the agent ID match
|
|
1924
|
+
with SetWorkflowID(group_id):
|
|
1925
|
+
result_ = await pydantic_agent.run(
|
|
1926
|
+
prompt_payload,
|
|
1927
|
+
message_history=self.get_message_history(),
|
|
1928
|
+
usage_limits=usage_limits,
|
|
1929
|
+
event_stream_handler=event_stream_handler,
|
|
1930
|
+
**kwargs,
|
|
1931
|
+
)
|
|
1932
|
+
return result_
|
|
1933
|
+
finally:
|
|
1934
|
+
# Always restore original toolsets
|
|
1935
|
+
pydantic_agent._toolsets = original_toolsets
|
|
1936
|
+
elif get_use_dbos():
|
|
1887
1937
|
with SetWorkflowID(group_id):
|
|
1888
1938
|
result_ = await pydantic_agent.run(
|
|
1889
1939
|
prompt_payload,
|
|
@@ -1893,11 +1943,8 @@ class BaseAgent(ABC):
|
|
|
1893
1943
|
**kwargs,
|
|
1894
1944
|
)
|
|
1895
1945
|
return result_
|
|
1896
|
-
|
|
1897
|
-
#
|
|
1898
|
-
pydantic_agent._toolsets = original_toolsets
|
|
1899
|
-
elif get_use_dbos():
|
|
1900
|
-
with SetWorkflowID(group_id):
|
|
1946
|
+
else:
|
|
1947
|
+
# Non-DBOS path (MCP servers are already included)
|
|
1901
1948
|
result_ = await pydantic_agent.run(
|
|
1902
1949
|
prompt_payload,
|
|
1903
1950
|
message_history=self.get_message_history(),
|
|
@@ -1906,74 +1953,111 @@ class BaseAgent(ABC):
|
|
|
1906
1953
|
**kwargs,
|
|
1907
1954
|
)
|
|
1908
1955
|
return result_
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
usage_limits=usage_limits,
|
|
1915
|
-
event_stream_handler=event_stream_handler,
|
|
1916
|
-
**kwargs,
|
|
1956
|
+
except* UsageLimitExceeded as ule:
|
|
1957
|
+
emit_info(f"Usage limit exceeded: {str(ule)}", group_id=group_id)
|
|
1958
|
+
emit_info(
|
|
1959
|
+
"The agent has reached its usage limit. You can ask it to continue by saying 'please continue' or similar.",
|
|
1960
|
+
group_id=group_id,
|
|
1917
1961
|
)
|
|
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)
|
|
1962
|
+
except* mcp.shared.exceptions.McpError as mcp_error:
|
|
1963
|
+
emit_info(f"MCP server error: {str(mcp_error)}", group_id=group_id)
|
|
1964
|
+
emit_info(f"{str(mcp_error)}", group_id=group_id)
|
|
1965
|
+
emit_info(
|
|
1966
|
+
"Try disabling any malfunctioning MCP servers",
|
|
1967
|
+
group_id=group_id,
|
|
1968
|
+
)
|
|
1969
|
+
except* asyncio.exceptions.CancelledError:
|
|
1970
|
+
emit_info("Cancelled")
|
|
1971
|
+
if get_use_dbos():
|
|
1972
|
+
await DBOS.cancel_workflow_async(group_id)
|
|
1973
|
+
except* InterruptedError as ie:
|
|
1974
|
+
emit_info(f"Interrupted: {str(ie)}")
|
|
1975
|
+
if get_use_dbos():
|
|
1976
|
+
await DBOS.cancel_workflow_async(group_id)
|
|
1977
|
+
except* Exception as other_error:
|
|
1978
|
+
|
|
1979
|
+
def contains_cloudflare_auth_error(exc: Exception) -> bool:
|
|
1980
|
+
if isinstance(exc, ExceptionGroup):
|
|
1981
|
+
return any(
|
|
1982
|
+
contains_cloudflare_auth_error(sub_exc)
|
|
1983
|
+
for sub_exc in exc.exceptions
|
|
1984
|
+
)
|
|
1985
|
+
return _is_cloudflare_auth_error(exc)
|
|
1986
|
+
|
|
1987
|
+
if (
|
|
1988
|
+
not _cloudflare_retry_attempted
|
|
1989
|
+
and contains_cloudflare_auth_error(other_error)
|
|
1949
1990
|
):
|
|
1950
|
-
|
|
1951
|
-
emit_info(
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
log_error(
|
|
1955
|
-
exc,
|
|
1956
|
-
context=f"Agent run (group_id={group_id})",
|
|
1957
|
-
include_traceback=True,
|
|
1991
|
+
_cloudflare_retry_attempted = True
|
|
1992
|
+
emit_info(
|
|
1993
|
+
"Detected Cloudflare 400 error (likely expired token), attempting refresh...",
|
|
1994
|
+
group_id=group_id,
|
|
1958
1995
|
)
|
|
1996
|
+
refreshed_token = None
|
|
1997
|
+
try:
|
|
1998
|
+
from newcode.plugins.claude_code_oauth.utils import (
|
|
1999
|
+
refresh_access_token,
|
|
2000
|
+
)
|
|
2001
|
+
|
|
2002
|
+
refreshed_token = refresh_access_token(force=True)
|
|
2003
|
+
except Exception as refresh_error:
|
|
2004
|
+
emit_info(
|
|
2005
|
+
f"Token refresh failed: {str(refresh_error)}",
|
|
2006
|
+
group_id=group_id,
|
|
2007
|
+
)
|
|
2008
|
+
|
|
2009
|
+
if refreshed_token:
|
|
2010
|
+
emit_info(
|
|
2011
|
+
"Token refresh successful, retrying request...",
|
|
2012
|
+
group_id=group_id,
|
|
2013
|
+
)
|
|
2014
|
+
_retry_after_cloudflare_refresh = True
|
|
2015
|
+
|
|
2016
|
+
if not _retry_after_cloudflare_refresh:
|
|
2017
|
+
# Filter out CancelledError and UsageLimitExceeded from the exception group - let it propagate
|
|
2018
|
+
remaining_exceptions = []
|
|
2019
|
+
|
|
2020
|
+
def collect_non_cancelled_exceptions(exc):
|
|
2021
|
+
if isinstance(exc, ExceptionGroup):
|
|
2022
|
+
for sub_exc in exc.exceptions:
|
|
2023
|
+
collect_non_cancelled_exceptions(sub_exc)
|
|
2024
|
+
elif not isinstance(
|
|
2025
|
+
exc, (asyncio.CancelledError, UsageLimitExceeded)
|
|
2026
|
+
):
|
|
2027
|
+
remaining_exceptions.append(exc)
|
|
2028
|
+
emit_info(
|
|
2029
|
+
f"Unexpected error: {str(exc)}", group_id=group_id
|
|
2030
|
+
)
|
|
2031
|
+
emit_info(f"{str(exc.args)}", group_id=group_id)
|
|
2032
|
+
# Log to file for debugging
|
|
2033
|
+
log_error(
|
|
2034
|
+
exc,
|
|
2035
|
+
context=f"Agent run (group_id={group_id})",
|
|
2036
|
+
include_traceback=True,
|
|
2037
|
+
)
|
|
1959
2038
|
|
|
1960
|
-
|
|
2039
|
+
collect_non_cancelled_exceptions(other_error)
|
|
1961
2040
|
|
|
1962
|
-
|
|
1963
|
-
|
|
2041
|
+
# If there are CancelledError exceptions in the group, re-raise them
|
|
2042
|
+
cancelled_exceptions = []
|
|
1964
2043
|
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
2044
|
+
def collect_cancelled_exceptions(exc):
|
|
2045
|
+
if isinstance(exc, ExceptionGroup):
|
|
2046
|
+
for sub_exc in exc.exceptions:
|
|
2047
|
+
collect_cancelled_exceptions(sub_exc)
|
|
2048
|
+
elif isinstance(exc, asyncio.CancelledError):
|
|
2049
|
+
cancelled_exceptions.append(exc)
|
|
1971
2050
|
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
2051
|
+
collect_cancelled_exceptions(other_error)
|
|
2052
|
+
finally:
|
|
2053
|
+
self.set_message_history(
|
|
2054
|
+
self.prune_interrupted_tool_calls(self.get_message_history())
|
|
2055
|
+
)
|
|
2056
|
+
|
|
2057
|
+
if _retry_after_cloudflare_refresh:
|
|
2058
|
+
continue
|
|
2059
|
+
|
|
2060
|
+
break
|
|
1977
2061
|
|
|
1978
2062
|
# Create the task FIRST
|
|
1979
2063
|
agent_task = asyncio.create_task(run_agent_task())
|
|
@@ -371,12 +371,10 @@ 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(
|
|
375
|
-
response
|
|
376
|
-
) or self._is_token_expired_error(response)
|
|
374
|
+
is_auth_error = self._is_cloudflare_html_error(response)
|
|
377
375
|
if is_auth_error:
|
|
378
376
|
logger.info(
|
|
379
|
-
"Detected 400
|
|
377
|
+
"Detected Cloudflare 400 error (expired token), attempting token refresh"
|
|
380
378
|
)
|
|
381
379
|
|
|
382
380
|
if is_auth_error:
|
|
@@ -537,79 +535,35 @@ class ClaudeCacheAsyncClient(httpx.AsyncClient):
|
|
|
537
535
|
"""Check if this is a Cloudflare HTML error response.
|
|
538
536
|
|
|
539
537
|
Cloudflare often returns HTML error pages with status 400 when
|
|
540
|
-
there are authentication issues.
|
|
538
|
+
there are authentication issues. The response looks like:
|
|
539
|
+
<html>
|
|
540
|
+
<head><title>400 Bad Request</title></head>
|
|
541
|
+
<body>
|
|
542
|
+
<center><h1>400 Bad Request</h1></center>
|
|
543
|
+
<hr><center>cloudflare</center>
|
|
544
|
+
</body>
|
|
545
|
+
</html>
|
|
541
546
|
"""
|
|
542
|
-
# Check content type
|
|
543
|
-
content_type = response.headers.get("content-type", "")
|
|
544
|
-
if "text/html" not in content_type.lower():
|
|
545
|
-
return False
|
|
546
|
-
|
|
547
|
-
# Check if body contains Cloudflare markers
|
|
548
547
|
try:
|
|
549
|
-
|
|
548
|
+
body = None
|
|
549
|
+
# Try reading the body from _content first (already consumed)
|
|
550
550
|
if hasattr(response, "_content") and response._content:
|
|
551
551
|
body = response._content.decode("utf-8", errors="ignore")
|
|
552
552
|
else:
|
|
553
|
-
# Try to read the text (this might be already consumed)
|
|
554
553
|
try:
|
|
555
554
|
body = response.text
|
|
556
555
|
except Exception:
|
|
557
556
|
return False
|
|
558
557
|
|
|
559
|
-
|
|
558
|
+
if not body:
|
|
559
|
+
return False
|
|
560
|
+
|
|
560
561
|
body_lower = body.lower()
|
|
561
562
|
return "cloudflare" in body_lower and "400 bad request" in body_lower
|
|
562
563
|
except Exception as exc:
|
|
563
564
|
logger.debug("Error checking for Cloudflare error: %s", exc)
|
|
564
565
|
return False
|
|
565
566
|
|
|
566
|
-
@staticmethod
|
|
567
|
-
def _is_token_expired_error(response: httpx.Response) -> bool:
|
|
568
|
-
"""Check if a 400 response indicates an expired or invalid OAuth token.
|
|
569
|
-
|
|
570
|
-
The Anthropic API returns JSON 400 errors when the token is expired.
|
|
571
|
-
Also checks stored token expiry as a heuristic.
|
|
572
|
-
"""
|
|
573
|
-
try:
|
|
574
|
-
# Check if the JSON body mentions auth/token issues
|
|
575
|
-
body = None
|
|
576
|
-
if hasattr(response, "_content") and response._content:
|
|
577
|
-
body = response._content.decode("utf-8", errors="ignore")
|
|
578
|
-
else:
|
|
579
|
-
try:
|
|
580
|
-
body = response.text
|
|
581
|
-
except Exception:
|
|
582
|
-
pass
|
|
583
|
-
|
|
584
|
-
if body:
|
|
585
|
-
body_lower = body.lower()
|
|
586
|
-
auth_markers = (
|
|
587
|
-
"expired",
|
|
588
|
-
"invalid_token",
|
|
589
|
-
"invalid_grant",
|
|
590
|
-
"token",
|
|
591
|
-
"unauthorized",
|
|
592
|
-
"authentication",
|
|
593
|
-
)
|
|
594
|
-
if any(marker in body_lower for marker in auth_markers):
|
|
595
|
-
return True
|
|
596
|
-
|
|
597
|
-
# Fallback: check if our stored token is actually expired
|
|
598
|
-
from newcode.plugins.claude_code_oauth.utils import (
|
|
599
|
-
is_token_expired,
|
|
600
|
-
load_stored_tokens,
|
|
601
|
-
)
|
|
602
|
-
|
|
603
|
-
tokens = load_stored_tokens()
|
|
604
|
-
if tokens and is_token_expired(tokens):
|
|
605
|
-
logger.info("Stored token is expired, treating 400 as auth error")
|
|
606
|
-
return True
|
|
607
|
-
|
|
608
|
-
except Exception as exc:
|
|
609
|
-
logger.debug("Error checking for token expired error: %s", exc)
|
|
610
|
-
|
|
611
|
-
return False
|
|
612
|
-
|
|
613
567
|
def _refresh_claude_oauth_token(self) -> str | None:
|
|
614
568
|
try:
|
|
615
569
|
from newcode.plugins.claude_code_oauth.utils import refresh_access_token
|
|
@@ -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:
|
|
@@ -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
|