devops-bot-sdk 1.6.64__tar.gz → 1.6.66__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 (229) hide show
  1. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/__init__.py +2 -2
  4. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/activity/models.py +52 -0
  5. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/activity/redact.py +4 -1
  6. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/activity/shipper.py +28 -8
  7. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/activity/spool.py +19 -5
  8. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_activity_core.py +1 -1
  9. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_activity_pipeline.py +61 -0
  10. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/README.md +0 -0
  11. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/devops_bot_sdk.egg-info/SOURCES.txt +0 -0
  12. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  13. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  14. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/devops_bot_sdk.egg-info/requires.txt +0 -0
  15. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  16. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/pyproject.toml +0 -0
  17. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/activity/__init__.py +0 -0
  18. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/activity/classifier.py +0 -0
  19. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/activity/collectors/__init__.py +0 -0
  20. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/activity/collectors/common.py +0 -0
  21. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/activity/collectors/linux_auditd.py +0 -0
  22. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/activity/collectors/macos_eslogger.py +0 -0
  23. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/activity/collectors/windows_eventlog.py +0 -0
  24. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/activity/os_setup.py +0 -0
  25. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/activity/recorder.py +0 -0
  26. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/activity/settings.py +0 -0
  27. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agentd.py +0 -0
  28. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/__init__.py +0 -0
  29. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/activity.py +0 -0
  30. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/builder.py +0 -0
  31. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/chat.py +0 -0
  32. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/connectors.py +0 -0
  33. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/engines/__init__.py +0 -0
  34. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/engines/claude_code.py +0 -0
  35. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/engines/http.py +0 -0
  36. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/engines/mcp.py +0 -0
  37. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/engines/n8n.py +0 -0
  38. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/engines/shell.py +0 -0
  39. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/integrations.py +0 -0
  40. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/inventory.py +0 -0
  41. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/library/__init__.py +0 -0
  42. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/manifest.py +0 -0
  43. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/prompt_studio.py +0 -0
  44. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/prompt_template.py +0 -0
  45. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/prompt_vars.py +0 -0
  46. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/run_log.py +0 -0
  47. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/schedules.py +0 -0
  48. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/secrets.py +0 -0
  49. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/ticket_run.py +0 -0
  50. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/usage.py +0 -0
  51. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/agents/varstore.py +0 -0
  52. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/browser_profile.py +0 -0
  53. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/cli.py +0 -0
  54. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/client.py +0 -0
  55. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/collectors/__init__.py +0 -0
  56. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/collectors/files.py +0 -0
  57. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/collectors/process.py +0 -0
  58. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/collectors/screenshot.py +0 -0
  59. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/config.py +0 -0
  60. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/crucial.py +0 -0
  61. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/custom_agents.py +0 -0
  62. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/departments.py +0 -0
  63. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/design_verify/__init__.py +0 -0
  64. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/design_verify/assets.py +0 -0
  65. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/design_verify/bootstrap.py +0 -0
  66. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/design_verify/browser.py +0 -0
  67. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/design_verify/compare.py +0 -0
  68. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/design_verify/loop.py +0 -0
  69. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/estimator.py +0 -0
  70. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/exceptions.py +0 -0
  71. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/firewall.py +0 -0
  72. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/formats.py +0 -0
  73. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/git_ops.py +0 -0
  74. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/github_fetch.py +0 -0
  75. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/github_ingest.py +0 -0
  76. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/graph_recon.py +0 -0
  77. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/graphify.py +0 -0
  78. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/hooks/__init__.py +0 -0
  79. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/hooks/crucial_guard.py +0 -0
  80. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/intake.py +0 -0
  81. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ipc/__init__.py +0 -0
  82. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ipc/electron_bridge.py +0 -0
  83. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ipc/handlers.py +0 -0
  84. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/journal.py +0 -0
  85. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/local_exec.py +0 -0
  86. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/memory_guard.py +0 -0
  87. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/models/__init__.py +0 -0
  88. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/models/envelope.py +0 -0
  89. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/models/requests.py +0 -0
  90. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/models/responses.py +0 -0
  91. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/models/snapshots.py +0 -0
  92. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/phase_timer.py +0 -0
  93. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/plan_run.py +0 -0
  94. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/platform_compat.py +0 -0
  95. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/prd_progress.py +0 -0
  96. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/proof_readers.py +0 -0
  97. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/providers.py +0 -0
  98. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/py.typed +0 -0
  99. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/resume.py +0 -0
  100. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/router.py +0 -0
  101. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/run_auto.py +0 -0
  102. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/scheduler.py +0 -0
  103. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/self_report.py +0 -0
  104. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/self_report_local.py +0 -0
  105. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/sse.py +0 -0
  106. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/swagger.py +0 -0
  107. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/task_file.py +0 -0
  108. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/task_types.py +0 -0
  109. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/test.py +0 -0
  110. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/test_pipeline.py +0 -0
  111. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ticket_fields.py +0 -0
  112. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/tickets.py +0 -0
  113. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/tiers/__init__.py +0 -0
  114. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/tiers/mini_agent.py +0 -0
  115. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/__init__.py +0 -0
  116. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/api.py +0 -0
  117. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/dashboard.py +0 -0
  118. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/health.py +0 -0
  119. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/ip_allowlist.py +0 -0
  120. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/jobs.py +0 -0
  121. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/models.py +0 -0
  122. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/runner.py +0 -0
  123. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/security.py +0 -0
  124. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/server.py +0 -0
  125. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/service.py +0 -0
  126. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/session.py +0 -0
  127. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/setup_manager.py +0 -0
  128. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/static/agents.html +0 -0
  129. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/static/app.js +0 -0
  130. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/static/chat.html +0 -0
  131. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/static/custom-agents.html +0 -0
  132. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/static/default-agents.html +0 -0
  133. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/static/index.html +0 -0
  134. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/static/report.html +0 -0
  135. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/static/runs.html +0 -0
  136. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/static/schedules.html +0 -0
  137. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/static/setup.html +0 -0
  138. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/static/styles.css +0 -0
  139. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/static/tickets.html +0 -0
  140. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/ui/swagger.py +0 -0
  141. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/updater.py +0 -0
  142. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/url_access.py +0 -0
  143. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/vendor/aidraftsman-cli-0.1.0.tgz +0 -0
  144. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/vps_health.py +0 -0
  145. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/sdk/write_hook.py +0 -0
  146. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/setup.cfg +0 -0
  147. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_activity_collectors.py +0 -0
  148. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_activity_macos.py +0 -0
  149. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_activity_os_setup.py +0 -0
  150. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_agent_activity.py +0 -0
  151. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_agent_activity_tracking.py +0 -0
  152. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_agent_chat_features.py +0 -0
  153. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_agent_crud_cli.py +0 -0
  154. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_agent_listing.py +0 -0
  155. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_agent_prompt_async.py +0 -0
  156. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_agent_prompt_models.py +0 -0
  157. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_agent_run_ticket.py +0 -0
  158. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_agent_schedules.py +0 -0
  159. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_agent_workspace_settings.py +0 -0
  160. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_agents_connectors_builder.py +0 -0
  161. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_allowed_tools_config.py +0 -0
  162. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_auto_agent_dispatch.py +0 -0
  163. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_browser_profile.py +0 -0
  164. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_cheap_tier.py +0 -0
  165. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_client_jira_projects.py +0 -0
  166. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_config_token_diagnostics.py +0 -0
  167. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_configure_token_owner.py +0 -0
  168. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_console_encoding.py +0 -0
  169. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_crash_recovery.py +0 -0
  170. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_departments.py +0 -0
  171. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_done_transition.py +0 -0
  172. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_epic_decompose_run.py +0 -0
  173. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_epic_inherited_agent_id.py +0 -0
  174. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_epic_session_isolation.py +0 -0
  175. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_estimator.py +0 -0
  176. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_fallback_cli.py +0 -0
  177. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_firewall_open_on_update.py +0 -0
  178. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_formats.py +0 -0
  179. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_git_action.py +0 -0
  180. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_github_fetch.py +0 -0
  181. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_github_ingest.py +0 -0
  182. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_graph_recon.py +0 -0
  183. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_graphify_build.py +0 -0
  184. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_graphify_hook_install.py +0 -0
  185. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_health.py +0 -0
  186. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_intake_job_outcome.py +0 -0
  187. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_intake_plan_review.py +0 -0
  188. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_journal.py +0 -0
  189. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_local_exec_provider.py +0 -0
  190. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_mcp_route_contract.py +0 -0
  191. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_memory_guard.py +0 -0
  192. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_phase_timer.py +0 -0
  193. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_plan_new_command.py +0 -0
  194. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_plan_run_activity.py +0 -0
  195. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_pr_identity.py +0 -0
  196. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_prd_artifact.py +0 -0
  197. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_project_path_resolution.py +0 -0
  198. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_proof_readers.py +0 -0
  199. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_providers.py +0 -0
  200. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_recon_session_isolation.py +0 -0
  201. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_resume.py +0 -0
  202. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_router.py +0 -0
  203. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_run_auto_agent_only.py +0 -0
  204. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_run_outputs.py +0 -0
  205. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_run_verdict_is_honest.py +0 -0
  206. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_schedule_api.py +0 -0
  207. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_schedule_outputs_and_plans.py +0 -0
  208. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_scheduler.py +0 -0
  209. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_self_report.py +0 -0
  210. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_service_token_warning.py +0 -0
  211. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_stale_panel_restart.py +0 -0
  212. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_swagger_command.py +0 -0
  213. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_task_file_and_plan_run.py +0 -0
  214. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_task_file_format_doc.py +0 -0
  215. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_ticket_cli.py +0 -0
  216. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_ticket_fields.py +0 -0
  217. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_ticket_run_resolve_by_id.py +0 -0
  218. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_ticket_speed_fixes.py +0 -0
  219. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_tier_flag_parity.py +0 -0
  220. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_ui_gate.py +0 -0
  221. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_ui_lifespan.py +0 -0
  222. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_update_timer.py +0 -0
  223. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_updater_extras.py +0 -0
  224. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_updater_pep668.py +0 -0
  225. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_updater_reports_the_truth.py +0 -0
  226. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_url_access.py +0 -0
  227. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_vps_health.py +0 -0
  228. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_windows_loop_identity.py +0 -0
  229. {devops_bot_sdk-1.6.64 → devops_bot_sdk-1.6.66}/tests/test_write_hook.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.6.64
3
+ Version: 1.6.66
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devops-bot-sdk
3
- Version: 1.6.64
3
+ Version: 1.6.66
4
4
  Summary: DevOps Bot Desktop SDK — thin client for the AgentOS Electron desktop app
5
5
  Author: noumanaziz2128
6
6
  License-Expression: LicenseRef-Proprietary
@@ -1,6 +1,6 @@
1
1
  """AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
2
2
 
3
- Version: 1.6.64
3
+ Version: 1.6.66
4
4
 
5
5
  Public surface:
6
6
  BackendClient.from_config() — create client from ~/.agentos/config.toml
@@ -34,7 +34,7 @@ Rules:
34
34
  - All data egress through submit_webhook only
35
35
  """
36
36
 
37
- __version__ = "1.6.64" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
37
+ __version__ = "1.6.66" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
38
38
  __author__ = "AgentOS" # sdk.client.SDK_VERSION and the UI server version all read this.
39
39
 
40
40
  from sdk.client import BackendClient
@@ -16,6 +16,7 @@ retried at any hop is stored once.
16
16
  """
17
17
  from __future__ import annotations
18
18
 
19
+ import json
19
20
  import os
20
21
  import sys
21
22
  import time
@@ -140,6 +141,57 @@ def _wire(obj: Any) -> dict[str, Any]:
140
141
  return out
141
142
 
142
143
 
144
+ #: String caps the relay validates (devops-bot-ai ``modules/vps_activity/schemas.py``).
145
+ #: A value over its cap is a 422, and the shipper drops a row the relay refuses — so
146
+ #: one over-long path or command line used to cost the whole event. Clamp instead.
147
+ WIRE_MAX_CHARS: dict[str, dict[str, int]] = {
148
+ "event": {
149
+ "action": 100,
150
+ "osUser": 255,
151
+ "processName": 255,
152
+ "command": 20_000,
153
+ "target": 4096,
154
+ "agentRunId": 128,
155
+ "sessionKey": 128,
156
+ },
157
+ "session": {
158
+ "osUser": 255,
159
+ "remoteIp": 45,
160
+ "agentRunId": 128,
161
+ "initiatedByKey": 128,
162
+ },
163
+ }
164
+ _CLAMP_MARK = "…"
165
+ #: Free-form dicts the relay does not cap. Bounded here so that, with the string caps
166
+ #: above, no single row can outgrow a request (2 MB gzipped / 10 MB raw) — a row that
167
+ #: could would be 413'd on its own and dropped.
168
+ WIRE_DICT_FIELDS = ("metadata", "actorReasons")
169
+ MAX_DICT_FIELD_BYTES = 64 * 1024
170
+
171
+
172
+ def fit_wire_limits(kind: str, wire: dict[str, Any]) -> dict[str, Any]:
173
+ """``wire`` with every over-long field shortened to fit the relay's limits."""
174
+ out = wire
175
+
176
+ def _set(name: str, value: Any) -> None:
177
+ nonlocal out
178
+ if out is wire:
179
+ out = dict(wire)
180
+ out[name] = value
181
+
182
+ for name, cap in WIRE_MAX_CHARS.get(kind, {}).items():
183
+ value = wire.get(name)
184
+ if isinstance(value, str) and len(value) > cap:
185
+ _set(name, value[: cap - len(_CLAMP_MARK)] + _CLAMP_MARK)
186
+ for name in WIRE_DICT_FIELDS:
187
+ value = wire.get(name)
188
+ if isinstance(value, dict):
189
+ size = len(json.dumps(value, separators=(",", ":"), default=str))
190
+ if size > MAX_DICT_FIELD_BYTES:
191
+ _set(name, {"truncated": True, "originalBytes": size})
192
+ return out
193
+
194
+
143
195
  # ── models ───────────────────────────────────────────────────────────────────
144
196
 
145
197
 
@@ -72,6 +72,7 @@ _PATTERNS: list[tuple[re.Pattern[str], str]] = [
72
72
 
73
73
  #: Hard cap so a pathological command line can't bloat the spool.
74
74
  MAX_COMMAND_CHARS = 20_000
75
+ TRUNCATED = "…[truncated]"
75
76
 
76
77
 
77
78
  def redact_command(command: str | None) -> str | None:
@@ -82,5 +83,7 @@ def redact_command(command: str | None) -> str | None:
82
83
  for pattern, repl in _PATTERNS:
83
84
  out = pattern.sub(repl, out)
84
85
  if len(out) > MAX_COMMAND_CHARS:
85
- out = out[:MAX_COMMAND_CHARS] + "…[truncated]"
86
+ # The marker counts toward the cap: the relay 422s anything longer, and the
87
+ # shipper drops a refused event rather than shipping it truncated.
88
+ out = out[: MAX_COMMAND_CHARS - len(TRUNCATED)] + TRUNCATED
86
89
  return out
@@ -7,8 +7,9 @@ Delivery rules — a row leaves the spool ONLY when the relay answered 202 (or i
7
7
  provably undeliverable):
8
8
 
9
9
  2xx delivered → delete the chunk
10
- network error, 5xx, 408, 429 retry (short backoff); if still failing, keep
10
+ network error, 5xx, 408 retry (short backoff); if still failing, keep
11
11
  everything and try again at the next flush
12
+ 429 wait out the relay's 60 s window, then retry
12
13
  401, 426 token / SDK version needs an operator → keep, stop
13
14
  this flush (retrying in a tight loop helps nobody)
14
15
  any other 4xx the relay READ the chunk and refused it. Dropping all
@@ -16,8 +17,9 @@ provably undeliverable):
16
17
  so the chunk is BISECTED until the offending row is
17
18
  isolated; only that single row is dropped.
18
19
 
19
- A flush drains the whole spool, chunk by chunk (≤500 events / ≤1 MB each), because
20
- five minutes of full OS auditing is routinely thousands of events.
20
+ A flush drains the whole spool, chunk by chunk (≤500 events / ≤4 MB each), paced under
21
+ the relay's 60-requests-a-minute budget, because five minutes of full OS auditing is
22
+ routinely thousands of events.
21
23
  """
22
24
  from __future__ import annotations
23
25
 
@@ -36,9 +38,18 @@ logger = logging.getLogger("sdk.activity.shipper")
36
38
  RETRY_DELAYS_S = (2.0, 8.0)
37
39
  #: Safety cap on chunks per flush so one flush can't run away.
38
40
  MAX_CHUNKS_PER_FLUSH = 200
41
+ #: The relay allows 60 batches per minute per token. Firing chunks back-to-back blew
42
+ #: through that after ~60 chunks; the 2 s / 8 s retries cannot outlast a 60 s window,
43
+ #: so the flush gave up and a busy host never caught up. Pace below the budget...
44
+ MIN_POST_GAP_S = 1.1
45
+ #: ...and if a 429 still lands (another process on the same token), wait the window
46
+ #: out rather than abandoning the flush.
47
+ RATE_LIMIT_WAIT_S = 61.0
48
+ MAX_RATE_LIMIT_WAITS = 3
39
49
 
40
50
  _NEEDS_OPERATOR = {401, 426}
41
51
  _TRANSIENT_4XX = {408, 429}
52
+ _RATE_LIMITED = 429
42
53
 
43
54
  Sleep = Callable[[float], Awaitable[Any]]
44
55
 
@@ -73,20 +84,27 @@ def _verdict(status: int | None) -> str:
73
84
 
74
85
  async def _attempt(sender: Sender, chunk: Chunk, sleep: Sleep) -> str:
75
86
  """One chunk with short in-flush retries → ``ok`` / ``drop`` / ``stop`` / ``later``."""
76
- for delay in (0.0, *RETRY_DELAYS_S):
77
- if delay:
78
- await sleep(delay)
87
+ retry_delays = list(RETRY_DELAYS_S)
88
+ rate_limit_waits = 0
89
+ while True:
79
90
  try:
80
91
  status: int | None = await sender.post_vps_activity_batch(chunk.to_wire())
81
92
  except Exception as exc: # noqa: BLE001 — httpx errors, DNS, TLS, ...
82
93
  logger.warning("activity: ship failed (%s); will retry", exc)
83
94
  status = None
95
+ if status == _RATE_LIMITED and rate_limit_waits < MAX_RATE_LIMIT_WAITS:
96
+ rate_limit_waits += 1
97
+ logger.info("activity: relay rate-limited; waiting %.0fs", RATE_LIMIT_WAIT_S)
98
+ await sleep(RATE_LIMIT_WAIT_S)
99
+ continue
84
100
  verdict = _verdict(status)
85
101
  if verdict != "retry":
86
102
  if verdict == "stop":
87
103
  logger.warning("activity: relay answered %s — needs operator attention", status)
88
104
  return verdict
89
- return "later"
105
+ if not retry_delays:
106
+ return "later"
107
+ await sleep(retry_delays.pop(0))
90
108
 
91
109
 
92
110
  async def _deliver(
@@ -135,10 +153,12 @@ async def flush_once(
135
153
  from sdk.client import BackendClient
136
154
 
137
155
  sender = BackendClient.from_config()
138
- for _ in range(max_chunks):
156
+ for i in range(max_chunks):
139
157
  chunk = spool.next_chunk()
140
158
  if not chunk:
141
159
  break
160
+ if i:
161
+ await sleep(MIN_POST_GAP_S)
142
162
  if not await _deliver(spool, sender, chunk, result, sleep):
143
163
  break
144
164
  else:
@@ -23,14 +23,21 @@ from dataclasses import dataclass, field
23
23
  from pathlib import Path
24
24
  from typing import Any
25
25
 
26
+ from sdk.activity.models import fit_wire_limits
27
+
26
28
  logger = logging.getLogger("sdk.activity.spool")
27
29
 
28
30
  #: Rows kept at most. ~200k events is days of a busy host; beyond it, shed oldest.
29
31
  DEFAULT_MAX_ROWS = 200_000
30
- #: Wire limits per request — the relay and the gateway enforce the same cap.
32
+ #: Wire limits per request — the relay and the gateway enforce the same row cap.
31
33
  MAX_EVENTS_PER_CHUNK = 500
32
34
  MAX_SESSIONS_PER_CHUNK = 500
33
- MAX_CHUNK_BYTES = 1_000_000
35
+ #: Uncompressed JSON per request. The relay takes 10 MB decompressed / 2 MB gzipped;
36
+ #: audit JSON gzips well past 2:1, so 4 MB stays under both. A 1 MB cap meant a host
37
+ #: with long command lines sent small chunks, ran into the relay's per-minute request
38
+ #: budget and fell behind until the spool shed its oldest rows. A chunk that still
39
+ #: comes back 413 is bisected by the shipper, so this is a target, not a cliff.
40
+ MAX_CHUNK_BYTES = 4_000_000
34
41
 
35
42
  _SCHEMA = """
36
43
  CREATE TABLE IF NOT EXISTS spool (
@@ -110,7 +117,10 @@ class Spool:
110
117
  with self._lock:
111
118
  self._db.execute(
112
119
  "INSERT OR IGNORE INTO spool (kind, key, payload) VALUES ('event', ?, ?)",
113
- (wire["eventId"], json.dumps(wire, separators=(",", ":"))),
120
+ (
121
+ wire["eventId"],
122
+ json.dumps(fit_wire_limits("event", wire), separators=(",", ":")),
123
+ ),
114
124
  )
115
125
  self._trim()
116
126
 
@@ -127,7 +137,10 @@ class Spool:
127
137
  "INSERT INTO spool (kind, key, payload) VALUES ('session', ?, ?) "
128
138
  "ON CONFLICT (kind, key) DO UPDATE SET "
129
139
  "payload = excluded.payload, rev = rev + 1",
130
- (wire["externalKey"], json.dumps(wire, separators=(",", ":"))),
140
+ (
141
+ wire["externalKey"],
142
+ json.dumps(fit_wire_limits("session", wire), separators=(",", ":")),
143
+ ),
131
144
  )
132
145
  self._trim()
133
146
 
@@ -176,7 +189,8 @@ class Spool:
176
189
  break
177
190
  if chunk.rows and size + len(payload) > max_bytes:
178
191
  break
179
- chunk.rows.append(Row(seq, rev, kind, json.loads(payload)))
192
+ # Re-fit on the way out too: rows queued by an older SDK predate the clamp.
193
+ chunk.rows.append(Row(seq, rev, kind, fit_wire_limits(kind, json.loads(payload))))
180
194
  n_events += kind == "event"
181
195
  n_sessions += kind == "session"
182
196
  size += len(payload)
@@ -176,7 +176,7 @@ class TestRedaction:
176
176
  def test_is_length_capped(self):
177
177
  out = redact_command("a" * (MAX_COMMAND_CHARS + 500))
178
178
  assert out.endswith("…[truncated]")
179
- assert len(out) < MAX_COMMAND_CHARS + 50
179
+ assert len(out) == MAX_COMMAND_CHARS # the relay 422s anything longer
180
180
 
181
181
 
182
182
  class TestClassifier:
@@ -66,6 +66,45 @@ class TestSpool:
66
66
  _sess("S1", actorType="HUMAN", endedAt="2026-10-09T12:00:00.000Z")
67
67
  ]
68
68
 
69
+ def test_over_long_fields_are_clamped_to_the_relay_caps_not_left_to_be_refused(self, spool):
70
+ from sdk.activity.models import WIRE_MAX_CHARS
71
+
72
+ spool.add_event(_ev("E1", command="c" * 30_000, target="/t" * 5000, processName="p" * 999))
73
+ spool.add_session(_sess("S1", osUser="u" * 999))
74
+ chunk = spool.next_chunk()
75
+ ev, sess = chunk.events[0], chunk.sessions[0]
76
+ for name in ("command", "target", "processName"):
77
+ assert len(ev[name]) == WIRE_MAX_CHARS["event"][name]
78
+ assert len(sess["osUser"]) == WIRE_MAX_CHARS["session"]["osUser"]
79
+ assert ev["action"] == "exec" # short fields untouched
80
+
81
+ def test_an_oversized_metadata_dict_is_replaced_so_the_row_still_ships(self, spool):
82
+ from sdk.activity.models import MAX_DICT_FIELD_BYTES
83
+
84
+ spool.add_event(_ev("E1", metadata={"blob": "x" * (MAX_DICT_FIELD_BYTES * 2)}))
85
+ spool.add_event(_ev("E2", metadata={"small": 1}))
86
+ events = spool.next_chunk().events
87
+ assert events[0]["metadata"]["truncated"] is True
88
+ assert events[0]["metadata"]["originalBytes"] > MAX_DICT_FIELD_BYTES
89
+ assert events[1]["metadata"] == {"small": 1}
90
+
91
+ def test_a_single_worst_case_row_fits_comfortably_in_one_request(self, spool):
92
+ from sdk.activity.models import MAX_DICT_FIELD_BYTES
93
+
94
+ spool.add_event(_ev(
95
+ "E1", command="c" * 99_999, target="t" * 99_999, processName="p" * 9_999,
96
+ metadata={"blob": "x" * (MAX_DICT_FIELD_BYTES * 10)},
97
+ ))
98
+ (row,) = spool.next_chunk().rows
99
+ import json as _json
100
+
101
+ assert len(_json.dumps(row.wire)) < 200_000 # vs 2 MB gzipped / 10 MB raw at the relay
102
+
103
+ def test_default_chunk_byte_cap_stays_under_the_relay_body_limit(self):
104
+ from sdk.activity import spool as spool_mod
105
+
106
+ assert 1_000_000 < spool_mod.MAX_CHUNK_BYTES < 10 * 1024 * 1024
107
+
69
108
  def test_rows_come_back_in_arrival_order_session_first(self, spool):
70
109
  spool.add_session(_sess("S1"))
71
110
  spool.add_event(_ev("E1"))
@@ -231,6 +270,28 @@ class TestShipper:
231
270
  result = _run(shipper.flush_once(spool, sender, sleep=Sleeps()))
232
271
  assert result.shipped_rows == 1
233
272
 
273
+ def test_a_429_waits_out_the_relay_window_instead_of_giving_up(self, spool):
274
+ spool.add_event(_ev("E1"))
275
+ sender = FakeSender(script=[429, 429, 429, 202])
276
+ sleeps = Sleeps()
277
+ result = _run(shipper.flush_once(spool, sender, sleep=sleeps))
278
+ assert result.shipped_rows == 1 and result.stopped is None
279
+ assert sleeps.delays == [shipper.RATE_LIMIT_WAIT_S] * 3
280
+
281
+ def test_endless_429s_keep_the_data_for_the_next_flush(self, spool):
282
+ spool.add_event(_ev("E1"))
283
+ sender = FakeSender(default=429)
284
+ result = _run(shipper.flush_once(spool, sender, sleep=Sleeps()))
285
+ assert result.stopped == "later" and spool.count() == 1
286
+
287
+ def test_chunks_are_paced_under_the_relay_request_budget(self, spool):
288
+ for i in range(1200):
289
+ spool.add_event(_ev(f"E{i:05d}"))
290
+ sleeps = Sleeps()
291
+ _run(shipper.flush_once(spool, FakeSender(), sleep=sleeps))
292
+ assert sleeps.delays == [shipper.MIN_POST_GAP_S] * 2 # 3 chunks, 2 gaps
293
+ assert shipper.MIN_POST_GAP_S * 60 > 60 # stays under 60 requests a minute
294
+
234
295
  @pytest.mark.parametrize("status", [401, 426])
235
296
  def test_token_or_version_problems_stop_without_retrying_or_dropping(self, spool, status):
236
297
  spool.add_event(_ev("E1"))