openhands-sdk 1.58.2__tar.gz → 1.59.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 (331) hide show
  1. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/PKG-INFO +1 -1
  2. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/__init__.py +0 -14
  3. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/acp_agent.py +50 -2
  4. openhands_sdk-1.59.0/openhands/sdk/agent/acp_skills.py +67 -0
  5. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/agent.py +38 -2
  6. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/conversation.py +5 -0
  7. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/event_store.py +131 -52
  8. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/impl/local_conversation.py +32 -9
  9. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/impl/remote_conversation.py +32 -9
  10. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/request.py +17 -4
  11. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/secret_registry.py +10 -10
  12. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/state.py +36 -0
  13. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/__init__.py +2 -1
  14. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/acp_tool_call.py +2 -2
  15. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/base.py +3 -2
  16. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/hook_execution.py +3 -3
  17. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/llm_completion_log.py +2 -2
  18. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/types.py +13 -3
  19. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/extensions/fetch.py +18 -11
  20. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/extensions/installation/manager.py +3 -3
  21. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/launch/finalize.py +4 -2
  22. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/launch/preview.py +1 -0
  23. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/launch/resolve.py +1 -0
  24. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/exceptions/classifier.py +31 -5
  25. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/llm.py +9 -1
  26. openhands_sdk-1.59.0/openhands/sdk/llm/utils/retry_after.py +67 -0
  27. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/retry_mixin.py +23 -6
  28. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/profiles/agent_profile.py +23 -0
  29. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/profiles/resolver.py +3 -0
  30. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/settings/__init__.py +0 -6
  31. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/settings/acp_install_catalog.py +1 -1
  32. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/settings/acp_providers.py +0 -209
  33. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/skills/skill.py +4 -0
  34. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/skills/utils.py +11 -0
  35. openhands_sdk-1.59.0/openhands/sdk/utils/masking.py +70 -0
  36. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands_sdk.egg-info/PKG-INFO +1 -1
  37. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands_sdk.egg-info/SOURCES.txt +4 -0
  38. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/pyproject.toml +1 -1
  39. openhands_sdk-1.58.2/openhands/sdk/utils/masking.py +0 -13
  40. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/__init__.py +0 -0
  41. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/acp_contracts.py +0 -0
  42. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/acp_file_credentials.py +0 -0
  43. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/acp_model_discovery.py +0 -0
  44. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/acp_models.py +0 -0
  45. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/acp_tracing.py +0 -0
  46. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/base.py +0 -0
  47. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/critic_mixin.py +0 -0
  48. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/parallel_executor.py +0 -0
  49. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/response_dispatch.py +0 -0
  50. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/stream_context.py +0 -0
  51. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/agent/utils.py +0 -0
  52. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/automation/__init__.py +0 -0
  53. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/banner.py +0 -0
  54. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/__init__.py +0 -0
  55. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/agent_context.py +0 -0
  56. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/condenser/__init__.py +0 -0
  57. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/condenser/base.py +0 -0
  58. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/condenser/llm_summarizing_condenser.py +0 -0
  59. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/condenser/no_op_condenser.py +0 -0
  60. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/condenser/pipeline_condenser.py +0 -0
  61. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/condenser/prompts/summarizing_events.j2 +0 -0
  62. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/condenser/prompts/summarizing_system.j2 +0 -0
  63. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/condenser/utils.py +0 -0
  64. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/memory.py +0 -0
  65. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/__init__.py +0 -0
  66. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/presets.py +0 -0
  67. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/prompt.py +0 -0
  68. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/registry.py +0 -0
  69. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/section.py +0 -0
  70. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/sections/__init__.py +0 -0
  71. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/sections/dynamic.py +0 -0
  72. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/sections/planning.py +0 -0
  73. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/sections/static.py +0 -0
  74. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/templates/ask_agent_template.j2 +0 -0
  75. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/prompts/templates/skill_knowledge_info.j2 +0 -0
  76. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/view/__init__.py +0 -0
  77. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/view/manipulation_indices.py +0 -0
  78. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/view/properties/__init__.py +0 -0
  79. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/view/properties/base.py +0 -0
  80. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/view/properties/batch_atomicity.py +0 -0
  81. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/view/properties/observation_uniqueness.py +0 -0
  82. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/view/properties/tool_call_matching.py +0 -0
  83. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/view/properties/tool_loop_atomicity.py +0 -0
  84. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/context/view/view.py +0 -0
  85. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/__init__.py +0 -0
  86. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/base.py +0 -0
  87. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/cancellation.py +0 -0
  88. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/conversation_stats.py +0 -0
  89. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/events_list_base.py +0 -0
  90. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/exceptions.py +0 -0
  91. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/fifo_lock.py +0 -0
  92. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/goal/__init__.py +0 -0
  93. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/goal/controller.py +0 -0
  94. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/goal/judge.py +0 -0
  95. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/goal/prompts.py +0 -0
  96. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/goal/runner.py +0 -0
  97. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/impl/__init__.py +0 -0
  98. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/message_request.py +0 -0
  99. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/persistence_const.py +0 -0
  100. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/resource_lock_manager.py +0 -0
  101. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/response_utils.py +0 -0
  102. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/stuck_detector.py +0 -0
  103. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/title_utils.py +0 -0
  104. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/types.py +0 -0
  105. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/visualizer/__init__.py +0 -0
  106. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/visualizer/base.py +0 -0
  107. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/conversation/visualizer/default.py +0 -0
  108. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/credential.py +0 -0
  109. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/__init__.py +0 -0
  110. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/base.py +0 -0
  111. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/impl/__init__.py +0 -0
  112. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/impl/agent_finished.py +0 -0
  113. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/impl/api/__init__.py +0 -0
  114. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/impl/api/chat_template.py +0 -0
  115. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/impl/api/client.py +0 -0
  116. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/impl/api/critic.py +0 -0
  117. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/impl/api/taxonomy.py +0 -0
  118. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/impl/empty_patch.py +0 -0
  119. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/impl/pass_critic.py +0 -0
  120. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/critic/result.py +0 -0
  121. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/condenser.py +0 -0
  122. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/conversation_error.py +0 -0
  123. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/conversation_state.py +0 -0
  124. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/error_classification.py +0 -0
  125. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/llm_convertible/__init__.py +0 -0
  126. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/llm_convertible/action.py +0 -0
  127. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/llm_convertible/message.py +0 -0
  128. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/llm_convertible/observation.py +0 -0
  129. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/llm_convertible/reasoning_utils.py +0 -0
  130. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/llm_convertible/system.py +0 -0
  131. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/resume_transcript.py +0 -0
  132. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/security.py +0 -0
  133. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/streaming_delta.py +0 -0
  134. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/token.py +0 -0
  135. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/event/user_action.py +0 -0
  136. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/extensions/__init__.py +0 -0
  137. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/extensions/installation/__init__.py +0 -0
  138. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/extensions/installation/info.py +0 -0
  139. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/extensions/installation/interface.py +0 -0
  140. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/extensions/installation/metadata.py +0 -0
  141. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/extensions/installation/utils.py +0 -0
  142. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/git/cached_repo.py +0 -0
  143. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/git/exceptions.py +0 -0
  144. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/git/git_changes.py +0 -0
  145. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/git/git_commits.py +0 -0
  146. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/git/git_diff.py +0 -0
  147. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/git/models.py +0 -0
  148. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/git/utils.py +0 -0
  149. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/hooks/__init__.py +0 -0
  150. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/hooks/config.py +0 -0
  151. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/hooks/conversation_hooks.py +0 -0
  152. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/hooks/executor.py +0 -0
  153. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/hooks/manager.py +0 -0
  154. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/hooks/types.py +0 -0
  155. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/io/__init__.py +0 -0
  156. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/io/base.py +0 -0
  157. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/io/cache.py +0 -0
  158. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/io/local.py +0 -0
  159. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/io/memory.py +0 -0
  160. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/launch/__init__.py +0 -0
  161. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/launch/errors.py +0 -0
  162. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/__init__.py +0 -0
  163. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/_message_normalization.py +0 -0
  164. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/_response_stream.py +0 -0
  165. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/_tokenizer.py +0 -0
  166. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/auth/__init__.py +0 -0
  167. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/auth/credentials.py +0 -0
  168. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/auth/openai.py +0 -0
  169. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/call_context.py +0 -0
  170. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/cleanup_profile.py +0 -0
  171. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/exceptions/__init__.py +0 -0
  172. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/exceptions/mapping.py +0 -0
  173. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/exceptions/types.py +0 -0
  174. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/fallback_strategy.py +0 -0
  175. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/llm_profile_store.py +0 -0
  176. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/llm_registry.py +0 -0
  177. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/llm_response.py +0 -0
  178. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/message.py +0 -0
  179. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/meta_profile_store.py +0 -0
  180. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/mixins/fn_call_converter.py +0 -0
  181. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/mixins/fn_call_examples.py +0 -0
  182. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/mixins/non_native_fc.py +0 -0
  183. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/options/__init__.py +0 -0
  184. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/options/chat_options.py +0 -0
  185. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/options/common.py +0 -0
  186. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/options/responses_options.py +0 -0
  187. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/provider_connection_store.py +0 -0
  188. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/router/__init__.py +0 -0
  189. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/router/base.py +0 -0
  190. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/router/impl/multimodal.py +0 -0
  191. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/router/impl/random.py +0 -0
  192. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/streaming.py +0 -0
  193. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/image_inline.py +0 -0
  194. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/image_resize.py +0 -0
  195. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/litellm_provider.py +0 -0
  196. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/metrics.py +0 -0
  197. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/model_features.py +0 -0
  198. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/model_info.py +0 -0
  199. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/model_prompt_spec.py +0 -0
  200. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/openhands_provider.py +0 -0
  201. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/providers/__init__.py +0 -0
  202. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/providers/openrouter.py +0 -0
  203. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/responses_serialization.py +0 -0
  204. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/runtime_metadata.py +0 -0
  205. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/telemetry.py +0 -0
  206. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/unverified_models.py +0 -0
  207. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/verified_models.py +0 -0
  208. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/llm/utils/vertex_preflight.py +0 -0
  209. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/logger/__init__.py +0 -0
  210. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/logger/_rich_handler.py +0 -0
  211. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/logger/logger.py +0 -0
  212. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/logger/rolling.py +0 -0
  213. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/marketplace/__init__.py +0 -0
  214. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/marketplace/registration.py +0 -0
  215. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/marketplace/registry.py +0 -0
  216. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/marketplace/types.py +0 -0
  217. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/mcp/__init__.py +0 -0
  218. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/mcp/client.py +0 -0
  219. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/mcp/config.py +0 -0
  220. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/mcp/definition.py +0 -0
  221. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/mcp/exceptions.py +0 -0
  222. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/mcp/oauth.py +0 -0
  223. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/mcp/tool.py +0 -0
  224. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/mcp/utils.py +0 -0
  225. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/observability/__init__.py +0 -0
  226. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/observability/laminar.py +0 -0
  227. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/observability/utils.py +0 -0
  228. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/__init__.py +0 -0
  229. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/discovery.py +0 -0
  230. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/fetch.py +0 -0
  231. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/format/__init__.py +0 -0
  232. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/format/agent_plugins.py +0 -0
  233. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/format/agent_plugins_mcp.py +0 -0
  234. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/format/base.py +0 -0
  235. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/format/claude_code.py +0 -0
  236. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/format/schemas/mcp-1.0.0.schema.json +0 -0
  237. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/format/schemas/plugin-1.0.0.schema.json +0 -0
  238. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/installed.py +0 -0
  239. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/loader.py +0 -0
  240. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/paths.py +0 -0
  241. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/plugin.py +0 -0
  242. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/source.py +0 -0
  243. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/strict_fetch.py +0 -0
  244. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/plugin/types.py +0 -0
  245. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/profiles/__init__.py +0 -0
  246. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/profiles/agent_profile_store.py +0 -0
  247. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/profiles/profile_refs.py +0 -0
  248. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/profiles/seed.py +0 -0
  249. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/py.typed +0 -0
  250. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/secret/__init__.py +0 -0
  251. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/secret/secrets.py +0 -0
  252. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/__init__.py +0 -0
  253. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/_shell_ast.py +0 -0
  254. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/analyzer.py +0 -0
  255. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/confirmation_policy.py +0 -0
  256. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/defense_in_depth/__init__.py +0 -0
  257. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/defense_in_depth/pattern.py +0 -0
  258. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/defense_in_depth/policy_rails.py +0 -0
  259. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/defense_in_depth/shell_semantics.py +0 -0
  260. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/defense_in_depth/utils.py +0 -0
  261. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/ensemble.py +0 -0
  262. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/grayswan/__init__.py +0 -0
  263. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/grayswan/analyzer.py +0 -0
  264. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/grayswan/utils.py +0 -0
  265. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/llm_analyzer.py +0 -0
  266. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/risk.py +0 -0
  267. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/shell_parser.py +0 -0
  268. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/toolshield_helpers.py +0 -0
  269. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/security/toolshield_llm_analyzer.py +0 -0
  270. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/settings/api_models.py +0 -0
  271. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/settings/metadata.py +0 -0
  272. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/settings/model.py +0 -0
  273. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/skills/__init__.py +0 -0
  274. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/skills/exceptions.py +0 -0
  275. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/skills/execute.py +0 -0
  276. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/skills/fetch.py +0 -0
  277. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/skills/installed.py +0 -0
  278. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/skills/trigger.py +0 -0
  279. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/skills/types.py +0 -0
  280. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/subagent/__init__.py +0 -0
  281. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/subagent/load.py +0 -0
  282. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/subagent/registry.py +0 -0
  283. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/subagent/schema.py +0 -0
  284. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/subagent/scope.py +0 -0
  285. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/testing/__init__.py +0 -0
  286. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/testing/test_llm.py +0 -0
  287. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/__init__.py +0 -0
  288. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/builtins/__init__.py +0 -0
  289. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/builtins/classify_and_switch_llm.py +0 -0
  290. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/builtins/finish.py +0 -0
  291. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/builtins/invoke_skill.py +0 -0
  292. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/builtins/switch_llm.py +0 -0
  293. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/builtins/think.py +0 -0
  294. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/builtins/vision_inspect.py +0 -0
  295. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/client_tool.py +0 -0
  296. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/defaults.py +0 -0
  297. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/registry.py +0 -0
  298. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/schema.py +0 -0
  299. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/spec.py +0 -0
  300. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/tool/tool.py +0 -0
  301. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/__init__.py +0 -0
  302. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/async_executor.py +0 -0
  303. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/async_utils.py +0 -0
  304. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/cipher.py +0 -0
  305. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/command.py +0 -0
  306. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/datetime.py +0 -0
  307. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/deprecation.py +0 -0
  308. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/files.py +0 -0
  309. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/github.py +0 -0
  310. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/json.py +0 -0
  311. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/models.py +0 -0
  312. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/paging.py +0 -0
  313. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/path.py +0 -0
  314. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/pydantic_secrets.py +0 -0
  315. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/redact.py +0 -0
  316. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/truncate.py +0 -0
  317. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/utils/visualize.py +0 -0
  318. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/__init__.py +0 -0
  319. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/base.py +0 -0
  320. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/local.py +0 -0
  321. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/models.py +0 -0
  322. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/remote/__init__.py +0 -0
  323. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/remote/async_remote_workspace.py +0 -0
  324. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/remote/base.py +0 -0
  325. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/remote/remote_workspace_mixin.py +0 -0
  326. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/repo.py +0 -0
  327. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands/sdk/workspace/workspace.py +0 -0
  328. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands_sdk.egg-info/dependency_links.txt +0 -0
  329. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands_sdk.egg-info/requires.txt +0 -0
  330. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/openhands_sdk.egg-info/top_level.txt +0 -0
  331. {openhands_sdk-1.58.2 → openhands_sdk-1.59.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhands-sdk
3
- Version: 1.58.2
3
+ Version: 1.59.0
4
4
  Summary: OpenHands SDK - Core functionality for building AI agents
5
5
  Project-URL: Source, https://github.com/OpenHands/software-agent-sdk
6
6
  Project-URL: Homepage, https://github.com/OpenHands/software-agent-sdk
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from importlib.metadata import PackageNotFoundError, version
4
- from typing import TYPE_CHECKING, Any
5
4
 
6
5
  from openhands.sdk.agent import (
7
6
  Agent,
@@ -109,10 +108,6 @@ from openhands.sdk.workspace import (
109
108
  )
110
109
 
111
110
 
112
- if TYPE_CHECKING:
113
- from openhands.sdk.settings.acp_providers import ACPModelOption
114
-
115
-
116
111
  try:
117
112
  __version__ = version("openhands-sdk")
118
113
  except PackageNotFoundError:
@@ -167,7 +162,6 @@ __all__ = [
167
162
  "ACP_PROVIDERS",
168
163
  "ACPAgentSettings",
169
164
  "ACPFileSecretSpec",
170
- "ACPModelOption",
171
165
  "ACPProviderInfo",
172
166
  "AgentSettingsBase",
173
167
  "AgentSettingsConfig",
@@ -211,11 +205,3 @@ __all__ = [
211
205
  "page_iterator",
212
206
  "__version__",
213
207
  ]
214
-
215
-
216
- def __getattr__(name: str) -> Any:
217
- if name == "ACPModelOption":
218
- from openhands.sdk.settings.acp_providers import _deprecated_model_option
219
-
220
- return _deprecated_model_option()
221
- raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -23,6 +23,7 @@ import inspect
23
23
  import json
24
24
  import os
25
25
  import re
26
+ import shutil
26
27
  import threading
27
28
  import time
28
29
  import uuid
@@ -106,6 +107,7 @@ from openhands.sdk.agent.acp_file_credentials import (
106
107
  write_secret_file,
107
108
  )
108
109
  from openhands.sdk.agent.acp_models import ACPModelInfo
110
+ from openhands.sdk.agent.acp_skills import render_skill_catalog, write_skill_files
109
111
  from openhands.sdk.agent.acp_tracing import ACPTurnTrace
110
112
  from openhands.sdk.agent.base import AgentBase
111
113
  from openhands.sdk.agent.stream_context import StreamContext
@@ -2020,6 +2022,10 @@ class ACPAgent(AgentBase):
2020
2022
  # "installed" — already in subprocess history; skip further injection
2021
2023
  _suffix_install_state: str = PrivateAttr(default="unused")
2022
2024
  _installed_suffix: str | None = PrivateAttr(default=None)
2025
+ # Skills written to disk and listed by path in the prompt, not inlined.
2026
+ _skill_locations: dict[str, Path] = PrivateAttr(default_factory=dict)
2027
+ _skills_root: Path | None = PrivateAttr(default=None)
2028
+ _additional_directories_supported: bool = PrivateAttr(default=False)
2023
2029
  _restart_session_on_next_turn: bool = PrivateAttr(default=False)
2024
2030
  # Stream identity for the turn in flight; see stream_context.py. Held on
2025
2031
  # the agent rather than threaded through the finalizers because the ACP
@@ -2331,6 +2337,7 @@ class ACPAgent(AgentBase):
2331
2337
 
2332
2338
  # Render the suffix once, pulling secrets from the conversation's
2333
2339
  # secret_registry to match the regular Agent's get_dynamic_context().
2340
+ self._materialize_skills(state)
2334
2341
  self._installed_suffix = self._render_suffix(state)
2335
2342
  # A prior session id means we may be resuming; used by ``truly_resumed``
2336
2343
  # below to decide whether the model state reported for this launch
@@ -2493,6 +2500,25 @@ class ACPAgent(AgentBase):
2493
2500
  )
2494
2501
  )
2495
2502
 
2503
+ def _skill_directories(self) -> list[str] | None:
2504
+ if self._skills_root and self._additional_directories_supported:
2505
+ return [str(self._skills_root)]
2506
+ return None
2507
+
2508
+ def _materialize_skills(self, state: ConversationState) -> None:
2509
+ self._skill_locations = {}
2510
+ self._skills_root = None
2511
+ context = self.agent_context
2512
+ if context is None or not context.skills:
2513
+ return
2514
+ _, available = context._partition_skills()
2515
+ if not available:
2516
+ return
2517
+ root = self._acp_file_secret_dir(state, "skills")
2518
+ shutil.rmtree(root, ignore_errors=True)
2519
+ self._skill_locations = write_skill_files(root, available)
2520
+ self._skills_root = root
2521
+
2496
2522
  def _render_suffix(self, state: ConversationState) -> str | None:
2497
2523
  """Render the system suffix once, including secrets from the registry.
2498
2524
 
@@ -2533,12 +2559,23 @@ class ACPAgent(AgentBase):
2533
2559
  if not secret_infos:
2534
2560
  return None
2535
2561
  agent_context = AgentContext(current_datetime=None)
2536
- elif agent_context.secrets:
2562
+ listed = [s for s in agent_context.skills if s.name in self._skill_locations]
2563
+ if listed:
2564
+ agent_context = agent_context.model_copy(
2565
+ update={"skills": [s for s in agent_context.skills if s not in listed]}
2566
+ )
2567
+ if agent_context.secrets:
2537
2568
  # The registry already carries these (and their descriptions), so
2538
2569
  # clear the agent_context copy to advertise from the registry alone
2539
2570
  # rather than re-merging a redundant second source.
2540
2571
  agent_context = agent_context.model_copy(update={"secrets": {}})
2541
- return agent_context.to_acp_prompt_context(additional_secret_infos=secret_infos)
2572
+ rendered = agent_context.to_acp_prompt_context(
2573
+ additional_secret_infos=secret_infos
2574
+ )
2575
+ if not listed:
2576
+ return rendered
2577
+ catalog = render_skill_catalog(listed, self._skill_locations)
2578
+ return f"{rendered}\n\n{catalog}" if rendered else catalog
2542
2579
 
2543
2580
  def _present_file_secret_names(self, state: ConversationState) -> set[str]:
2544
2581
  """Reserved file-content secret names supplied for this conversation.
@@ -3161,6 +3198,15 @@ class ACPAgent(AgentBase):
3161
3198
  if init_response.agent_capabilities is not None
3162
3199
  else None
3163
3200
  )
3201
+ session_caps = (
3202
+ init_response.agent_capabilities.session_capabilities
3203
+ if init_response.agent_capabilities is not None
3204
+ else None
3205
+ )
3206
+ self._additional_directories_supported = (
3207
+ session_caps is not None
3208
+ and session_caps.additional_directories is not None
3209
+ )
3164
3210
  acp_mcp_servers = _mcp_config_to_acp_servers(self.mcp_config, mcp_caps)
3165
3211
  if acp_mcp_servers:
3166
3212
  logger.info(
@@ -3256,6 +3302,7 @@ class ACPAgent(AgentBase):
3256
3302
  load_response = await conn.load_session(
3257
3303
  cwd=working_dir,
3258
3304
  session_id=prior_session_id,
3305
+ additional_directories=self._skill_directories(),
3259
3306
  mcp_servers=acp_mcp_servers,
3260
3307
  )
3261
3308
  session_id = prior_session_id
@@ -3301,6 +3348,7 @@ class ACPAgent(AgentBase):
3301
3348
  session_meta = build_session_model_meta(agent_name, self.acp_model)
3302
3349
  response = await conn.new_session(
3303
3350
  cwd=working_dir,
3351
+ additional_directories=self._skill_directories(),
3304
3352
  mcp_servers=acp_mcp_servers,
3305
3353
  **session_meta,
3306
3354
  )
@@ -0,0 +1,67 @@
1
+ """Expose skills to any ACP agent as files plus a catalog that points at them."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+ from collections.abc import Sequence
8
+ from html import escape
9
+ from pathlib import Path
10
+
11
+ from openhands.sdk.skills import Skill
12
+
13
+
14
+ def _description(skill: Skill) -> str:
15
+ if skill.description:
16
+ return skill.description
17
+ return next(
18
+ (line.strip() for line in skill.content.splitlines() if line.strip()),
19
+ skill.name,
20
+ )
21
+
22
+
23
+ def _directory_name(name: str, taken: set[str]) -> str:
24
+ base = re.sub(r"[^A-Za-z0-9._-]+", "-", name).strip(".-") or "skill"
25
+ candidate, suffix = base, 2
26
+ while candidate.lower() in taken:
27
+ candidate, suffix = f"{base}-{suffix}", suffix + 1
28
+ taken.add(candidate.lower())
29
+ return candidate
30
+
31
+
32
+ def write_skill_files(root: Path, skills: Sequence[Skill]) -> dict[str, Path]:
33
+ """Write each skill to ``root/<dir>/SKILL.md``; return its path by skill name."""
34
+ locations: dict[str, Path] = {}
35
+ taken: set[str] = set()
36
+ for skill in skills:
37
+ skill_dir = root / _directory_name(skill.name, taken)
38
+ skill_dir.mkdir(parents=True, exist_ok=True)
39
+ path = skill_dir / "SKILL.md"
40
+ name = json.dumps(skill.name, ensure_ascii=False)
41
+ description = json.dumps(_description(skill)[:1000], ensure_ascii=False)
42
+ path.write_text(
43
+ f"---\nname: {name}\ndescription: {description}\n---\n\n{skill.content}\n",
44
+ encoding="utf-8",
45
+ )
46
+ locations[skill.name] = path
47
+ return locations
48
+
49
+
50
+ def render_skill_catalog(skills: Sequence[Skill], locations: dict[str, Path]) -> str:
51
+ entries = [
52
+ " <skill>\n"
53
+ f" <name>{escape(skill.name)}</name>\n"
54
+ f" <description>{escape(_description(skill)[:1024])}</description>\n"
55
+ f" <location>{escape(str(locations[skill.name]))}</location>\n"
56
+ " </skill>"
57
+ for skill in skills
58
+ if skill.name in locations
59
+ ]
60
+ return (
61
+ "<SKILLS>\n"
62
+ "The following skills are available. When one is relevant to the task, "
63
+ "read its SKILL.md at the given location with your file-reading tool "
64
+ "and follow its instructions.\n\n"
65
+ "<available_skills>\n" + "\n".join(entries) + "\n</available_skills>\n"
66
+ "</SKILLS>"
67
+ )
@@ -52,6 +52,7 @@ from openhands.sdk.event.condenser import (
52
52
  )
53
53
  from openhands.sdk.event.error_classification import AGENT_OUTCOME
54
54
  from openhands.sdk.event.security import SecurityAnalysisEvent
55
+ from openhands.sdk.event.types import ROOT_PARENT_ID, EventID
55
56
  from openhands.sdk.llm import (
56
57
  LLM,
57
58
  ImageContent,
@@ -561,12 +562,25 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
561
562
  )
562
563
  return
563
564
 
565
+ # A damaged event log can lose the SystemPromptEvent's file. The first
566
+ # surviving event still names it as its parent, or names the copy that an
567
+ # earlier resume re-created under the same id.
568
+ restored_prompt_id: EventID | None = None
569
+ if has_user_message:
570
+ parent_id = state.events[0].parent_id
571
+ if parent_id is not None and parent_id in state.events:
572
+ parent = state.events[state.events.get_index(parent_id)]
573
+ if isinstance(parent, SystemPromptEvent):
574
+ return
575
+ elif parent_id not in (None, ROOT_PARENT_ID):
576
+ restored_prompt_id = parent_id
577
+
564
578
  # Assert: A user message should never appear before the system prompt.
565
579
  #
566
580
  # NOTE: This is a best-effort check based on the first few events only.
567
581
  # Remote conversations can include a ConversationStateUpdateEvent near the
568
582
  # start, so we scan a small prefix window.
569
- if has_user_message:
583
+ if has_user_message and restored_prompt_id is None:
570
584
  event_types = [type(e).__name__ for e in prefix_events]
571
585
  logger.error(
572
586
  f"init_state: User message found in prefix before SystemPromptEvent! "
@@ -598,7 +612,29 @@ class Agent(CriticMixin, ResponseDispatchMixin, AgentBase):
598
612
  if dynamic_context
599
613
  else None,
600
614
  )
601
- on_event(event)
615
+ if restored_prompt_id is None:
616
+ on_event(event)
617
+ return
618
+
619
+ # Re-create it as an explicit root under its old id, so the surviving
620
+ # history hangs off it again; appending must not move HEAD onto it.
621
+ logger.warning(
622
+ "SystemPromptEvent '%s' is missing from the event log (its file was "
623
+ "lost); re-creating it as the root of the surviving history. "
624
+ "conversation_id=%s",
625
+ restored_prompt_id,
626
+ state.id,
627
+ )
628
+ head = state.active_branch(limit=1)
629
+ head_is_empty = state.head_is_empty
630
+ on_event(
631
+ event.model_copy(
632
+ update={"id": restored_prompt_id, "parent_id": ROOT_PARENT_ID}
633
+ )
634
+ )
635
+ state.leaf_event_id = head[-1].id if head else None
636
+ state.head_is_empty = head_is_empty
637
+ state.rebuild_view()
602
638
 
603
639
  def get_dynamic_context(self, state: ConversationState) -> str | None:
604
640
  """Get dynamic context for the system prompt, including secrets from state.
@@ -73,6 +73,7 @@ class Conversation:
73
73
  token_callbacks: list[ConversationTokenCallbackType] | None = None,
74
74
  hook_config: HookConfig | None = None,
75
75
  max_iteration_per_run: int = 500,
76
+ max_budget_per_run: float | None = None,
76
77
  stuck_detection: bool = True,
77
78
  stuck_detection_thresholds: (
78
79
  StuckDetectionThresholds | Mapping[str, int] | None
@@ -103,6 +104,7 @@ class Conversation:
103
104
  token_callbacks: list[ConversationTokenCallbackType] | None = None,
104
105
  hook_config: HookConfig | None = None,
105
106
  max_iteration_per_run: int = 500,
107
+ max_budget_per_run: float | None = None,
106
108
  stuck_detection: bool = True,
107
109
  stuck_detection_thresholds: (
108
110
  StuckDetectionThresholds | Mapping[str, int] | None
@@ -133,6 +135,7 @@ class Conversation:
133
135
  token_callbacks: list[ConversationTokenCallbackType] | None = None,
134
136
  hook_config: HookConfig | None = None,
135
137
  max_iteration_per_run: int = 500,
138
+ max_budget_per_run: float | None = None,
136
139
  stuck_detection: bool = True,
137
140
  stuck_detection_thresholds: (
138
141
  StuckDetectionThresholds | Mapping[str, int] | None
@@ -200,6 +203,7 @@ class Conversation:
200
203
  token_callbacks=token_callbacks,
201
204
  hook_config=hook_config,
202
205
  max_iteration_per_run=max_iteration_per_run,
206
+ max_budget_per_run=max_budget_per_run,
203
207
  stuck_detection=stuck_detection,
204
208
  stuck_detection_thresholds=stuck_detection_thresholds,
205
209
  visualizer=visualizer,
@@ -223,6 +227,7 @@ class Conversation:
223
227
  token_callbacks=token_callbacks,
224
228
  hook_config=hook_config,
225
229
  max_iteration_per_run=max_iteration_per_run,
230
+ max_budget_per_run=max_budget_per_run,
226
231
  stuck_detection=stuck_detection,
227
232
  stuck_detection_thresholds=stuck_detection_thresholds,
228
233
  visualizer=visualizer,
@@ -1,4 +1,5 @@
1
1
  # state.py
2
+ import bisect
2
3
  import operator
3
4
  from collections.abc import Callable, Iterator
4
5
  from contextlib import AbstractContextManager, nullcontext
@@ -26,6 +27,9 @@ LOCK_TIMEOUT_SECONDS = 30
26
27
  # ``exists()``. The count lives in the name because ``FileStore.read`` is cached.
27
28
  LENGTH_MARKER_PATTERN: Final[str] = ".eventlog-len-{length}.marker"
28
29
 
30
+ # Holes listed by name in the lost-files warning; the rest are only counted.
31
+ MAX_REPORTED_HOLES: Final[int] = 5
32
+
29
33
  # ROOT_PARENT_ID now lives in event.types (single source of truth); it is used
30
34
  # below in _effective_parent_id and re-exported here so existing
31
35
  # ``from event_store import ROOT_PARENT_ID`` importers keep working.
@@ -55,6 +59,13 @@ class EventLog(EventsListBase):
55
59
  self._dir = dir_path
56
60
  self._id_to_idx: dict[EventID, int] = {}
57
61
  self._idx_to_id: dict[int, EventID] = {}
62
+ # On-disk index per log index, recorded only where the two differ (behind
63
+ # a lost event file). The on-disk index names the file and is the
64
+ # event's durable seq; new files are allocated after the highest one.
65
+ self._disk_idx: dict[int, int] = {}
66
+ self._next_disk_idx = 0
67
+ self._reported_lost = 0
68
+ self._reported_missing: set[EventID] = set()
58
69
  self._event_cache: dict[int, Event] = {}
59
70
  self._lock_path = f"{dir_path}/{LOCK_FILE_NAME}"
60
71
  self._write_guard = None
@@ -81,6 +92,28 @@ class EventLog(EventsListBase):
81
92
  raise IndexError("Event index out of range")
82
93
  return self._idx_to_id[idx]
83
94
 
95
+ def get_seq(self, idx: int) -> int:
96
+ """Return the durable sequence number of the event at ``idx``.
97
+
98
+ The seq is the event's on-disk index (the ``{idx:05d}`` filename
99
+ component). It equals ``idx`` unless an earlier event file was lost, and
100
+ unlike ``idx`` it never changes across a reload, so it is the value to
101
+ hand out as a resume cursor.
102
+ """
103
+ if idx < 0:
104
+ idx += self._length
105
+ if idx < 0 or idx >= self._length:
106
+ raise IndexError("Event index out of range")
107
+ return self._disk_idx.get(idx, idx)
108
+
109
+ def index_after_seq(self, seq: int) -> int:
110
+ """Return the index of the first event whose seq is above ``seq``.
111
+
112
+ Returns ``len(self)`` when there is none. Seqs grow with the index, so
113
+ this is a binary search.
114
+ """
115
+ return bisect.bisect_right(range(self._length), seq, key=self.get_seq)
116
+
84
117
  def __contains__(self, item: object) -> bool:
85
118
  """Whether the log contains a given event id or ``Event`` (by id).
86
119
 
@@ -97,15 +130,30 @@ class EventLog(EventsListBase):
97
130
 
98
131
  Legacy events predating the tree have no ``parent_id``; they fall back to
99
132
  the linear chain (event ``idx - 1``) so old conversations load unbranched
100
- with no disk rewrite.
133
+ with no disk rewrite. An explicit parent whose file was lost is bridged
134
+ the same way, so the branch keeps its surviving ancestors (the system
135
+ prompt above all) instead of ending at the hole.
101
136
  """
102
- if event.parent_id == ROOT_PARENT_ID:
137
+ parent_id = event.parent_id
138
+ if parent_id == ROOT_PARENT_ID:
103
139
  return None # explicit root (feature-created root at idx > 0)
104
- if event.parent_id is not None:
105
- return event.parent_id # explicit (new events)
140
+ if parent_id is not None:
141
+ if parent_id in self._id_to_idx:
142
+ return parent_id # explicit (new events)
143
+ self._report_missing(parent_id)
106
144
  if idx == 0:
107
145
  return None # genuine root
108
- return self.get_id(idx - 1) # legacy linear chain (back-compat)
146
+ return self.get_id(idx - 1) # legacy linear chain, or bridge over a hole
147
+
148
+ def _report_missing(self, event_id: EventID) -> None:
149
+ """Warn once per id that the log references an event whose file is gone."""
150
+ if event_id in self._reported_missing:
151
+ return
152
+ self._reported_missing.add(event_id)
153
+ logger.warning(
154
+ "Event '%s' is missing from the event log (its file was lost).",
155
+ event_id,
156
+ )
109
157
 
110
158
  def path_to_root(
111
159
  self, leaf_id: EventID | None, limit: int | None = None
@@ -114,8 +162,9 @@ class EventLog(EventsListBase):
114
162
 
115
163
  ``leaf_id=None`` yields ``[]``. ``limit`` keeps only the last ``limit``
116
164
  events (walking back from the leaf), so callers wanting a recent window
117
- stay O(limit) instead of O(branch). Raises ValueError on a cycle, KeyError
118
- if ``leaf_id`` or an ancestor is missing.
165
+ stay O(limit) instead of O(branch). Raises ValueError on a cycle. An
166
+ ancestor whose file was lost is bridged (see ``_effective_parent_id``);
167
+ a ``leaf_id`` missing from the log yields ``[]``.
119
168
  """
120
169
  chain: list[Event] = []
121
170
  seen: set[EventID] = set()
@@ -124,7 +173,11 @@ class EventLog(EventsListBase):
124
173
  if cur_id in seen:
125
174
  raise ValueError(f"Cycle in event tree at {cur_id}")
126
175
  seen.add(cur_id)
127
- idx = self.get_index(cur_id)
176
+ idx = self._id_to_idx.get(cur_id)
177
+ if idx is None:
178
+ # Only the leaf can be unknown: ancestors are bridged.
179
+ self._report_missing(cur_id)
180
+ break
128
181
  chain.append(evt := self[idx])
129
182
  cur_id = self._effective_parent_id(idx, evt)
130
183
  return chain[::-1]
@@ -227,9 +280,15 @@ class EventLog(EventsListBase):
227
280
  nullcontext() if self._write_guard is None else self._write_guard()
228
281
  )
229
282
  idx = self._length
283
+ # Never reuse an on-disk index: behind a lost file the dense
284
+ # index lags it, and a shared index makes the reload order
285
+ # depend on the event ids.
286
+ disk_idx = max(idx, self._next_disk_idx)
230
287
  with write_guard:
231
- target_path = self._path(idx, event_id=evt_id)
232
- self._fs.write(target_path, payload)
288
+ self._fs.write(self._file_path(disk_idx, evt_id), payload)
289
+ if disk_idx != idx:
290
+ self._disk_idx[idx] = disk_idx
291
+ self._next_disk_idx = disk_idx + 1
233
292
  self._idx_to_id[idx] = evt_id
234
293
  self._id_to_idx[evt_id] = idx
235
294
  self._event_cache[idx] = event
@@ -292,17 +351,25 @@ class EventLog(EventsListBase):
292
351
  Preserves existing index mappings and only scans new events.
293
352
  """
294
353
  # Preserve existing mappings
295
- existing_idx_to_id = dict(self._idx_to_id)
354
+ existing = [
355
+ (idx, evt_id, self._disk_idx.get(idx, idx))
356
+ for idx, evt_id in self._idx_to_id.items()
357
+ ]
296
358
 
297
359
  # Re-scan to pick up new events
298
360
  scanned_length = self._scan_and_build_index()
299
361
 
300
- # Restore any mappings that were lost (e.g., for non-UUID event IDs)
301
- for idx, evt_id in existing_idx_to_id.items():
302
- if idx not in self._idx_to_id:
303
- self._idx_to_id[idx] = evt_id
304
- if evt_id not in self._id_to_idx:
305
- self._id_to_idx[evt_id] = idx
362
+ # Restore mappings the scan cannot see (e.g., for non-UUID event IDs).
363
+ # Only whole pairs, and only when neither side was rescanned: behind a
364
+ # lost file a rescanned index can belong to a different event.
365
+ for idx, evt_id, disk_idx in existing:
366
+ if idx in self._idx_to_id or evt_id in self._id_to_idx:
367
+ continue
368
+ self._idx_to_id[idx] = evt_id
369
+ self._id_to_idx[evt_id] = idx
370
+ if disk_idx != idx:
371
+ self._disk_idx[idx] = disk_idx
372
+ self._next_disk_idx = max(self._next_disk_idx, disk_idx + 1)
306
373
 
307
374
  # Use the higher of scanned length or disk_length
308
375
  self._length = max(scanned_length, disk_length)
@@ -311,58 +378,57 @@ class EventLog(EventsListBase):
311
378
  return self._length
312
379
 
313
380
  def _path(self, idx: int, *, event_id: EventID | None = None) -> str:
314
- return f"{self._dir}/{
315
- EVENT_FILE_PATTERN.format(
316
- idx=idx, event_id=event_id or self._idx_to_id[idx]
317
- )
318
- }"
381
+ return self._file_path(
382
+ self._disk_idx.get(idx, idx), event_id or self._idx_to_id[idx]
383
+ )
384
+
385
+ def _file_path(self, disk_idx: int, event_id: EventID) -> str:
386
+ name = EVENT_FILE_PATTERN.format(idx=disk_idx, event_id=event_id)
387
+ return f"{self._dir}/{name}"
388
+
389
+ def _reset_index(self) -> None:
390
+ self._id_to_idx.clear()
391
+ self._idx_to_id.clear()
392
+ self._disk_idx.clear()
393
+ self._event_cache.clear()
394
+ self._next_disk_idx = 0
319
395
 
320
396
  def _scan_and_build_index(self) -> int:
321
397
  try:
322
398
  paths = self._fs.list(self._dir)
323
399
  except Exception:
324
- self._id_to_idx.clear()
325
- self._idx_to_id.clear()
326
- self._event_cache.clear()
400
+ self._reset_index()
327
401
  return 0
328
402
 
329
- by_idx: dict[int, EventID] = {}
403
+ found: list[tuple[int, EventID]] = []
330
404
  for p in paths:
331
405
  name = posix_path_name(p)
332
406
  m = EVENT_NAME_RE.match(name)
333
407
  if m:
334
- idx = int(m.group("idx"))
335
- evt_id = m.group("event_id")
336
- by_idx[idx] = evt_id
408
+ found.append((int(m.group("idx")), m.group("event_id")))
337
409
  elif name.startswith("."):
338
410
  # Our own sidecars (lock file, count marker), not event files.
339
411
  continue
340
412
  else:
341
413
  logger.warning(f"Unrecognized event file name: {name}")
342
414
 
343
- if not by_idx:
344
- self._id_to_idx.clear()
345
- self._idx_to_id.clear()
346
- self._event_cache.clear()
347
- return 0
348
-
349
- n = 0
350
- while True:
351
- if n not in by_idx:
352
- if any(i > n for i in by_idx.keys()):
353
- logger.warning(
354
- "Event index gap detected: "
355
- f"expect next index {n} but got {sorted(by_idx.keys())}"
356
- )
357
- break
358
- n += 1
359
-
360
- self._id_to_idx.clear()
361
- self._idx_to_id.clear()
362
- self._event_cache.clear()
363
- for i in range(n):
364
- evt_id = by_idx[i]
415
+ self._reset_index()
416
+ # Index every event file in on-disk order instead of stopping at the
417
+ # first missing index, so one lost file does not make every later event
418
+ # unknown. The dense index closes the hole; the on-disk index stays the
419
+ # event's file name and seq.
420
+ found.sort()
421
+ holes: list[tuple[int, int]] = []
422
+ lost = 0
423
+ for i, (disk_idx, evt_id) in enumerate(found):
424
+ if disk_idx > self._next_disk_idx:
425
+ lost += disk_idx - self._next_disk_idx
426
+ if len(holes) < MAX_REPORTED_HOLES:
427
+ holes.append((self._next_disk_idx, disk_idx - 1))
428
+ self._next_disk_idx = disk_idx + 1
365
429
  self._idx_to_id[i] = evt_id
430
+ if disk_idx != i:
431
+ self._disk_idx[i] = disk_idx
366
432
  if evt_id in self._id_to_idx:
367
433
  logger.warning(
368
434
  f"Duplicate event ID '{evt_id}' found during scan. "
@@ -371,4 +437,17 @@ class EventLog(EventsListBase):
371
437
  )
372
438
  else:
373
439
  self._id_to_idx[evt_id] = i
374
- return n
440
+ if lost != self._reported_lost:
441
+ self._reported_lost = lost
442
+ if lost:
443
+ listed = ", ".join(str(a) if a == b else f"{a}-{b}" for a, b in holes)
444
+ logger.warning(
445
+ "Event log %s is missing %d event file(s) (on-disk index %s%s); "
446
+ "the %d surviving events stay readable.",
447
+ self._dir,
448
+ lost,
449
+ listed,
450
+ ", ..." if lost > sum(b - a + 1 for a, b in holes) else "",
451
+ len(found),
452
+ )
453
+ return len(found)