iac-code 0.3.1__tar.gz → 0.4.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.
Files changed (304) hide show
  1. {iac_code-0.3.1 → iac_code-0.4.0}/PKG-INFO +1 -1
  2. iac_code-0.4.0/src/iac_code/__init__.py +2 -0
  3. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/acp/server.py +135 -28
  4. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/acp/session.py +7 -1
  5. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/acp/slash_registry.py +41 -2
  6. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/agent/agent_loop.py +61 -0
  7. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/cli/main.py +1 -1
  8. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/commands/__init__.py +40 -2
  9. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/commands/auth.py +240 -8
  10. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/commands/clear.py +11 -1
  11. iac_code-0.4.0/src/iac_code/commands/memory.py +85 -0
  12. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/commands/registry.py +18 -0
  13. iac_code-0.4.0/src/iac_code/commands/rename.py +43 -0
  14. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/commands/resume.py +25 -4
  15. iac_code-0.4.0/src/iac_code/commands/skills.py +29 -0
  16. iac_code-0.4.0/src/iac_code/commands/status.py +98 -0
  17. iac_code-0.4.0/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.mo +0 -0
  18. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.po +726 -187
  19. iac_code-0.4.0/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.mo +0 -0
  20. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.po +723 -187
  21. iac_code-0.4.0/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.mo +0 -0
  22. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.po +724 -187
  23. iac_code-0.4.0/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.mo +0 -0
  24. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.po +710 -187
  25. iac_code-0.4.0/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.mo +0 -0
  26. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.po +721 -187
  27. iac_code-0.4.0/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.mo +0 -0
  28. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.po +703 -187
  29. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/memory/memory_manager.py +71 -16
  30. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/memory/memory_tools.py +6 -1
  31. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/manager.py +22 -0
  32. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/registry.py +1 -0
  33. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/thinking.py +1 -0
  34. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/agent_factory.py +6 -3
  35. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/providers/aliyun.py +107 -5
  36. iac_code-0.4.0/src/iac_code/services/providers/aliyun_oauth.py +583 -0
  37. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/session_index.py +48 -24
  38. iac_code-0.4.0/src/iac_code/services/session_metadata.py +78 -0
  39. iac_code-0.4.0/src/iac_code/services/session_resolver.py +73 -0
  40. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/session_storage.py +116 -5
  41. iac_code-0.4.0/src/iac_code/services/session_usage.py +176 -0
  42. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/bundled/iac_aliyun/SKILL.md +9 -0
  43. iac_code-0.4.0/src/iac_code/skills/bundled/iac_aliyun/references/template-parameter-recommendation.md +165 -0
  44. iac_code-0.4.0/src/iac_code/skills/management.py +81 -0
  45. iac_code-0.4.0/src/iac_code/skills/settings.py +61 -0
  46. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/skill_tool.py +13 -0
  47. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/base.py +4 -0
  48. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/cloud/aliyun/aliyun_api.py +18 -2
  49. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/cloud/aliyun/ros_client.py +9 -2
  50. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/cloud/aliyun/ros_stack_instances.py +3 -2
  51. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/cloud/registry.py +10 -0
  52. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/banner.py +15 -2
  53. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/dialogs/resume_picker.py +25 -2
  54. iac_code-0.4.0/src/iac_code/ui/dialogs/skills_picker.py +297 -0
  55. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/repl.py +289 -79
  56. iac_code-0.4.0/src/iac_code/ui/suggestions/command_provider.py +135 -0
  57. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/suggestions/token_extractor.py +20 -0
  58. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/project_paths.py +44 -0
  59. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code.egg-info/PKG-INFO +1 -1
  60. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code.egg-info/SOURCES.txt +12 -0
  61. iac_code-0.3.1/src/iac_code/__init__.py +0 -2
  62. iac_code-0.3.1/src/iac_code/i18n/locales/de/LC_MESSAGES/messages.mo +0 -0
  63. iac_code-0.3.1/src/iac_code/i18n/locales/es/LC_MESSAGES/messages.mo +0 -0
  64. iac_code-0.3.1/src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.mo +0 -0
  65. iac_code-0.3.1/src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.mo +0 -0
  66. iac_code-0.3.1/src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.mo +0 -0
  67. iac_code-0.3.1/src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.mo +0 -0
  68. iac_code-0.3.1/src/iac_code/ui/suggestions/command_provider.py +0 -43
  69. {iac_code-0.3.1 → iac_code-0.4.0}/LICENSE +0 -0
  70. {iac_code-0.3.1 → iac_code-0.4.0}/MANIFEST.in +0 -0
  71. {iac_code-0.3.1 → iac_code-0.4.0}/README.md +0 -0
  72. {iac_code-0.3.1 → iac_code-0.4.0}/pyproject.toml +0 -0
  73. {iac_code-0.3.1 → iac_code-0.4.0}/setup.cfg +0 -0
  74. {iac_code-0.3.1 → iac_code-0.4.0}/setup.py +0 -0
  75. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/__init__.py +0 -0
  76. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/agent_card.py +0 -0
  77. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/app.py +0 -0
  78. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/artifacts.py +0 -0
  79. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/client.py +0 -0
  80. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/events.py +0 -0
  81. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/executor.py +0 -0
  82. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/exposure.py +0 -0
  83. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/metrics.py +0 -0
  84. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/parts.py +0 -0
  85. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/persistence.py +0 -0
  86. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/push.py +0 -0
  87. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/push_queue.py +0 -0
  88. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/push_secrets.py +0 -0
  89. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/push_worker.py +0 -0
  90. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/router.py +0 -0
  91. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/signing.py +0 -0
  92. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/task_store.py +0 -0
  93. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/transport.py +0 -0
  94. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/transports/__init__.py +0 -0
  95. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/transports/base.py +0 -0
  96. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/transports/dispatcher.py +0 -0
  97. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/transports/grpc.py +0 -0
  98. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/transports/grpc_jsonrpc.py +0 -0
  99. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/transports/http.py +0 -0
  100. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/transports/redis_streams.py +0 -0
  101. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/transports/stdio.py +0 -0
  102. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/transports/unix.py +0 -0
  103. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/transports/websocket.py +0 -0
  104. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/a2a/types.py +0 -0
  105. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/acp/__init__.py +0 -0
  106. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/acp/convert.py +0 -0
  107. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/acp/http_sse.py +0 -0
  108. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/acp/mcp.py +0 -0
  109. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/acp/metrics.py +0 -0
  110. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/acp/state.py +0 -0
  111. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/acp/tools.py +0 -0
  112. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/acp/types.py +0 -0
  113. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/acp/version.py +0 -0
  114. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/agent/__init__.py +0 -0
  115. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/agent/agent_tool.py +0 -0
  116. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/agent/agent_types.py +0 -0
  117. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/agent/message.py +0 -0
  118. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/agent/system_prompt.py +0 -0
  119. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/cli/__init__.py +0 -0
  120. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/cli/headless.py +0 -0
  121. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/cli/install_git_bash.py +0 -0
  122. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/cli/output_formats.py +0 -0
  123. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/commands/compact.py +0 -0
  124. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/commands/debug.py +0 -0
  125. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/commands/effort.py +0 -0
  126. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/commands/exit.py +0 -0
  127. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/commands/help.py +0 -0
  128. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/commands/model.py +0 -0
  129. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/commands/tasks.py +0 -0
  130. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/config.py +0 -0
  131. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/i18n/__init__.py +0 -0
  132. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/memory/__init__.py +0 -0
  133. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/__init__.py +0 -0
  134. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/anthropic_provider.py +0 -0
  135. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/azure_openai_provider.py +0 -0
  136. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/base.py +0 -0
  137. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/dashscope_provider.py +0 -0
  138. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/deepseek_provider.py +0 -0
  139. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/gemini_provider.py +0 -0
  140. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/kimi_provider.py +0 -0
  141. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/lmstudio_provider.py +0 -0
  142. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/minimax_provider.py +0 -0
  143. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/modelscope_provider.py +0 -0
  144. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/ollama_provider.py +0 -0
  145. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/openai_provider.py +0 -0
  146. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/openrouter_provider.py +0 -0
  147. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/retry.py +0 -0
  148. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/siliconflow_provider.py +0 -0
  149. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/stream_watchdog.py +0 -0
  150. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/volcengine_provider.py +0 -0
  151. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/providers/zhipu_provider.py +0 -0
  152. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/__init__.py +0 -0
  153. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/capabilities/__init__.py +0 -0
  154. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/capabilities/auto_detect.py +0 -0
  155. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/capabilities/multimodal.py +0 -0
  156. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/cloud_credentials.py +0 -0
  157. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/context_manager.py +0 -0
  158. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/permissions/__init__.py +0 -0
  159. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/permissions/loader.py +0 -0
  160. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/permissions/pipeline.py +0 -0
  161. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/permissions/storage.py +0 -0
  162. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/providers/__init__.py +0 -0
  163. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/qwenpaw_source.py +0 -0
  164. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/telemetry/__init__.py +0 -0
  165. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/telemetry/attributes.py +0 -0
  166. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/telemetry/client.py +0 -0
  167. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/telemetry/config.py +0 -0
  168. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/telemetry/constants.py +0 -0
  169. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/telemetry/content_serializer.py +0 -0
  170. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/telemetry/events.py +0 -0
  171. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/telemetry/fallback.py +0 -0
  172. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/telemetry/identity.py +0 -0
  173. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/telemetry/metrics.py +0 -0
  174. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/telemetry/names.py +0 -0
  175. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/telemetry/sanitize.py +0 -0
  176. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/telemetry/sink.py +0 -0
  177. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/telemetry/tracing.py +0 -0
  178. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/telemetry/types.py +0 -0
  179. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/token_budget.py +0 -0
  180. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/token_counter.py +0 -0
  181. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/services/update_checker.py +0 -0
  182. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/__init__.py +0 -0
  183. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/auto_trigger.py +0 -0
  184. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/bundled/__init__.py +0 -0
  185. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/bundled/iac_aliyun/__init__.py +0 -0
  186. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/bundled/iac_aliyun/auto_trigger.py +0 -0
  187. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/ecs.md +0 -0
  188. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/oss.md +0 -0
  189. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/rds.md +0 -0
  190. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/redis.md +0 -0
  191. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/slb.md +0 -0
  192. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/bundled/iac_aliyun/references/cloud-products/vpc.md +0 -0
  193. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/bundled/iac_aliyun/references/ros-template.md +0 -0
  194. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/bundled/iac_aliyun/references/template-parameters.md +0 -0
  195. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/bundled/iac_aliyun/references/terraform-template.md +0 -0
  196. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/bundled/iac_aliyun/scripts/tf2ros.py +0 -0
  197. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/bundled/simplify.py +0 -0
  198. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/discovery.py +0 -0
  199. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/frontmatter.py +0 -0
  200. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/listing.py +0 -0
  201. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/loader.py +0 -0
  202. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/processor.py +0 -0
  203. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/renderer.py +0 -0
  204. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/skills/skill_definition.py +0 -0
  205. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/state/__init__.py +0 -0
  206. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/state/app_state.py +0 -0
  207. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tasks/__init__.py +0 -0
  208. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tasks/notification_queue.py +0 -0
  209. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tasks/task_state.py +0 -0
  210. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tasks/task_tools.py +0 -0
  211. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/__init__.py +0 -0
  212. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/bash/__init__.py +0 -0
  213. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/bash/bash_tool.py +0 -0
  214. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/bash/command_parser.py +0 -0
  215. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/bash/mode_validation.py +0 -0
  216. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/bash/path_validation.py +0 -0
  217. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/bash/permissions.py +0 -0
  218. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/bash/readonly_commands.py +0 -0
  219. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/bash/rule_matching.py +0 -0
  220. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/bash/safety_checks.py +0 -0
  221. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/cloud/__init__.py +0 -0
  222. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/cloud/aliyun/__init__.py +0 -0
  223. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/cloud/aliyun/aliyun_doc_search.py +0 -0
  224. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/cloud/aliyun/api_hooks.py +0 -0
  225. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/cloud/aliyun/endpoints.yml +0 -0
  226. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/cloud/aliyun/hooks/__init__.py +0 -0
  227. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/cloud/aliyun/hooks/ros_parameters.py +0 -0
  228. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/cloud/aliyun/hooks/ros_validate.py +0 -0
  229. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/cloud/aliyun/ros_stack.py +0 -0
  230. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/cloud/aliyun/ros_yaml.py +0 -0
  231. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/cloud/aliyun/user_agent.py +0 -0
  232. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/cloud/base_api.py +0 -0
  233. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/cloud/base_stack.py +0 -0
  234. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/cloud/types.py +0 -0
  235. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/edit_file.py +0 -0
  236. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/glob.py +0 -0
  237. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/grep.py +0 -0
  238. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/list_files.py +0 -0
  239. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/read_file.py +0 -0
  240. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/result_storage.py +0 -0
  241. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/tool_executor.py +0 -0
  242. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/web_fetch.py +0 -0
  243. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/tools/write_file.py +0 -0
  244. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/types/__init__.py +0 -0
  245. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/types/permissions.py +0 -0
  246. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/types/skill_source.py +0 -0
  247. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/types/stream_events.py +0 -0
  248. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/__init__.py +0 -0
  249. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/components/__init__.py +0 -0
  250. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/components/dialog.py +0 -0
  251. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/components/divider.py +0 -0
  252. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/components/fuzzy_picker.py +0 -0
  253. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/components/progress_bar.py +0 -0
  254. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/components/search_box.py +0 -0
  255. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/components/select.py +0 -0
  256. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/components/status_icon.py +0 -0
  257. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/components/tabs.py +0 -0
  258. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/core/__init__.py +0 -0
  259. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/core/in_place_render.py +0 -0
  260. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/core/input_history.py +0 -0
  261. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/core/key_event.py +0 -0
  262. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/core/prompt_input.py +0 -0
  263. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/core/raw_input.py +0 -0
  264. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/core/raw_input_win.py +0 -0
  265. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/core/screen.py +0 -0
  266. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/dialogs/__init__.py +0 -0
  267. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/dialogs/global_search.py +0 -0
  268. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/dialogs/history_search.py +0 -0
  269. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/dialogs/model_picker.py +0 -0
  270. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/dialogs/quick_open.py +0 -0
  271. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/keybindings/__init__.py +0 -0
  272. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/keybindings/manager.py +0 -0
  273. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/renderer.py +0 -0
  274. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/spinner.py +0 -0
  275. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/suggestions/__init__.py +0 -0
  276. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/suggestions/aggregator.py +0 -0
  277. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/suggestions/directory_provider.py +0 -0
  278. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/suggestions/file_provider.py +0 -0
  279. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/suggestions/shell_history_provider.py +0 -0
  280. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/suggestions/skill_provider.py +0 -0
  281. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/suggestions/types.py +0 -0
  282. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/ui/transcript_view.py +0 -0
  283. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/__init__.py +0 -0
  284. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/background_housekeeping.py +0 -0
  285. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/cleanup.py +0 -0
  286. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/console.py +0 -0
  287. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/file_security.py +0 -0
  288. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/image/__init__.py +0 -0
  289. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/image/clipboard.py +0 -0
  290. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/image/format_detect.py +0 -0
  291. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/image/pasted_content.py +0 -0
  292. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/image/processor.py +0 -0
  293. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/image/resizer.py +0 -0
  294. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/image/store.py +0 -0
  295. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/json_utils.py +0 -0
  296. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/log.py +0 -0
  297. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/platform.py +0 -0
  298. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/signals.py +0 -0
  299. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/tool_input_parser.py +0 -0
  300. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code/utils/windows_paths.py +0 -0
  301. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code.egg-info/dependency_links.txt +0 -0
  302. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code.egg-info/entry_points.txt +0 -0
  303. {iac_code-0.3.1 → iac_code-0.4.0}/src/iac_code.egg-info/requires.txt +0 -0
  304. {iac_code-0.3.1 → iac_code-0.4.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.1
3
+ Version: 0.4.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
@@ -0,0 +1,2 @@
1
+ __version__ = "0.4.0"
2
+ __release_date__ = "2026-06-03"
@@ -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
  # ---------------------------------------------------------------------------
@@ -165,9 +165,11 @@ class ACPSession:
165
165
  conn: acp.Client,
166
166
  mcp_configs: list[dict] | None = None,
167
167
  metrics: ACPMetrics | None = None,
168
+ memory_manager=None,
168
169
  ) -> None:
169
170
  self.id = session_id
170
171
  self.agent_loop = agent_loop
172
+ self.memory_manager = memory_manager
171
173
  self._conn = conn
172
174
  self._current_task: asyncio.Task | None = None
173
175
  self._replay_task: asyncio.Task[None] | None = None
@@ -292,7 +294,11 @@ class ACPSession:
292
294
  prompt_text = acp_blocks_to_prompt_text(prompt)
293
295
  slash_registry = ACPSlashRegistry()
294
296
  if slash_registry.is_slash_command(prompt_text):
295
- result = await slash_registry.execute(prompt_text, self.agent_loop)
297
+ result = await slash_registry.execute(
298
+ prompt_text,
299
+ self.agent_loop,
300
+ memory_manager=self.memory_manager,
301
+ )
296
302
  await self._conn.session_update(
297
303
  session_id=self.id,
298
304
  update=acp.schema.AgentMessageChunk(
@@ -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)
@@ -15,6 +15,7 @@ from loguru import logger
15
15
  from iac_code.agent.message import ContentBlock, TextBlock, ThinkingBlock, ToolResultBlock, ToolUseBlock
16
16
  from iac_code.i18n import _
17
17
  from iac_code.services.context_manager import ContextManager
18
+ from iac_code.services.session_usage import SessionUsageStore, SessionUsageTotals
18
19
  from iac_code.tools.base import ToolContext, ToolRegistry, ToolResult
19
20
  from iac_code.tools.result_storage import ResultStorage
20
21
  from iac_code.tools.tool_executor import ToolCallRequest, ToolExecutor
@@ -65,6 +66,7 @@ class AgentLoop:
65
66
  tool_registry: ToolRegistry,
66
67
  max_turns: int = 100,
67
68
  session_storage: Any = None, # SessionStorage
69
+ session_usage_store: SessionUsageStore | None = None,
68
70
  session_id: str | None = None,
69
71
  resume_messages: list | None = None,
70
72
  cwd: str | None = None,
@@ -79,6 +81,8 @@ class AgentLoop:
79
81
  self._session_storage = session_storage
80
82
  self._session_id = session_id or str(uuid.uuid4())[:8]
81
83
  self._cwd = cwd or os.getcwd()
84
+ self._session_usage_store = session_usage_store or SessionUsageStore()
85
+ self._session_usage_totals = self._session_usage_store.load(self._cwd, self._session_id)
82
86
  self._permission_context = permission_context
83
87
  self._permission_context_getter = permission_context_getter
84
88
  self._auto_trigger_skills = auto_trigger_skills or []
@@ -113,6 +117,10 @@ class AgentLoop:
113
117
  self.system_prompt = system_prompt
114
118
  self.context_manager.set_system_prompt(system_prompt)
115
119
 
120
+ def set_auto_trigger_skills(self, skill_commands: list[Any] | None) -> None:
121
+ """Refresh skills considered for automatic trigger injection."""
122
+ self._auto_trigger_skills = list(skill_commands or [])
123
+
116
124
  def _get_tool_definitions(self):
117
125
  """Convert tool registry to provider ToolDefinition format."""
118
126
  from iac_code.providers.base import ToolDefinition
@@ -250,6 +258,7 @@ class AgentLoop:
250
258
  final_text_chunks.append(event.text)
251
259
  if isinstance(event, MessageEndEvent):
252
260
  final_stop_reason = event.stop_reason
261
+ self._record_session_usage(event.usage)
253
262
  yield event
254
263
  except asyncio.CancelledError:
255
264
  log_event(Events.SESSION_CANCELLED, {"stage": "in_query"})
@@ -609,6 +618,7 @@ class AgentLoop:
609
618
  messages=[ProviderMessage.user(compaction_prompt)],
610
619
  system="You are a helpful assistant that summarizes conversations concisely.",
611
620
  )
621
+ self._record_response_usage(response)
612
622
  if response.text:
613
623
  original, new = self.context_manager.apply_compaction(response.text)
614
624
  duration_ms = int((time.monotonic() - started) * 1000)
@@ -650,6 +660,7 @@ class AgentLoop:
650
660
  messages=[ProviderMessage.user(compaction_prompt)],
651
661
  system="You are a helpful assistant that summarizes conversations concisely.",
652
662
  )
663
+ self._record_response_usage(response)
653
664
  if response.text:
654
665
  original, compacted = self.context_manager.apply_compaction(response.text)
655
666
  return CompactResult(
@@ -691,6 +702,7 @@ class AgentLoop:
691
702
  self.context_manager.reset()
692
703
  if resume_messages:
693
704
  self.context_manager.load_messages(resume_messages)
705
+ self._session_usage_totals = self._session_usage_store.load(self._cwd, self._session_id)
694
706
  self._result_storage = ResultStorage(
695
707
  storage_dir=os.path.join(str(get_config_dir()), "tool-results", session_id),
696
708
  )
@@ -712,5 +724,54 @@ class AgentLoop:
712
724
  self._auto_loaded_skills.clear()
713
725
  self.context_manager.reset()
714
726
 
727
+ @property
728
+ def session_id(self) -> str:
729
+ return self._session_id
730
+
731
+ @property
732
+ def max_turns(self) -> int:
733
+ return self._max_turns
734
+
715
735
  def get_context_usage(self) -> dict:
716
736
  return self.context_manager.get_usage()
737
+
738
+ def get_session_usage(self) -> SessionUsageTotals:
739
+ return self._session_usage_totals.copy()
740
+
741
+ def _record_session_usage(self, usage: Usage) -> None:
742
+ if not self._session_usage_totals.add(usage):
743
+ return
744
+
745
+ provider = self._get_runtime_provider_key()
746
+ model = self._provider_manager.get_model_name() if hasattr(self._provider_manager, "get_model_name") else ""
747
+ try:
748
+ self._session_usage_store.append(
749
+ self._cwd,
750
+ self._session_id,
751
+ usage,
752
+ provider=provider,
753
+ model=model,
754
+ )
755
+ except Exception as exc:
756
+ logger.debug("Failed to persist session usage for {}: {}", self._session_id, exc)
757
+
758
+ def _record_response_usage(self, response: Any) -> None:
759
+ usage = getattr(response, "usage", None)
760
+ if isinstance(usage, Usage):
761
+ self._record_session_usage(usage)
762
+
763
+ def _get_runtime_provider_key(self) -> str:
764
+ if hasattr(self._provider_manager, "get_provider_key"):
765
+ try:
766
+ provider_key = self._provider_manager.get_provider_key()
767
+ except Exception:
768
+ pass
769
+ else:
770
+ if isinstance(provider_key, str):
771
+ return provider_key
772
+ try:
773
+ from iac_code.config import get_active_provider_key
774
+
775
+ return get_active_provider_key() or ""
776
+ except Exception:
777
+ return ""
@@ -86,7 +86,7 @@ def main(
86
86
  debug: bool = typer.Option(False, "--debug", "-d", help=_("Enable debug logging")),
87
87
  verbose: bool = typer.Option(False, "--verbose", help=_("Show headless progress on stderr")),
88
88
  version: bool = typer.Option(False, "--version", "-v", "-V", is_eager=True, help=_("Show version and exit")),
89
- resume: str = typer.Option("", "--resume", "-r", help=_("Resume a session by ID")),
89
+ resume: str = typer.Option("", "--resume", "-r", help=_("Resume a session by ID or name")),
90
90
  continue_session: bool = typer.Option(False, "--continue", "-c", help=_("Resume the most recent session")),
91
91
  install_completion: bool = typer.Option(
92
92
  None,
@@ -7,9 +7,13 @@ from iac_code.commands.debug import debug_command
7
7
  from iac_code.commands.effort import effort_command
8
8
  from iac_code.commands.exit import exit_command
9
9
  from iac_code.commands.help import help_command
10
+ from iac_code.commands.memory import memory_command
10
11
  from iac_code.commands.model import model_command
11
- from iac_code.commands.registry import Command, CommandRegistry, LocalCommand, PromptCommand
12
+ from iac_code.commands.registry import Command, CommandRegistry, CommandResult, LocalCommand, PromptCommand
13
+ from iac_code.commands.rename import rename_command
12
14
  from iac_code.commands.resume import resume_command
15
+ from iac_code.commands.skills import skills_command
16
+ from iac_code.commands.status import status_command
13
17
  from iac_code.i18n import _
14
18
 
15
19
 
@@ -87,6 +91,15 @@ def create_default_registry() -> CommandRegistry:
87
91
  history_mode="session",
88
92
  )
89
93
  )
94
+ registry.register(
95
+ LocalCommand(
96
+ name="memory",
97
+ description=_("View and manage persistent memories"),
98
+ handler=memory_command,
99
+ arg_hint=_("[<name>|search <query>|delete <name>|help]"),
100
+ history_mode="session",
101
+ )
102
+ )
90
103
  registry.register(
91
104
  LocalCommand(
92
105
  name="resume",
@@ -96,7 +109,32 @@ def create_default_registry() -> CommandRegistry:
96
109
  history_mode="session",
97
110
  )
98
111
  )
112
+ registry.register(
113
+ LocalCommand(
114
+ name="rename",
115
+ description=_("Rename the current session"),
116
+ handler=rename_command,
117
+ arg_hint="<name>",
118
+ history_mode="session",
119
+ )
120
+ )
121
+ registry.register(
122
+ LocalCommand(
123
+ name="skills",
124
+ description=_("Manage skills"),
125
+ handler=skills_command,
126
+ history_mode="session",
127
+ )
128
+ )
129
+ registry.register(
130
+ LocalCommand(
131
+ name="status",
132
+ description=_("Show current session status"),
133
+ handler=status_command,
134
+ history_mode="session",
135
+ )
136
+ )
99
137
  return registry
100
138
 
101
139
 
102
- __all__ = ["Command", "CommandRegistry", "LocalCommand", "PromptCommand", "create_default_registry"]
140
+ __all__ = ["Command", "CommandRegistry", "CommandResult", "LocalCommand", "PromptCommand", "create_default_registry"]