iac-code 0.4.0__tar.gz → 0.5.0__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.
- {iac_code-0.4.0 → iac_code-0.5.0}/MANIFEST.in +1 -1
- {iac_code-0.4.0 → iac_code-0.5.0}/PKG-INFO +2 -2
- {iac_code-0.4.0 → iac_code-0.5.0}/README.md +2 -2
- {iac_code-0.4.0 → iac_code-0.5.0}/pyproject.toml +1 -0
- iac_code-0.5.0/src/iac_code/__init__.py +2 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/executor.py +18 -2
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/persistence.py +2 -3
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/push.py +1 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/push_queue.py +17 -4
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/push_secrets.py +9 -1
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/task_store.py +7 -6
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/transports/redis_streams.py +8 -1
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/transports/unix.py +18 -2
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/acp/http_sse.py +53 -3
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/acp/server.py +55 -5
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/acp/session.py +26 -2
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/acp/slash_registry.py +11 -8
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/acp/tools.py +39 -4
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/agent/agent_loop.py +483 -15
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/agent/agent_tool.py +80 -25
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/agent/agent_types.py +11 -9
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/agent/message.py +58 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/agent/system_prompt.py +48 -6
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/cli/main.py +6 -0
- iac_code-0.5.0/src/iac_code/cli/update.py +38 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/commands/__init__.py +22 -3
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/commands/auth.py +2 -0
- iac_code-0.5.0/src/iac_code/commands/memory.py +241 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/commands/model.py +25 -10
- iac_code-0.5.0/src/iac_code/commands/prompt.py +693 -0
- iac_code-0.5.0/src/iac_code/commands/status.py +203 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/commands/tasks.py +2 -1
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/config.py +3 -2
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/i18n/__init__.py +13 -1
- iac_code-0.5.0/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.mo +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.po +1335 -696
- iac_code-0.5.0/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.mo +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.po +1338 -696
- iac_code-0.5.0/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.mo +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.po +1341 -696
- iac_code-0.5.0/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.mo +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.po +1305 -696
- iac_code-0.5.0/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.mo +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.po +1338 -696
- iac_code-0.5.0/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.mo +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.po +1303 -696
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/memory/memory_manager.py +24 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/memory/memory_tools.py +17 -11
- iac_code-0.5.0/src/iac_code/memory/project_memory.py +149 -0
- iac_code-0.5.0/src/iac_code/memory/recall.py +452 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/anthropic_provider.py +28 -8
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/base.py +1 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/dashscope_provider.py +21 -2
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/manager.py +101 -29
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/openai_provider.py +12 -1
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/openrouter_provider.py +12 -9
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/agent_factory.py +33 -4
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/context_manager.py +76 -3
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/permissions/loader.py +1 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/permissions/pipeline.py +32 -2
- iac_code-0.5.0/src/iac_code/services/permissions/trusted_roots.py +21 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/providers/aliyun.py +48 -25
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/providers/aliyun_oauth.py +23 -7
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/session_index.py +43 -1
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/telemetry/__init__.py +2 -1
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/telemetry/identity.py +27 -1
- iac_code-0.5.0/src/iac_code/services/token_counter.py +149 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/update_checker.py +8 -3
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/auto_trigger.py +16 -3
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/bundled/__init__.py +4 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/bundled/iac_aliyun/SKILL.md +4 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/bundled/iac_aliyun/auto_trigger.py +10 -0
- iac_code-0.5.0/src/iac_code/skills/bundled/pac_aliyun/SKILL.md +65 -0
- iac_code-0.5.0/src/iac_code/skills/bundled/pac_aliyun/__init__.py +20 -0
- iac_code-0.5.0/src/iac_code/skills/bundled/pac_aliyun/auto_trigger.py +87 -0
- iac_code-0.5.0/src/iac_code/skills/bundled/pac_aliyun/references/infraguard-policy-generation.md +75 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/renderer.py +119 -30
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/skill_tool.py +6 -1
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/state/app_state.py +1 -1
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tasks/task_state.py +20 -5
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tasks/task_tools.py +2 -1
- iac_code-0.5.0/src/iac_code/tools/bash/argv_safety.py +1112 -0
- iac_code-0.5.0/src/iac_code/tools/bash/path_validation.py +249 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/bash/permissions.py +33 -28
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/bash/readonly_commands.py +9 -24
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/bash/safety_checks.py +10 -53
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/cloud/aliyun/ros_stack.py +313 -139
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/cloud/base_stack.py +111 -49
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/cloud/types.py +10 -1
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/edit_file.py +24 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/glob.py +56 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/grep.py +127 -4
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/list_files.py +24 -5
- iac_code-0.5.0/src/iac_code/tools/path_safety.py +218 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/read_file.py +99 -19
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/tool_executor.py +1 -9
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/web_fetch.py +54 -6
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/write_file.py +24 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/types/permissions.py +1 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/types/stream_events.py +9 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/core/input_history.py +5 -5
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/core/prompt_input.py +10 -8
- iac_code-0.5.0/src/iac_code/ui/dialogs/memory.py +150 -0
- iac_code-0.5.0/src/iac_code/ui/dialogs/memory_editor.py +341 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/dialogs/resume_picker.py +9 -7
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/renderer.py +251 -47
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/repl.py +162 -54
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/suggestions/command_provider.py +15 -7
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/suggestions/shell_history_provider.py +36 -2
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/file_security.py +19 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code.egg-info/PKG-INFO +2 -2
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code.egg-info/SOURCES.txt +13 -0
- iac_code-0.4.0/src/iac_code/__init__.py +0 -2
- iac_code-0.4.0/src/iac_code/commands/memory.py +0 -85
- iac_code-0.4.0/src/iac_code/commands/status.py +0 -98
- iac_code-0.4.0/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.mo +0 -0
- iac_code-0.4.0/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.mo +0 -0
- iac_code-0.4.0/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.mo +0 -0
- iac_code-0.4.0/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.mo +0 -0
- iac_code-0.4.0/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.mo +0 -0
- iac_code-0.4.0/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.mo +0 -0
- iac_code-0.4.0/src/iac_code/services/token_counter.py +0 -81
- iac_code-0.4.0/src/iac_code/tools/bash/path_validation.py +0 -118
- {iac_code-0.4.0 → iac_code-0.5.0}/LICENSE +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/setup.cfg +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/setup.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/agent_card.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/app.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/artifacts.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/client.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/events.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/exposure.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/metrics.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/parts.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/push_worker.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/router.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/signing.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/transport.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/transports/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/transports/base.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/transports/dispatcher.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/transports/grpc.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/transports/grpc_jsonrpc.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/transports/http.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/transports/stdio.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/transports/websocket.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/a2a/types.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/acp/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/acp/convert.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/acp/mcp.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/acp/metrics.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/acp/state.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/acp/types.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/acp/version.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/agent/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/cli/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/cli/headless.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/cli/install_git_bash.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/cli/output_formats.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/commands/clear.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/commands/compact.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/commands/debug.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/commands/effort.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/commands/exit.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/commands/help.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/commands/registry.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/commands/rename.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/commands/resume.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/commands/skills.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/memory/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/azure_openai_provider.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/deepseek_provider.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/gemini_provider.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/kimi_provider.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/lmstudio_provider.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/minimax_provider.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/modelscope_provider.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/ollama_provider.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/registry.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/retry.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/siliconflow_provider.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/stream_watchdog.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/thinking.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/volcengine_provider.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/providers/zhipu_provider.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/capabilities/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/capabilities/auto_detect.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/capabilities/multimodal.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/cloud_credentials.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/permissions/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/permissions/storage.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/providers/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/qwenpaw_source.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/session_metadata.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/session_resolver.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/session_storage.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/session_usage.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/telemetry/attributes.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/telemetry/client.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/telemetry/config.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/telemetry/constants.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/telemetry/content_serializer.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/telemetry/events.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/telemetry/fallback.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/telemetry/metrics.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/telemetry/names.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/telemetry/sanitize.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/telemetry/sink.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/telemetry/tracing.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/telemetry/types.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/services/token_budget.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/bundled/iac_aliyun/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/ecs.md +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/oss.md +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/rds.md +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/redis.md +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/slb.md +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/vpc.md +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/bundled/iac_aliyun/references/ros-template.md +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/bundled/iac_aliyun/references/template-parameter-recommendation.md +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/bundled/iac_aliyun/references/template-parameters.md +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/bundled/iac_aliyun/references/terraform-template.md +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/bundled/iac_aliyun/scripts/tf2ros.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/bundled/simplify.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/discovery.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/frontmatter.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/listing.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/loader.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/management.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/processor.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/settings.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/skills/skill_definition.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/state/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tasks/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tasks/notification_queue.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/base.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/bash/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/bash/bash_tool.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/bash/command_parser.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/bash/mode_validation.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/bash/rule_matching.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/cloud/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/cloud/aliyun/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/cloud/aliyun/aliyun_api.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/cloud/aliyun/aliyun_doc_search.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/cloud/aliyun/api_hooks.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/cloud/aliyun/endpoints.yml +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/cloud/aliyun/hooks/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/cloud/aliyun/hooks/ros_parameters.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/cloud/aliyun/ros_client.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/cloud/aliyun/ros_stack_instances.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/cloud/aliyun/ros_yaml.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/cloud/aliyun/user_agent.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/cloud/base_api.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/cloud/registry.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/tools/result_storage.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/types/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/types/skill_source.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/banner.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/components/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/components/dialog.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/components/divider.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/components/fuzzy_picker.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/components/progress_bar.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/components/search_box.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/components/select.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/components/status_icon.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/components/tabs.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/core/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/core/in_place_render.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/core/key_event.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/core/raw_input.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/core/raw_input_win.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/core/screen.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/dialogs/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/dialogs/global_search.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/dialogs/history_search.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/dialogs/model_picker.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/dialogs/quick_open.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/dialogs/skills_picker.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/keybindings/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/keybindings/manager.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/spinner.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/suggestions/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/suggestions/aggregator.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/suggestions/directory_provider.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/suggestions/file_provider.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/suggestions/skill_provider.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/suggestions/token_extractor.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/suggestions/types.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/ui/transcript_view.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/background_housekeeping.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/cleanup.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/console.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/image/__init__.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/image/clipboard.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/image/format_detect.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/image/pasted_content.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/image/processor.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/image/resizer.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/image/store.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/json_utils.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/log.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/platform.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/project_paths.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/signals.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/tool_input_parser.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code/utils/windows_paths.py +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code.egg-info/dependency_links.txt +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code.egg-info/entry_points.txt +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code.egg-info/requires.txt +0 -0
- {iac_code-0.4.0 → iac_code-0.5.0}/src/iac_code.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: iac_code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Your AI-powered Infrastructure as Code assistant
|
|
5
5
|
Classifier: Programming Language :: Python :: 3
|
|
6
6
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -21,7 +21,7 @@ License-File: LICENSE
|
|
|
21
21
|
<img src="website/static/img/logo-with-front.png" alt="iac-code" width="200">
|
|
22
22
|
</p>
|
|
23
23
|
<p align="center">
|
|
24
|
-
<em>AI-powered Infrastructure as Code assistant for Alibaba Cloud
|
|
24
|
+
<em>AI-powered Infrastructure as Code assistant for cloud infrastructure through natural language interaction. Currently supports Alibaba Cloud ROS and Terraform workflows.</em>
|
|
25
25
|
</p>
|
|
26
26
|
<p align="center">
|
|
27
27
|
<a href="https://github.com/aliyun/iac-code/actions/workflows/test.yml"><img src="https://github.com/aliyun/iac-code/actions/workflows/test.yml/badge.svg" alt="Test"></a>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<img src="website/static/img/logo-with-front.png" alt="iac-code" width="200">
|
|
3
3
|
</p>
|
|
4
4
|
<p align="center">
|
|
5
|
-
<em>AI-powered Infrastructure as Code assistant for Alibaba Cloud
|
|
5
|
+
<em>AI-powered Infrastructure as Code assistant for cloud infrastructure through natural language interaction. Currently supports Alibaba Cloud ROS and Terraform workflows.</em>
|
|
6
6
|
</p>
|
|
7
7
|
<p align="center">
|
|
8
8
|
<a href="https://github.com/aliyun/iac-code/actions/workflows/test.yml"><img src="https://github.com/aliyun/iac-code/actions/workflows/test.yml/badge.svg" alt="Test"></a>
|
|
@@ -73,4 +73,4 @@ See the [Contributing Guide](https://aliyun.github.io/iac-code/getting-started/c
|
|
|
73
73
|
|
|
74
74
|
| [DingTalk](https://qr.dingtalk.com/action/joingroup?code=v1,k1,ubm/77U7qRh/STFZUNBP26X4PNg2z6+uhiPcLGtDNfU=&_dt_no_comment=1&origin=11) | [Discord](https://discord.gg/qECFuFBwF) |
|
|
75
75
|
| :----------------------------------------------------------: | :----------------------------------------------------------: |
|
|
76
|
-
| [<img src="website/static/img/qrcode-dingtalk.jpg" width="120" height="120" alt="DingTalk">](https://qr.dingtalk.com/action/joingroup?code=v1,k1,ubm/77U7qRh/STFZUNBP26X4PNg2z6+uhiPcLGtDNfU=&_dt_no_comment=1&origin=11) | [<img src="website/static/img/qrcode-discord.jpg" width="120" height="120" alt="Discord">](https://discord.gg/qECFuFBwF) |
|
|
76
|
+
| [<img src="website/static/img/qrcode-dingtalk.jpg" width="120" height="120" alt="DingTalk">](https://qr.dingtalk.com/action/joingroup?code=v1,k1,ubm/77U7qRh/STFZUNBP26X4PNg2z6+uhiPcLGtDNfU=&_dt_no_comment=1&origin=11) | [<img src="website/static/img/qrcode-discord.jpg" width="120" height="120" alt="Discord">](https://discord.gg/qECFuFBwF) |
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import contextlib
|
|
4
5
|
import logging
|
|
5
6
|
import os
|
|
6
7
|
import uuid
|
|
@@ -27,7 +28,7 @@ from iac_code.a2a.types import (
|
|
|
27
28
|
)
|
|
28
29
|
from iac_code.services.agent_factory import AgentFactoryOptions, create_agent_runtime
|
|
29
30
|
from iac_code.services.session_storage import SessionStorage
|
|
30
|
-
from iac_code.services.telemetry import use_session_id
|
|
31
|
+
from iac_code.services.telemetry import use_session_id, use_user_id
|
|
31
32
|
|
|
32
33
|
logger = logging.getLogger(__name__)
|
|
33
34
|
_CONTEXT_LOCK_ACQUIRE_TIMEOUT_SECONDS = 1
|
|
@@ -87,6 +88,7 @@ class IacCodeA2AExecutor(AgentExecutor):
|
|
|
87
88
|
getattr(context, "message", None), "metadata", None
|
|
88
89
|
)
|
|
89
90
|
cwd = self._resolve_cwd(metadata)
|
|
91
|
+
user_id = self._resolve_user_id(metadata)
|
|
90
92
|
prompt = self._prompt_from_context(context, cwd=cwd)
|
|
91
93
|
except Exception as exc:
|
|
92
94
|
if _is_retryable_executor_error(exc):
|
|
@@ -222,7 +224,8 @@ class IacCodeA2AExecutor(AgentExecutor):
|
|
|
222
224
|
context_id=context_id,
|
|
223
225
|
state=TaskState.TASK_STATE_WORKING,
|
|
224
226
|
)
|
|
225
|
-
|
|
227
|
+
user_id_ctx = use_user_id(user_id) if user_id else contextlib.nullcontext()
|
|
228
|
+
with use_session_id(ctx.session_id), user_id_ctx:
|
|
226
229
|
async for event in runtime.agent_loop.run_streaming(prompt):
|
|
227
230
|
text_chunk = await publish_stream_event(
|
|
228
231
|
event_queue,
|
|
@@ -348,6 +351,19 @@ class IacCodeA2AExecutor(AgentExecutor):
|
|
|
348
351
|
resolved_cwd.mkdir(parents=True, exist_ok=True)
|
|
349
352
|
return str(resolved_cwd)
|
|
350
353
|
|
|
354
|
+
def _resolve_user_id(self, metadata: Any | None) -> str | None:
|
|
355
|
+
if metadata is not None and hasattr(metadata, "DESCRIPTOR"):
|
|
356
|
+
metadata = MessageToDict(metadata, preserving_proto_field_name=False)
|
|
357
|
+
if not isinstance(metadata, Mapping):
|
|
358
|
+
return None
|
|
359
|
+
raw_iac_meta = metadata.get("iac_code")
|
|
360
|
+
if not isinstance(raw_iac_meta, Mapping):
|
|
361
|
+
return None
|
|
362
|
+
raw_user_id = raw_iac_meta.get("user_id")
|
|
363
|
+
if isinstance(raw_user_id, str) and raw_user_id.strip():
|
|
364
|
+
return raw_user_id.strip()
|
|
365
|
+
return None
|
|
366
|
+
|
|
351
367
|
def _prompt_from_context(self, context: RequestContext, *, cwd: str) -> str:
|
|
352
368
|
message = getattr(context, "message", None)
|
|
353
369
|
if not isinstance(message, Message):
|
|
@@ -8,6 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
from typing import cast
|
|
9
9
|
|
|
10
10
|
from iac_code.a2a.types import validate_protocol_id
|
|
11
|
+
from iac_code.utils.file_security import atomic_write_text
|
|
11
12
|
|
|
12
13
|
_INTERRUPTED_RESTORE_STATES = {"submitted", "working", "auth-required"}
|
|
13
14
|
|
|
@@ -175,9 +176,7 @@ class A2APersistenceStore:
|
|
|
175
176
|
|
|
176
177
|
@staticmethod
|
|
177
178
|
def _write_json(path: Path, data: dict[str, object]) -> None:
|
|
178
|
-
|
|
179
|
-
tmp.write_text(json.dumps(data, ensure_ascii=False, sort_keys=True), encoding="utf-8")
|
|
180
|
-
tmp.replace(path)
|
|
179
|
+
atomic_write_text(path, json.dumps(data, ensure_ascii=False, sort_keys=True))
|
|
181
180
|
|
|
182
181
|
@staticmethod
|
|
183
182
|
def _read_json(path: Path) -> dict[str, object] | None:
|
|
@@ -262,6 +262,7 @@ class A2APushNotifier:
|
|
|
262
262
|
config = self.load_config(task_id)
|
|
263
263
|
if config is None:
|
|
264
264
|
return False
|
|
265
|
+
validate_push_callback_url(config.callback_url)
|
|
265
266
|
payload = {"taskId": task_id, "contextId": context_id, "state": state}
|
|
266
267
|
for attempt in range(self._max_attempts):
|
|
267
268
|
try:
|
|
@@ -50,6 +50,7 @@ class A2APushJob:
|
|
|
50
50
|
"configId": self.config_id,
|
|
51
51
|
"url": self.url,
|
|
52
52
|
"payload": self.payload,
|
|
53
|
+
"headers": redact_push_headers(self.headers),
|
|
53
54
|
"attempt": self.attempt,
|
|
54
55
|
"nextAttemptAt": self.next_attempt_at,
|
|
55
56
|
"lastError": self.last_error,
|
|
@@ -137,7 +138,10 @@ class LocalFileA2APushQueue:
|
|
|
137
138
|
current = time.time() if now is None else now
|
|
138
139
|
self._recover_expired_inflight(current)
|
|
139
140
|
for path in sorted(self.pending_dir.glob("*.json")):
|
|
140
|
-
|
|
141
|
+
try:
|
|
142
|
+
job = self._read(path)
|
|
143
|
+
except FileNotFoundError:
|
|
144
|
+
continue
|
|
141
145
|
if job.next_attempt_at > current:
|
|
142
146
|
continue
|
|
143
147
|
target = self.inflight_dir / path.name
|
|
@@ -146,7 +150,10 @@ class LocalFileA2APushQueue:
|
|
|
146
150
|
next_attempt_at=current + self._inflight_timeout_seconds,
|
|
147
151
|
last_error=job.last_error,
|
|
148
152
|
)
|
|
149
|
-
|
|
153
|
+
try:
|
|
154
|
+
path.replace(target)
|
|
155
|
+
except FileNotFoundError:
|
|
156
|
+
continue
|
|
150
157
|
self._write(target, leased)
|
|
151
158
|
return self._read(target)
|
|
152
159
|
return None
|
|
@@ -265,8 +272,14 @@ class RedisStreamsA2APushQueue:
|
|
|
265
272
|
members = await self._redis.zrangebyscore(self._retry_key, "-inf", now, start=0, num=10)
|
|
266
273
|
for member in members:
|
|
267
274
|
encoded = _decode_redis_field(member)
|
|
268
|
-
|
|
269
|
-
|
|
275
|
+
pipe = self._redis.pipeline() if hasattr(self._redis, "pipeline") else None
|
|
276
|
+
if pipe is not None:
|
|
277
|
+
pipe.xadd(self._stream, {"job": encoded})
|
|
278
|
+
pipe.zrem(self._retry_key, member)
|
|
279
|
+
await pipe.execute()
|
|
280
|
+
else:
|
|
281
|
+
await self._redis.xadd(self._stream, {"job": encoded})
|
|
282
|
+
await self._redis.zrem(self._retry_key, member)
|
|
270
283
|
|
|
271
284
|
async def _claim_expired(self) -> A2APushJob | None:
|
|
272
285
|
xautoclaim = getattr(self._redis, "xautoclaim", None)
|
|
@@ -23,6 +23,7 @@ class A2APushSecretKeyring:
|
|
|
23
23
|
self._env_managed = False
|
|
24
24
|
self._active_key_id = ""
|
|
25
25
|
self._keys: dict[str, str] = {}
|
|
26
|
+
self._created_at: dict[str, int] = {}
|
|
26
27
|
|
|
27
28
|
@property
|
|
28
29
|
def active_key_id(self) -> str:
|
|
@@ -55,6 +56,7 @@ class A2APushSecretKeyring:
|
|
|
55
56
|
if key_id in self._keys:
|
|
56
57
|
raise A2APushSecretError(f"A2A push secret encryption key already exists: {key_id}")
|
|
57
58
|
self._keys[key_id] = Fernet.generate_key().decode("ascii")
|
|
59
|
+
self._created_at[key_id] = int(time.time())
|
|
58
60
|
self._active_key_id = key_id
|
|
59
61
|
self._write()
|
|
60
62
|
return key_id
|
|
@@ -74,6 +76,7 @@ class A2APushSecretKeyring:
|
|
|
74
76
|
return
|
|
75
77
|
self._active_key_id = _new_key_id()
|
|
76
78
|
self._keys = {self._active_key_id: Fernet.generate_key().decode("ascii")}
|
|
79
|
+
self._created_at = {self._active_key_id: int(time.time())}
|
|
77
80
|
self._loaded = True
|
|
78
81
|
self._write()
|
|
79
82
|
|
|
@@ -86,6 +89,11 @@ class A2APushSecretKeyring:
|
|
|
86
89
|
for item in keys
|
|
87
90
|
if isinstance(item, dict) and item.get("id") and item.get("fernetKey")
|
|
88
91
|
}
|
|
92
|
+
self._created_at = {
|
|
93
|
+
str(item["id"]): int(item["createdAt"])
|
|
94
|
+
for item in keys
|
|
95
|
+
if isinstance(item, dict) and item.get("id") and item.get("createdAt") is not None
|
|
96
|
+
}
|
|
89
97
|
self._active_key_id = str(data.get("activeKeyId") or "")
|
|
90
98
|
if not self._active_key_id or self._active_key_id not in self._keys:
|
|
91
99
|
raise A2APushSecretError("A2A push secret keyring does not contain its active key")
|
|
@@ -96,7 +104,7 @@ class A2APushSecretKeyring:
|
|
|
96
104
|
data = {
|
|
97
105
|
"activeKeyId": self._active_key_id,
|
|
98
106
|
"keys": [
|
|
99
|
-
{"id": key_id, "fernetKey": key, "createdAt": int(time.time())}
|
|
107
|
+
{"id": key_id, "fernetKey": key, "createdAt": self._created_at.get(key_id, int(time.time()))}
|
|
100
108
|
for key_id, key in sorted(self._keys.items())
|
|
101
109
|
],
|
|
102
110
|
}
|
|
@@ -51,12 +51,13 @@ class A2ATaskStore(TaskStore):
|
|
|
51
51
|
async def save(self, task: Task, context: ServerCallContext | None = None) -> None:
|
|
52
52
|
owner = self._owner(context)
|
|
53
53
|
task_id = validate_protocol_id(task.id)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
async with self._mutation_lock:
|
|
55
|
+
owner_tasks = self._sdk_tasks.setdefault(owner, {})
|
|
56
|
+
previous = owner_tasks.get(task_id)
|
|
57
|
+
if previous is not None:
|
|
58
|
+
self._remove_sdk_task_from_index(owner, task_id, previous.context_id)
|
|
59
|
+
owner_tasks[task_id] = _copy_task(task)
|
|
60
|
+
self._sdk_tasks_by_context.setdefault(owner, {}).setdefault(task.context_id, set()).add(task_id)
|
|
60
61
|
|
|
61
62
|
async def delete(self, task_id: str, context: ServerCallContext | None = None) -> None:
|
|
62
63
|
owner = self._owner(context)
|
|
@@ -97,13 +97,18 @@ class RedisStreamsA2AClient:
|
|
|
97
97
|
|
|
98
98
|
async def _read_responses(self, correlation_id: str) -> AsyncIterator[RedisStreamsMessage]:
|
|
99
99
|
deadline = asyncio.get_running_loop().time() + self._timeout_seconds
|
|
100
|
+
# Start from "0-0" instead of "$" to avoid a race where the server
|
|
101
|
+
# responds before the first xread call. After each batch we advance
|
|
102
|
+
# the cursor to the last entry ID we saw, which is the standard
|
|
103
|
+
# Redis Streams consumer pattern.
|
|
104
|
+
last_id = "0-0"
|
|
100
105
|
consecutive_errors = 0
|
|
101
106
|
max_consecutive_errors = 3
|
|
102
107
|
while asyncio.get_running_loop().time() < deadline:
|
|
103
108
|
try:
|
|
104
109
|
remaining = deadline - asyncio.get_running_loop().time()
|
|
105
110
|
rows = await asyncio.wait_for(
|
|
106
|
-
self._redis.xread({self._response_stream:
|
|
111
|
+
self._redis.xread({self._response_stream: last_id}, count=10, block=100),
|
|
107
112
|
timeout=max(0.001, min(0.2, remaining)),
|
|
108
113
|
)
|
|
109
114
|
consecutive_errors = 0
|
|
@@ -121,6 +126,8 @@ class RedisStreamsA2AClient:
|
|
|
121
126
|
continue
|
|
122
127
|
for _stream, entries in rows:
|
|
123
128
|
for entry_id, fields in entries:
|
|
129
|
+
# Advance cursor past this entry so we don't re-read it.
|
|
130
|
+
last_id = entry_id if isinstance(entry_id, str) else entry_id.decode("utf-8")
|
|
124
131
|
message = parse_redis_entry(entry_id, fields)
|
|
125
132
|
if message.correlation_id == correlation_id:
|
|
126
133
|
yield message
|
|
@@ -2,6 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import contextlib
|
|
5
|
+
import os
|
|
6
|
+
import stat
|
|
5
7
|
from collections.abc import AsyncIterator
|
|
6
8
|
from pathlib import Path
|
|
7
9
|
from typing import Any
|
|
@@ -10,10 +12,19 @@ from iac_code.a2a.transports.dispatcher import A2ARuntimeComponents
|
|
|
10
12
|
from iac_code.a2a.transports.stdio import StdioA2AClient, StdioA2AServer
|
|
11
13
|
|
|
12
14
|
|
|
15
|
+
def _is_socket(path: Path) -> bool:
|
|
16
|
+
try:
|
|
17
|
+
return stat.S_ISSOCK(os.lstat(path).st_mode)
|
|
18
|
+
except OSError:
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
|
|
13
22
|
def validate_socket_path(socket_path: str) -> Path:
|
|
14
23
|
path = Path(socket_path)
|
|
15
24
|
if not path.parent.exists():
|
|
16
25
|
raise ValueError(f"Unix socket parent does not exist: {path.parent}")
|
|
26
|
+
if path.exists() and not _is_socket(path):
|
|
27
|
+
raise ValueError(f"Path exists and is not a Unix socket: {path}")
|
|
17
28
|
return path
|
|
18
29
|
|
|
19
30
|
|
|
@@ -22,11 +33,15 @@ class UnixA2AServer:
|
|
|
22
33
|
self._components = components
|
|
23
34
|
self._socket_path = validate_socket_path(socket_path)
|
|
24
35
|
self._server: asyncio.AbstractServer | None = None
|
|
36
|
+
self._owns_socket = False
|
|
25
37
|
|
|
26
38
|
async def serve(self) -> None:
|
|
27
39
|
if self._socket_path.exists():
|
|
40
|
+
if not _is_socket(self._socket_path):
|
|
41
|
+
raise ValueError(f"Path exists and is not a Unix socket: {self._socket_path}")
|
|
28
42
|
self._socket_path.unlink()
|
|
29
43
|
self._server = await asyncio.start_unix_server(self._handle_client, path=str(self._socket_path))
|
|
44
|
+
self._owns_socket = True
|
|
30
45
|
async with self._server:
|
|
31
46
|
await self._server.serve_forever()
|
|
32
47
|
|
|
@@ -43,8 +58,9 @@ class UnixA2AServer:
|
|
|
43
58
|
if self._server is not None:
|
|
44
59
|
self._server.close()
|
|
45
60
|
await self._server.wait_closed()
|
|
46
|
-
|
|
47
|
-
|
|
61
|
+
if self._owns_socket:
|
|
62
|
+
with contextlib.suppress(FileNotFoundError):
|
|
63
|
+
self._socket_path.unlink()
|
|
48
64
|
await self._components.aclose()
|
|
49
65
|
|
|
50
66
|
|
|
@@ -247,9 +247,39 @@ class HTTPConnectionBridge:
|
|
|
247
247
|
self._initialized.set()
|
|
248
248
|
else:
|
|
249
249
|
try:
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
# Apply backpressure: block until space is available
|
|
251
|
+
# (up to a timeout) rather than silently discarding.
|
|
252
|
+
await asyncio.wait_for(self._sse_queue.put(message), timeout=30.0)
|
|
253
|
+
except asyncio.TimeoutError:
|
|
254
|
+
# Client is not consuming events within a reasonable
|
|
255
|
+
# window — send an error notification and tear down.
|
|
256
|
+
logger.error(
|
|
257
|
+
"SSE queue full for connection %s: client not consuming events, closing",
|
|
258
|
+
self.connection_id[:8],
|
|
259
|
+
)
|
|
260
|
+
error_event = json.dumps(
|
|
261
|
+
{
|
|
262
|
+
"jsonrpc": "2.0",
|
|
263
|
+
"method": "notifications/cancelled",
|
|
264
|
+
"params": {
|
|
265
|
+
"reason": "SSE queue full: client is not consuming events",
|
|
266
|
+
},
|
|
267
|
+
}
|
|
268
|
+
)
|
|
269
|
+
# Make room by discarding the oldest pending event so
|
|
270
|
+
# the error notification reaches the client.
|
|
271
|
+
try:
|
|
272
|
+
self._sse_queue.get_nowait()
|
|
273
|
+
except asyncio.QueueEmpty:
|
|
274
|
+
pass
|
|
275
|
+
try:
|
|
276
|
+
self._sse_queue.put_nowait(error_event)
|
|
277
|
+
except asyncio.QueueFull:
|
|
278
|
+
pass
|
|
279
|
+
# Break out of the read loop — the finally block will
|
|
280
|
+
# send the sentinel and trigger connection teardown.
|
|
281
|
+
fatal_error = True
|
|
282
|
+
break
|
|
253
283
|
except asyncio.CancelledError:
|
|
254
284
|
raise
|
|
255
285
|
except Exception:
|
|
@@ -290,11 +320,31 @@ _connections: dict[str, HTTPConnectionBridge] = {}
|
|
|
290
320
|
|
|
291
321
|
_ACP_CONN_HEADER = "Acp-Connection-Id"
|
|
292
322
|
_INIT_TIMEOUT = 30.0
|
|
323
|
+
# Maximum allowed request body size (4 MiB). Requests exceeding this limit
|
|
324
|
+
# are rejected with 413 to prevent unbounded memory consumption.
|
|
325
|
+
_MAX_BODY_BYTES = 4 * 1024 * 1024
|
|
293
326
|
|
|
294
327
|
|
|
295
328
|
async def _handle_post(request: Request) -> Response:
|
|
296
329
|
"""Handle ``POST /acp`` — JSON-RPC requests from the client."""
|
|
330
|
+
# Enforce body size limit to prevent unbounded memory consumption.
|
|
331
|
+
content_length = request.headers.get("content-length")
|
|
332
|
+
if content_length is not None:
|
|
333
|
+
try:
|
|
334
|
+
if int(content_length) > _MAX_BODY_BYTES:
|
|
335
|
+
return JSONResponse(
|
|
336
|
+
{"error": "Request body too large"},
|
|
337
|
+
status_code=413,
|
|
338
|
+
)
|
|
339
|
+
except (ValueError, OverflowError):
|
|
340
|
+
pass
|
|
341
|
+
|
|
297
342
|
body = await request.body()
|
|
343
|
+
if len(body) > _MAX_BODY_BYTES:
|
|
344
|
+
return JSONResponse(
|
|
345
|
+
{"error": "Request body too large"},
|
|
346
|
+
status_code=413,
|
|
347
|
+
)
|
|
298
348
|
message = body.decode("utf-8")
|
|
299
349
|
|
|
300
350
|
try:
|
|
@@ -3,13 +3,17 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import contextlib
|
|
5
5
|
import logging
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
6
8
|
import time
|
|
7
9
|
import uuid
|
|
10
|
+
from pathlib import Path
|
|
8
11
|
from typing import Any
|
|
9
12
|
|
|
10
13
|
import acp
|
|
11
14
|
|
|
12
15
|
from iac_code import __version__
|
|
16
|
+
from iac_code.a2a.parts import allowed_cwd_roots, is_relative_to
|
|
13
17
|
from iac_code.acp.metrics import ACPMetrics
|
|
14
18
|
from iac_code.acp.session import ACPSession, Message, _is_auth_error
|
|
15
19
|
from iac_code.acp.slash_registry import ACP_SUPPORTED_COMMANDS
|
|
@@ -23,7 +27,11 @@ from iac_code.services.agent_factory import AgentFactoryOptions, create_agent_ru
|
|
|
23
27
|
from iac_code.services.session_index import SessionEntry, SessionIndex
|
|
24
28
|
from iac_code.services.session_resolver import ResolutionStatus, resolve_session_argument
|
|
25
29
|
from iac_code.services.session_storage import SessionStorage
|
|
26
|
-
from iac_code.utils.project_paths import
|
|
30
|
+
from iac_code.utils.project_paths import (
|
|
31
|
+
_looks_like_windows_path,
|
|
32
|
+
format_resume_command,
|
|
33
|
+
same_project_path,
|
|
34
|
+
)
|
|
27
35
|
|
|
28
36
|
SESSION_IDLE_TIMEOUT = 3600 # 1 hour
|
|
29
37
|
CLEANUP_INTERVAL = 300 # 5 minutes
|
|
@@ -31,6 +39,10 @@ CLEANUP_INTERVAL = 300 # 5 minutes
|
|
|
31
39
|
logger = logging.getLogger(__name__)
|
|
32
40
|
|
|
33
41
|
|
|
42
|
+
def _runtime_command_memory_manager(runtime: object) -> object | None:
|
|
43
|
+
return getattr(runtime, "legacy_memory_manager", None) or getattr(runtime, "memory_manager", None)
|
|
44
|
+
|
|
45
|
+
|
|
34
46
|
class ACPServer:
|
|
35
47
|
def __init__(self) -> None:
|
|
36
48
|
self.conn: acp.Client | None = None
|
|
@@ -101,6 +113,36 @@ class ACPServer:
|
|
|
101
113
|
raise acp.RequestError.invalid_params({"session_id": "Session not found"})
|
|
102
114
|
return session
|
|
103
115
|
|
|
116
|
+
@staticmethod
|
|
117
|
+
def _validate_cwd(cwd: str) -> str:
|
|
118
|
+
"""Validate the working directory path.
|
|
119
|
+
|
|
120
|
+
Ensures the path is absolute and falls within one of the allowed CWD
|
|
121
|
+
roots (same policy as the A2A executor). Returns the original *cwd*
|
|
122
|
+
string on success (not resolved) so that session storage lookups
|
|
123
|
+
remain consistent. Raises :class:`acp.RequestError` on failure.
|
|
124
|
+
"""
|
|
125
|
+
if not cwd:
|
|
126
|
+
raise acp.RequestError.invalid_params({"cwd": "cwd must be an absolute path"})
|
|
127
|
+
# Accept both POSIX and Windows absolute paths. ``os.path.isabs``
|
|
128
|
+
# treats rooted POSIX-style paths (``/tmp``) as absolute on Windows
|
|
129
|
+
# too, while ``_looks_like_windows_path`` catches drive-letter forms
|
|
130
|
+
# (``C:\foo``) on POSIX hosts.
|
|
131
|
+
is_windows_path = _looks_like_windows_path(cwd)
|
|
132
|
+
if not os.path.isabs(cwd) and not is_windows_path:
|
|
133
|
+
raise acp.RequestError.invalid_params({"cwd": "cwd must be an absolute path"})
|
|
134
|
+
# When a Windows-style path is received on a non-Windows host, we
|
|
135
|
+
# cannot resolve it against the local filesystem; skip the
|
|
136
|
+
# root-containment and is_dir checks for that case only.
|
|
137
|
+
if is_windows_path and sys.platform != "win32":
|
|
138
|
+
return cwd
|
|
139
|
+
resolved = Path(cwd).resolve()
|
|
140
|
+
if not any(is_relative_to(resolved, root) for root in allowed_cwd_roots()):
|
|
141
|
+
raise acp.RequestError.invalid_params({"cwd": "cwd is outside allowed roots"})
|
|
142
|
+
if resolved.exists() and not resolved.is_dir():
|
|
143
|
+
raise acp.RequestError.invalid_params({"cwd": "cwd is not a directory"})
|
|
144
|
+
return cwd
|
|
145
|
+
|
|
104
146
|
async def initialize(
|
|
105
147
|
self,
|
|
106
148
|
protocol_version: int,
|
|
@@ -144,6 +186,8 @@ class ACPServer:
|
|
|
144
186
|
if self.conn is None:
|
|
145
187
|
raise acp.RequestError.internal_error({"error": "ACP client not connected"})
|
|
146
188
|
|
|
189
|
+
cwd = self._validate_cwd(cwd)
|
|
190
|
+
|
|
147
191
|
# Convert MCP server configs from ACP protocol types to internal dicts
|
|
148
192
|
mcp_configs = _convert_mcp_servers(mcp_servers)
|
|
149
193
|
|
|
@@ -161,7 +205,7 @@ class ACPServer:
|
|
|
161
205
|
self.conn,
|
|
162
206
|
mcp_configs=mcp_configs,
|
|
163
207
|
metrics=self.metrics,
|
|
164
|
-
memory_manager=
|
|
208
|
+
memory_manager=_runtime_command_memory_manager(runtime),
|
|
165
209
|
)
|
|
166
210
|
self.sessions[session.id] = session
|
|
167
211
|
self.metrics.record_session_created()
|
|
@@ -268,6 +312,8 @@ class ACPServer:
|
|
|
268
312
|
if self.conn is None:
|
|
269
313
|
raise acp.RequestError.internal_error({"error": "ACP client not connected"})
|
|
270
314
|
|
|
315
|
+
cwd = self._validate_cwd(cwd)
|
|
316
|
+
|
|
271
317
|
# 1. Already active in memory — return immediately
|
|
272
318
|
if session_id in self.sessions:
|
|
273
319
|
model = load_saved_model() or DEFAULT_MODEL
|
|
@@ -303,7 +349,7 @@ class ACPServer:
|
|
|
303
349
|
self.conn,
|
|
304
350
|
mcp_configs=mcp_configs,
|
|
305
351
|
metrics=self.metrics,
|
|
306
|
-
memory_manager=
|
|
352
|
+
memory_manager=_runtime_command_memory_manager(runtime),
|
|
307
353
|
)
|
|
308
354
|
self.sessions[session_id] = session
|
|
309
355
|
self.metrics.record_session_created()
|
|
@@ -334,6 +380,8 @@ class ACPServer:
|
|
|
334
380
|
if self.conn is None:
|
|
335
381
|
raise acp.RequestError.internal_error({"error": "ACP client not connected"})
|
|
336
382
|
|
|
383
|
+
cwd = self._validate_cwd(cwd)
|
|
384
|
+
|
|
337
385
|
# 1. Collect history from the source session
|
|
338
386
|
history: list[Message] = []
|
|
339
387
|
if session_id in self.sessions:
|
|
@@ -372,7 +420,7 @@ class ACPServer:
|
|
|
372
420
|
self.conn,
|
|
373
421
|
mcp_configs=mcp_configs,
|
|
374
422
|
metrics=self.metrics,
|
|
375
|
-
memory_manager=
|
|
423
|
+
memory_manager=_runtime_command_memory_manager(runtime),
|
|
376
424
|
)
|
|
377
425
|
self.sessions[new_session_id] = session
|
|
378
426
|
self.metrics.record_session_created()
|
|
@@ -396,6 +444,8 @@ class ACPServer:
|
|
|
396
444
|
mcp_servers: list[MCPServer] | None = None,
|
|
397
445
|
**kwargs: Any,
|
|
398
446
|
) -> acp.schema.ResumeSessionResponse:
|
|
447
|
+
cwd = self._validate_cwd(cwd)
|
|
448
|
+
|
|
399
449
|
# 1. If session is still active in memory by exact id, enforce project ownership before returning.
|
|
400
450
|
active_session = self.sessions.get(session_id)
|
|
401
451
|
if active_session is not None:
|
|
@@ -482,7 +532,7 @@ class ACPServer:
|
|
|
482
532
|
self.conn,
|
|
483
533
|
mcp_configs=mcp_configs,
|
|
484
534
|
metrics=self.metrics,
|
|
485
|
-
memory_manager=
|
|
535
|
+
memory_manager=_runtime_command_memory_manager(runtime),
|
|
486
536
|
)
|
|
487
537
|
self.sessions[resolved_session_id] = session
|
|
488
538
|
self.metrics.record_session_created()
|
|
@@ -18,7 +18,14 @@ from iac_code.acp.slash_registry import ACPSlashRegistry
|
|
|
18
18
|
from iac_code.acp.state import TurnState
|
|
19
19
|
from iac_code.acp.tools import ACPTerminalBashTool
|
|
20
20
|
from iac_code.acp.types import ACPContentBlock
|
|
21
|
-
from iac_code.agent.message import
|
|
21
|
+
from iac_code.agent.message import (
|
|
22
|
+
Message,
|
|
23
|
+
TextBlock,
|
|
24
|
+
ThinkingBlock,
|
|
25
|
+
ToolResultBlock,
|
|
26
|
+
ToolUseBlock,
|
|
27
|
+
is_recalled_memory_message,
|
|
28
|
+
)
|
|
22
29
|
from iac_code.services.telemetry import use_session_id
|
|
23
30
|
from iac_code.state.app_state import lookup_permission, record_permission
|
|
24
31
|
from iac_code.types.permissions import PermissionDecision
|
|
@@ -64,6 +71,9 @@ def _history_message_to_updates(msg: Message) -> list[Any]:
|
|
|
64
71
|
``ToolCallProgress``.
|
|
65
72
|
* **user** tool-result blocks are emitted as completed ``ToolCallProgress``.
|
|
66
73
|
"""
|
|
74
|
+
if is_recalled_memory_message(msg):
|
|
75
|
+
return []
|
|
76
|
+
|
|
67
77
|
updates: list[Any] = []
|
|
68
78
|
content = msg.content
|
|
69
79
|
|
|
@@ -157,6 +167,20 @@ _PREFIX_ALLOW_RULE = "allow_rule:"
|
|
|
157
167
|
_PREFIX_DENY_RULE = "deny_rule:"
|
|
158
168
|
|
|
159
169
|
|
|
170
|
+
def _tool_supports_blanket_allow(agent_loop, tool_name: str) -> bool:
|
|
171
|
+
"""Return False only when the registered tool explicitly disables blanket allow."""
|
|
172
|
+
registry = getattr(agent_loop, "tool_registry", None)
|
|
173
|
+
get_tool = getattr(registry, "get", None)
|
|
174
|
+
if get_tool is None:
|
|
175
|
+
return True
|
|
176
|
+
|
|
177
|
+
tool = get_tool(tool_name)
|
|
178
|
+
if tool is None:
|
|
179
|
+
return True
|
|
180
|
+
|
|
181
|
+
return bool(getattr(tool, "supports_blanket_allow", True))
|
|
182
|
+
|
|
183
|
+
|
|
160
184
|
class ACPSession:
|
|
161
185
|
def __init__(
|
|
162
186
|
self,
|
|
@@ -457,7 +481,7 @@ class ACPSession:
|
|
|
457
481
|
kind="allow_always",
|
|
458
482
|
)
|
|
459
483
|
)
|
|
460
|
-
|
|
484
|
+
elif _tool_supports_blanket_allow(self.agent_loop, tool_name):
|
|
461
485
|
options.append(
|
|
462
486
|
acp.schema.PermissionOption(
|
|
463
487
|
option_id=_OPTION_ALLOW_ALWAYS,
|