devops-bot-sdk 1.7.48__tar.gz → 1.7.50__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 (307) hide show
  1. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/PKG-INFO +1 -1
  2. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/devops_bot_sdk.egg-info/PKG-INFO +1 -1
  3. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/devops_bot_sdk.egg-info/SOURCES.txt +2 -0
  4. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/__init__.py +2 -2
  5. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/help_data/areas.json +9 -9
  6. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/help_data/releases.json +10 -0
  7. devops_bot_sdk-1.7.50/sdk/help_data/snapshot.json +17 -0
  8. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ipc/handlers.py +20 -4
  9. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/local_exec.py +146 -10
  10. devops_bot_sdk-1.7.50/tests/test_fallback_folder_changes.py +39 -0
  11. devops_bot_sdk-1.7.50/tests/test_fallback_parity.py +127 -0
  12. devops_bot_sdk-1.7.48/sdk/help_data/snapshot.json +0 -17
  13. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/README.md +0 -0
  14. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/devops_bot_sdk.egg-info/dependency_links.txt +0 -0
  15. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/devops_bot_sdk.egg-info/entry_points.txt +0 -0
  16. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/devops_bot_sdk.egg-info/requires.txt +0 -0
  17. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/devops_bot_sdk.egg-info/top_level.txt +0 -0
  18. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/pyproject.toml +0 -0
  19. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/__init__.py +0 -0
  20. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/classifier.py +0 -0
  21. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/collectors/__init__.py +0 -0
  22. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/collectors/common.py +0 -0
  23. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/collectors/linux_auditd.py +0 -0
  24. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/collectors/macos_eslogger.py +0 -0
  25. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/collectors/windows_eventlog.py +0 -0
  26. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/models.py +0 -0
  27. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/os_setup.py +0 -0
  28. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/recorder.py +0 -0
  29. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/redact.py +0 -0
  30. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/settings.py +0 -0
  31. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/shipper.py +0 -0
  32. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/activity/spool.py +0 -0
  33. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agent_sharing.py +0 -0
  34. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agentd.py +0 -0
  35. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/__init__.py +0 -0
  36. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/activity.py +0 -0
  37. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/builder.py +0 -0
  38. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/chat.py +0 -0
  39. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/connectors.py +0 -0
  40. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/engines/__init__.py +0 -0
  41. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/engines/claude_code.py +0 -0
  42. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/engines/http.py +0 -0
  43. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/engines/mcp.py +0 -0
  44. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/engines/n8n.py +0 -0
  45. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/engines/shell.py +0 -0
  46. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/integrations.py +0 -0
  47. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/inventory.py +0 -0
  48. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/library/__init__.py +0 -0
  49. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/manifest.py +0 -0
  50. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/prompt_studio.py +0 -0
  51. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/prompt_template.py +0 -0
  52. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/prompt_vars.py +0 -0
  53. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/run_log.py +0 -0
  54. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/schedules.py +0 -0
  55. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/secrets.py +0 -0
  56. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/ticket_run.py +0 -0
  57. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/usage.py +0 -0
  58. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/agents/varstore.py +0 -0
  59. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/browser_profile.py +0 -0
  60. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/claude_usage.py +0 -0
  61. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/cli.py +0 -0
  62. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/client.py +0 -0
  63. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/collectors/__init__.py +0 -0
  64. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/collectors/files.py +0 -0
  65. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/collectors/process.py +0 -0
  66. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/collectors/screenshot.py +0 -0
  67. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/config.py +0 -0
  68. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/crucial.py +0 -0
  69. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/custom_agents.py +0 -0
  70. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/deferred_tickets.py +0 -0
  71. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/departments.py +0 -0
  72. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/design_verify/__init__.py +0 -0
  73. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/design_verify/assets.py +0 -0
  74. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/design_verify/bootstrap.py +0 -0
  75. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/design_verify/browser.py +0 -0
  76. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/design_verify/compare.py +0 -0
  77. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/design_verify/loop.py +0 -0
  78. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/estimator.py +0 -0
  79. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/exceptions.py +0 -0
  80. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/fallback_handoff.py +0 -0
  81. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/faqs.py +0 -0
  82. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/firewall.py +0 -0
  83. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/formats.py +0 -0
  84. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/git_ops.py +0 -0
  85. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/github_fetch.py +0 -0
  86. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/github_ingest.py +0 -0
  87. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/graph_recon.py +0 -0
  88. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/graphify.py +0 -0
  89. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/help_actions.py +0 -0
  90. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/help_data/commands.json +0 -0
  91. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/help_data/help.json +0 -0
  92. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/help_data/pages.json +0 -0
  93. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/helpdesk.py +0 -0
  94. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/helpdesk_kb.md +0 -0
  95. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/hooks/__init__.py +0 -0
  96. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/hooks/crucial_guard.py +0 -0
  97. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/install_marker.py +0 -0
  98. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/instructions_template.py +0 -0
  99. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/intake.py +0 -0
  100. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ipc/__init__.py +0 -0
  101. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ipc/electron_bridge.py +0 -0
  102. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/journal.py +0 -0
  103. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/log_colors.py +0 -0
  104. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/memory_guard.py +0 -0
  105. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/model_policy.py +0 -0
  106. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/models/__init__.py +0 -0
  107. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/models/envelope.py +0 -0
  108. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/models/requests.py +0 -0
  109. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/models/responses.py +0 -0
  110. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/models/snapshots.py +0 -0
  111. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/phase_timer.py +0 -0
  112. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/plan_run.py +0 -0
  113. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/platform_compat.py +0 -0
  114. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/prd_progress.py +0 -0
  115. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/proof_readers.py +0 -0
  116. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/providers.py +0 -0
  117. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/py.typed +0 -0
  118. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/resume.py +0 -0
  119. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/router.py +0 -0
  120. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/run_auto.py +0 -0
  121. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/schedule_tickets.py +0 -0
  122. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/schedule_timeline.py +0 -0
  123. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/scheduler.py +0 -0
  124. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/scope_plan.py +0 -0
  125. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/self_report.py +0 -0
  126. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/self_report_local.py +0 -0
  127. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/sse.py +0 -0
  128. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/swagger.py +0 -0
  129. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/task_file.py +0 -0
  130. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/task_types.py +0 -0
  131. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/test.py +0 -0
  132. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/test_env.py +0 -0
  133. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/test_pipeline.py +0 -0
  134. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ticket_board.py +0 -0
  135. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ticket_bulk.py +0 -0
  136. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ticket_fields.py +0 -0
  137. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ticket_plan.py +0 -0
  138. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ticket_pools.py +0 -0
  139. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/tickets.py +0 -0
  140. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/tiers/__init__.py +0 -0
  141. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/tiers/mini_agent.py +0 -0
  142. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/__init__.py +0 -0
  143. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/api.py +0 -0
  144. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/dashboard.py +0 -0
  145. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/health.py +0 -0
  146. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/ip_allowlist.py +0 -0
  147. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/jobs.py +0 -0
  148. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/models.py +0 -0
  149. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/runner.py +0 -0
  150. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/security.py +0 -0
  151. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/server.py +0 -0
  152. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/service.py +0 -0
  153. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/session.py +0 -0
  154. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/setup_manager.py +0 -0
  155. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/agents.html +0 -0
  156. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/app.js +0 -0
  157. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/chat.html +0 -0
  158. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/custom-agents.html +0 -0
  159. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/default-agents.html +0 -0
  160. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/faqs.html +0 -0
  161. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/help.html +0 -0
  162. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/index.html +0 -0
  163. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/report.html +0 -0
  164. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/runs.html +0 -0
  165. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/schedules.html +0 -0
  166. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/setup.html +0 -0
  167. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/styles.css +0 -0
  168. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/static/tickets.html +0 -0
  169. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/ui/swagger.py +0 -0
  170. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/updater.py +0 -0
  171. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/url_access.py +0 -0
  172. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/vps_health.py +0 -0
  173. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/vps_identity.py +0 -0
  174. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/sdk/write_hook.py +0 -0
  175. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/setup.cfg +0 -0
  176. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_activity_claude_runs.py +0 -0
  177. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_activity_collectors.py +0 -0
  178. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_activity_core.py +0 -0
  179. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_activity_live_tracking.py +0 -0
  180. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_activity_macos.py +0 -0
  181. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_activity_os_setup.py +0 -0
  182. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_activity_pipeline.py +0 -0
  183. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_activity.py +0 -0
  184. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_activity_tracking.py +0 -0
  185. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_chat_features.py +0 -0
  186. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_crud_cli.py +0 -0
  187. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_listing.py +0 -0
  188. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_prompt_async.py +0 -0
  189. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_prompt_models.py +0 -0
  190. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_run_ticket.py +0 -0
  191. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_schedules.py +0 -0
  192. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_sharing.py +0 -0
  193. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agent_workspace_settings.py +0 -0
  194. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_agents_connectors_builder.py +0 -0
  195. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_allowed_tools_config.py +0 -0
  196. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_approval_park.py +0 -0
  197. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_auto_agent_dispatch.py +0 -0
  198. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_auto_ticket_queue.py +0 -0
  199. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_backend_only_allowlist.py +0 -0
  200. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_browser_profile.py +0 -0
  201. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_cheap_tier.py +0 -0
  202. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_claude_usage_gate.py +0 -0
  203. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_client_jira_projects.py +0 -0
  204. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_config_token_diagnostics.py +0 -0
  205. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_configure_token_owner.py +0 -0
  206. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_console_encoding.py +0 -0
  207. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_crash_recovery.py +0 -0
  208. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_create_safe_retry.py +0 -0
  209. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_deferred_tickets.py +0 -0
  210. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_departments.py +0 -0
  211. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_done_transition.py +0 -0
  212. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_epic_decompose_run.py +0 -0
  213. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_epic_inherited_agent_id.py +0 -0
  214. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_epic_session_isolation.py +0 -0
  215. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_estimator.py +0 -0
  216. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_fallback_cli.py +0 -0
  217. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_fallback_cli_upgrade.py +0 -0
  218. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_fallback_rate_limit.py +0 -0
  219. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_faqs.py +0 -0
  220. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_firewall_open_on_update.py +0 -0
  221. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_formats.py +0 -0
  222. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_git_action.py +0 -0
  223. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_github_fetch.py +0 -0
  224. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_github_ingest.py +0 -0
  225. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_graph_off_by_default.py +0 -0
  226. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_graph_recon.py +0 -0
  227. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_graphify_build.py +0 -0
  228. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_graphify_hook_install.py +0 -0
  229. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_health.py +0 -0
  230. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_help_actions.py +0 -0
  231. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_help_attachments.py +0 -0
  232. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_help_questions.py +0 -0
  233. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_helpdesk.py +0 -0
  234. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_infographic_data.py +0 -0
  235. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_install_marker.py +0 -0
  236. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_instructions_template.py +0 -0
  237. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_intake_job_outcome.py +0 -0
  238. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_intake_plan_review.py +0 -0
  239. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_journal.py +0 -0
  240. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_limit_fallback_handoff.py +0 -0
  241. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_local_exec_provider.py +0 -0
  242. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_log_colors_backlog.py +0 -0
  243. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_mcp_route_contract.py +0 -0
  244. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_memory_guard.py +0 -0
  245. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_model_policy.py +0 -0
  246. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_panel_help.py +0 -0
  247. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_panel_ticket_schedules.py +0 -0
  248. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_parallel_tickets.py +0 -0
  249. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_phase_timer.py +0 -0
  250. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_plan_ahead.py +0 -0
  251. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_plan_new_command.py +0 -0
  252. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_plan_run_activity.py +0 -0
  253. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_pool_pr_modes.py +0 -0
  254. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_pr_identity.py +0 -0
  255. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_prd_artifact.py +0 -0
  256. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_project_path_resolution.py +0 -0
  257. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_proof_readers.py +0 -0
  258. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_providers.py +0 -0
  259. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_quiet_run_log.py +0 -0
  260. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_recon_session_isolation.py +0 -0
  261. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_resume.py +0 -0
  262. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_router.py +0 -0
  263. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_run_auto_agent_only.py +0 -0
  264. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_run_outputs.py +0 -0
  265. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_run_verdict_is_honest.py +0 -0
  266. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_schedule_api.py +0 -0
  267. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_schedule_outputs_and_plans.py +0 -0
  268. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_schedule_tickets.py +0 -0
  269. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_schedule_timeline.py +0 -0
  270. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_scheduler.py +0 -0
  271. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_scheduler_transient_retry.py +0 -0
  272. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_self_report.py +0 -0
  273. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_service_token_warning.py +0 -0
  274. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_stale_panel_restart.py +0 -0
  275. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_swagger_command.py +0 -0
  276. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_task_file_and_plan_run.py +0 -0
  277. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_task_file_format_doc.py +0 -0
  278. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_theme_switch.py +0 -0
  279. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_board.py +0 -0
  280. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_bulk.py +0 -0
  281. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_cli.py +0 -0
  282. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_detail_view.py +0 -0
  283. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_fields.py +0 -0
  284. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_plan.py +0 -0
  285. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_pools.py +0 -0
  286. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_run_resolve_by_id.py +0 -0
  287. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_schedule_and_agent.py +0 -0
  288. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ticket_speed_fixes.py +0 -0
  289. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_tier_flag_parity.py +0 -0
  290. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ui_bruteforce_guard.py +0 -0
  291. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ui_gate.py +0 -0
  292. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ui_lifespan.py +0 -0
  293. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_ui_no_native_dialogs.py +0 -0
  294. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_update_timer.py +0 -0
  295. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_updater_extras.py +0 -0
  296. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_updater_ignores_cwd.py +0 -0
  297. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_updater_pep668.py +0 -0
  298. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_updater_reports_the_truth.py +0 -0
  299. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_url_access.py +0 -0
  300. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_version_flag.py +0 -0
  301. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_vps_health.py +0 -0
  302. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_vps_only_reporting.py +0 -0
  303. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_windows_loop_identity.py +0 -0
  304. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_windows_panel_task.py +0 -0
  305. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_windows_stop_and_pidfile.py +0 -0
  306. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/tests/test_windows_task_paths.py +0 -0
  307. {devops_bot_sdk-1.7.48 → devops_bot_sdk-1.7.50}/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.7.48
3
+ Version: 1.7.50
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.7.48
3
+ Version: 1.7.50
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
@@ -210,6 +210,8 @@ tests/test_epic_session_isolation.py
210
210
  tests/test_estimator.py
211
211
  tests/test_fallback_cli.py
212
212
  tests/test_fallback_cli_upgrade.py
213
+ tests/test_fallback_folder_changes.py
214
+ tests/test_fallback_parity.py
213
215
  tests/test_fallback_rate_limit.py
214
216
  tests/test_faqs.py
215
217
  tests/test_firewall_open_on_update.py
@@ -1,6 +1,6 @@
1
1
  """AgentOS Desktop SDK — thin HTTPS/SSE client for the Electron app.
2
2
 
3
- Version: 1.7.48
3
+ Version: 1.7.50
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.7.48" # SINGLE SOURCE OF TRUTH — bump on every change; pyproject,
37
+ __version__ = "1.7.50" # 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
@@ -35,6 +35,15 @@
35
35
  "latest_pr": 183,
36
36
  "latest_date": "2026-10-28"
37
37
  },
38
+ {
39
+ "area": "AI fallback",
40
+ "changes_30d": 4,
41
+ "features_30d": 2,
42
+ "fixes_30d": 2,
43
+ "latest": "AI Draftsman fallback resumes, fails loudly when blocked, sees non-git edits",
44
+ "latest_pr": 192,
45
+ "latest_date": "2026-11-04"
46
+ },
38
47
  {
39
48
  "area": "Windows",
40
49
  "changes_30d": 4,
@@ -44,15 +53,6 @@
44
53
  "latest_pr": 165,
45
54
  "latest_date": "2026-10-24"
46
55
  },
47
- {
48
- "area": "AI fallback",
49
- "changes_30d": 3,
50
- "features_30d": 2,
51
- "fixes_30d": 1,
52
- "latest": "continue a limit-hit ticket on the AI Draftsman CLI with its context",
53
- "latest_pr": 164,
54
- "latest_date": "2026-10-24"
55
- },
56
56
  {
57
57
  "area": "Agents",
58
58
  "changes_30d": 2,
@@ -1,4 +1,14 @@
1
1
  [
2
+ {
3
+ "pr": 192,
4
+ "date": "2026-11-04",
5
+ "merged_at": "2026-11-04T14:03:12+05:00",
6
+ "type": "Fix",
7
+ "area": "AI fallback",
8
+ "title": "AI Draftsman fallback resumes, fails loudly when blocked, sees non-git edits",
9
+ "version": "1.7.50",
10
+ "url": "https://github.com/consultancy-outfit/DevOpsBot-SDK/pull/192"
11
+ },
2
12
  {
3
13
  "pr": 191,
4
14
  "date": "2026-11-03",
@@ -0,0 +1,17 @@
1
+ [
2
+ {
3
+ "version": "1.7.50",
4
+ "commit": "4f370e5",
5
+ "pushed_at": "2026-11-04T14:03:12+05:00",
6
+ "generated_at": "2026-11-04T09:04:20+00:00",
7
+ "commands": 60,
8
+ "commands_invalid": 0,
9
+ "commands_undocumented": 0,
10
+ "panel_pages": 11,
11
+ "tests": 2624,
12
+ "prs_7d": 10,
13
+ "prs_30d": 44,
14
+ "features_30d": 13,
15
+ "fixes_30d": 19
16
+ }
17
+ ]
@@ -3125,7 +3125,10 @@ async def _code_one_repo(
3125
3125
  and not result.get("auth_expired"):
3126
3126
  errored = not result.get("ok")
3127
3127
  # "did real work happen?" — actual Write/Edit count, not files merely read.
3128
- empty = result.get("ok") and _write_count == 0
3128
+ # `ad` (the fallback CLI) streams no tool events, so _write_count stays
3129
+ # 0 on it — its folder diff (files_changed) is the evidence there.
3130
+ empty = (result.get("ok") and _write_count == 0
3131
+ and not result.get("files_changed"))
3129
3132
  if not (errored or empty):
3130
3133
  break
3131
3134
  repair += 1
@@ -3300,8 +3303,13 @@ async def _code_one_repo(
3300
3303
  # have errored out.
3301
3304
  if not result.get("ok"):
3302
3305
  issues.append(f"run errored: {(result.get('error') or 'failed')[:160]}")
3303
- elif _write_count == 0:
3304
- issues.append("task produced no files/deliverables")
3306
+ elif _write_count == 0 and not result.get("files_changed"):
3307
+ # Say what the agent itself reported: "produced no files" alone gave
3308
+ # the operator nothing to act on (e.g. a fallback run that could not
3309
+ # run the script needed to edit a binary .xlsx).
3310
+ said = " ".join(str(result.get("result") or "").split())
3311
+ issues.append("task produced no files/deliverables"
3312
+ + (f" — agent said: {said[-240:]}" if said else ""))
3305
3313
  if issues:
3306
3314
  await send(Envelope(
3307
3315
  type="step_update", thread_id=thread_id,
@@ -3556,7 +3564,10 @@ async def handle_pipeline_approve(
3556
3564
  type=Envelope.TYPE_ERROR, thread_id=thread_id,
3557
3565
  data={"code": "LOCAL_EXEC_INCOMPLETE", "message": err[:300], "task_id": task_id},
3558
3566
  ).model_dump())
3559
- return
3567
+ # Tell the ticket loop too: it used to see None here, count the ticket as
3568
+ # carried through, and print "✓ completed — 1/1" under a ticket it had
3569
+ # just moved to Backlog.
3570
+ return {"status": "incomplete", "task_id": task_id, "error": err[:300]}
3560
3571
 
3561
3572
  # Surface deliberate no-ops so a skipped repo is transparent, not invisible.
3562
3573
  if out_of_scope:
@@ -5817,6 +5828,11 @@ async def _run_one_task_steps(
5817
5828
  send,
5818
5829
  )
5819
5830
 
5831
+ # Ran, but non-compliant (no deliverable, no push/PR …): the handler
5832
+ # already moved it to Backlog and said why — it was NOT carried through.
5833
+ if isinstance(outcome, dict) and outcome.get("status") == "incomplete":
5834
+ return "skipped"
5835
+
5820
5836
  # ── Claude CLI auth expired ────────────────────────────────────
5821
5837
  # The runner's `claude` login lapsed. Waiting won't fix it, so we
5822
5838
  # PAUSE (not drain-to-Backlog): keep the ticket In Progress, alert
@@ -1077,19 +1077,46 @@ async def _run_claude_cli(
1077
1077
  # AD_API_KEY in the env. `agentos fallback status --verify` checks it against
1078
1078
  # the server.
1079
1079
  #
1080
- # What it does NOT do: honour the PreToolUse approval guard, resume a Claude
1081
- # session, or stream progress events. So a resumed run never falls back (the
1082
- # prompt would be "continue" with no context behind it), and the permission
1083
- # mode defaults to acceptEdits — file edits apply, but a shell command that is
1084
- # not read-only is denied rather than run unguarded. Set
1080
+ # Sessions: an `ad` run's session id comes back as ``ad:<id>``, so every caller
1081
+ # that resumes a run (the repair loop, a usage-limit resume, recon follow-ups)
1082
+ # resumes it on `ad` with ``ad -p -r <id>`` — never on claude, which has no such
1083
+ # session. A CLAUDE session is still never resumed on `ad` (the prompt would be
1084
+ # "continue" with no context behind it).
1085
+ #
1086
+ # What it does NOT do yet: honour the PreToolUse approval guard or stream
1087
+ # progress events (that needs `ad` itself to grow `stream-json`, `--allowedTools`
1088
+ # and a pre-tool hook). So the permission mode defaults to acceptEdits — file
1089
+ # edits apply, but a shell command that is not read-only, and every web fetch,
1090
+ # is denied rather than run unguarded. A run that was denied what it needed and
1091
+ # produced nothing fails LOUDLY with the denied calls named (`ad` reports them
1092
+ # as `permission_denials`), instead of "ran fine, made nothing". Set
1085
1093
  # AGENTOS_FALLBACK_PERMISSION_MODE=bypass to let it run every command.
1086
1094
  #
1087
1095
  # Settings (process env or ~/.agentos/.env):
1096
+ # AGENTOS_ENGINE claude (default) | ad — `ad` runs every fresh
1097
+ # run on the AI Draftsman CLI first, for a box
1098
+ # with no Claude login at all
1088
1099
  # AGENTOS_FALLBACK_CLI path to the CLI (default: `ad` on PATH), or "off"
1089
1100
  # AGENTOS_FALLBACK_AGENT agent id/name for `--agent` (default: gateway's choice)
1101
+ # AGENTOS_FALLBACK_MODEL model slug for `--model` (overrides the agent)
1090
1102
  # AGENTOS_FALLBACK_PERMISSION_MODE default | acceptEdits | plan | bypass
1091
1103
 
1092
1104
  _FALLBACK_MODES = ("default", "acceptEdits", "plan", "bypass")
1105
+ #: Marks a session id as an `ad` one, so a resume goes back to `ad`.
1106
+ AD_SESSION_PREFIX = "ad:"
1107
+
1108
+
1109
+ def ad_session_id(session_id: str | None) -> str | None:
1110
+ """The raw `ad` session id inside ``ad:<id>``, or None for a claude session."""
1111
+ if session_id and session_id.startswith(AD_SESSION_PREFIX):
1112
+ return session_id[len(AD_SESSION_PREFIX):] or None
1113
+ return None
1114
+
1115
+
1116
+ def engine() -> str:
1117
+ """``"ad"`` when AGENTOS_ENGINE asks for the AI Draftsman CLI first, else ``"claude"``."""
1118
+ value = _setting("AGENTOS_ENGINE").lower()
1119
+ return "ad" if value in ("ad", "aidraftsman", "ai-draftsman", "fallback") else "claude"
1093
1120
 
1094
1121
 
1095
1122
  def _setting(key: str) -> str:
@@ -1440,6 +1467,48 @@ def _git_changed_files(path: Path) -> set[str] | None:
1440
1467
  return out
1441
1468
 
1442
1469
 
1470
+ # Folders a working-folder snapshot never looks in: VCS/tool state, caches and the
1471
+ # graphify index the SDK itself rebuilds — a change there is not a deliverable.
1472
+ _SNAPSHOT_SKIP_DIRS = {"node_modules", "graphify-out", "__pycache__", "venv", ".venv"}
1473
+ _SNAPSHOT_MAX_FILES = 20000
1474
+
1475
+
1476
+ def _folder_snapshot(path: Path) -> dict[str, tuple[int, int]] | None:
1477
+ """``{file: (mtime_ns, size)}`` for a plain (non-git) working folder.
1478
+
1479
+ `_git_changed_files` answers None outside a repo, which left a fallback run
1480
+ in a documentation / analyst folder with NO record of what it wrote — so a
1481
+ task that updated a workbook in place was judged "produced no files". This
1482
+ is the non-git equivalent. None when the folder is too big to walk cheaply.
1483
+ """
1484
+ out: dict[str, tuple[int, int]] = {}
1485
+ try:
1486
+ for root, dirs, files in os.walk(path):
1487
+ dirs[:] = [d for d in dirs if not d.startswith(".") and d not in _SNAPSHOT_SKIP_DIRS]
1488
+ for f in files:
1489
+ if f.startswith("~$"): # Office lock file, not a deliverable
1490
+ continue
1491
+ fp = os.path.join(root, f)
1492
+ try:
1493
+ st = os.stat(fp)
1494
+ except OSError:
1495
+ continue
1496
+ out[fp] = (st.st_mtime_ns, st.st_size)
1497
+ if len(out) > _SNAPSHOT_MAX_FILES:
1498
+ return None
1499
+ except OSError:
1500
+ return None
1501
+ return out
1502
+
1503
+
1504
+ def _snapshot_changes(before: dict[str, tuple[int, int]] | None,
1505
+ after: dict[str, tuple[int, int]] | None) -> list[str]:
1506
+ """Files created or modified between two `_folder_snapshot`s."""
1507
+ if before is None or after is None:
1508
+ return []
1509
+ return sorted(fp for fp, sig in after.items() if before.get(fp) != sig)
1510
+
1511
+
1443
1512
  # A 429 / RESOURCE_EXHAUSTED from the AI Draftsman gateway is a "slow down", not
1444
1513
  # a failure: `ad` (≥ 1.1.2) retries a few in-stream frames itself, but when it
1445
1514
  # gives up the whole run used to fail — a ticket went to Backlog, `agentos help`
@@ -1479,12 +1548,17 @@ async def run_fallback_cli(
1479
1548
  extra_env: dict[str, str] | None = None,
1480
1549
  tier: str | None = None,
1481
1550
  provider: str | None = None,
1551
+ resume_session_id: str | None = None,
1482
1552
  _upgraded: bool = False,
1483
1553
  _rl_attempt: int = 0,
1484
1554
  _rl_waited: float = 0.0,
1485
1555
  ) -> dict[str, Any]:
1486
1556
  """Run ``prompt`` on the AI Draftsman CLI. Same result shape as the claude run.
1487
1557
 
1558
+ ``resume_session_id`` is an ``ad:<id>`` from an earlier `ad` run: the prompt
1559
+ continues that conversation (``-r <id>``). The result's ``session_id`` is
1560
+ ``ad:<id>`` too, so the caller's repair / resume paths come back here.
1561
+
1488
1562
  An outdated `ad` fails its first request (its tool schema is rejected) before
1489
1563
  touching anything, so that one failure upgrades the CLI and runs again, once.
1490
1564
  A rate limit (429 / RESOURCE_EXHAUSTED) waits and runs again — see above.
@@ -1502,9 +1576,15 @@ async def run_fallback_cli(
1502
1576
  if mode not in _FALLBACK_MODES:
1503
1577
  mode = "acceptEdits"
1504
1578
  args = ["-p", "--output-format", "json", "--permission-mode", mode]
1579
+ fb_model = _setting("AGENTOS_FALLBACK_MODEL")
1505
1580
  agent = _setting("AGENTOS_FALLBACK_AGENT")
1506
- if agent:
1581
+ if fb_model:
1582
+ args += ["--model", fb_model]
1583
+ elif agent:
1507
1584
  args += ["--agent", agent]
1585
+ ad_resume = ad_session_id(resume_session_id)
1586
+ if ad_resume:
1587
+ args += ["-r", ad_resume]
1508
1588
  cmd = _fallback_argv(cli, *args)
1509
1589
 
1510
1590
  env = dict(os.environ)
@@ -1520,7 +1600,11 @@ async def run_fallback_cli(
1520
1600
  env["GIT_CONFIG_VALUE_0"] = "https://github.com/"
1521
1601
 
1522
1602
  before = _git_changed_files(path)
1523
- print(f"aidraftsman fallback spawn | mode={mode} | {path}", flush=True)
1603
+ # Not a git repo: track the folder's files directly, so an in-place edit of
1604
+ # a workbook / document still counts as a deliverable.
1605
+ snap_before = _folder_snapshot(path) if before is None else None
1606
+ print(f"aidraftsman fallback spawn | mode={mode}"
1607
+ + (f" | resume {ad_resume}" if ad_resume else "") + f" | {path}", flush=True)
1524
1608
  proc = await asyncio.create_subprocess_exec(
1525
1609
  *cmd, cwd=str(path), env=env,
1526
1610
  stdin=asyncio.subprocess.PIPE,
@@ -1542,7 +1626,10 @@ async def run_fallback_cli(
1542
1626
  raise
1543
1627
 
1544
1628
  after = _git_changed_files(path)
1545
- files_changed = sorted(after - before) if before is not None and after is not None else []
1629
+ if before is not None and after is not None:
1630
+ files_changed = sorted(after - before)
1631
+ else:
1632
+ files_changed = _snapshot_changes(snap_before, _folder_snapshot(path))
1546
1633
  err_text = stderr.decode("utf-8", errors="replace").strip()
1547
1634
 
1548
1635
  data: dict[str, Any] = {}
@@ -1563,7 +1650,8 @@ async def run_fallback_cli(
1563
1650
  if done.startswith("AI Draftsman CLI updated"):
1564
1651
  return await run_fallback_cli(
1565
1652
  prompt, project_path, timeout=timeout, github_token=github_token,
1566
- extra_env=extra_env, tier=tier, provider=provider, _upgraded=True)
1653
+ extra_env=extra_env, tier=tier, provider=provider,
1654
+ resume_session_id=resume_session_id, _upgraded=True)
1567
1655
  err += f" — the AI Draftsman CLI is outdated; run `{FALLBACK_INSTALL_HINT}`"
1568
1656
  auth_expired = proc.returncode == 2 or _is_auth_error(err)
1569
1657
  if not auth_expired and _is_fallback_rate_limited(err):
@@ -1577,7 +1665,8 @@ async def run_fallback_cli(
1577
1665
  await asyncio.sleep(wait)
1578
1666
  return await run_fallback_cli(
1579
1667
  prompt, project_path, timeout=timeout, github_token=github_token,
1580
- extra_env=extra_env, tier=tier, provider=provider, _upgraded=_upgraded,
1668
+ extra_env=extra_env, tier=tier, provider=provider,
1669
+ resume_session_id=resume_session_id, _upgraded=_upgraded,
1581
1670
  _rl_attempt=attempt, _rl_waited=_rl_waited + wait)
1582
1671
  err += (f" — still rate-limited after {_rl_attempt} retr{'y' if _rl_attempt == 1 else 'ies'} "
1583
1672
  f"and {_rl_waited / 60:.0f} min of waiting")
@@ -1591,8 +1680,33 @@ async def run_fallback_cli(
1591
1680
  in_tok = int(usage.get("prompt_tokens", 0) or 0)
1592
1681
  out_tok = int(usage.get("completion_tokens", 0) or 0)
1593
1682
  text = str(data.get("result") or "")
1683
+ sid = str(data.get("session_id") or "")
1684
+ session_id = f"{AD_SESSION_PREFIX}{sid}" if sid else None
1685
+ denied = [str(d) for d in (data.get("permission_denials") or []) if d]
1686
+ if denied:
1687
+ print(f"aidraftsman was denied {len(denied)} call(s) in {mode} mode: "
1688
+ + "; ".join(d[:120] for d in denied[:5]), flush=True)
1689
+ if denied and not files_changed:
1690
+ # `ad -p` exits 0 after a denial and answers around it, which read as
1691
+ # "ran fine, made nothing" (MWM-217: every web fetch and the Python
1692
+ # that edits the .xlsx were refused). Name what was blocked and how to
1693
+ # allow it — this is the failure, not the task.
1694
+ shown = "; ".join(d[:120] for d in denied[:5])
1695
+ more = f" (+{len(denied) - 5} more)" if len(denied) > 5 else ""
1696
+ return _result(
1697
+ ok=False,
1698
+ error=(f"AI Draftsman was blocked from {len(denied)} action(s) it needed "
1699
+ f"in {mode} mode and produced nothing: {shown}{more} — set "
1700
+ "AGENTOS_FALLBACK_PERMISSION_MODE=bypass to allow shell commands "
1701
+ "and web fetches, or log Claude in"),
1702
+ result=text, longest_text=text, session_id=session_id,
1703
+ input_tokens=in_tok, output_tokens=out_tok,
1704
+ total_tokens=int(usage.get("total_tokens", 0) or 0) or in_tok + out_tok,
1705
+ tier=tier, provider=provider, model="aidraftsman",
1706
+ )
1594
1707
  return _result(
1595
1708
  ok=True,
1709
+ session_id=session_id,
1596
1710
  result=text,
1597
1711
  longest_text=text,
1598
1712
  input_tokens=in_tok,
@@ -1715,6 +1829,20 @@ async def _run_claude_local(
1715
1829
  if force_fallback:
1716
1830
  print("claude usage limit — continuing this ticket on the AI Draftsman CLI",
1717
1831
  flush=True)
1832
+ return await run_fallback_cli(
1833
+ prompt, project_path, timeout=timeout, github_token=github_token,
1834
+ extra_env=extra_env, tier=tier, provider=provider,
1835
+ resume_session_id=resume_session_id if ad_session_id(resume_session_id) else None,
1836
+ )
1837
+ if ad_session_id(resume_session_id):
1838
+ # The session being continued is an `ad` one: it only exists there.
1839
+ return await run_fallback_cli(
1840
+ prompt, project_path, timeout=timeout, github_token=github_token,
1841
+ extra_env=extra_env, tier=tier, provider=provider,
1842
+ resume_session_id=resume_session_id,
1843
+ )
1844
+ if not resume_session_id and engine() == "ad":
1845
+ print("AGENTOS_ENGINE=ad — running on the AI Draftsman CLI", flush=True)
1718
1846
  return await run_fallback_cli(
1719
1847
  prompt, project_path, timeout=timeout, github_token=github_token,
1720
1848
  extra_env=extra_env, tier=tier, provider=provider,
@@ -1747,6 +1875,14 @@ async def _run_claude_local(
1747
1875
  )
1748
1876
  if fallback["ok"]:
1749
1877
  return fallback
1878
+ if fallback.get("session_id") and not fallback.get("auth_expired") \
1879
+ and not fallback.get("limit_exceeded"):
1880
+ # `ad` really ran and failed on its own terms (e.g. blocked actions):
1881
+ # that is the actionable reason, and its session can be repaired. Keep
1882
+ # claude's reason alongside it.
1883
+ fallback["error"] = (f"{fallback.get('error')} | claude: "
1884
+ f"{(result.get('error') or 'not working')[:160]}")
1885
+ return fallback
1750
1886
  # Both failed: keep the claude result (its limit/auth flags drive the
1751
1887
  # caller's parking and alerts) and say what the fallback hit too.
1752
1888
  result["error"] = f"{result.get('error')} | aidraftsman fallback: {fallback.get('error')}"
@@ -0,0 +1,39 @@
1
+ """A fallback (`ad`) run in a plain, non-git working folder must still report
2
+ the files it changed — otherwise an in-place workbook edit is judged "task
3
+ produced no files/deliverables" and the ticket goes to Backlog (MWM-217)."""
4
+ from __future__ import annotations
5
+
6
+ import os
7
+
8
+ from sdk import local_exec
9
+
10
+
11
+ def test_in_place_edit_and_new_file_are_seen(tmp_path):
12
+ wb = tmp_path / "UK_Shopping_Centres_list_Review_v3.xlsx"
13
+ wb.write_bytes(b"old")
14
+ untouched = tmp_path / "notes.txt"
15
+ untouched.write_text("same")
16
+ before = local_exec._folder_snapshot(tmp_path)
17
+
18
+ wb.write_bytes(b"new and longer")
19
+ st = os.stat(wb)
20
+ os.utime(wb, ns=(st.st_atime_ns, st.st_mtime_ns + 1_000_000_000))
21
+ (tmp_path / "summary.md").write_text("done")
22
+
23
+ changed = local_exec._snapshot_changes(before, local_exec._folder_snapshot(tmp_path))
24
+ assert changed == sorted([str(wb), str(tmp_path / "summary.md")])
25
+
26
+
27
+ def test_tool_state_and_office_lock_files_are_not_deliverables(tmp_path):
28
+ before = local_exec._folder_snapshot(tmp_path)
29
+ (tmp_path / "graphify-out").mkdir()
30
+ (tmp_path / "graphify-out" / "graph.json").write_text("{}")
31
+ (tmp_path / ".claude").mkdir()
32
+ (tmp_path / ".claude" / "x").write_text("x")
33
+ (tmp_path / "~$Book.xlsx").write_text("lock")
34
+ assert local_exec._snapshot_changes(before, local_exec._folder_snapshot(tmp_path)) == []
35
+
36
+
37
+ def test_no_snapshot_means_no_claim(tmp_path):
38
+ assert local_exec._snapshot_changes(None, {}) == []
39
+ assert local_exec._snapshot_changes({}, None) == []
@@ -0,0 +1,127 @@
1
+ """The AI Draftsman CLI (`ad`) as a full fallback: its sessions resume on `ad`,
2
+ blocked actions fail loudly, AGENTOS_ENGINE=ad runs it first, and
3
+ AGENTOS_FALLBACK_MODEL picks its model."""
4
+ from __future__ import annotations
5
+
6
+ import asyncio
7
+ import json
8
+ import sys
9
+
10
+ import pytest
11
+
12
+ from sdk import local_exec
13
+
14
+ pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="POSIX script stand-in")
15
+
16
+
17
+ def _fake_ad(tmp_path, payload, *, write=True):
18
+ """An `ad` stand-in: records argv, optionally writes a file, prints ``payload``."""
19
+ record = tmp_path / "record.json"
20
+ script = tmp_path / "ad"
21
+ script.write_text(f"""#!{sys.executable}
22
+ import json, os, sys
23
+ sys.stdin.read()
24
+ json.dump({{"argv": sys.argv[1:]}}, open({str(record)!r}, "w"))
25
+ if {write!r}:
26
+ open("out.txt", "w").write("x")
27
+ print(json.dumps({payload!r}))
28
+ """)
29
+ script.chmod(0o755)
30
+ return script, record
31
+
32
+
33
+ def _claude_must_not_run(monkeypatch):
34
+ async def _boom(*a, **k):
35
+ raise AssertionError("claude must not be spawned")
36
+ monkeypatch.setattr(local_exec, "_run_claude_cli", _boom)
37
+
38
+
39
+ def test_an_ad_run_returns_a_resumable_ad_session(tmp_path, monkeypatch):
40
+ cli, _ = _fake_ad(tmp_path, {"result": "ok", "session_id": "abc123", "usage": {}})
41
+ monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
42
+ result = asyncio.run(local_exec.run_fallback_cli("x", str(tmp_path / "p")))
43
+ assert result["ok"] is True
44
+ assert result["session_id"] == "ad:abc123"
45
+
46
+
47
+ def test_an_ad_session_resumes_on_ad_not_claude(tmp_path, monkeypatch):
48
+ cli, record = _fake_ad(tmp_path, {"result": "ok", "session_id": "abc123", "usage": {}})
49
+ monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
50
+ _claude_must_not_run(monkeypatch)
51
+ result = asyncio.run(local_exec.run_claude_local(
52
+ "fix the failing step", str(tmp_path / "p"), resume_session_id="ad:abc123"))
53
+ assert result["ok"] is True
54
+ argv = json.loads(record.read_text())["argv"]
55
+ assert argv[-2:] == ["-r", "abc123"]
56
+
57
+
58
+ def test_engine_ad_runs_ad_first(tmp_path, monkeypatch):
59
+ cli, record = _fake_ad(tmp_path, {"result": "ok", "session_id": "s", "usage": {}})
60
+ monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
61
+ monkeypatch.setenv("AGENTOS_ENGINE", "ad")
62
+ _claude_must_not_run(monkeypatch)
63
+ result = asyncio.run(local_exec.run_claude_local("x", str(tmp_path / "p")))
64
+ assert result["ok"] is True and record.exists()
65
+
66
+
67
+ def test_default_engine_is_claude(monkeypatch):
68
+ monkeypatch.delenv("AGENTOS_ENGINE", raising=False)
69
+ monkeypatch.setattr(local_exec, "_setting", lambda key: "")
70
+ assert local_exec.engine() == "claude"
71
+
72
+
73
+ def test_fallback_model_is_passed_instead_of_the_agent(tmp_path, monkeypatch):
74
+ cli, record = _fake_ad(tmp_path, {"result": "ok", "usage": {}})
75
+ monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
76
+ monkeypatch.setenv("AGENTOS_FALLBACK_MODEL", "gemini-2.5-pro")
77
+ monkeypatch.setenv("AGENTOS_FALLBACK_AGENT", "dev")
78
+ asyncio.run(local_exec.run_fallback_cli("x", str(tmp_path / "p")))
79
+ argv = json.loads(record.read_text())["argv"]
80
+ assert "--model" in argv and argv[argv.index("--model") + 1] == "gemini-2.5-pro"
81
+ assert "--agent" not in argv
82
+
83
+
84
+ def test_blocked_and_nothing_produced_is_a_loud_failure(tmp_path, monkeypatch):
85
+ """MWM-217: `ad` exits 0 after its web fetches and the .xlsx script were denied."""
86
+ cli, _ = _fake_ad(tmp_path, {
87
+ "result": "I could not fetch the sources.", "session_id": "s1", "usage": {},
88
+ "permission_denials": ["web_fetch(https://example.org)", "bash(python fill.py)"],
89
+ }, write=False)
90
+ monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
91
+ result = asyncio.run(local_exec.run_fallback_cli("x", str(tmp_path / "p")))
92
+ assert result["ok"] is False
93
+ assert "blocked from 2 action(s)" in result["error"]
94
+ assert "bash(python fill.py)" in result["error"]
95
+ assert "AGENTOS_FALLBACK_PERMISSION_MODE=bypass" in result["error"]
96
+ assert result["session_id"] == "ad:s1" # still repairable on `ad`
97
+
98
+
99
+ def test_blocked_but_produced_work_still_succeeds(tmp_path, monkeypatch):
100
+ cli, _ = _fake_ad(tmp_path, {
101
+ "result": "done", "session_id": "s1", "usage": {},
102
+ "permission_denials": ["bash(npm test)"],
103
+ })
104
+ monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
105
+ result = asyncio.run(local_exec.run_fallback_cli("x", str(tmp_path / "p")))
106
+ assert result["ok"] is True
107
+ assert result["files_changed"]
108
+
109
+
110
+ def test_logged_out_claude_and_blocked_ad_reports_the_block(tmp_path, monkeypatch):
111
+ """Both failed, but `ad` really ran: its reason is the one to act on, not a
112
+ pause waiting for a Claude login."""
113
+ cli, _ = _fake_ad(tmp_path, {
114
+ "result": "", "session_id": "s1", "usage": {},
115
+ "permission_denials": ["web_fetch(https://example.org)"],
116
+ }, write=False)
117
+ monkeypatch.setenv("AGENTOS_FALLBACK_CLI", str(cli))
118
+
119
+ async def _logged_out(*a, **k):
120
+ return local_exec._result(ok=False, error="Not logged in · Please run /login",
121
+ auth_expired=True)
122
+ monkeypatch.setattr(local_exec, "_run_claude_cli", _logged_out)
123
+ result = asyncio.run(local_exec.run_claude_local("x", str(tmp_path / "p")))
124
+ assert result["ok"] is False
125
+ assert result["auth_expired"] is False
126
+ assert "blocked from 1 action(s)" in result["error"]
127
+ assert "claude: Not logged in" in result["error"]
@@ -1,17 +0,0 @@
1
- [
2
- {
3
- "version": "1.7.48",
4
- "commit": "84377c8",
5
- "pushed_at": "2026-11-03T14:46:05+05:00",
6
- "generated_at": "2026-11-03T09:47:28+00:00",
7
- "commands": 60,
8
- "commands_invalid": 0,
9
- "commands_undocumented": 0,
10
- "panel_pages": 11,
11
- "tests": 2613,
12
- "prs_7d": 11,
13
- "prs_30d": 43,
14
- "features_30d": 13,
15
- "fixes_30d": 18
16
- }
17
- ]