iac-code 0.3.1__tar.gz → 0.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (310) hide show
  1. {iac_code-0.3.1 → iac_code-0.4.1}/PKG-INFO +1 -1
  2. iac_code-0.4.1/src/iac_code/__init__.py +2 -0
  3. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/executor.py +18 -2
  4. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/acp/server.py +135 -28
  5. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/acp/session.py +22 -2
  6. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/acp/slash_registry.py +41 -2
  7. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/acp/tools.py +39 -4
  8. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/agent/agent_loop.py +121 -8
  9. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/agent/agent_tool.py +79 -25
  10. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/agent/agent_types.py +11 -9
  11. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/cli/main.py +7 -1
  12. iac_code-0.4.1/src/iac_code/cli/update.py +38 -0
  13. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/commands/__init__.py +40 -2
  14. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/commands/auth.py +242 -8
  15. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/commands/clear.py +11 -1
  16. iac_code-0.4.1/src/iac_code/commands/memory.py +85 -0
  17. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/commands/model.py +25 -10
  18. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/commands/registry.py +18 -0
  19. iac_code-0.4.1/src/iac_code/commands/rename.py +43 -0
  20. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/commands/resume.py +25 -4
  21. iac_code-0.4.1/src/iac_code/commands/skills.py +29 -0
  22. iac_code-0.4.1/src/iac_code/commands/status.py +104 -0
  23. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/commands/tasks.py +2 -1
  24. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/config.py +3 -2
  25. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/i18n/__init__.py +13 -1
  26. iac_code-0.4.1/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.mo +0 -0
  27. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.po +1304 -568
  28. iac_code-0.4.1/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.mo +0 -0
  29. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.po +1302 -568
  30. iac_code-0.4.1/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.mo +0 -0
  31. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.po +1306 -568
  32. iac_code-0.4.1/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.mo +0 -0
  33. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.po +1276 -568
  34. iac_code-0.4.1/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.mo +0 -0
  35. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.po +1300 -568
  36. iac_code-0.4.1/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.mo +0 -0
  37. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.po +1269 -568
  38. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/memory/memory_manager.py +71 -16
  39. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/memory/memory_tools.py +8 -3
  40. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/manager.py +104 -27
  41. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/openai_provider.py +9 -0
  42. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/openrouter_provider.py +12 -9
  43. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/registry.py +1 -0
  44. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/thinking.py +1 -0
  45. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/agent_factory.py +8 -3
  46. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/context_manager.py +51 -3
  47. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/permissions/loader.py +1 -0
  48. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/permissions/pipeline.py +32 -2
  49. iac_code-0.4.1/src/iac_code/services/permissions/trusted_roots.py +21 -0
  50. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/providers/aliyun.py +131 -6
  51. iac_code-0.4.1/src/iac_code/services/providers/aliyun_oauth.py +599 -0
  52. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/session_index.py +74 -25
  53. iac_code-0.4.1/src/iac_code/services/session_metadata.py +78 -0
  54. iac_code-0.4.1/src/iac_code/services/session_resolver.py +73 -0
  55. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/session_storage.py +116 -5
  56. iac_code-0.4.1/src/iac_code/services/session_usage.py +176 -0
  57. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/telemetry/__init__.py +2 -1
  58. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/telemetry/identity.py +27 -1
  59. iac_code-0.4.1/src/iac_code/services/token_counter.py +149 -0
  60. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/update_checker.py +8 -3
  61. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/bundled/iac_aliyun/SKILL.md +9 -0
  62. iac_code-0.4.1/src/iac_code/skills/bundled/iac_aliyun/references/template-parameter-recommendation.md +165 -0
  63. iac_code-0.4.1/src/iac_code/skills/management.py +81 -0
  64. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/renderer.py +119 -30
  65. iac_code-0.4.1/src/iac_code/skills/settings.py +61 -0
  66. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/skill_tool.py +16 -1
  67. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tasks/task_state.py +20 -5
  68. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tasks/task_tools.py +2 -1
  69. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/base.py +4 -0
  70. iac_code-0.4.1/src/iac_code/tools/bash/argv_safety.py +1112 -0
  71. iac_code-0.4.1/src/iac_code/tools/bash/path_validation.py +249 -0
  72. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/bash/permissions.py +33 -28
  73. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/bash/readonly_commands.py +9 -24
  74. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/bash/safety_checks.py +10 -53
  75. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/cloud/aliyun/aliyun_api.py +18 -2
  76. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/cloud/aliyun/ros_client.py +9 -2
  77. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/cloud/aliyun/ros_stack.py +313 -139
  78. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/cloud/aliyun/ros_stack_instances.py +3 -2
  79. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/cloud/base_stack.py +111 -49
  80. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/cloud/registry.py +10 -0
  81. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/cloud/types.py +10 -1
  82. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/edit_file.py +24 -0
  83. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/glob.py +56 -0
  84. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/grep.py +127 -4
  85. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/list_files.py +24 -5
  86. iac_code-0.4.1/src/iac_code/tools/path_safety.py +218 -0
  87. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/read_file.py +99 -19
  88. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/tool_executor.py +1 -9
  89. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/web_fetch.py +54 -6
  90. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/write_file.py +24 -0
  91. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/types/permissions.py +1 -0
  92. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/types/stream_events.py +9 -0
  93. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/banner.py +15 -2
  94. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/core/input_history.py +5 -5
  95. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/core/prompt_input.py +10 -8
  96. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/dialogs/resume_picker.py +31 -8
  97. iac_code-0.4.1/src/iac_code/ui/dialogs/skills_picker.py +297 -0
  98. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/renderer.py +246 -46
  99. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/repl.py +378 -101
  100. iac_code-0.4.1/src/iac_code/ui/suggestions/command_provider.py +135 -0
  101. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/suggestions/shell_history_provider.py +36 -2
  102. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/suggestions/token_extractor.py +20 -0
  103. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/file_security.py +19 -0
  104. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/project_paths.py +44 -0
  105. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code.egg-info/PKG-INFO +1 -1
  106. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code.egg-info/SOURCES.txt +16 -0
  107. iac_code-0.3.1/src/iac_code/__init__.py +0 -2
  108. iac_code-0.3.1/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.mo +0 -0
  109. iac_code-0.3.1/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.mo +0 -0
  110. iac_code-0.3.1/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.mo +0 -0
  111. iac_code-0.3.1/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.mo +0 -0
  112. iac_code-0.3.1/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.mo +0 -0
  113. iac_code-0.3.1/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.mo +0 -0
  114. iac_code-0.3.1/src/iac_code/services/token_counter.py +0 -81
  115. iac_code-0.3.1/src/iac_code/tools/bash/path_validation.py +0 -118
  116. iac_code-0.3.1/src/iac_code/ui/suggestions/command_provider.py +0 -43
  117. {iac_code-0.3.1 → iac_code-0.4.1}/LICENSE +0 -0
  118. {iac_code-0.3.1 → iac_code-0.4.1}/MANIFEST.in +0 -0
  119. {iac_code-0.3.1 → iac_code-0.4.1}/README.md +0 -0
  120. {iac_code-0.3.1 → iac_code-0.4.1}/pyproject.toml +0 -0
  121. {iac_code-0.3.1 → iac_code-0.4.1}/setup.cfg +0 -0
  122. {iac_code-0.3.1 → iac_code-0.4.1}/setup.py +0 -0
  123. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/__init__.py +0 -0
  124. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/agent_card.py +0 -0
  125. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/app.py +0 -0
  126. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/artifacts.py +0 -0
  127. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/client.py +0 -0
  128. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/events.py +0 -0
  129. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/exposure.py +0 -0
  130. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/metrics.py +0 -0
  131. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/parts.py +0 -0
  132. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/persistence.py +0 -0
  133. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/push.py +0 -0
  134. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/push_queue.py +0 -0
  135. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/push_secrets.py +0 -0
  136. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/push_worker.py +0 -0
  137. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/router.py +0 -0
  138. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/signing.py +0 -0
  139. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/task_store.py +0 -0
  140. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/transport.py +0 -0
  141. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/transports/__init__.py +0 -0
  142. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/transports/base.py +0 -0
  143. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/transports/dispatcher.py +0 -0
  144. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/transports/grpc.py +0 -0
  145. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/transports/grpc_jsonrpc.py +0 -0
  146. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/transports/http.py +0 -0
  147. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/transports/redis_streams.py +0 -0
  148. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/transports/stdio.py +0 -0
  149. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/transports/unix.py +0 -0
  150. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/transports/websocket.py +0 -0
  151. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/a2a/types.py +0 -0
  152. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/acp/__init__.py +0 -0
  153. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/acp/convert.py +0 -0
  154. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/acp/http_sse.py +0 -0
  155. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/acp/mcp.py +0 -0
  156. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/acp/metrics.py +0 -0
  157. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/acp/state.py +0 -0
  158. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/acp/types.py +0 -0
  159. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/acp/version.py +0 -0
  160. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/agent/__init__.py +0 -0
  161. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/agent/message.py +0 -0
  162. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/agent/system_prompt.py +0 -0
  163. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/cli/__init__.py +0 -0
  164. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/cli/headless.py +0 -0
  165. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/cli/install_git_bash.py +0 -0
  166. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/cli/output_formats.py +0 -0
  167. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/commands/compact.py +0 -0
  168. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/commands/debug.py +0 -0
  169. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/commands/effort.py +0 -0
  170. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/commands/exit.py +0 -0
  171. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/commands/help.py +0 -0
  172. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/memory/__init__.py +0 -0
  173. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/__init__.py +0 -0
  174. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/anthropic_provider.py +0 -0
  175. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/azure_openai_provider.py +0 -0
  176. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/base.py +0 -0
  177. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/dashscope_provider.py +0 -0
  178. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/deepseek_provider.py +0 -0
  179. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/gemini_provider.py +0 -0
  180. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/kimi_provider.py +0 -0
  181. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/lmstudio_provider.py +0 -0
  182. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/minimax_provider.py +0 -0
  183. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/modelscope_provider.py +0 -0
  184. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/ollama_provider.py +0 -0
  185. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/retry.py +0 -0
  186. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/siliconflow_provider.py +0 -0
  187. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/stream_watchdog.py +0 -0
  188. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/volcengine_provider.py +0 -0
  189. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/providers/zhipu_provider.py +0 -0
  190. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/__init__.py +0 -0
  191. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/capabilities/__init__.py +0 -0
  192. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/capabilities/auto_detect.py +0 -0
  193. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/capabilities/multimodal.py +0 -0
  194. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/cloud_credentials.py +0 -0
  195. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/permissions/__init__.py +0 -0
  196. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/permissions/storage.py +0 -0
  197. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/providers/__init__.py +0 -0
  198. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/qwenpaw_source.py +0 -0
  199. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/telemetry/attributes.py +0 -0
  200. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/telemetry/client.py +0 -0
  201. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/telemetry/config.py +0 -0
  202. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/telemetry/constants.py +0 -0
  203. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/telemetry/content_serializer.py +0 -0
  204. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/telemetry/events.py +0 -0
  205. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/telemetry/fallback.py +0 -0
  206. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/telemetry/metrics.py +0 -0
  207. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/telemetry/names.py +0 -0
  208. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/telemetry/sanitize.py +0 -0
  209. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/telemetry/sink.py +0 -0
  210. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/telemetry/tracing.py +0 -0
  211. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/telemetry/types.py +0 -0
  212. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/services/token_budget.py +0 -0
  213. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/__init__.py +0 -0
  214. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/auto_trigger.py +0 -0
  215. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/bundled/__init__.py +0 -0
  216. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/bundled/iac_aliyun/__init__.py +0 -0
  217. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/bundled/iac_aliyun/auto_trigger.py +0 -0
  218. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/ecs.md +0 -0
  219. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/oss.md +0 -0
  220. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/rds.md +0 -0
  221. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/redis.md +0 -0
  222. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/slb.md +0 -0
  223. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/vpc.md +0 -0
  224. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/bundled/iac_aliyun/references/ros-template.md +0 -0
  225. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/bundled/iac_aliyun/references/template-parameters.md +0 -0
  226. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/bundled/iac_aliyun/references/terraform-template.md +0 -0
  227. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/bundled/iac_aliyun/scripts/tf2ros.py +0 -0
  228. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/bundled/simplify.py +0 -0
  229. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/discovery.py +0 -0
  230. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/frontmatter.py +0 -0
  231. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/listing.py +0 -0
  232. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/loader.py +0 -0
  233. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/processor.py +0 -0
  234. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/skills/skill_definition.py +0 -0
  235. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/state/__init__.py +0 -0
  236. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/state/app_state.py +0 -0
  237. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tasks/__init__.py +0 -0
  238. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tasks/notification_queue.py +0 -0
  239. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/__init__.py +0 -0
  240. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/bash/__init__.py +0 -0
  241. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/bash/bash_tool.py +0 -0
  242. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/bash/command_parser.py +0 -0
  243. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/bash/mode_validation.py +0 -0
  244. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/bash/rule_matching.py +0 -0
  245. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/cloud/__init__.py +0 -0
  246. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/cloud/aliyun/__init__.py +0 -0
  247. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/cloud/aliyun/aliyun_doc_search.py +0 -0
  248. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/cloud/aliyun/api_hooks.py +0 -0
  249. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/cloud/aliyun/endpoints.yml +0 -0
  250. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/cloud/aliyun/hooks/__init__.py +0 -0
  251. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/cloud/aliyun/hooks/ros_parameters.py +0 -0
  252. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py +0 -0
  253. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/cloud/aliyun/ros_yaml.py +0 -0
  254. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/cloud/aliyun/user_agent.py +0 -0
  255. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/cloud/base_api.py +0 -0
  256. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/tools/result_storage.py +0 -0
  257. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/types/__init__.py +0 -0
  258. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/types/skill_source.py +0 -0
  259. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/__init__.py +0 -0
  260. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/components/__init__.py +0 -0
  261. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/components/dialog.py +0 -0
  262. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/components/divider.py +0 -0
  263. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/components/fuzzy_picker.py +0 -0
  264. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/components/progress_bar.py +0 -0
  265. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/components/search_box.py +0 -0
  266. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/components/select.py +0 -0
  267. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/components/status_icon.py +0 -0
  268. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/components/tabs.py +0 -0
  269. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/core/__init__.py +0 -0
  270. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/core/in_place_render.py +0 -0
  271. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/core/key_event.py +0 -0
  272. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/core/raw_input.py +0 -0
  273. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/core/raw_input_win.py +0 -0
  274. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/core/screen.py +0 -0
  275. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/dialogs/__init__.py +0 -0
  276. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/dialogs/global_search.py +0 -0
  277. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/dialogs/history_search.py +0 -0
  278. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/dialogs/model_picker.py +0 -0
  279. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/dialogs/quick_open.py +0 -0
  280. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/keybindings/__init__.py +0 -0
  281. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/keybindings/manager.py +0 -0
  282. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/spinner.py +0 -0
  283. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/suggestions/__init__.py +0 -0
  284. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/suggestions/aggregator.py +0 -0
  285. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/suggestions/directory_provider.py +0 -0
  286. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/suggestions/file_provider.py +0 -0
  287. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/suggestions/skill_provider.py +0 -0
  288. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/suggestions/types.py +0 -0
  289. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/ui/transcript_view.py +0 -0
  290. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/__init__.py +0 -0
  291. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/background_housekeeping.py +0 -0
  292. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/cleanup.py +0 -0
  293. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/console.py +0 -0
  294. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/image/__init__.py +0 -0
  295. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/image/clipboard.py +0 -0
  296. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/image/format_detect.py +0 -0
  297. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/image/pasted_content.py +0 -0
  298. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/image/processor.py +0 -0
  299. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/image/resizer.py +0 -0
  300. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/image/store.py +0 -0
  301. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/json_utils.py +0 -0
  302. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/log.py +0 -0
  303. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/platform.py +0 -0
  304. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/signals.py +0 -0
  305. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/tool_input_parser.py +0 -0
  306. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code/utils/windows_paths.py +0 -0
  307. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code.egg-info/dependency_links.txt +0 -0
  308. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code.egg-info/entry_points.txt +0 -0
  309. {iac_code-0.3.1 → iac_code-0.4.1}/src/iac_code.egg-info/requires.txt +0 -0
  310. {iac_code-0.3.1 → iac_code-0.4.1}/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.1
3
+ Version: 0.4.1
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
@@ -0,0 +1,2 @@
1
+ __version__ = "0.4.1"
2
+ __release_date__ = "2026-06-05"
@@ -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
- with use_session_id(ctx.session_id):
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):
@@ -18,8 +18,12 @@ from iac_code.acp.types import ACPContentBlock, MCPServer
18
18
  from iac_code.acp.version import negotiate_version
19
19
  from iac_code.commands import LocalCommand, create_default_registry
20
20
  from iac_code.config import DEFAULT_MODEL, get_active_provider_key, load_saved_model
21
+ from iac_code.i18n import _
21
22
  from iac_code.services.agent_factory import AgentFactoryOptions, create_agent_runtime
23
+ from iac_code.services.session_index import SessionEntry, SessionIndex
24
+ from iac_code.services.session_resolver import ResolutionStatus, resolve_session_argument
22
25
  from iac_code.services.session_storage import SessionStorage
26
+ from iac_code.utils.project_paths import format_resume_command, same_project_path
23
27
 
24
28
  SESSION_IDLE_TIMEOUT = 3600 # 1 hour
25
29
  CLEANUP_INTERVAL = 300 # 5 minutes
@@ -152,7 +156,12 @@ class ACPServer:
152
156
  runtime.session_id,
153
157
  )
154
158
  session = ACPSession(
155
- runtime.session_id, runtime.agent_loop, self.conn, mcp_configs=mcp_configs, metrics=self.metrics
159
+ runtime.session_id,
160
+ runtime.agent_loop,
161
+ self.conn,
162
+ mcp_configs=mcp_configs,
163
+ metrics=self.metrics,
164
+ memory_manager=getattr(runtime, "memory_manager", None),
156
165
  )
157
166
  self.sessions[session.id] = session
158
167
  self.metrics.record_session_created()
@@ -228,25 +237,16 @@ class ACPServer:
228
237
  cwd: str | None = None,
229
238
  **kwargs: Any,
230
239
  ) -> acp.schema.ListSessionsResponse:
231
- from iac_code.utils.project_paths import get_project_dir, get_projects_dir
232
-
233
- session_ids: list[str] = []
234
- if cwd:
235
- project_dir = get_project_dir(cwd)
236
- if project_dir.exists():
237
- session_ids = [p.stem for p in project_dir.glob("*.jsonl")]
238
- else:
239
- projects_root = get_projects_dir()
240
- if projects_root.exists():
241
- session_ids = [p.stem for p in projects_root.glob("*/*.jsonl")]
240
+ index = SessionIndex()
241
+ entries = index.list_for_cwd(cwd) if cwd else index.list_all_projects()
242
242
  return acp.schema.ListSessionsResponse(
243
243
  sessions=[
244
244
  acp.schema.SessionInfo(
245
- session_id=session_id,
246
- cwd=cwd or "",
247
- title=session_id,
245
+ session_id=entry.session_id,
246
+ cwd=entry.cwd or cwd or "",
247
+ title=entry.title,
248
248
  )
249
- for session_id in session_ids
249
+ for entry in entries
250
250
  ],
251
251
  next_cursor=None,
252
252
  )
@@ -297,7 +297,14 @@ class ACPServer:
297
297
  runtime.agent_loop.context_manager.load_messages(history)
298
298
 
299
299
  # 4. Register session
300
- session = ACPSession(session_id, runtime.agent_loop, self.conn, mcp_configs=mcp_configs, metrics=self.metrics)
300
+ session = ACPSession(
301
+ session_id,
302
+ runtime.agent_loop,
303
+ self.conn,
304
+ mcp_configs=mcp_configs,
305
+ metrics=self.metrics,
306
+ memory_manager=getattr(runtime, "memory_manager", None),
307
+ )
301
308
  self.sessions[session_id] = session
302
309
  self.metrics.record_session_created()
303
310
  logger.info("Session loaded, session_id=%s, history_messages=%d", session_id, len(history))
@@ -360,7 +367,12 @@ class ACPServer:
360
367
 
361
368
  # 4. Register the forked session
362
369
  session = ACPSession(
363
- new_session_id, runtime.agent_loop, self.conn, mcp_configs=mcp_configs, metrics=self.metrics
370
+ new_session_id,
371
+ runtime.agent_loop,
372
+ self.conn,
373
+ mcp_configs=mcp_configs,
374
+ metrics=self.metrics,
375
+ memory_manager=getattr(runtime, "memory_manager", None),
364
376
  )
365
377
  self.sessions[new_session_id] = session
366
378
  self.metrics.record_session_created()
@@ -384,20 +396,66 @@ class ACPServer:
384
396
  mcp_servers: list[MCPServer] | None = None,
385
397
  **kwargs: Any,
386
398
  ) -> acp.schema.ResumeSessionResponse:
387
- # 1. If session is still active in memory, return directly
388
- if session_id in self.sessions:
399
+ # 1. If session is still active in memory by exact id, enforce project ownership before returning.
400
+ active_session = self.sessions.get(session_id)
401
+ if active_session is not None:
402
+ error = _active_session_project_error(cwd, session_id, session_id, active_session)
403
+ if error is not None:
404
+ raise error
389
405
  await self._push_available_commands(session_id)
390
406
  return acp.schema.ResumeSessionResponse()
391
407
 
392
408
  if self.conn is None:
393
409
  raise acp.RequestError.internal_error({"error": "ACP client not connected"})
394
410
 
395
- # 2. Try to load persisted history from SessionStorage
411
+ resolution = resolve_session_argument(SessionIndex(), cwd, session_id)
412
+ if resolution.status == ResolutionStatus.NOT_FOUND:
413
+ raise _invalid_params(_("Session not found"), {"session_id": session_id})
414
+ if resolution.status == ResolutionStatus.AMBIGUOUS_NAME:
415
+ candidate_ids = [entry.session_id for entry in resolution.candidates]
416
+ message = _("Session name is ambiguous. Candidates: {candidates}").format(
417
+ candidates=", ".join(candidate_ids)
418
+ )
419
+ raise _invalid_params(
420
+ message,
421
+ {
422
+ "session_id": session_id,
423
+ "candidates": [_resume_candidate_data(entry) for entry in resolution.candidates],
424
+ },
425
+ )
426
+
427
+ entry = resolution.entry
428
+ if entry is None: # pragma: no cover - defensive guard for inconsistent resolver output
429
+ raise _invalid_params(_("Session not found"), {"session_id": session_id})
430
+
431
+ resolved_session_id = entry.session_id
432
+ if entry.cwd and not same_project_path(entry.cwd, cwd):
433
+ hint = _resume_command(entry.cwd, resolved_session_id)
434
+ message = _("Session belongs to another project. Run: {hint}").format(hint=hint)
435
+ raise _invalid_params(
436
+ message,
437
+ {
438
+ "session_id": session_id,
439
+ "resolved_session_id": resolved_session_id,
440
+ "cwd": entry.cwd,
441
+ "hint": hint,
442
+ },
443
+ )
444
+
445
+ active_session = self.sessions.get(resolved_session_id)
446
+ if active_session is not None:
447
+ error = _active_session_project_error(cwd, session_id, resolved_session_id, active_session)
448
+ if error is not None:
449
+ raise error
450
+ await self._push_available_commands(resolved_session_id)
451
+ return acp.schema.ResumeSessionResponse()
452
+
453
+ # 2. Try to load persisted history from SessionStorage.
396
454
  storage = SessionStorage()
397
- if not storage.exists(cwd, session_id):
398
- raise acp.RequestError.invalid_params({"session_id": "Session not found"})
455
+ if not storage.exists(cwd, resolved_session_id):
456
+ raise _invalid_params(_("Session not found"), {"session_id": session_id})
399
457
 
400
- history = storage.load(cwd, session_id)
458
+ history = storage.load(cwd, resolved_session_id)
401
459
  history = SessionStorage.repair_interrupted(history)
402
460
 
403
461
  # Convert MCP server configs from ACP protocol types to internal dicts
@@ -405,7 +463,7 @@ class ACPServer:
405
463
 
406
464
  # 3. Rebuild agent runtime with restored history
407
465
  model = load_saved_model() or DEFAULT_MODEL
408
- runtime = self._create_runtime_with_auth_check(model=model, session_id=session_id, cwd=cwd)
466
+ runtime = self._create_runtime_with_auth_check(model=model, session_id=resolved_session_id, cwd=cwd)
409
467
  replace_bash_with_acp_terminal(
410
468
  runtime.tool_registry,
411
469
  self.client_capabilities,
@@ -418,10 +476,17 @@ class ACPServer:
418
476
  runtime.agent_loop.context_manager.load_messages(history)
419
477
 
420
478
  # 4. Register the resumed session
421
- session = ACPSession(session_id, runtime.agent_loop, self.conn, mcp_configs=mcp_configs, metrics=self.metrics)
422
- self.sessions[session_id] = session
479
+ session = ACPSession(
480
+ resolved_session_id,
481
+ runtime.agent_loop,
482
+ self.conn,
483
+ mcp_configs=mcp_configs,
484
+ metrics=self.metrics,
485
+ memory_manager=getattr(runtime, "memory_manager", None),
486
+ )
487
+ self.sessions[resolved_session_id] = session
423
488
  self.metrics.record_session_created()
424
- await self._push_available_commands(session_id)
489
+ await self._push_available_commands(resolved_session_id)
425
490
 
426
491
  return acp.schema.ResumeSessionResponse()
427
492
 
@@ -619,6 +684,48 @@ def _convert_mcp_servers(mcp_servers: list[MCPServer] | None) -> list[dict[str,
619
684
  return configs
620
685
 
621
686
 
687
+ def _invalid_params(message: str, data: dict[str, Any] | None = None) -> acp.RequestError:
688
+ """Create an ACP invalid-params error with a useful message."""
689
+ return acp.RequestError(-32602, message, data)
690
+
691
+
692
+ def _resume_command(cwd: str, session_id: str) -> str:
693
+ return format_resume_command(cwd, session_id)
694
+
695
+
696
+ def _active_session_cwd(session: ACPSession) -> str | None:
697
+ cwd = getattr(session.agent_loop, "_cwd", None)
698
+ return cwd if isinstance(cwd, str) and cwd else None
699
+
700
+
701
+ def _active_session_project_error(
702
+ cwd: str, session_id: str, resolved_session_id: str, session: ACPSession
703
+ ) -> acp.RequestError | None:
704
+ active_cwd = _active_session_cwd(session)
705
+ if not active_cwd or same_project_path(active_cwd, cwd):
706
+ return None
707
+ hint = _resume_command(active_cwd, resolved_session_id)
708
+ message = _("Session belongs to another project. Run: {hint}").format(hint=hint)
709
+ return _invalid_params(
710
+ message,
711
+ {
712
+ "session_id": session_id,
713
+ "resolved_session_id": resolved_session_id,
714
+ "cwd": active_cwd,
715
+ "hint": hint,
716
+ },
717
+ )
718
+
719
+
720
+ def _resume_candidate_data(entry: SessionEntry) -> dict[str, str | None]:
721
+ return {
722
+ "session_id": entry.session_id,
723
+ "name": entry.name,
724
+ "cwd": entry.cwd,
725
+ "command": _resume_command(entry.cwd, entry.session_id),
726
+ }
727
+
728
+
622
729
  # ---------------------------------------------------------------------------
623
730
  # Auth methods declaration
624
731
  # ---------------------------------------------------------------------------
@@ -157,6 +157,20 @@ _PREFIX_ALLOW_RULE = "allow_rule:"
157
157
  _PREFIX_DENY_RULE = "deny_rule:"
158
158
 
159
159
 
160
+ def _tool_supports_blanket_allow(agent_loop, tool_name: str) -> bool:
161
+ """Return False only when the registered tool explicitly disables blanket allow."""
162
+ registry = getattr(agent_loop, "tool_registry", None)
163
+ get_tool = getattr(registry, "get", None)
164
+ if get_tool is None:
165
+ return True
166
+
167
+ tool = get_tool(tool_name)
168
+ if tool is None:
169
+ return True
170
+
171
+ return bool(getattr(tool, "supports_blanket_allow", True))
172
+
173
+
160
174
  class ACPSession:
161
175
  def __init__(
162
176
  self,
@@ -165,9 +179,11 @@ class ACPSession:
165
179
  conn: acp.Client,
166
180
  mcp_configs: list[dict] | None = None,
167
181
  metrics: ACPMetrics | None = None,
182
+ memory_manager=None,
168
183
  ) -> None:
169
184
  self.id = session_id
170
185
  self.agent_loop = agent_loop
186
+ self.memory_manager = memory_manager
171
187
  self._conn = conn
172
188
  self._current_task: asyncio.Task | None = None
173
189
  self._replay_task: asyncio.Task[None] | None = None
@@ -292,7 +308,11 @@ class ACPSession:
292
308
  prompt_text = acp_blocks_to_prompt_text(prompt)
293
309
  slash_registry = ACPSlashRegistry()
294
310
  if slash_registry.is_slash_command(prompt_text):
295
- result = await slash_registry.execute(prompt_text, self.agent_loop)
311
+ result = await slash_registry.execute(
312
+ prompt_text,
313
+ self.agent_loop,
314
+ memory_manager=self.memory_manager,
315
+ )
296
316
  await self._conn.session_update(
297
317
  session_id=self.id,
298
318
  update=acp.schema.AgentMessageChunk(
@@ -451,7 +471,7 @@ class ACPSession:
451
471
  kind="allow_always",
452
472
  )
453
473
  )
454
- else:
474
+ elif _tool_supports_blanket_allow(self.agent_loop, tool_name):
455
475
  options.append(
456
476
  acp.schema.PermissionOption(
457
477
  option_id=_OPTION_ALLOW_ALWAYS,
@@ -1,7 +1,7 @@
1
1
  """ACP slash command registry.
2
2
 
3
3
  Manages commands supported over the ACP protocol.
4
- Only /compact, /clear, and /debug are allowed;
4
+ Only /compact, /clear, /debug, /memory, and /rename are allowed;
5
5
  all other slash commands are rejected with a clear message.
6
6
  """
7
7
 
@@ -10,10 +10,12 @@ from __future__ import annotations
10
10
  import logging
11
11
 
12
12
  from iac_code.i18n import _
13
+ from iac_code.services.session_metadata import normalize_session_name
14
+ from iac_code.services.session_storage import SessionStorage
13
15
 
14
16
  logger = logging.getLogger(__name__)
15
17
 
16
- ACP_SUPPORTED_COMMANDS: frozenset[str] = frozenset({"compact", "clear", "debug"})
18
+ ACP_SUPPORTED_COMMANDS: frozenset[str] = frozenset({"compact", "clear", "debug", "memory", "rename"})
17
19
 
18
20
 
19
21
  class ACPSlashRegistry:
@@ -51,6 +53,10 @@ class ACPSlashRegistry:
51
53
  return await self._handle_clear(agent_loop)
52
54
  if cmd_name == "debug":
53
55
  return self._handle_debug(args_str)
56
+ if cmd_name == "memory":
57
+ return self._handle_memory(args_str, context.get("memory_manager"))
58
+ if cmd_name == "rename":
59
+ return self._handle_rename(args_str, agent_loop)
54
60
 
55
61
  # Should not reach here
56
62
  return _("Command '/{cmd_name}' handler not implemented.").format(cmd_name=cmd_name) # pragma: no cover
@@ -123,3 +129,36 @@ class ACPSlashRegistry:
123
129
  return _("Debug logging disabled.")
124
130
 
125
131
  return _("Usage: /debug [on|off]")
132
+
133
+ def _handle_memory(self, args: str, memory_manager) -> str:
134
+ """View and manage persistent memories."""
135
+ if memory_manager is None:
136
+ return _("Memory manager is unavailable.")
137
+
138
+ from iac_code.commands.memory import execute_memory_command
139
+
140
+ return execute_memory_command(memory_manager, args.split())
141
+
142
+ def _handle_rename(self, args: str, agent_loop) -> str:
143
+ """Rename the current ACP session non-interactively."""
144
+ parts = args.split()
145
+ if len(parts) != 1:
146
+ return _("Usage: /rename <name>")
147
+
148
+ cwd = getattr(agent_loop, "_cwd", None)
149
+ session_id = getattr(agent_loop, "_session_id", None)
150
+ git_branch = getattr(agent_loop, "_current_git_branch", None)
151
+ if not isinstance(cwd, str) or not isinstance(session_id, str):
152
+ return _("Rename is only available after a session is created.")
153
+ if not isinstance(git_branch, str):
154
+ git_branch = None
155
+
156
+ try:
157
+ name = normalize_session_name(parts[0])
158
+ result = SessionStorage().rename_session(cwd, session_id, name, git_branch=git_branch)
159
+ except ValueError as exc:
160
+ return str(exc)
161
+
162
+ if result == "unchanged":
163
+ return _("Session is already named {name}").format(name=name)
164
+ return _("Renamed session to {name}").format(name=name)
@@ -5,6 +5,7 @@ from contextlib import suppress
5
5
 
6
6
  import acp
7
7
 
8
+ from iac_code.i18n import _
8
9
  from iac_code.tools.base import Tool, ToolContext, ToolResult
9
10
 
10
11
  TERMINAL_TIMEOUT = 300 # 5 minutes default timeout
@@ -32,9 +33,37 @@ class ACPTerminalBashTool(Tool):
32
33
  def timeout(self) -> float | None:
33
34
  return self._original.timeout
34
35
 
36
+ @property
37
+ def supports_blanket_allow(self) -> bool:
38
+ return self._original.supports_blanket_allow
39
+
40
+ def user_facing_name(self, input: dict | None = None) -> str:
41
+ return self._original.user_facing_name(input)
42
+
43
+ def get_activity_description(self, input: dict | None = None) -> str | None:
44
+ return self._original.get_activity_description(input)
45
+
46
+ def get_tool_use_summary(self, input: dict | None = None) -> str | None:
47
+ return self._original.get_tool_use_summary(input)
48
+
49
+ def render_tool_use_message(self, input: dict, *, verbose: bool = False) -> str | None:
50
+ return self._original.render_tool_use_message(input, verbose=verbose)
51
+
52
+ def render_tool_result_message(self, output: str, *, is_error: bool = False, verbose: bool = False) -> str | None:
53
+ return self._original.render_tool_result_message(output, is_error=is_error, verbose=verbose)
54
+
55
+ def render_tool_use_error_message(self, error: str) -> str | None:
56
+ return self._original.render_tool_use_error_message(error)
57
+
58
+ def streaming_preview_fields(self) -> list[str]:
59
+ return self._original.streaming_preview_fields()
60
+
35
61
  def is_read_only(self, input: dict | None = None) -> bool:
36
62
  return self._original.is_read_only(input)
37
63
 
64
+ def is_concurrency_safe(self, tool_input: dict) -> bool:
65
+ return self._original.is_concurrency_safe(tool_input)
66
+
38
67
  def is_destructive(self, input: dict | None = None) -> bool:
39
68
  return self._original.is_destructive(input)
40
69
 
@@ -44,7 +73,7 @@ class ACPTerminalBashTool(Tool):
44
73
  async def execute(self, *, tool_input: dict, context: ToolContext) -> ToolResult:
45
74
  command = tool_input.get("command")
46
75
  if not command:
47
- return ToolResult.error("Bash command is required.")
76
+ return ToolResult.error(_("Bash command is required."))
48
77
 
49
78
  timeout = tool_input.get("timeout", TERMINAL_TIMEOUT)
50
79
  terminal_id: str | None = None
@@ -74,7 +103,7 @@ class ACPTerminalBashTool(Tool):
74
103
  except asyncio.TimeoutError:
75
104
  with suppress(Exception):
76
105
  await self._conn.kill_terminal(session_id=self._session_id, terminal_id=terminal_id)
77
- return ToolResult.error(f"Command timed out after {timeout} seconds")
106
+ return ToolResult.error(_("Command timed out after {timeout} seconds").format(timeout=timeout))
78
107
 
79
108
  if output.exit_status:
80
109
  exit_status = output.exit_status
@@ -84,9 +113,15 @@ class ACPTerminalBashTool(Tool):
84
113
  # at the session layer in a future phase.
85
114
 
86
115
  if exit_status.signal:
87
- return ToolResult.error(f"Command terminated by signal: {exit_status.signal}\n{output.output}")
116
+ return ToolResult.error(
117
+ _("Command terminated by signal: {signal}").format(signal=exit_status.signal) + "\n" + output.output
118
+ )
88
119
  if exit_status.exit_code not in (None, 0):
89
- return ToolResult.error(f"Command failed with exit code {exit_status.exit_code}\n{output.output}")
120
+ return ToolResult.error(
121
+ _("Command failed with exit code {exit_code}").format(exit_code=exit_status.exit_code)
122
+ + "\n"
123
+ + output.output
124
+ )
90
125
  return ToolResult.success(output.output)
91
126
  except asyncio.CancelledError:
92
127
  if terminal_id is not None: