newcode 0.2.5__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.5 → newcode-0.2.6}/PKG-INFO +2 -3
- {newcode-0.2.5 → newcode-0.2.6}/README.md +1 -2
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/base_agent.py +178 -94
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/attachments.py +4 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/clipboard.py +5 -1
- newcode-0.2.6/newcode/image_utils.py +85 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/claude_code_oauth/register_callbacks.py +51 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/claude_code_oauth/token_refresh_heartbeat.py +4 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/__init__.py +0 -23
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/browser/browser_screenshot.py +5 -1
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/browser/terminal_screenshot_tools.py +6 -2
- {newcode-0.2.5 → newcode-0.2.6}/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.6}/.gitignore +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/LICENSE +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/__main__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/agent_c_reviewer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/agent_code_agent.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/agent_code_reviewer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/agent_cpp_reviewer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/agent_creator_agent.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/agent_golang_reviewer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/agent_javascript_reviewer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/agent_manager.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/agent_pack_leader.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/agent_planning.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/agent_python_programmer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/agent_python_reviewer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/agent_qa_browser.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/agent_qa_expert.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/agent_security_auditor.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/agent_terminal_qa.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/agent_typescript_reviewer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/event_stream_handler.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/json_agent.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/pack/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/pack/bloodhound.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/pack/husky.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/pack/retriever.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/pack/shepherd.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/pack/terrier.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/pack/watchdog.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/prompt_reviewer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/agents/subagent_stream_handler.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/api/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/api/app.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/api/main.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/api/pty_manager.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/api/routers/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/api/routers/agents.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/api/routers/commands.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/api/routers/config.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/api/routers/sessions.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/api/templates/terminal.html +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/api/websocket.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/chatgpt_codex_client.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/claude_cache_client.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/cli_runner.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/add_model_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/agent_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/autosave_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/colors_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/command_handler.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/command_registry.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/config_commands.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/core_commands.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/diff_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/file_path_completion.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/load_context_completion.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/base.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/catalog_server_installer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/custom_server_form.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/custom_server_installer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/edit_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/handler.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/help_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/install_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/install_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/list_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/logs_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/remove_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/restart_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/search_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/start_all_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/start_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/status_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/stop_all_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/stop_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/test_command.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/utils.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp/wizard_utils.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/mcp_completion.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/model_picker_completion.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/model_settings_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/motd.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/onboarding_slides.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/onboarding_wizard.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/pin_command_completion.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/prompt_toolkit_completion.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/session_commands.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/skills_completion.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/uc_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/utils.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/command_line/wiggum_state.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/config.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/error_logging.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/gemini_code_assist.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/gemini_model.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/hook_engine/README.md +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/hook_engine/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/hook_engine/aliases.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/hook_engine/engine.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/hook_engine/executor.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/hook_engine/matcher.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/hook_engine/models.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/hook_engine/registry.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/hook_engine/validator.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/http_utils.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/keymap.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/main.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_/async_lifecycle.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_/blocking_startup.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_/captured_stdio_server.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_/circuit_breaker.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_/config_wizard.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_/dashboard.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_/error_isolation.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_/examples/retry_example.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_/health_monitor.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_/managed_server.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_/manager.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_/mcp_logs.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_/registry.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_/retry_manager.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_/server_registry_catalog.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_/status_tracker.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_/system_tools.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_prompts/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/mcp_prompts/hook_creator.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/messaging/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/messaging/bus.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/messaging/commands.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/messaging/markdown_patches.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/messaging/message_queue.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/messaging/messages.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/messaging/queue_console.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/messaging/renderers.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/messaging/rich_renderer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/messaging/spinner/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/messaging/spinner/console_spinner.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/messaging/spinner/spinner_base.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/messaging/subagent_console.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/model_factory.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/model_switching.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/model_utils.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/models.json +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/models_dev_api.json +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/models_dev_parser.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/agent_skills/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/agent_skills/config.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/agent_skills/discovery.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/agent_skills/downloader.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/agent_skills/installer.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/agent_skills/metadata.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/agent_skills/prompt_builder.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/agent_skills/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/agent_skills/remote_catalog.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/agent_skills/skill_catalog.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/agent_skills/skills_install_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/agent_skills/skills_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/accounts.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/antigravity_model.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/config.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/constants.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/oauth.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/storage.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/test_plugin.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/token.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/transport.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/antigravity_oauth/utils.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/chatgpt_oauth/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/chatgpt_oauth/config.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/chatgpt_oauth/oauth_flow.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/chatgpt_oauth/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/chatgpt_oauth/test_plugin.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/chatgpt_oauth/utils.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/claude_code_hooks/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/claude_code_hooks/config.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/claude_code_hooks/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/claude_code_oauth/README.md +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/claude_code_oauth/SETUP.md +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/claude_code_oauth/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/claude_code_oauth/config.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/claude_code_oauth/test_plugin.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/claude_code_oauth/utils.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/customizable_commands/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/customizable_commands/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/example_custom_command/README.md +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/example_custom_command/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/file_permission_handler/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/file_permission_handler/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/frontend_emitter/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/frontend_emitter/emitter.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/frontend_emitter/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/hook_creator/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/hook_creator/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/hook_manager/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/hook_manager/config.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/hook_manager/hooks_menu.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/hook_manager/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/oauth_puppy_html.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/shell_safety/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/shell_safety/agent_shell_safety.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/shell_safety/command_cache.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/shell_safety/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/synthetic_status/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/synthetic_status/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/synthetic_status/status_api.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/universal_constructor/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/universal_constructor/models.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/universal_constructor/register_callbacks.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/universal_constructor/registry.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/universal_constructor/sandbox.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/prompts/antigravity_system_prompt.md +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/pydantic_patches.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/reopenable_async_client.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/round_robin_model.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/session_storage.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/status_display.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/summarization_agent.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/terminal_utils.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/agent_tools.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/ask_user_question/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/ask_user_question/constants.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/ask_user_question/demo_tui.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/ask_user_question/handler.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/ask_user_question/models.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/ask_user_question/registration.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/ask_user_question/renderers.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/ask_user_question/terminal_ui.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/ask_user_question/theme.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/ask_user_question/tui_loop.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/browser/__init__.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/browser/browser_control.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/browser/browser_interactions.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/browser/browser_locators.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/browser/browser_manager.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/browser/browser_navigation.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/browser/browser_scripts.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/browser/browser_workflows.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/browser/chromium_terminal_manager.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/browser/terminal_command_tools.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/browser/terminal_tools.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/command_runner.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/common.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/display.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/file_modifications.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/file_operations.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/skills_tools.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/subagent_context.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/tools_content.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/tools/universal_constructor.py +0 -0
- {newcode-0.2.5 → newcode-0.2.6}/newcode/uvx_detection.py +0 -0
- {newcode-0.2.5 → 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())
|
|
@@ -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
|
|
@@ -380,6 +380,55 @@ def _register_model_types() -> List[Dict[str, Any]]:
|
|
|
380
380
|
# Using a dict to allow multiple concurrent agent runs (keyed by session_id)
|
|
381
381
|
_active_heartbeats: Dict[str, Any] = {}
|
|
382
382
|
|
|
383
|
+
# Persistent session-level heartbeat that runs for the entire app lifetime
|
|
384
|
+
_session_heartbeat: Optional[Any] = None
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
async def _on_startup() -> None:
|
|
388
|
+
"""Start a persistent token refresh heartbeat for idle sessions."""
|
|
389
|
+
global _session_heartbeat
|
|
390
|
+
|
|
391
|
+
try:
|
|
392
|
+
if _session_heartbeat is not None:
|
|
393
|
+
return
|
|
394
|
+
|
|
395
|
+
tokens = load_stored_tokens()
|
|
396
|
+
if not tokens:
|
|
397
|
+
return
|
|
398
|
+
|
|
399
|
+
from .token_refresh_heartbeat import (
|
|
400
|
+
SESSION_HEARTBEAT_INTERVAL_SECONDS,
|
|
401
|
+
TokenRefreshHeartbeat,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
heartbeat = TokenRefreshHeartbeat(interval=SESSION_HEARTBEAT_INTERVAL_SECONDS)
|
|
405
|
+
await heartbeat.start()
|
|
406
|
+
_session_heartbeat = heartbeat
|
|
407
|
+
logger.debug(
|
|
408
|
+
"Started persistent session-level token refresh heartbeat (interval: 1h)"
|
|
409
|
+
)
|
|
410
|
+
except Exception as exc:
|
|
411
|
+
logger.debug(
|
|
412
|
+
"Failed to start persistent session-level token refresh heartbeat: %s", exc
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
async def _on_shutdown() -> None:
|
|
417
|
+
"""Stop the persistent session-level token refresh heartbeat."""
|
|
418
|
+
global _session_heartbeat
|
|
419
|
+
|
|
420
|
+
try:
|
|
421
|
+
if _session_heartbeat is None:
|
|
422
|
+
return
|
|
423
|
+
|
|
424
|
+
await _session_heartbeat.stop()
|
|
425
|
+
_session_heartbeat = None
|
|
426
|
+
logger.debug("Stopped persistent session-level token refresh heartbeat")
|
|
427
|
+
except Exception as exc:
|
|
428
|
+
logger.debug(
|
|
429
|
+
"Failed to stop persistent session-level token refresh heartbeat: %s", exc
|
|
430
|
+
)
|
|
431
|
+
|
|
383
432
|
|
|
384
433
|
async def _on_agent_run_start(
|
|
385
434
|
agent_name: str,
|
|
@@ -449,5 +498,7 @@ async def _on_agent_run_end(
|
|
|
449
498
|
register_callback("custom_command_help", _custom_help)
|
|
450
499
|
register_callback("custom_command", _handle_custom_command)
|
|
451
500
|
register_callback("register_model_type", _register_model_types)
|
|
501
|
+
register_callback("startup", _on_startup)
|
|
502
|
+
register_callback("shutdown", _on_shutdown)
|
|
452
503
|
register_callback("agent_run_start", _on_agent_run_start)
|
|
453
504
|
register_callback("agent_run_end", _on_agent_run_end)
|
{newcode-0.2.5 → newcode-0.2.6}/newcode/plugins/claude_code_oauth/token_refresh_heartbeat.py
RENAMED
|
@@ -28,6 +28,10 @@ HEARTBEAT_INTERVAL_SECONDS = 120
|
|
|
28
28
|
# Minimum time between refresh attempts to avoid hammering the endpoint
|
|
29
29
|
MIN_REFRESH_INTERVAL_SECONDS = 60
|
|
30
30
|
|
|
31
|
+
# Session-level heartbeat interval - refresh token once per hour during idle sessions
|
|
32
|
+
# This prevents tokens from expiring when the app is open but no agent is running
|
|
33
|
+
SESSION_HEARTBEAT_INTERVAL_SECONDS = 3600
|
|
34
|
+
|
|
31
35
|
# Global tracking of last refresh time to coordinate across heartbeats
|
|
32
36
|
_last_refresh_time: float = 0.0
|
|
33
37
|
|
|
@@ -88,19 +88,6 @@ from newcode.tools.file_operations import (
|
|
|
88
88
|
register_list_files,
|
|
89
89
|
register_read_file,
|
|
90
90
|
)
|
|
91
|
-
|
|
92
|
-
# Scheduler tools
|
|
93
|
-
from newcode.tools.scheduler_tools import (
|
|
94
|
-
register_scheduler_create_task,
|
|
95
|
-
register_scheduler_daemon_status,
|
|
96
|
-
register_scheduler_delete_task,
|
|
97
|
-
register_scheduler_list_tasks,
|
|
98
|
-
register_scheduler_run_task,
|
|
99
|
-
register_scheduler_start_daemon,
|
|
100
|
-
register_scheduler_stop_daemon,
|
|
101
|
-
register_scheduler_toggle_task,
|
|
102
|
-
register_scheduler_view_log,
|
|
103
|
-
)
|
|
104
91
|
from newcode.tools.skills_tools import (
|
|
105
92
|
register_activate_skill,
|
|
106
93
|
register_list_or_search_skills,
|
|
@@ -188,16 +175,6 @@ TOOL_REGISTRY = {
|
|
|
188
175
|
"list_or_search_skills": register_list_or_search_skills,
|
|
189
176
|
# Universal Constructor
|
|
190
177
|
"universal_constructor": register_universal_constructor,
|
|
191
|
-
# Scheduler Tools
|
|
192
|
-
"scheduler_list_tasks": register_scheduler_list_tasks,
|
|
193
|
-
"scheduler_create_task": register_scheduler_create_task,
|
|
194
|
-
"scheduler_delete_task": register_scheduler_delete_task,
|
|
195
|
-
"scheduler_toggle_task": register_scheduler_toggle_task,
|
|
196
|
-
"scheduler_daemon_status": register_scheduler_daemon_status,
|
|
197
|
-
"scheduler_start_daemon": register_scheduler_start_daemon,
|
|
198
|
-
"scheduler_stop_daemon": register_scheduler_stop_daemon,
|
|
199
|
-
"scheduler_run_task": register_scheduler_run_task,
|
|
200
|
-
"scheduler_view_log": register_scheduler_view_log,
|
|
201
178
|
}
|
|
202
179
|
|
|
203
180
|
|
|
@@ -12,6 +12,7 @@ from typing import Any, Dict, Optional, Union
|
|
|
12
12
|
|
|
13
13
|
from pydantic_ai import BinaryContent, RunContext, ToolReturn
|
|
14
14
|
|
|
15
|
+
from newcode.image_utils import constrain_image_dimensions
|
|
15
16
|
from newcode.messaging import emit_error, emit_info, emit_success
|
|
16
17
|
from newcode.tools.common import generate_group_id
|
|
17
18
|
|
|
@@ -122,13 +123,16 @@ async def take_screenshot(
|
|
|
122
123
|
|
|
123
124
|
screenshot_path = result.get("screenshot_path", "(not saved)")
|
|
124
125
|
|
|
126
|
+
# Constrain dimensions for API compliance (Claude max 2000px for many-image requests)
|
|
127
|
+
constrained_bytes = constrain_image_dimensions(result["screenshot_bytes"])
|
|
128
|
+
|
|
125
129
|
# Return as ToolReturn with BinaryContent so the model can SEE the image!
|
|
126
130
|
return ToolReturn(
|
|
127
131
|
return_value=f"Screenshot captured successfully. Saved to: {screenshot_path}",
|
|
128
132
|
content=[
|
|
129
133
|
f"Here's the browser screenshot ({target}):",
|
|
130
134
|
BinaryContent(
|
|
131
|
-
data=
|
|
135
|
+
data=constrained_bytes,
|
|
132
136
|
media_type="image/png",
|
|
133
137
|
),
|
|
134
138
|
"Please analyze what you see and describe any relevant details.",
|